mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
docs/contributing-volume-batching
583
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
da050ef23a |
docs(contributing): add contribution volume and batching guidance
Documents expectations for keeping contribution flow sustainable during the stabilization phase: one issue per bug (no bundled multi-finding audits), keep only a few items open at a time, and hold AI-assisted contributions to the same per-item verification bar. Complements the existing issue-approved gate, which already covers PR-to-issue linkage and scope; the gap was submission volume. |
||
|
|
3967d49d64 |
feat(tui): permission prompt takes free-text feedback inline (#780)
* feat(tui): let the permission prompt take free-text feedback inline "No, and tell Zero what to do differently" now opens an inline input on the permission card instead of silently cancelling: type an instruction, Enter sends it, Esc returns to the option list. The text is delivered as a Deny decision whose Reason is the typed instruction, so the agent surfaces it via deniedPermissionResult as the tool result and the model adjusts in the SAME turn — where the old cancel returned an error and aborted the run. Empty input + Enter falls back to a plain cancel (the prior meaning). No agent-side change: PermissionDecision.Reason already carries free text to the model. The UI mirrors the ask_user "type your own answer" pattern — the feedback field reuses the shared composer input and the card renders it in the transcript body. Also: a focused permission prompt now suppresses the composer (and idle hints/plan panel), keeping only the status line, exactly like the ask_user modal. Its keys are inert anyway, so a "describe a task for zero…" box below the card was misleading — and in feedback mode the shared input would otherwise echo in two places. Tests cover open / submit-as-Deny-with-text / empty-cancels / Esc-returns, the composer suppression in both modes, and that the feedback text renders exactly once. * fix(tui): preserve the composer draft across permission feedback (#780 review) anand + CodeRabbit (Major): entering feedback mode cleared the shared composer input via SetValue(""), and neither submit nor cancel restored it — so a user with a half-typed or queued next-turn message who chose "tell Zero what to do differently" lost that draft. Snapshot the composer value into pendingPermissionPrompt.savedDraft when feedback mode opens, and restore it on BOTH exits (submit and Esc-cancel). Only the feedback text is consumed, delivered via the decision Reason. Adds regression tests for both paths; mutation-verified against the SetValue("") regression. * fix(tui): guard permission-feedback backspace and clicks (#780 review) Vasanthdev2004 review, all on the permission path: 1. Real bug: Backspace on an empty feedback field dropped a staged attachment. The keyBackspace case runs before the typing branch and, on an empty composer (feedback mode clears it), fell to removeLastAttachment — silently dropping a staged image/doc that savedDraft does not restore. Route Backspace to the shared input while typing so it is a plain edit of the feedback text. 2. Defence-in-depth: the mouse click resolver in handleTranscriptSelectionMouse resolved a clicked permission option (Allow included) with no typing check. It is safe today only because renderFocusedPermissionPrompt returns nil clickable offsets in feedback mode — a single early-return in a function other PRs also edit. Add an explicit !typing guard on the click path so the safety is stated, not emergent. 3. Test hardening (also raised by CodeRabbit): assert len(got) == 1 before indexing got[0] in the draft-preservation test, so a regression fails cleanly instead of panicking. Tests: TestPermissionFeedbackBackspaceKeepsStagedAttachment (mutation-verified against the missing guard) and TestPermissionFeedbackRendersNoClickableOptionsWhileTyping. --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
89bdc6719a |
fix(tools): read_file recovers a backwards line range instead of erroring (#779)
* fix(tools): read_file recovers a backwards line range instead of erroring renderReadFileRange hard-failed when end_line < start_line, costing the caller a whole retry for an arithmetic slip. It was also inconsistent: start_line past EOF returns a friendly note and end_line past EOF is auto-clamped to the last line — only a backwards range hard-errored. Recover it the same way: clamp end_line UP to start_line (read just that line) and surface the adjustment in the output so it is never silent. Valid ranges (end_line >= start_line) are byte-for-byte unchanged. The change is in the argument-validation phase, before any file I/O, so there is no runtime cost; if anything it saves the agent a wasted retry turn. Tests: TestReadFileToolRecoversBackwardsRange (recovery + note + reads only start_line, mutation-verified against the old hard error) and TestReadFileToolValidRangeUnchangedByRecovery (valid ranges carry no note and read the identical window). * test(tools): assert full valid-range output in read_file recovery test (#779 review) anand + CodeRabbit: TestReadFileToolValidRangeUnchangedByRecovery claimed byte-for-byte preservation but only checked substrings, so an altered header/separator or a stray out-of-range line would still pass. Compare the whole result.Output against the exact expected rendering. --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
722bb31216 |
fix(tui): stop the permission card clashing on cool themes (#778)
The focused permission prompt was the only prompt card in the TUI that used
brand/warning colours for its interior, and both read badly on cool themes such
as dracula:
1. The selected option ("Yes, proceed") was filled with zeroTheme.badge — the
brand chip meant for short labels (" 0 ", " ASK ", " SPEC REVIEW "), a
full-brightness accent fill. Every other selectable list in the TUI (model
picker, provider list, / palette) highlights its selected row with onSel/selBg
instead. The permission card was the outlier, painting a saturated accent slab
across the row and skipping the card tint every other line composed onto.
2. The card BODY was tinted with permBg, an amber-family wash intended to signal
a warning surface. It works on the warm default palette but renders as a
brown-yellow slab over cool themes, and it made the permission card the only
prompt whose body carries a fill — ask_user, spec review and plan all use a
transparent body (lipgloss.NewStyle()).
Fix both to match the rest of the TUI: the selected row uses onSel(ink) like
every other picker, and the body drops its permBg fill for a transparent one.
Warning identity is unchanged — the amber PERMISSION badge and the amber-mixed
border still mark it as a permission gate; only the clashing interior fills go.
TestFocusedPermissionSelectedRowUsesSelectionTintNotBrandChip asserts against
palette entries (not hardcoded hex, so it survives a theme retune): the selected
row carries selBg and not the brand accent, the body carries no permBg wash, and
the PERMISSION badge keeps its amber fill so the card still reads as a warning.
Co-authored-by: KRATOS <[email protected]>
|
||
|
|
a21a052a4a |
feat(sandbox): disable the sandbox via config (#687) (#746)
* feat(sandbox): disable the sandbox via config (#687) Add a sandbox.enabled field (`"sandbox": {"enabled": false}`). ModeDisabled already short-circuits the engine but had no config surface. - SandboxConfig.Enabled *bool (pointer distinguishes an explicit false from an omitted key). - mergeConfig honors it (global config / CLI); applyConfiguredSandboxPolicy maps enabled:false to ModeDisabled. - mergeProjectConfig intentionally does NOT merge it: a cloned repo must not be able to disable the sandbox that constrains it — same posture as AdditionalWriteRoots and the Network tighten-only rule. * fix(config): honor sandbox.enabled from the CLI override path (#687 review) applyOverrides copied BlockUnixSockets/MonitorDenials but not the new Enabled tri-state pointer, so a CLI/programmatic Overrides{Sandbox:{Enabled:&false}} was dropped — contradicting the 'global config / CLI can disable' contract. Preserve the pointer in applyOverrides (runs after all config sources, so an explicit override wins) and add a resolver regression test: an explicit false override disables, and an explicit true override wins over a lower-precedence config false. * fix(config): keep sandbox.enabled out of the provider-command path (#687 review) mergeConfig now copies Sandbox.Enabled, and it is shared by two callers: the global-config merge (trusted) and the provider-command merge (not). A provider command is an arbitrary executable named in config, and LoadProviderCommand parses its stdout into a full FileConfig -- so one returning a valid provider plus {"sandbox":{"enabled":false}} could disable the sandbox meant to constrain it, despite the setting being documented as global-config/CLI only. Confirmed reproducible in both directions before the fix. Clear Sandbox.Enabled from the provider-command config before merging, mirroring the carve-out mergeProjectConfig already applies for the same reason. Done at the call site rather than inside mergeConfig because that helper must keep honouring the setting for the trusted global-config merge. Adds TestResolveSandboxEnabledIgnoredFromProviderCommand covering both directions (cannot disable, cannot enable) and asserting the provider itself is still applied, so the guard cannot be satisfied by dropping the command wholesale. --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
f079b90f82 |
fix(sandbox): AST second opinion for interactive-command bypasses (#473) (#745)
* fix(sandbox): AST second opinion for interactive-command bypasses (#473) The interactive-command guard split shell commands with a hand-written parser (splitShellSegments), which mis-handles unusual quoting, command substitution, subshells, and newline separators — an interactive program hidden by one slips through and the agent hangs until the per-command timeout. Add astCommandFields(): mvdan.cc/sh/v3/syntax (already used by analyzer.go) to re-extract the real simple commands, then apply the SAME per-program checks the regex path uses (interactivePrograms + hasNonInteractiveFlag). This only ADDS detections and classifies every program exactly as before — ssh with a trailing command, python -c, etc. stay allowed — while catching the splitter's bypasses. Unparseable input (Windows cmd.exe, obfuscation) yields no commands and falls through to the regex path; the guard never hard-blocks on a parse error. * fix(sandbox): AST pass runs the full pipeline + guards dynamic programs (#473 review) Address review on #745: - The AST second opinion now applies the complete detection pipeline via a new inspectCommandFields helper (multi-word interactive segments, sh -c payload recursion, then the per-program lookup), so parser-hidden bypasses like { git rebase -i HEAD~1; } and { sh -c 'vim file.txt'; } are caught, not just bare interactive programs. - astCommandFields now skips a CallExpr whose program word is not a static literal (isLiteralWord), so a substitution concatenated with a literal ($(printf foo)vim -> foovim) can't fabricate an interactive program name. Regression tests added for both boundaries. (The un-braced $(printf foo)vim false positive is a separate, pre-existing hand-parser behavior — present on main before this PR — and is out of scope here.) * fix(sandbox): require literal words for the whole AST call (#473 review) astCommandFields guarded only the program word, then ran every argument through wordText, which keeps literal/quoted parts and silently drops CmdSubst/ParamExp. A dynamic ARGUMENT was therefore reconstructed lossily: `git $(printf foo)rebase -i HEAD~1` runs as `git foorebase -i` (a non-existent, non-interactive subcommand) but collapsed to `git rebase -i` and was blocked -- a false positive on a command the user never wrote, and new on this branch (the base correctly reports not-interactive). Extend the literalness guard to every word in the call: if any word carries an expansion, skip that AST command rather than classify a reconstruction whose runtime value is unknowable. Skipping is the safe direction -- the hand-written passes still run first, and a miss falls through to the normal permission prompt instead of hard-blocking. Genuine detection is unchanged: `git rebase -i HEAD~1` and the #473 bypass cases still classify exactly as before. --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
b30c3971b4 |
fix(tui): model rows labelled by id when the description is prose; keep the sidebar under the / palette (#775)
* fix(tui): label model rows by id when the description is prose
The model picker (provider wizard AND onboarding, which share displayLabel)
labelled each row with the provider's Description, falling back to the ID only
when the description was "generic". That is right when Description holds a short
display name -- "Grok 4.3" for x-ai/grok-4.3, which is what the field is for and
why the row prefers it over an ugly raw ID.
Providers do not all honour that. Aggregators commonly return a prose blurb
instead ("Fast DeepSeek model for efficient chat, coding help, and agent
loops"), which fails as a label twice: it overflows the row, and because such
blurbs are written per model FAMILY rather than per model, several DIFFERENT
models collapse to byte-identical rows. Observed in a live picker with two
identical "Open-weight GPT model for self-hosted reasoning..." rows and two
identical "Kimi multimodal agent model..." rows -- the distinguishing ID was
visible only in the detail line of whichever row happened to be highlighted, so
the list could not be used to pick between them.
providerWizardGenericModelDescription cannot catch these: it rejects vague
descriptions ("live model", "catalog default"), whereas these are specific, just
shared and sentence-shaped. So gate on shape instead -- a display name is short
and few-worded; anything longer is prose and the ID (unique by construction)
makes the better label. Friendly names are still preferred where providers
supply them, so the existing behaviour is unchanged for well-behaved catalogs.
* fix(tui): keep the sidebar open under the / command palette
sidebarAvailable grouped suggestionsActive() with the full-screen overlays
(setup, wizards, pickers), whose stated rationale is that they "take over the
chat column and render at full width". That is not true of the `/` palette:
suggestionOverlay draws it through centerRenderedBlock at
minInt(width, suggestionPaletteMaxWidth), a centred box capped at 76 cells
floating over the chat column.
Treating it as full-screen collapsed the whole layout on every `/`: the sidebar
disappeared, the plan panel dropped out of it and re-rendered inline above the
composer, and the transcript re-wrapped to full width -- a jarring reflow for a
popup, and worst mid-run with a live plan on screen.
It cannot overlap the sidebar either: twoColumnTranscriptView renders the
overlay with width = chatColumnWidth, so the palette is capped at
min(chatColumn, 76) and centred inside the chat column. Mouse hit-testing is
unaffected because sidebarLineAtMouse carries its own suggestionsActive() guard,
so clicks still reach the palette rather than the sidebar rows beneath it.
The genuinely full-width overlays keep suppressing the sidebar, pinned by the
new test alongside the palette case.
---------
Co-authored-by: KRATOS <[email protected]>
|
||
|
|
da9fb50f54 |
fix(tools): give write_stdin's invalid-session errors the same recovery guidance (#749 follow-up) (#768)
* fix(tools): give write_stdin's invalid-session errors the same recovery guidance Follow-up to #749 (write_stdin session-id probing) / #702. write_stdin had three "no valid live session" error paths with three different messages and signatures: a missing session_id returned "session_id is required", a zero/negative/non-integer one returned "session_id must be at least 1", and only a valid-but-unknown id got the rich UnknownExecSessionError recovery guidance ("do not guess or probe; start a session with exec_command, or use edit_file/ write_file"). So a model that called write_stdin with session_id 0 (the common "I have no id" placeholder) got a terse error with no way forward, and naming the minimum nudged it toward trying 1, 2, 3... the exact id-probing #749 suppresses. The three signatures also meant a model moving 0 to 1 to 2 reset the repeated-failure streak on each class of mistake. Route the missing / non-integer / <1 cases through the same UnknownExecSessionError message. Now every "no live session" mistake gives the same recovery guidance and, because that message is id-invariant, shares one repeated-failure signature, so any mix of missing/zero/probing accumulates toward the halt. Updates TestWriteStdinRequiresPositiveSessionID to cover missing/zero/negative/ non-integer and assert the recovery message. Guardrail signature/halt tests unchanged. * test(tools): cover the explicit session_id: nil case for write_stdin |
||
|
|
684901165d |
fix(sandbox): don't auto-allow shell when re-entrancy skips wrapping (#727) (#744)
The ambient ZERO_SANDBOXED=1 + ZERO_SANDBOX_BACKEND markers are user-controlled at an unsandboxed process boundary. Policy could auto-allow an ordinary shell command (backend reports a native wrapping sandbox) while the runner, seeing the same markers, returned an unwrapped pass-through plan — the command then ran directly on the host with no isolation and no prompt. shellSandboxActive now mirrors the runner: when IsAlreadySandboxed() (the markers are present, so wrapping is skipped) the sandbox is not the boundary for this command, so it reports inactive and the shell command takes the normal approval path instead of auto-allowing. Immediate mitigation per the issue; authenticated launch provenance is the longer-term fix. Co-authored-by: KRATOS <[email protected]> |
||
|
|
e1975c1b39 |
fix(perfbench): grant write tools so mutating tasks measure real edits (#763)
* fix(perfbench): grant write tools so mutating tasks measure real edits The turn benchmark invoked `zero exec` in its default read-only posture, which exposes no write or shell tools (no edit_file/apply_patch/ write_file/exec_command/bash). So the mutating classes (edit/fix/ refactor) could never apply a change: a run either ground to the turn ceiling hunting for an edit tool or reported a no-tool blocker, and the only tasks that 'passed' were ones whose oracle grep matched the stamped .zero-answer.txt narration rather than a real edit. This invalidated every mutating-class number in both baselines. A live trace of fix-07 (a one-line admin->user prefix change) showed the model calling only read_file/grep/lsp_navigate/update_plan and then stating it had no file-writing tool. Adding --skip-permissions-unsafe exposes the full tool set; each task already runs in an isolated throwaway fixture copy, so there is nothing to protect. With the flag, edit-01/fix-01/ fix-07/refactor-01 go from failing (or false-passing) to 4/4 real passes, and tool_execution rises from ~0.4% to ~29% of wall as the write tools are actually exercised. Adds TestBuildTurnExecArgsGrantsWriteTools pinning the flag as a correctness contract. * fix(perfbench): use --auto member and let the oracle judge incomplete exits Reviewer feedback on the write-tools grant: --skip-permissions-unsafe is broader than the benchmark needs. It also drops the workspace, network, and destructive-command guards and auto-retries sandbox-blocked commands unsandboxed. Switch to --auto member, which grants exactly the write + sandboxed-shell tools the mutating classes need while keeping those safeguards. member-auto's shell is sandboxed, though, so on a host without sandbox setup the agent's own self-verification (go test/build) can't run and the turn exits INCOMPLETE even after a correct edit. The runner used to treat any nonzero exit as failure before consulting the oracle, which would false-fail those correct edits. Make the oracle authoritative for oracle-bearing tasks: the stamped test/grep against the actual fixture files is ground truth, so a correct edit passes regardless of the exit code. Latency-only tasks have no oracle, so a nonzero exit still fails them. Verified with a 4-task glm-5.2 smoke (edit-01/fix-01/fix-07/refactor-01): 4/4 real passes under --auto member. Updates the exec-args test to assert --auto member is present and --skip-permissions-unsafe is absent, and adds TestOracleAuthoritativeOnIncompleteExit and TestNonzeroExitStillFailsLatencyOnly. * fix(perfbench): reject fixtureless tasks so the write-tools grant can't escape isolation CodeRabbit and gnanam both flagged the same structural gap: buildTurnExecArgs grants the write + sandboxed-shell tools to every invocation, but the fixture isolation was conditional (a task with no workspaceFixture ran in the caller's cwd). So the grant and the guard could drift apart, and a fixtureless task would run an agent with write/shell tools in the caller's real repo dir. Make the isolation a checked precondition: NewTurnExecRunner now rejects a task with no workspaceFixture before launching zero exec, failing loudly rather than silently running in cwd. Every shipped task already declares a fixture, so this only fires on a malformed or newly added one. Adds TestTurnRunnerRejectsFixturelessTask (rejection is pre-launch: it returns the fixture error even with a nonexistent binary). * fix(perfbench): defer to the oracle only for INCOMPLETE (exit 4), not every nonzero exit anand's follow-up: the previous commit cleared VerifyErr for any nonzero run_end whenever a VerificationCommand was present, which would also convert a crash (1), usage error (2), provider failure (3), or interruption (130) into a passing result whenever a partial edit happened to satisfy the oracle. Gate the oracle override on exitCode == exitIncomplete (4): the completion gate's "work maybe done but not signaled" case, the only one where a correct edit can legitimately exit nonzero (its sandboxed self-verify couldn't run under --auto member). Every other nonzero exit stays authoritative, so a partial edit can't launder a crashed or interrupted run into a pass. The 4-task glm-5.2 smoke stays 4/4 (those tasks exit INCOMPLETE, not some other code). Adds TestNonIncompleteExitStaysAuthoritative (a correct edit that reports exit 1 must still fail) and mirrors internal/cli.exitIncomplete as a local execExitIncomplete constant. * test(perfbench): pin the exit-4 oracle deferral from both sides; drop dead fixture branch Follow-ups from a self-review of the exit-4 gating: - Add TestIncompleteExitStillFailsWhenOracleFails: an INCOMPLETE (exit 4) run that did NO work must still FAIL, because deferring to the oracle still means the oracle is consulted. The sibling TestOracleAuthoritativeOnIncompleteExit applies a correct edit and would pass either way, so without this a regression that turned exit 4 into a blanket pass would go unnoticed. - Broaden TestNonIncompleteExitStaysAuthoritative to a table over exit 1/3/130 (crash/provider/interrupt), not just 1, so every named launder-risk code is covered. - Tighten the NewTurnExecRunner doc: the verdict gate is the VerificationCommand (which runs the stamped OracleTest via go test), not OracleTest on its own. - Drop the now-dead "if WorkspaceFixture != empty" branch: the fixtureless guard added earlier already guarantees it is set. No behavior change; the 4-task glm-5.2 smoke stays 4/4. |
||
|
|
8c6d3022bd |
feat(tui): add /undo as an alias for /rewind (#698) (#747)
* feat(tui): add /undo as an alias for /rewind (#698) /undo was unregistered and fell through to commandUnknown. Add it to the /rewind command's aliases (the same mechanism /quit, /find, /mcp-status use), so it dispatches to the existing rewind handler with args intact. * test(tui): cover the /undo numeric-sequence form (#698 review) Add a parseCommand("/undo 123") assertion so both the latest and numeric <sequence> forms of the /rewind contract are exercised through the alias. --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
974fc036c2 |
fix(oauth): refuse redirects on credential POSTs (#729) (#741)
Token exchange/refresh and the device authorization/poll POSTs validated only the initial endpoint, then let the http.Client follow a 307/308 that replays the form body (code, PKCE verifier, refresh_token, client_secret) to an unvalidated origin. Add withoutRedirects(): a shallow client copy whose CheckRedirect refuses every redirect (ErrUnsafeRedirect). Applied at PostToken (covers ExchangeCode, Refresh, and MCP which delegate to it), RequestDeviceCode, and pollDeviceOnce. The caller's client is never mutated; legitimate token endpoints that return 200 are unaffected. Co-authored-by: KRATOS <[email protected]> |
||
|
|
4cbd144d11 |
fix(providers): stop "provider not found" for env-derived profiles (#716)
* fix(providers): stop "provider not found" for env-derived profiles * fix(providers): address env profile review feedback * fix(providers): keep env JSON response schemas stable |
||
|
|
c0b6b9e5c9 |
docs(readme): note govulncheck/golangci-lint install to GOPATH/bin (#711)
* docs(readme): note govulncheck/golangci-lint install to GOPATH/bin The installed binaries land in $GOPATH/bin (default ~/go/bin), which must be on PATH to run govulncheck directly. Document the export snippet so the lint/security tooling from the contributor setup actually resolves. * docs(readme): say "them" instead of singling out govulncheck golangci-lint installs to the same $GOPATH/bin as govulncheck, so the PATH note applies to both, not just the one named. Co-Authored-By: Claude Sonnet 5 <[email protected]> * docs(readme): honor GOBIN and make the PATH snippet idempotent go install writes to GOBIN when set, with GOPATH/bin as the fallback, and rerunning the old snippet appended the same directory repeatedly. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: Claude Sonnet 5 <[email protected]> |
||
|
|
d74ceb1127 |
fix(tui): cache settled alt-screen transcript (#647)
* fix(tui): cache settled alt-screen transcript * fix(tui): invalidate settled alt-screen cache on in-place status row updates setDoctorStatusRow, setSandboxSetupStatusRow, and setCompactStatusRow all mutate an already-flushed transcript row in place (m.transcript[i] = row) without invalidating the alt-screen settled cache. Since none of frontier, width, or row count change on such an update, settleTranscript's rebuild guard never fires and the View fast path serves the stale cached snapshot - e.g. the doctor connectivity spinner freezes on its first frame. Zero altScreenSettledWidth on the in-place path, mirroring the existing pattern in toggleTranscriptRow, so the next settle rebuilds the cache. Addresses PR #647 review feedback from Vasanthdev2004. * fix(tui): invalidate settled transcript cache * fix(tui): invalidate settled file selection cards |
||
|
|
fbf85984f6 |
fix(agent): stop write_stdin session_id probing thrash (#702) (#749)
A model that calls write_stdin with no live session probed sequential session_ids (1, 2, 3, …), each failing, and the run ground on for a long time before halting. The repeated-failure guard keys on a normalized, 80-char-truncated signature of the tool error, and the old message "Unknown exec session_id %d" put the varying id inside that prefix, so every probe produced a fresh signature and the 6x-same-error halt never fired. - Rewrite the unknown-session error so stable recovery guidance leads and the id trails past the signature window: probing now collapses to one signature and trips the halt, and the model is told how to recover (start an exec_command, or use the file-edit tools). - Pin session_id's schema Minimum to 1 so the advertised schema matches the runtime floor (intArg min 1). - system_prompt: state that session_id is only ever an id returned by a still-running exec_command and must never be guessed or probed. Tests pin the id-invariance of the signature and that distinct-id probing now halts at the guard's stop threshold, plus the schema floor and the recovery text. The TUI "Sent input" label on a failed write_stdin (issue criterion 4) is a separate cosmetic concern in a shared hot path and is left as a follow-up. |
||
|
|
9242b9e037 |
Add transcript-grounded structured task state (#761)
* Add transcript-grounded structured task state Project existing plan, tool-result, changed-file, verification, and completion events into a deterministic per-run snapshot. Emit content-free aggregate snapshots to traces. Carry bounded objective context through compaction and ground completion checks only when the projection matches the transcript; mismatches retain transcript-derived behavior and drop stale carried state. Tested: make fmt-check Tested: go vet ./... Tested: go test ./... Tested: go test -race ./internal/agent ./internal/trace Tested: go run ./cmd/zero-release build Tested: go run ./cmd/zero-release smoke Tested: govulncheck ./... (no vulnerabilities found) Advisory: pinned golangci-lint reports only unrelated pre-existing findings Tested: git diff HEAD --check * Preserve task objective across repeated compaction Keep the immutable run objective when plan calls have been elided, while admitting mutable task fields only when plan parity still matches the transcript. Make plan parsing match update_plan rejection semantics, coalesce and cap task-state trace events, rename parity to its plan-only scope, use explicit nilable dependencies, and remove confirmable objective fingerprints from traces. Tested: make fmt-check Tested: go vet ./... Tested: go test ./... Tested: go test -race ./internal/agent ./internal/trace Tested: go run ./cmd/zero-release build Tested: go run ./cmd/zero-release smoke Tested: govulncheck ./... (no vulnerabilities found) Advisory: pinned golangci-lint reports only unrelated pre-existing findings Tested: git diff HEAD --check |
||
|
|
2e267bdbee |
feat(tui): add isolated /btw conversations (#748)
* feat(tui): add isolated btw conversations Add /btw with inline-question support, isolated non-resumable session forks, background main-run routing, and safe return behavior. Refs #637 * test(tui): cover btw return controls Preserve drafts when Ctrl+C is pressed during an active BTW run and cover returning to the parent through the documented /btw toggle. * Address BTW isolation review feedback * Reject explicit resumes of side sessions * Harden BTW isolation lifecycle |
||
|
|
739a47e3ea |
perf(agent): preserve prompt cache prefixes (#760)
* perf(agent): preserve prompt cache prefixes Build the system prompt once per run, fingerprint the exact emitted prompt and ordered tool definitions, and expose the full system-prompt hash in traces. Add normalized and serialized two-turn regressions covering append-only messages, stable tools, and a stable session cache key. * fix(tools): stabilize registry iteration order Return registered tools in name order so provider-visible tool lists and prompt-prefix fingerprints cannot drift with Go map iteration. Add a regression covering the registry ordering contract. |
||
|
|
ce4a996ffa |
fix(oauth): validate discovered endpoints before merge/use (#511) (#739)
Discovery metadata was merged without the https/loopback endpoint rule that configured endpoints already pass, letting a malicious issuer downgrade the authorization/token/device/registration endpoint after config validation. - Add exported ValidateEndpointURL as the single endpoint-safety rule; validateTokenEndpoint now delegates to it. - Backstop in BuildAuthorizationURL (the choke point both the provider and MCP flows build their browser URL through). - Validate each discovered endpoint before merge in the provider resolveEndpoints and validate resolved MCP metadata in both branches; fail closed on insecure. Co-authored-by: KRATOS <[email protected]> |
||
|
|
378d538e24 |
perf(agent): execution profiles with one-shot posture escalation (PR10b+PR10c) (#740)
* perf(agent): execution profiles, --exec-profile, bench passthrough, TUI /profile (PR10b+10c) * fix(execprofile): harden profiles against review findings - fast escalates on critical-risk mutations, not high: the sandbox marks every shell command high before command analysis, so a high trigger spent the one-shot on the first go test of any task - escalation can now restore the provider-default effort a profile displaced (PostureEscalation.RestoreDefaultEffort; a plain empty target means leave untouched) - TUI /turns while a profile is active pins the budget: disarms the escalation turn target (mirroring exec's explicit --max-turns rule) and profile reverts use touched bits instead of value equality, so explicit choices survive even when they coincide with profile values - spec-draft runs get the same escalation policy as the main run instead of the displaced budget with no safety net - bench validates and canonicalizes --exec-profile before spawning: an unknown name previously produced an exit-0 report full of millisecond usage-error walls - turn summary prints exec-profile alongside mode; zero exec rejects an inline-empty --exec-profile=; balanced trace label documented and pinned as deliberate attribution * fix(execprofile): explicit effort choices disarm the escalation effort restore An explicit effort set AFTER profile selection could be cleared by a mid-run escalation (the effort analog of the /turns pin, introduced by RestoreDefaultEffort): TUI /effort and Ctrl+T now disarm the restore on a policy clone, and the spec-draft path only arms it when no explicit --spec-reasoning-effort governs the draft. * fix(execprofile): address review findings on the profile surfaces - --use-spec projects away a profile's self-correct knob instead of silently arming an option the draft runner ignores (the explicit --self-correct --use-spec combination is already a parse-time error) - RunTurnBench canonicalizes and validates ExecProfile at the library boundary, so a direct caller's casing/whitespace cannot fork report labels and an unknown name fails before anything runs - reverting a profile whose displaced budget was zero clears ZERO_MAX_TURNS (SetMaxTurnsEnv ignores zero, so sub-agents would have kept the removed profile's budget) - the TUI effort-disarm test fails instead of skipping when the catalog stops filling low effort * fix(tui): reconcile an active profile's effort fill on model switch The fill is per-model, not a session preference: fast selected on a model without low fills nothing, and switching to a model that supports it should behave like selecting the profile there; the reverse switch must not keep sending a level the destination does not support. Both switch paths (the /model command and the cross-provider picker) now re-derive the fill, leaving explicitly touched efforts alone and keeping the escalation's effort restore in step. Also: the revert-env test asserts key removal via LookupEnv, and applyProfileTurnBudget's pinned-budget parameter is named and documented as covering mode presets, not just the flag. * fix(tui): void profile effort bookkeeping when the model-switch drop erases it The /model path's pre-existing unsupported-effort drop ran before the profile reconciliation and left contradictory state behind: a dropped touched override kept blocking the profile from governing, and a dropped profile fill left a stale applied value with the effort restore still armed. The drop itself is load-bearing (the TUI run path forwards the effort to the provider unfiltered), so instead of preserving a known-unsupported level, the voided choice now resets the profile's effort bookkeeping and the reconcile re-derives from scratch. The whole sequence lives in one ring-parameterized helper driven by the switch target's authoritative ring, unit-tested against synthetic rings for both the void-then-refill arc and the supported-override survival. * fix(tui): only drop an explicit effort when the target ring is authoritative A nil ring meant both a catalog model with no effort controls and a live-discovered custom model the catalog cannot vouch for. The drop now requires an authoritative ring (a resolved catalog entry, whose ring may legitimately be empty); an unknown ring preserves an explicit preference, matching the cross-provider picker's behavior, while the profile's own fill still applies only where support is known. |
||
|
|
015452c1c9 |
fix(perfbench): keep the stamped answer file out of negative oracle greps (#737)
* fix(perfbench): keep the stamped answer file out of negative oracle greps * fix(perfbench): exclude stamped answer from positive oracle greps too |
||
|
|
af875df587 |
perf(agent): posture-escalation signals and controller (PR10a) (#736)
* trace(perf): add posture escalation counter * feat(agent): typed posture-escalation policy and tool-result risk field * feat(agent): posture controller wiring and executed-risk stamping * test(agent): cover posture controller, escalation act path, and risk stamping * test(agent): pin zero risk on not-executed results * fix(agent): wire uncertain-path escalation and validate risk thresholds |
||
|
|
dbd9443014 |
fix(perfbench): absolutize the bench binary and make errored tasks first-class (#730)
* fix(perfbench): absolutize the bench binary and make errored tasks first-class * fix(perfbench): state that errored tasks count as pass-rate failures * fix(perfbench): align errored-task wording with the actual tier accounting * chore(perfbench): never commit generated baseline reports |
||
|
|
0fae754460 |
docs: refresh repository agent guidelines (#734)
* docs: refresh repository agent guidelines * docs: make validation guidance non-mutating |
||
|
|
2cc6f43bf7 |
docs(community): wire Discussions as the front door for questions and ideas (#732)
* docs(community): wire Discussions as the front door for questions and ideas * docs(community): restore LF endings and make feature routing coherent |
||
|
|
77960229b8 |
fix(doctor): detect missing native binary during runtime checks (#450)
* fix(doctor): report missing npm native binary in wrapper Co-authored-by: Cursor <[email protected]> * fix(doctor): preserve JSON output for missing native binary * fix(doctor): preserve doctor CLI behavior in wrapper fallback Co-authored-by: Cursor <[email protected]> * style: gofmt npmwrapper doctor fallback tests Co-authored-by: Cursor <[email protected]> * fix(doctor): reuse shared Bun recovery helpers in wrapper fallback Extract bunRecoveryParagraph and related helpers so the doctor missing-binary text path and the generic wrapper bail share the same copy. Add regression tests with ZERO_WRAPPER_SIMULATE_BUN to guard against future drift. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]> |
||
|
|
60dc84e7a3 |
perf(openai): optimized turn session — background prewarm and prefix telemetry (PR8) (#723)
* trace(perf): add provider prewarm span and prefix-stability counters * feat(openai): optimized turn session with prewarm and prefix telemetry * feat(providers): gate optimized OpenAI turn sessions behind env flag * feat(cli): wire optimized turn sessions into headless exec * docs(readme): document the optimized OpenAI turn-session toggle * fix(openai): include tool descriptions in prefix digest, harden gate and test bounds * fix(openai): isolate prewarm tracing, skip non-reusable transports, fingerprint wire order and cache key * test(openai): pin keep-alive transports in probe-expecting tests * feat(providers): preserve resolved capabilities on switched-model fallback sessions |
||
|
|
30e2c3f7ff |
perf(providers): provider capabilities and default turn-session adapter (PR7) (#720)
* perf(providers): add provider capabilities and default turn-session adapter (PR7) * fix(providers): project effective reasoning efforts into capabilities * feat(agent): carry optimized turn sessions across mid-run model switches * test(agent): cover swap-time session open failure |
||
|
|
18cce358a2 |
Add deterministic completion policy (#719)
Extract completion decisions from the agent loop into a feature-gated typed policy with complete, incomplete, and uncertain outcomes. Preserve bounded plan-stall nudges and allow at most one semantic acceptance check for self-correcting runs. Tested: make build Tested: make test Tested: go fmt ./... Tested: go vet ./... Tested: make lint Tested: govulncheck ./... Note: repository-wide pinned golangci-lint reports 36 pre-existing unrelated findings; internal/agent/... reports 0 issues. |
||
|
|
e5670c427f |
perf(output): add token-aware semantic output budgeting (PR11) (#717)
* perf(output): add token-aware budget contract * perf(output): add semantic retention policies * perf(output): budget results after redaction * perf(output): propagate tool truncation metadata * perf(output): trace semantic budget decisions * perf(output): classify core tool output * docs(output): describe semantic output budgeting * test(output): make spill fallback portable on windows * fix(output): address semantic budgeting review findings * test(output): make hook budgeting tests portable * fix(output): retain oversized single-line file content * fix(output): apply semantic budgets to captured output * fix(output): preserve hook budgets and search paths * fix(output): preserve alias and truncation metadata * test(trace): validate output budget metadata --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
31d45d5f14 |
perf(agent): concurrent read-only tool batches via capability gate (#715)
Upgrade the parallel tool planner from SideEffectRead probes to the PR5 CapabilitiesOf contract: EffectReadOnly + ThreadSafe + auto-allowed, with resource-key conflict boundaries so same-path reads stay sequential. Mark audited pure reads ThreadSafe (read_file, read_minified_file, list_directory, glob, grep, skill, web_fetch). Glob/grep use scopedScan directory keys (nil for workspace-wide "." so scans do not false-conflict). Tests cover the capability gate, key conflicts, extendParallelRun windows, and catalog ThreadSafe audit for concurrent-safe reads. Co-authored-by: KRATOS <[email protected]> |
||
|
|
727ad4d321 |
perf(turn-bench): Phase 0 — strengthen oracles so pass rate can't be misread as correctness (#712)
* perf(turn-bench): Phase 0 — strengthen oracles so pass rate can't be misread as correctness
The baseline manifest's pass/fail contract had three holes that let a weak
or no-op agent read as a pass:
- edit grep oracles were substring checks: a reworded line (e.g.
fmt.Println("debug: starting") -> fmt.Println("starting up")) passed
`! grep 'debug: starting'`.
- refactor lived in the build tier with a non-positive `go build ./...`,
so a no-op refactor passed.
- the 24 read-only tasks (nav/longproc/longctx/parallel) had no oracle;
a zero-exit only proved the turn ran.
This makes the tiers honest:
- edit/refactor now carry a stamped oracle_test.go compiled by
`go test ./...` — the Go compiler is the structural verifier, so a
no-op refactor, a missing field, or a reworded-but-not-removed line
fails to compile/run. The test is stamped into the fixture copy AFTER
the agent run, so it can't interfere with the agent's own build or be
pre-seen.
- nav gains an answer-oracle: the harness captures the agent's final
answer (.zero-answer.txt) and greps it for determinable facts. Count
tasks (nav-04/05/08) use a structured `count: N` token so the oracle is
tight rather than a loose prose-contains on "0".
- refactor graduates from build to correctness (its oracle is now
positive); nav graduates from latency-only to correctness. The build
tier is empty (buildOnlyClasses: []).
- longproc/longctx/parallel stay permanently latency-only — there is no
deterministic oracle for "report whether the build succeeds" or
"summarize a large file"; documented in MANIFEST.md.
Tier counts: correctness 34 (edit 10 + fix 8 + nav 10 + refactor 6),
build 0, latency 14. Total 48 unchanged.
TurnSchemaVersion 2 -> 3: the result shape is unchanged (only class-list
membership shifts) but correctnessPassRate now means something strictly
stronger, so the bump prevents a v2->v2 cross-version comparison from
misreading the jump as a model improvement.
Prerequisite fix: the mutating fixtures had no go.mod, so `go test ./...`
in the temp copy failed with "cannot find main module". Added go.mod per
fixture (edit/fix/refactor/nav) and moved copyFixture's temp dir one level
below the system temp root — Go ignores go.mod in direct children of the
temp dir (a hijack guard), which would have broken every compiler-backed
oracle. This also fixes the pre-existing fix/refactor oracles, which were
silently broken because the test suite used fake runners that never ran
go test.
Tests: stub-binary gating tests prove each oracle fails the wrong thing
(no-op refactor, missing field, wrong nav answer, reworded debug print) and
passes the right thing (real refactor, added field, correct answer); an
edit-01 test proves the scoped negative grep doesn't false-fail a correct
rename that leaves the doc comment. Aggregation, schema, and unit tests
cover the v3 tier model and the answer-capture helper.
* Address CodeRabbit review on PR #712: tighten oracles + isolation
- nav count oracles (nav-01/04/05/08): anchored line-exact
`^count: N$` with the fixture's exact expected count, so a substring
like "the count: 0" or a wrong count like "count: 3" fails instead
of rubber-stamping. nav-01 added to the structured-count contract.
- stampOracleAndAnswer now errors when a task has an oracle or
verification but no workspace fixture, so verification can't run in
the caller's cwd with the oracle never compiled (false-pass closed).
- copyFixture uses a unique 0700 os.MkdirTemp parent under the temp
root and nests the copy beneath it (grandchild of temp root), so the
Go go.mod hijack guard is satisfied and cleanup removes the whole
parent. Avoids a predictable shared world-readable dir.
- Rejection tests require a non-empty VerifyErr via a shared
assertVerifyFailed helper, proving the failure came from
verification rather than a harness error or a silent non-pass.
- Unify streamJSONExitCode + streamJSONFinalText behind one
parseStreamJSON parser (behavior-preserving; scanner limits and
malformed-event handling now live in one place).
* Address gnanam1990 review on PR #712: close count=0 gameability + stronger oracles
- nav count tasks (nav-04/05/08) had a count=0 ground truth, so an agent
that never opened the workspace and always emitted "count: 0" passed
three correctness tasks -- the exact pass-rate-misread-as-correctness
failure this PR closes. nav-04 now has a real TestGreet (count 1) and
nav-05 a real TODO (count 1); nav-01's file count moves to 5. nav-08
stays at 0 (a real third-party import would need a network fetch and
break the offline suite) but its oracle now requires the answer to name
both stdlib imports (fmt from main.go, testing from main_test.go), so a
blind count:0 or a main.go-only read fails. nav-08's prompt now asks the
agent to enumerate and classify every import, so naming the stdlib
imports is part of a correct answer, not a false-fail.
- refactor-05: stamp a behavioral oracle (TestGreetWrapped) so deleting
both Wrapper and greetWrapped, or stubbing the caller to return "",
fails to compile/run. The negative grep alone passed that wrong edit.
- refactor-04: replace the brittle exact-text shell grep with a stamped
file-reading oracle using a spacing-tolerant regex, so a bare
map[string]int decl under any spacing/:= variant can't dodge it, and
the Stats type must be referenced in main.go.
- edit-05: also assert the "debug: starting" message text is absent, so
a respell onto fmt.Printf / backticks / log.Println / println that
still prints the message fails (the fmt.Println(" check missed those).
- v3 doc comments on ClassSummary / TurnBenchResult /
FormatTurnBenchSummary now describe the v3 tiers (correctness =
edit/fix/nav/refactor, build empty, latency = longproc/longctx/
parallel) instead of the stale v2 model.
- stampOracleAndAnswer failure now mutates the existing outcome instead
of returning a fresh one, so the already-parsed trace and trace issue
survive a stamp error (attribution is preserved, pass/fail unchanged).
- Schema comment provenance: "PR #701" -> neutral "Phase 0 oracle
hardening" (this PR is #712).
- New gating + satisfiable stub tests for refactor-04/05 and edit-05, and
count-oracle zero-guess / inspection-proof tests for nav-04/05/08.
* Fix macOS CI: copyFixture isolation test was slash-sensitive
TestCopyFixtureIsolatesSourceFromMutation compared filepath.Dir(parent)
against os.TempDir() verbatim. On macOS $TMPDIR ends in a trailing slash
("/var/folders/.../T/") while filepath.Dir strips it, so the equality
false-failed even though the parent is genuinely a direct child of the
temp root. Linux (/tmp, no slash) and Windows (no slash in this Go
build) matched by accident.
The production copyFixture nesting is correct on all platforms (the
grandchild copy sits one level below the temp root, satisfying Go's
go.mod hijack guard); only the assertion was slash-sensitive. Normalize
both sides with filepath.Clean.
* Close nav-04/05 blind-count-one gameability (anandh8x review)
nav-04 and nav-05 had a count-only oracle (anchored '^count:1$'). Once the
ground truth became 1, an agent that never inspects the fixture and always
emits 'count: 1' passed both — the blind-count-one surface the prior
non-zero-ground-truth fix opened. Pair the count with a workspace-derived
named fact that proves inspection, mirroring nav-08's fmt+testing:
- nav-04 now also requires 'TestGreet' (the one test function in
main_test.go) — a specific, non-generic name a blind agent can't produce
without reading main.go to learn a greet function exists. Fully closes
the blind-count-one surface.
- nav-05 now also requires 'main.go' (the file holding the one TODO).
main.go is the conventional entrypoint and the most guessable filename, so
this reduces rather than fully closes the surface — a blind 'count: 1, in
main.go' can still pass. Accepted for Phase 0: the blind floor drops below
100% (a bare 'count: 1' fails), so a model still moves the pass rate by
inspecting. MANIFEST.md documents this residual honestly.
Two gating tests per task: RejectsBlindCountOne (bare 'count: 1' now fails)
and, for nav-05, RejectsSubstring (main.go present but unanchored count
still fails — parity with nav-04). Existing accept tests still pass (their
answers already name TestGreet / main.go).
Doc nits also fixed: MANIFEST.md #701 -> #712; refactor-05 removed from the
'no oracleTest' inventory (it now has TestGreetWrapped); the nav-01 named-
fact prose corrected to the three files the oracle actually anchors on;
test comment #701 -> #712.
|
||
|
|
e666395d47 |
chore(main): release 0.4.0 (#625)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>v0.4.0 |
||
|
|
9043baedcf |
fix(sandbox): scrub dynamic credential env vars (#682)
* fix(sandbox): scrub dynamic credential env vars * fix(sandbox): tolerate env assignments in configured sensitive keys A config value mistakenly given as a full assignment (e.g. "COMPANY_LLM_SECRET=...") would never match the real env key during scrubbing, silently re-exposing the credential to sandboxed commands. normalizeSensitiveEnvKeys now keeps only the name part before '=' and re-trims it, per PR review. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(sandbox): scrub credentials on direct/degraded command plans directCommandPlan (used when the native sandbox backend is unavailable, disabled, or not required, including the EnforcementDegraded fallback) left Env nil whenever spec.Env was nil, so exec.Cmd inherited the full caller environment unscrubbed. Configured apiKeyEnv credentials and dynamically named ZERO_OAUTH_<NAME>_CLIENT_SECRET variables leaked through that path even though the wrapped sandbox plans already scrubbed them. Route direct plans through scrubSensitiveEnv the same way wrapped plans do, snapshotting os.Environ() when spec.Env is nil so scrubbing still applies. Adds a regression test covering the degraded-fallback path with both a configured and a dynamically named secret. Co-Authored-By: Claude Sonnet 5 <[email protected]> --------- Co-authored-by: Claude Fable 5 <[email protected]> |
||
|
|
4d8c31cf16 |
fix(config): enforce MCP trust boundary so project config cannot override user disable (#609)
A user-level MCP server disable is now sticky. The project config (a cloned repo's ./.zero/config.json) cannot re-enable a server the user disabled, and cannot disable a server the user explicitly enabled. The previous merge just assigned base.Disabled = next.Disabled, so a repo that set disabled:true could silently defeat a user's explicit mcp enable <server>. The guard is now symmetric via a mergeMCPServer canReenable flag: once a higher-trust scope has explicitly set the disabled flag, a lower-trust layer may not override that decision in either direction. Only the CLI override scope (canReenable=true) may lift a sticky user decision. The prior disabledSet-before-check ordering bug is also fixed: the higher-trust scope's prior disabledSet/Disabled is captured before folding in next's flag, so a project layer can still disable a default-enabled server (e.g. firecrawl) rather than being silently ignored. Ports the fix onto main's refactored MCP merge (internal/config/mcp_merge.go and mergeProjectMCPConfig), which splits project-scope merging from the general mergeMCPConfig path. Fixes #512 |
||
|
|
824ecdbcf9 |
fix(hooks): run sessionEnd hooks after Esc/Ctrl+C interrupts (#606)
* fix(hooks): run sessionEnd hooks after Esc/Ctrl+C interrupts On interrupt, Run returns with ctx already canceled. The deferred dispatchSessionEnd call passed that same ctx into Hooks.Dispatch, whose context.WithTimeout derives an already-canceled child context, so the hook process never actually launches (recorded as timed-out/error in the audit log). Since most sessions end via interrupt, sessionEnd hooks were effectively unreliable. Detach the sessionEnd dispatch from the run context's cancellation with context.WithoutCancel so the dispatcher's own timeout still applies without being short-circuited by the run's cancellation. Co-Authored-By: Claude Sonnet 5 <[email protected]> * test(agent): fall back to GOROOT for hook regression * test(agent): justify GOROOT fallback --------- Co-authored-by: Claude Sonnet 5 <[email protected]> |
||
|
|
6b9c2f0c08 |
feat(aimlapi): AI/ML API provider with guided onboarding (top-up + key issuance) (#655)
* feat(aimlapi): AI/ML API guided onboarding (top-up + key issuance) Add the AI/ML API provider integration to the zero CLI: a guided TUI onboarding sub-flow that takes an existing key or an email top-up and writes the issued key into the provider profile, attributed to the Gitlawb rebate partner. - internal/aimlapi: OpenAI-compatible client, partner-checkout top-up, key validation, streaming, and endpoint config (Paths A/B/C). - providercatalog / providermodelcatalog: register the aimlapi provider with default headers and models. - tui: aimlapi onboarding screen wired into the provider wizard and onboarding flow. Default endpoints point at production (auth/app/api/pay/aimlapi.com); each is overridable via AIMLAPI_*_URL env vars. Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(aimlapi): address review findings in onboarding flow - topup: use errors.As for the 5xx retry check so a wrapped APIError still retries instead of aborting a paid checkout. - onboarding: a Path-A top-up on an email with no account now lands on a single coherent "key saved, balance not topped up" screen instead of a green "ready" plus a red error. - onboarding: an empty amount field is rejected instead of silently falling through to the $25 default. - collapse the two identical top-up prompt constants into MsgTopUpPrompt and drop the dead amountPrompt() branch. - reuse stdlib maps.Clone instead of a third bespoke header-copy helper. Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(aimlapi): retry transient poll errors and sharpen amount validation * fix(aimlapi): retry transient poll errors and surface real amount errors * docs(aimlapi): document exported client API; drop out-of-scope gitignore entry Addresses the two CodeRabbit PR checks on #655: - Add doc comments to the exported aimlapi client API (Client, APIError, the session/checkout/onboarding types and their methods) and ParseAmountUSD, to satisfy the docstring-coverage check. - Remove the unrelated /zero.exe~ .gitignore entry that an earlier review-fix commit added; it is out of scope for the provider work, so the PR no longer touches .gitignore. Co-Authored-By: Claude Opus 4.8 <[email protected]> * fix(aimlapi): render aimlapi row like other recommended providers * fix(aimlapi): resolve onboarding review findings (Path A funding, base URL, request cancellation) * fix(aimlapi): validate pasted API keys * test(aimlapi): clarify pasted key validation * test(aimlapi): cover key-balance low-balance transition * fix(aimlapi): reject non-finite top-up amounts * fix(cli): scope and canonicalize provider headers * fix(tui): distinguish invalid verification codes * feat(aimlapi): resume account-bound top-ups safely * feat(tui): offer top-up for existing AIMLAPI keys * feat(tui): reuse AIMLAPI env key on first run * feat(tui): reuse configured AIMLAPI profiles * test(aimlapi): trim redundant flow coverage * fix(tui): preserve existing AIMLAPI profile metadata * fix(tui): clone AIMLAPI headers in env fallback * fix(aimlapi): address remaining review findings * fix(aimlapi): harden onboarding lifecycle * fix(aimlapi): preserve validated checkout context * fix(aimlapi): harden checkout retry state * fix(tui): clarify AIMLAPI configured choices * fix(aimlapi): make checkout retries idempotent * fix(aimlapi): validate checkout responses * fix(providers): reconcile aimlapi catalog after rebase --------- Co-authored-by: Lookoff123 <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]> |
||
|
|
8ef8576df7 |
perf(tools): add explicit effect metadata for safe concurrency (#705)
* perf(tools): add explicit tool effect metadata Introduce fail-closed EffectClass and ToolCapabilities on every built-in tool so a later concurrent read-only batch PR can decide safety from explicit metadata rather than tool names. Plugin and MCP tools stay EffectUnknown. No concurrent execution is enabled. * fix(tools): address review feedback on effect metadata Harden capability normalization and resource keys before PR6 consumes them: - Force ThreadSafe=false for every non-ReadOnly effect at read time - Reject duplicate builtin names in ValidateBuiltinCatalog - Coerce numeric session ids and accept "session" for process keys - Strip path/query before userinfo in endpoint host parsing - Wire apply_patch keys from unified-diff paths + cwd - Reclassify bash as WorkspaceWrite (one-shot shell mutator) - Expand regression tests for keys, normalization, and gates * fix(tools): keep multiFileResourceKeys used by apply_patch keys Satisfies unused lint after switching apply_patch to applyPatchResourceKeys. * fix(tools): resolve CodeRabbit residual capability/key issues - Join apply_patch diff paths under cwd for conflict keys - Validate constructor-declared capabilities before normalization - Unify session/process ID key extraction helper --------- Co-authored-by: KRATOS <[email protected]> |
||
|
|
1c5c6e78a8 |
feat(perf): emit prompt-prefix hash fingerprint per turn (#704)
Compute a seven-field SHA-256 fingerprint of the cacheable prompt prefix (base instructions, confirmation policy, project context, skills, tools, tool schemas, complete) and emit one prefix_hash event per turn through the trace recorder. The complete hash is the SHA-256 of the canonical join of the other six, so any sub-component drift is observable both individually and in aggregate. The decomposition exists so a downstream observer can detect which sub-component of the prefix broke cache stability across turns: a stable complete_prefix hash means the cacheable prefix is byte- identical, and the sub-hash that changed names the culprit. No behavior change for untraced runs. For OpenAI, the existing prompt_cache_key (session ID) is unchanged; the fingerprint is purely additive observability. The Anthropic cache_control breakpoint work already in main is not touched. Co-authored-by: anandh8x <[email protected]> |
||
|
|
7f669f4550 |
feat(tui): Ctrl+X leader chords and emacs menu navigation (#699)
* feat(tui): Ctrl+X leader chords and emacs menu navigation Add Ctrl+X leader shortcuts for common slash commands (with Ctrl+X ? chord map), and Ctrl+P/N previous/next selection in modals and pickers. * fix(tui): drop Ctrl+X e /edit leader chord /edit replaces the composer and would discard an in-progress draft; leave it unbound so leader shortcuts stay draft-preserving. * fix(tui): consume idle Ctrl+N as a no-op Reserve Ctrl+N for emacs menu navigation so it never falls through to remapped global bindings when no selection surface is open. * docs: drop manual CHANGELOG entries for TUI shortcuts |
||
|
|
2b42cd567b |
fix(tui): stop the composer cursor blinking while typing or unfocused (#672)
* fix(tui): stop the composer cursor blinking while typing or unfocused The composer's cursor blinked on a fixed timer unconditionally, including mid-keystroke, which made typing feel janky since the cursor's job is to mark the current position. Gate the existing blink tick on two states instead: force solid while a keystroke landed within the last 500ms (reusing the existing lastCharTime tracker), and hide it entirely while the terminal window is unfocused (new terminalFocused field, driven by tea.FocusMsg/ BlurMsg). Idle and focused keeps the prior blink behavior unchanged. Also decouples view.ReportFocus from the notifier (it was previously gated on m.notifier != nil, which is effectively always true in production but left cursor-focus behavior fragile if that ever changes) so focus reporting is requested unconditionally. * fix(tui): apply caret visibility synchronously and in the argument-hint line - A keypress, focus, or blur now updates composerCursorVisible in its own message handler instead of waiting for the next 530ms composerBlinkMsg tick, which could render a typed character caret-less, leave a visible caret in an unfocused terminal, or keep it hidden after a refocus. - commandArgumentHintComposerLine takes the caret visibility state instead of painting its cursor cell unconditionally, so the required-argument hint path follows the same focus and blink contract as the main composer renderer. - New tests assert the state immediately after each message, with no blink tick in between, and cover the argument-hint render in both states. * fix(tui): apply the caret state on composer paste A paste is the same immediate-input transition as a keypress: routePaste now refreshes lastCharTime and sets the caret visible for both the bracketed tea.PasteMsg and right-click clipboardReadMsg paths, so a paste right after the blink phase hid the caret renders solid immediately instead of waiting for a tick that would toggle off a stale idle state. |
||
|
|
665bd4b3d0 |
WIP: feat(tui): TUI theme presets (Dune and Neon) (#634)
* draft(tui): add theme interface and Claude/Codex presets proposal Introduces the Theme interface alongside the initial specifications and styling models for the Claude (card-based, warm) and Codex (high-density, split-screen cyberpunk) layout presets. Detailed design plans are documented in docs/THEMES.md. * fix(tui): implement Claude/Codex themes on the existing palette system theme.go had replaced the tuiTheme/buildTheme/palette architecture every renderer depends on with a standalone Theme interface using the wrong lipgloss import, breaking the build and orphaning the existing theme registry, /theme command, and picker. Revert theme.go to the working implementation and add claude and codex as real palette entries in theme_palettes.go, following the file's own extension pattern. Both pass the WCAG AA contrast and gray-ramp invariants asserted across the whole registry, and are picked up automatically by --theme, ZERO_THEME, /theme, and the picker. Rewrite docs/THEMES.md to describe the actual palette-registry architecture instead of the removed interface. * fix(tui): rename Claude/Codex to neutral Dune/Neon, ensure WCAG AA contrast compliance, and add tests * style(tui): gofmt theme_select_test.go (drop trailing whitespace) The ubuntu smoke run fails on gofmt -l; this clears the only unformatted file in the draft so CI goes green. * test(tui): cover the CLI/env resolution path for the dune and neon presets TestNewThemePresetsWired only checked lookupTheme and the IsDark grouping. Add coverage for the actual --theme/ZERO_THEME precedence path (resolveThemeMode) and confirm applyTheme swaps zeroTheme to each preset's own palette rather than a built-in fallback. * fix(tui): raise Dune/Neon palette contrast and fix theme docs Darken dune's accent, blue, and red tokens so the selected-row caret, local-model dot, and diff deletion text clear WCAG AA against selBg and delBg, including after ANSI-256 downsampling. Also correct THEMES.md's description of bare /theme (it opens a live-preview picker, not a list), and add the dune/neon names to the ZERO_THEME/--theme tables in README.md and README_ZH.md. * fix(tui): keep Dune and Neon readable after ANSI-256 downsampling - Dune's accent becomes #724028: still AA on selBg and panel in truecolor (5.46:1 and 7.03:1), and its xterm-256 quantization (#444444) now clears AA on quantized selBg at 6.47:1 where the previous #7c4712 quantized to #875f00 at 3.81:1. Blue already survived quantization at 4.67:1. - Neon's diff bands move to hexes that quantize to distinct green and red xterm entries (addBg #083c10 -> #005f00, delBg #3c0810 -> #5f0000, addBgWord #147828 -> #008700, delBgWord #74202e -> #870000) instead of collapsing to the same grays, while keeping every truecolor invariant. - New TestExtendedThemeANSI256Contrast quantizes the palette tokens the way a 256-color terminal does and asserts the Dune selected-row pairs stay AA and the Neon bands keep their green/red identity and mutual separation. - docs/THEMES.md is now linked from the Documentation sections of both READMEs, and the Unreleased changelog inventory says twelve themes and names dune and neon. * fix(tui): keep Neon diff content AA after quantization, fix error border - faintest becomes #74c468: line numbers now quantize to #87d75f, which clears AA on the xterm-green addBg (#005f00) where the old #58af69 quantized to 2.95:1, while staying dimmer than faint so the gray ramp stays monotonic. - addInk becomes #ecffdc, quantizing to #ffffd7 for 4.60:1 on the word band's #008700 (was 4.29:1). Both rendered diff-content pairs are now asserted in the ANSI-256 test instead of documented as omissions. - cardErr becomes #9a4042: 3.01:1 against the panel (2.43:1 before) and 3.65:1 quantized, meeting the 3:1 non-text component threshold, with the border surface asserted in both renderings. - docs/THEMES.md no longer claims the extended invariants run against the whole registry: it now says new palettes must be added to the per-palette contrast tests or given equivalent coverage. |
||
|
|
9acb4113cc |
feat(cli): show ZERO wordmark on --version (#673)
* feat(cli): show ZERO wordmark on --version The TUI's empty-state ZERO ascii art gets exposed via a new tui.Wordmark helper and printed above the version line for `zero -v`/`--version`. Coloring only applies when stdout is a real TTY and NO_COLOR isn't set, so redirected or piped output (scripts parsing the version) stays plain. * fix(release): match --version output by suffix, not exact string The release smoke check compared zero --version output to an exact "zero <version>" string, which broke once --version started printing the wordmark above the version line. Check for a "version: <version>" suffix instead so the smoke check doesn't couple to the wordmark art. * fix(cli,tui): keep --version machine-readable off-TTY, drop wordmark color - When stdout is not a terminal, --version prints exactly "zero <version>" again: pipes, redirects, command substitutions, and the release smoke contract all parse that single record, so the banner is a TTY-only affordance now. - The TTY path keeps the banner but restores "zero <version>" as the version record instead of "version: <version>". - Wordmark renders uncolored: only newModel resolves --theme/ZERO_THEME, so painting the global dark palette here was unreadable on light terminals. The terminal's default foreground works on any background. - smokeVersion goes back to requiring the exact "zero <version>" output instead of the suffix match that papered over the banner. |
||
|
|
d66a9dda69 |
feat(providers): add AI/ML API preset (rebased onto main) (#621)
* feat(providers): add AI/ML API preset * fix(providers): relocate aimlapi preset and drop referral header Addresses the two review asks on #402: - Move the AI/ML API descriptor out of catalog slot #2 (it sat above the first-party OpenAI/Anthropic/Google entries) down next to openrouter in the aggregator cluster. Re-sync expectedCatalogIDs and the TransportOpenAICompat order-list to match. - Drop the always-on X-AIMLAPI-Partner-ID / Integration-Repo / Integration-Version attribution headers. There's no partner arrangement behind the preset, so a non-overridable referral tag on every user's request isn't something to ship on a community preset. aimlapi now rides through plain CopyHeaders like every other provider. Removes HeadersForCatalog (it existed only for this special-case), reverts the factory and discovery call sites to CopyHeaders, and drops the header test. The factory/discovery tests now assert the endpoint, auth, and the user's own custom headers survive while no attribution header is sent. * fix(providers): address CodeRabbit review on #621 - Document AIMLAPIID for docstring coverage. - Apply providerio.CopyHeaders for Anthropic and Google provider paths, matching the OpenAI path introduced for aimlapi header isolation. Co-authored-by: Cursor <[email protected]> * fix(providers): gofmt factory.go to unblock CI smoke Remove mixed tab/space indentation in the openai.New options block so ubuntu-latest fmt-check passes. --------- Co-authored-by: Dmitry Tumanov <[email protected]> Co-authored-by: Vasanthdev2004 <[email protected]> Co-authored-by: Cursor <[email protected]> Co-authored-by: KRATOS <[email protected]> |
||
|
|
7d579996b4 |
feat(skills): discover shared ~/.agents/skills with multi-root skill loading (#696)
* feat(skills): discover shared ~/.agents/skills with multi-root skill loading Add ~/.agents/skills as a read-only global discovery root after the primary Zero skills dir and before plugin roots. Unify runtime and CLI discovery on one multi-root path (DiscoveryRoots / LoadFromRoots / GlobalRoots) and always register the multi-root skill tool so bare runs and plugin runs share the same load surface. Install/remove/lock still target only the primary Zero skills directory. Fixes #610 * docs(skills): note lock stays on the primary write root Document that install/remove/lock target only the primary Zero skills directory, matching multi-root discovery write-root isolation. * fix(skills): bubble primary root load errors from LoadFromRoots Return non-missing failures from the first non-empty skills root so a broken primary dir is not reported as an empty skill list. Keep fail-open behavior for optional roots (~/.agents/skills, plugins). * fix(skills): treat non-directory skills root as error on Windows Windows maps ENOTDIR to ErrNotExist, so ReadDir on a regular file looked like a missing skills dir. Reclassify existing non-directories and assert the portable load behavior in tests. |
||
|
|
abe24bcb10 |
Add per-turn tracing and a turn-benchmark harness (Phase 0 baseline) (#700)
* Add per-turn tracing and a turn-benchmark harness (Phase 0 baseline)
Introduce an opt-in `internal/trace` package that attributes a run's wall
time to named spans — prompt build, provider connect/queue, generation,
tool queue/execution, permission wait, verification, compaction,
persistence — plus counters for model requests, tool calls, retries,
reconnects, compactions, completion nudges, acceptance checks, model
switches, and input/cached/output tokens. The recorder is nil-safe (a nil
*Recorder is a no-op), so every stamp site can call it unguarded; an
untraced run is byte-identical to today.
The recorder threads into agent.Run via a new `Options.Trace` field and a
single `trace.WithContext` assignment, so the shared providerio seam
(connect around client.Do, queue around OAuth resolve, first token at the
first non-keepalive SSE payload) reaches it through `trace.FromContext`
without any provider-interface change. All instrumentation is opt-in: when
`Options.Trace` is nil the hot path is untouched.
Headless `zero exec` gains `--trace <path>` (and `ZERO_TRACE=<path>`),
writing an agenteval-contract-compatible NDJSON trace (`-` for stderr).
The trace is pure observation; enabling it does not change behavior.
A new per-turn benchmark harness extends `internal/perfbench` (reusing the
existing TaskSet/NumericStats/subprocess runner shapes): `RunTurnBench`
aggregates per-span median/P95 across iterations, ranks the top three
controllable latency sources by share of attributed time, rolls up per
task class, and totals tokens/counts. A checked-in 42-task manifest across
seven classes (nav, edit, fix, refactor, longproc, longctx, parallel) and
local offline fixtures back it, and `make baseline` runs it against the
built `zero`, writing `internal/perfbench/reports/baseline.json`. The
report is machine-specific and regenerated, not hand-edited.
The manifest is JSON rather than YAML to avoid adding a dependency;
per-task verification targets the specific test (`go test -run <name>`)
so fix tasks are independent. Each task is a fresh `zero exec` process, so
iterations are cold-start samples; a warm in-process path is left for
follow-up.
Verified: `go build ./...`, `go vet ./...`, `gofmt -l` clean on touched
files; `go test -race` green for internal/trace, internal/agent,
internal/perfbench, and cmd/zero-perf-bench.
* test(trace): make TestAttributionRatio clock-independent
The test recorded two synthetic 10ms spans with no real delay and asserted
the attribution ratio stayed <= 1.0. On a precise clock (Linux/macOS CI) the
wall is microseconds while attributed is 20ms, so attributed/wall blows past
1.0 and the test failed — even though the ratio legitimately exceeds 1.0
when spans overlap (parallel tool execution). On Windows the coarse timer
masked it, so the failure only surfaced in CI.
Assert the contract instead: attributed is the deterministic sum of span
durations, and the ratio equals attributed/wall (0 when wall is zero). Add a
zero-wall case for the divide-by-zero guard.
* Address review feedback on the per-turn tracer + benchmark harness
Benchmark runner (internal/perfbench):
- A nonzero agent exit code no longer counts as Passed. The runner set
VerifyErr but then fell through to outcome.Passed = true, so a crashed
run could be recorded as a pass. Return before the verification block
when VerifyErr is already set.
- A task now passes only when every iteration passes. The previous
"any iteration passes" rule let a flaky fail hide behind one good run.
- Each invocation copies its workspace fixture to a fresh temp dir and
runs the agent + verifier there, so mutating tasks (edit/fix/refactor)
can't dirty the checked-in fixtures or bleed into the next iteration.
- Trace load failures (missing/malformed trace file) are surfaced as
result warnings via a new TraceIssue field instead of being swallowed.
Tracer (internal/trace):
- Finish freezes the recorder: Counter, addSpan, and the first-event
stamps are dropped after Finish returns, so a snapshot captures exactly
one turn and late stamps can't mutate it.
- WriteText propagates the first write error instead of silently
swallowing sink failures.
Provider seam (internal/providers/providerio):
- FirstTokenAt is stamped only after a payload is accepted as real model
output, not before handle() decides to drop it as an error payload.
Agent loop (internal/agent):
- The compaction counter increments only when the history actually
shrinks, in both the proactive and reactive paths — paid no-ops no
longer inflate the compaction total.
- Document on Options.Trace that a fresh Recorder is required per Run.
CLI (internal/cli/exec):
- Finish the trace at the agent.Run boundary and defer only the
serialization, so the snapshot covers exactly the turn.
- Reject --trace / ZERO_TRACE for spec-draft runs with a clear error
instead of silently accepting and writing nothing.
Manifest + fixtures:
- Strengthen the edit verifiers: rename and value-bump tasks now assert
the old symbol is gone, and the getter task greps the method signature.
- Add the missing testdata/edit fixture (10 edit tasks referenced a
directory that was never committed, so the whole edit class errored
out at run time).
- The manifest test now stats each fixture so a missing dir fails at
load time instead of at every run.
- longproc benchmark keeps Process live across the loop; README code
fence gets a language tag and documents the per-invocation fixture copy.
- Add tests for the tracing wrapper (nil vs wired recorder, OnUsage
forwarding, token aggregation) and the frozen-recorder contract.
* Address review feedback: exclusive-time attribution + wired spans + fix-suite compile
Rewrite the trace attribution model so concurrent/nested spans no longer
double-count. Spans record wall intervals; the recorder derives a parent for
each by interval containment and computes exclusive time (duration minus the
union of its children). AttributedDuration sums only top-level spans; Coverage
is the union of intervals over wall, capped at 1, the honest ">=95%" metric.
Top-latency ranking uses exclusive time so shares sum to ~1 instead of being
inflated by overlapping provider_connect/generation and nested
permission_wait/tool_execution.
Wire the spans that were named but never stamped: verification around the
self-correct verify pass, and StampFirstVisibleEvent on the first forwarded
text event. Drop the tool_queue/process_wait/persistence constants that had no
stamps, and rename the misnamed near-noise prompt_build span to tool_partition.
Trim RequiredEventKeys to events a healthy short run actually emits; move
conditional phases (tools, permission, compaction, verification) to
OptionalEventKeys so a short trace does not false-fail.
Make ReadNDJSON fail loudly on a corrupt file: require a type:trace header,
error on span/counter lines that appear before it, and error when a header
yields no spans or counters.
Fix the blocking fix-suite compile error: Label's %d format verb on a string
tripped go test's vet before any fix task could verify. Make it a runtime
wrong-prefix bug ("admin-" vs "user-") that still type-checks, so each fix
task is independent again.
Manifest: correct refactor-04's stats.go to main.go, reword fix-07 to the
wrong-prefix bug, add a latency-only description (read-only tasks carry no
verification oracle by design), and frame the report README on exclusive time
and coverage.
Tests updated for the occurrence/exclusive/coverage model; trace, perfbench,
and agent green under -race.
* Fix exclusive-time round-trip, identical-interval nesting, and empty-trace parse
Three correctness bugs in the attribution model found by an adversarial review
of the prior commit, all undermining the no-double-count guarantee:
1. exclusive_ms:0 was overwritten with the inclusive Duration on re-parse. A
parent whose children tile its interval legitimately has exclusive time 0;
WriteNDJSON emits exclusive_ms:0, but ReadNDJSON's "<= 0 means missing"
fallback turned that back into the full duration, so every re-parsed trace
re-introduced the double-counting the model exists to prevent (the benchmark
harness, which ranks re-parsed traces, inherits it). Gate the fallback on
key ABSENCE, so a written 0 is preserved.
2. deriveNesting formed a 2-cycle for two spans with identical [start, end]
intervals: the symmetric containment check made each parent the other,
dropping both from top-level and zeroing AttributedDuration. Break the tie
deterministically — strict containment is an unambiguous parent; for
identical intervals, only the lower-indexed span may parent the other, so
one stays top-level.
3. ReadNDJSON returned a valid empty TurnTrace for empty/blank-only input, so a
run that crashed before emitting a trace (or where --trace was not honored)
masqueraded as a clean zero-attribution sample. Empty input now errors, so
the harness records a TraceIssue.
Also: decode counters via json.Number so int64 values above 2^53 round-trip
exactly instead of losing precision through float64; and drop the harness's
own "exclusive <= 0 -> Duration" ranking fallback, which was the same
double-count on the re-parsed path. Added regression tests covering the
zero-exclusive round-trip, the identical-interval nesting, empty-input
rejection, and counter precision.
* perf(turn-bench): split pass/fail into oracle tiers so the pass rate can't be misread as correctness
The turn benchmark counted any exit-0 task as passed, including the 24
read-only tasks (nav/longproc/longctx/parallel) that carry no verification
oracle — so the JSON tasksPassed / "N/M passed" headline could read as a
correctness verdict it never was. Split pass/fail into three oracle tiers:
- correctness (edit grep + fix go test): the only pass rate that can move
with model quality (tasksVerified / tasksPassed / correctnessPassRate)
- build-only (refactor go build): a non-positive oracle, reported as
buildPassRate and excluded from correctnessPassRate; the manifest declares
buildOnlyClasses: ["refactor"]
- latency-only (no oracle): counted in latencyOnlyTasks, never in any pass
rate
NewTurnExecRunner no longer sets Passed=true for tasks with no
verificationCommand, and the summary headline names each tier explicitly so
an exit-0 read-only run cannot inflate a correctness number. Bumps
TurnSchemaVersion to 2. Class breakdown and tier contract documented in
internal/perfbench/MANIFEST.md and reports/README.md.
Also install the trace first-token/visible-event forwarding handlers in
agent.Run whenever options.Trace is set, not only when a UI OnText /
OnReasoning callback is set, so a headless traced run (zero exec --trace)
still captures FirstTokenAt. forwardedVisibleText stays tied to the user
callback so stall-retry semantics are unchanged. Adds a regression
assertion to the existing traced-run test.
* Address CodeRabbit review on
|
||
|
|
75a78e715b |
feat(cli): wire MCP serve WorkspaceRoot and --add-dir scope (#694)
* feat(cli): wire MCP serve WorkspaceRoot and --add-dir scope Pass the resolved workspace into mcp.ServeOptions and honor --add-dir so resources/list and scoped tools can expose extra roots beyond cwd. * fix(cli): keep lexical serve --add-dir roots for path matching Use symlink-resolved paths only for deduplication so Scope roots stay aligned with the un-evaluated WorkspaceRoot MCP serve already passes. |
||
|
|
8502449430 | test(tui): isolate provider wizard credentials (#688) | ||
|
|
c0bcfdda41 |
chore(deps): bump go module dependencies (#669)
Updates charm.land bubbles/bubbletea/lipgloss v2, golang.org/x/sys, golang.org/x/sync, and dlclark/regexp2/v2 to their latest patch releases. |