The SSH renderer previously declared 'provisionalPendingPreview: "collapsed"',
which only opted the COLLAPSED pending shape out of the transcript's
stable-prefix ratchet. Once the user expanded an in-flight SSH preview (ctrl+o)
and the framed block outgrew the viewport, the pending rows became
ratchet-eligible and committed to native scrollback before the result render
inserted the 'Output' section. The settled render then re-anchored the frame,
producing two distinct stranded shapes in history:
- a stale 'pending SSH: [host]' header pinned above the final '<- SSH: [host]'
frame (header variant), and
- the pending bottom border row reused in-place as the new 'Output' separator,
with a fresh '...' footer pushed below it (footer variant).
Flip 'provisionalPendingPreview' to 'true' so every pending shape — collapsed
or expanded — is treated as provisional and stays out of native scrollback
until the result render commits a settled frame. The 'collapsed'-only opt-out
remains correct for renderers (bash, eval) whose expanded pending preview is
top-anchored and survives the result render without re-anchoring.
Added two contract tests asserting expanded pending SSH is commit-unstable
and that bash/eval expanded pending preview is still commit-stable — keeping
the opt-in renderer-scoped.
Fixes#3714
- Introduced guest snapshot reconciliation to maintain host state consistency during session switching.
- Improved yield tool reliability by implementing incremental schema validation and strict parameter enforcement.
- Fixed a calculation edge case in the status line to prevent negative time values during activity tracking.
- Expanded the test suite with new validation for session interruption, collab state synchronization, and process error handling.
- Added logic to `assembleYieldResult` to automatically accumulate incremental yields into arrays for schema-identified array properties.
- Updated `YieldTool` to bypass schema validation for incremental stream yields, allowing partial data emissions that don't satisfy the full output schema yet.
- Enhanced `YieldTool` parameter declaration to remove blocking top-level JSON schema combinators, ensuring compatibility with strict-mode providers (OpenAI/Codex).
- Updated `parseYieldType` to gracefully handle `null` type values emitted by strict providers for untyped final yields.
- Added regression tests for array-valued findings alignment and strict-mode tool schema compatibility.
- Extended `tools/yield.ts` with typed incremental sections, raw last-turn terminal results, and updated yield guidance in the subagent system prompts.
- Reworked `task/executor.ts`, `task/render.ts`, and `task/types.ts` to assemble typed yield sections, render reviewer results from incremental yield data, and preserve the typed result shape.
- Switched `prompts/agents/reviewer.md`, `review-request.md`, and `review-custom-request.md` from `report_finding` calls to incremental `yield` sections.
- Added incremental-yield coverage in `test/task/executor-warnings.test.ts`, `test/task/render-yield-shape.test.ts`, `test/tools/yield-extraction.test.ts`, and `test/tools/yield.test.ts`.
- Implemented mutable session titles with audit tracking, including storage persistence for SQL and Redis backends.
- Added comprehensive session management features such as idle recap triggers, incremental subagent yield submissions, and automated title refreshing.
- Enhanced task tracking in the TodoTool with progress prioritization and improved session cleanup logic.
- Introduced citation tag handling for OpenAI-compatible source markers and improved edit parsing.
Kept the ask tool question and options visible when switching to the Other custom input editor.
Added a regression test for the custom editor title context.
Fixes#3660
The CI gate failed in several non-overlapping ways once the full coding-agent suite ran here: tests wrote into the real $HOME (`/srv/agent-home`) which is read-only, a fixture rotated stored Anthropic API keys but Settings reloaded the user models.yml and shadowed them, an OAuth callback server bound to `hostname:"localhost"` (loopback unreachable in this runtime), a built-in tool metadata assertion only saw `github` when `gh` was installed, and the GithubTool `pr_checkout` worktree assertions assumed `~/.omp/wt` but `XDG_DATA_HOME` redirected `getWorktreesDir()` to `$XDG_DATA_HOME/omp/wt`.
Fixes:
- `packages/ai/src/registry/oauth/callback-server.ts`: drop `hostname: "localhost"` when no caller-supplied hostname overrides it. Bun on Linux refused inbound connections to the listener when bound explicitly to `localhost`; defaulting to Bun.serves default binding restores loopback connectivity.
- `packages/coding-agent/test/status-line-path.test.ts`: route the `~/Projects` fixtures through a writable temp home (spy `os.homedir()`), housed under the repo `.wt/` worktree scratch so the temp home is not classified as a status-line scratch root.
- `packages/coding-agent/test/skills.test.ts`: ditto for the `~/.pi-skills-test-*` mkdtemp in the tilde-expansion test.
- `packages/coding-agent/test/marketplace/project-scope.test.ts`: stop writing `~/.git`; build the entire home-dir guard fixture in a temp dir and spy `os.homedir()`.
- `packages/coding-agent/test/oauth-flow.test.ts`: wrap each callback fetch in a brief retry so the simulated browser redirect tolerates the few-ms gap before the Bun callback server starts accepting connections.
- `packages/coding-agent/test/tools/gh.test.ts`: extend `setupTempHome()` to clear `XDG_DATA_HOME`/`XDG_STATE_HOME`/`XDG_CACHE_HOME` for the duration of the test so the rebuilt dirs resolver routes `getWorktreesDir()` back through the spied home, then restore them on cleanup.
- `packages/coding-agent/test/tool-discovery/initial-tools.test.ts`: instantiate `GithubTool` directly in the metadata fixture so the assertion runs even when `gh` is unavailable (GithubTool.createIf returns null without `gh`).
- `packages/coding-agent/test/agent-session-retry-cap.test.ts`: pass an isolated `models.yml` path to `ModelRegistry` so the two-Anthropic-key fixture is the authoritative credential source instead of any user-level command-backed Anthropic key.
Verification:
- `bun check` → passed
- `bun run test` (full coding-agent suite, 4 buckets, all chunks) → 0 fails
Fixes#3639
- Update `acp-builtins.test.ts` to support interactive session movement and path session file testing.
- Clarify `/move` command routing in `/move` slash command tests.
- Rename internal `search` references to `grep` to align with product definitions.
- Refactor TUI render tests to use `Promise.withResolvers` for cleaner flow control.
- Redesigned the Todo HUD as a connector tree with fixed-budget stage previews.
- Anchored status and HUD containers to prevent redundant UI elements in terminal scrollback.
- Implemented tree-based rendering for project phases and tasks while removing dynamic border rules.
- Upgraded `sherpa-onnx` and related packages to support current infrastructure.
- Enhanced the edit renderer to support visual tracking of delete and move/rename operations.
- Updated diff computation to correctly handle file-level changes and suppress erroneous "No changes" warnings.
- Improved terminal output with a clearer activity indicator and accurate state representation during multi-file operations.
- Extended the rendering pipeline to display source-to-destination paths for file renames and added validation tests for edit workflows.
- Renamed the `find` and `search` tools to `glob` and `grep` respectively across the codebase to improve command clarity.
- Implemented full-stack support for the renamed tools, including CLI arguments, system prompts, SDK exports, and tool registration.
- Added automated migration logic in `settings` to transform legacy `find` and `search` configuration keys to their new equivalents.
- Updated the `collab-web` renderer registry to ensure backwards compatibility with legacy tool outputs.
- Removed the enforced minimum length for the `items` array in the tool schema to prevent unnecessary validation errors on operations that ignore the field.
- Added tests to verify schema acceptance of empty `items` arrays and confirmed that runtime errors are still correctly thrown for empty inputs during specific operations like `append`.
- Suppressed main-UI relay for sibling broadcast legs when the main agent is a direct target.
- Added `suppressRelay` option to `IrcBus.send` to allow selective disabling of relay rendering.
- Ensured broadcast fan-outs avoid rendering the same message twice in the main transcript.
The >4 MiB JS fallback re-introduced regex-dialect divergence by size. Oversized line-mode virtual resources are now searched in line-boundary chunks (each <= NATIVE_GREP_MAX_FILE_BYTES) through native grep, with matched line numbers offset by each chunk's start, so RE2 dialect parity holds for all line-mode sizes. A single line larger than the cap (un-grepable) is JS-tested individually. Multiline keeps the JS fallback, since chunk boundaries would drop cross-line matches. Test now proves (?i)NEEDLE matches a >4 MiB resource.
- search: native grep silently skips files above NATIVE_GREP_MAX_FILE_BYTES (4 MiB), so the RE2 virtual path dropped matches for large virtual resources (history://, big artifacts). searchVirtualResources now falls back to a JS RegExp matcher for oversized content (the pre-RE2 behavior) while keeping native parity for normal sizes; buildVirtualMatches still rebuilds context/ranges.
- ssh write: a trailing-slash target (ssh://h/dir/) is now rejected before staging, so the mkdir -p parent-creation no longer leaves a directory behind on a refused write.
- search: the native virtual probe capped matched-line detection at INTERNAL_TOTAL_CAP before range filtering, so a ranged virtual selector (ssh://h/log:5000-5100) over a file with >2000 earlier matches returned nothing. The probe now uses the line-count bound for ranged resources so range filtering sees every hit.
- ssh write: writeRemoteFile staged into a temp beside the destination before creating parents, so writing a new nested path failed with 'No such file or directory'. It now mkdir -p's the parent before staging, matching local write, keeping the directory/special-file refusal checks.
searchVirtualResources matched with JS RegExp, so an ssh:// (or other virtual) search diverged from local search for RE2-valid but JS-invalid patterns (e.g. (?i)x, [[:digit:]]) — throwing 'Invalid regex' or returning different matches even when local grep had already validated the pattern in a mixed scope. It now detects matched line numbers with native grep (the same RE2 matcher local search uses) and rebuilds the existing forward-only, range-trimmed context windows via buildVirtualMatches, so virtual/remote and local search share one dialect. Removed the now-dead JS-regex helpers (probeRegexDialect, compileVirtualRegex, searchVirtualResource{Lines,Multiline}, findLineIndex).
search's generic glob-char guard saw the [ ] of an IPv6 authority (ssh://[::1]/etc/hosts) and threw 'Glob patterns are not supported' before the SSH handler could strip the brackets. The check now runs only on the path portion for ssh:// URLs, so IPv6 literals resolve while a glob in the remote path (ssh://host/p*) still rejects.
- reject explicit ssh:// port 0 before connecting (Codex P2)
- keep a path-less ssh://host:port authority port out of selector peeling
- restore ResolveContext on ProtocolHandler.complete (symmetry with resolve/write)
- clarify search/read selector-parity docs + add read-side regression
- `read ssh://host/dir` lists a remote directory one level deep; `ssh://host/` lists the remote root
- add statRemotePath + listRemoteDir; resolve reads first and classifies on error (directory -> one-level listing, dirs-first, dotfiles included)
- directory resources carry isDirectory + immutable and expose no sourcePath
- search refuses a virtual (no-sourcePath) directory resource instead of grepping the listing text
- writeRemoteFile refuses a directory destination and cleans up its temp on that path
- buildSshTarget rejects destinations beginning with "-" (SSH argument-injection / local RCE guard)
- gate ssh:// read/search/write at the exec approval tier; substring scan covers search's pre-expansion delimited paths and write's hashline-wrapped paths
- validate the entire materialized buffer as UTF-8 instead of only the first 8 KiB prefix
- write peels read selectors (raw/conflicts) so it targets the same file read does, and rejects line-range/malformed selectors instead of silently stripping them
- write to a uniquely named remote temp; document symlink-replacement on write as a v1 limit
The reload-cache regression fixture used ReturnType<typeof loadConfig>,
which violates the repository style rule banning ReturnType<>. Import and
use the explicit LspConfig type instead.
Fixes#3546