hub start and for:"ready" waits polled the live daemon state, so a process
that flipped starting→ready→exited within one 50ms poll interval was only
ever observed as "exited" and the wait blocked for the full readiness
timeout — despite #markReady durably recording readyAt. A pre-ready exit
had the same failure since terminal states only woke the wait during broker
shutdown.
Wake both waits on readyAt !== undefined || terminalState(state); readyTimedOut
= !ready then falls out. The start renderer reports "Process exited before
readiness was observed." for a pre-ready exit. Adds two regression tests that
hang to their caps on the old code.
Fixes#6303
Emitted session_shutdown after model rendering and centralized managed timer cleanup across one-shot listings and agent sessions.
Added regression coverage for the extension shutdown lifecycle.
Fixes#6297
clampTimeout resolved the per-tool default (bash 300s) whenever the agent
omitted `timeout` and only enforced the tool's own min/max, so the
tools.maxTimeout global ceiling — applied solely in sdk.ts on explicitly
numeric args — was bypassed on the common default-fallback path.
Thread maxTimeout into clampTimeout so the resolved effective timeout,
including the default path, is capped before the per-tool floor/ceiling
apply. Explicit values below the cap still win; maxTimeout <= 0 stays
no-cap. Applied at every call site (bash, eval, browser, debug, lsp,
fetch, and the session-level bash executor), and the bash clamp notice
now names the global ceiling when it is the binding limit.
Fixes#6294
- Carried startup-selected fallback role and primary selector into AgentSession.
- Continued remaining role fallback entries after the startup fallback fails.
- Added regression coverage for chained startup failover.
Fixes#6283
Versioned request-header restoration metadata inside v10 cache rows so only markers written by the old id-only matcher can bypass an unrestorable marker through requestModelId. Current aliases whose live headers differ from their static base remain unresolved and are refetched or dropped.
Added catalog and startup-registry regressions for custom-header aliases while preserving legacy Copilot -1m cache recovery.
Fixes#6284
Copilot -1m long-context variants are synthesized with transport
headers and a requestModelId to a bundled base. The v10 cache omits
headers; the writer only matched a same-id static entry, so these
variants were flagged unrestorable and dropped on the next offline
read, vanishing from the picker with a "Could not restore model"
warning. The startup registry loader dropped them the same way.
Restore/match headers through requestModelId in the cache writer, the
model-manager restore path, and the coding-agent startup loader, and
bypass a stale unrestorable marker written by the old id-only writer.
Fixes#6284
- Carried configured role identity through deferred CLI model resolution.
- Consulted ordered authenticated role fallbacks after unavailable primaries.
- Added startup regression coverage for missing primary and fallback entries.
Fixes#6283
resolveCodexDiscoveryAccounts now returns null when any stored Codex OAuth
account fails to resolve (e.g. a transient refresh failure), and the manager's
resolveAccounts callback propagates null to skip discovery. Previously a failed
account was silently dropped before unionCodexModels saw it, so the remaining
accounts were unioned and cached as the authoritative catalog, hiding the
failed account's models for the cache TTL. Aborting keeps the previous/bundled
catalog.
Add a ModelRegistry regression test with one refreshable and one failing Codex
account asserting discovery makes no /models call and bundled models survive.
Fixes#6265
Keep the bearer resolved by the discovery preflight when it is not among the
stored OAuth account resolutions. This preserves Codex discovery for env,
runtime/config override, and stored non-OAuth credential sources while still
unioning all configured OAuth account catalogs.
Add a ModelRegistry regression test that drives runtime-key discovery and
asserts the resolved bearer reaches the Codex models endpoint.
Fixes#6265
Serialize queued global saves and remove opportunistically persisted roles from the pending set after a successful write when their value has not changed again. This prevents a redundant follow-up save from replaying stale local values over newer external edits.
Expanded the locked-save regression to externally change the same role after the first write and verify flush leaves that newer disk value intact.
Snapshot model roles when each save starts and merge any newer pending values before replacing the in-memory global settings. This keeps a second model switch intact while an earlier locked save is in flight.
Added a deterministic regression test that blocks the first file-lock operation, changes another role, and verifies both memory and disk.
Global setModelRole marked the whole modelRoles path modified and
saveNow overwrote the freshly re-read disk record with the stale
in-memory map, clobbering concurrent/external per-role edits despite
the re-read intended to preserve them. Track modified global roles
individually and merge only those into the re-read file, mirroring the
project save path.
Fixes#6260
A non-retriable provider error on the continuation turn after a failed
tool result ended the run, but #persistSessionMessageIfMissing dropped
the empty error turn as reload poison, so the session JSONL stopped at
the last tool result and the provider errorMessage was lost with no
durable record of why the run stopped. When retry, model fallback, and
compaction all decline the turn, the non-retry terminal error tail now
persists it via the same helper the retry-lifecycle dead-ends use; the
empty turn stays off the wire on reload via the transform-messages
empty-assistant filter, matching the existing retry-exhaustion path.
Fixes#6249
Used the shared JSON Schema validator for Type.Unsafe so direct safeParse calls and composed shim schemas reject invalid values while preserving valid input identity.
Added direct and composed runtime regression assertions.
MCP-backed tools never declared an explicit strict value, so OpenAI-family
serializers (post-#4336/#4340) had no false to preserve and models over-filled
mutually exclusive optional fields. Task/subagent proxies also rebuilt a raw
tools/call instead of executing through the source MCPTool, bypassing intent
stripping, placeholder pruning, local-URL resolution, reconnect, abort, and
result metadata; strict servers rejected proxied calls with
unrecognized_keys ["i"].
- MCPTool/DeferredMCPTool now declare `readonly strict = false as const`.
- createMCPProxyTools delegates to the current source tool, re-resolved by raw
MCP server/tool metadata so reconnect replacements are honored, and keeps the
Task 60s timeout by combining its abort signal with the caller's.
- Regression coverage: strict flags, proxy parity for i/placeholder shaping,
declared-i passthrough, and reconnect re-resolution.
Fixes#6208
Preserved raw JSON Schema documents while keeping the shim validator identity-based, allowing provider wire conversion and runtime argument validation to consume MCP input schemas.
Added direct shim and remapped-extension regression coverage.
Fixes#6221
- Exempted the handler-less conflict:// scheme from the URI-like guard so parseConflictUri still splices registered blocks.
- Extended the near-miss regression to assert conflict://1 reaches the resolver.
Fixes#6123
The fullscreen Plan Review overlay stayed mounted, focused, and fully
interactive after a choice was picked. showPlanReview's finish() resolves
the choice promise but deliberately defers the hide until #approvePlan
(guarded for #5688/#5319/#5689), so during slow async approval work — e.g.
context compaction — arrow keys still moved the cursor and repeat
Enter/Esc were silently swallowed by the settle guard, with the
fullscreen buffer masking all progress underneath. Users on Ghostty and
macOS Terminal read this as "/plan is frozen".
The overlay now flips to a committed state the moment a choice fires:
handleInput is a no-op, and the render shows a "<choice> — submitting…"
indicator plus an "applying your selection" footer. The deferred-hide
timing is untouched, so the existing stale-buffer and focus guards stay
intact.
Fixes#5926
The read-only git helpers spawned `git` directly and only inspected the
result exit code. When git is absent from PATH, Bun's spawn throws ENOENT
(uv_spawn 'git') at launch, which escaped as an unhandled rejection and
crashed the process (e.g. Windows without git, relying on WSL git).
Translate a missing-binary launch failure into a non-zero GitCommandResult
in the async git() wrapper and into a null degrade in the sync reftable ref
readers, matching the existing non-zero-exit handling. Mutating/checked
commands keep surfacing the clean "git is not installed." error.
Fixes#6169
Bundled the release history as a fallback when package assets cannot be resolved, while preserving external package-directory overrides.
Added regression coverage for the compiled-binary fallback.
Fixes#6172
On a cache-cold interactive launch, a modelRoles.default pointing at a
models.yml discovery provider (openai-models-list) was silently replaced
by an unrelated authenticated provider's default. createAgentSession
resolves the default role before background discovery populates the
catalog, so the configured provider had no models yet and the fallback
fell through to pickDefaultAvailableModel.
Await one cache-aware discovery pass and re-resolve the configured
default whenever it is still unresolved (not only when nothing resolved
at all) before accepting a bundled-provider fallback.
Fixes#6162
Made startup resolution rank configured providers before catalog-only matches while preserving explicit provider pins and catalog fallback behavior.
Added regression coverage for shared bare model IDs across authenticated and unauthenticated providers.
Fixes#6150
- Seeded dirty-baseline blobs into the parent object database before reconstructing filtered agent commits.
- Used three-way synthetic-tree application for committed and trailing task state while preserving parent WIP.
- Added a focused merge regression covering unrelated edits in the same tracked file.
Fixes#6135
Short-circuited session_stop emission when an abort or disposal is already in progress, avoiding extension work whose result cannot be used.
Added deterministic coverage for an abort racing the final settle pass.
Fixes#6134
The single 30s hang-guard from #4290 (sized for metadata fetches per #4229)
wrapped every Hindsight op including reflect, an agentic retrieve+synthesize
call whose healthy latency routinely exceeds 30s, so ordinary successful
reflects were aborted client-side.
- Give HindsightApi per-op deadlines with sensible defaults (reflect 120s,
retain 60s, recall/request 30s) plus a HindsightTimeouts option bag.
- Report the effective seconds in the timeout error instead of a hard-coded
"after 30s".
- Add hindsight.requestTimeoutMs / reflectTimeoutMs / recallTimeoutMs /
retainTimeoutMs settings and matching HINDSIGHT_*_TIMEOUT_MS env vars.
- Preserve caller-abort merging via withTimeoutSignal.
Fixes#6125
- Blocked malformed and unregistered URI-like paths before filesystem resolution.
- Suggested canonical xd:// spelling while preserving explicitly escaped local paths.
- Added regression coverage for xdt://, xd:/, and xd/ near misses.
Fixes#6123
- Dismissed the fullscreen plan review and settled its pending choice when a turn-ending provider error is pinned.
- Added regression coverage for restored editor focus and visible error presentation.
Fixes#6086
Removed display-only home shortening from the provider-facing project prompt and covered home-relative project paths with a regression test.
Fixes#6100
Tracked active subagent session model changes in progress snapshots so prewalk handoffs replace the starting-model badge.
Added regression coverage for a prewalk handoff and documented the fix.
Fixes#6083
#spawnTcp reserved a port, released it, spawned the adapter, then immediately
connected. On WSL2 with networkingMode=mirrored the Windows relay keeps
accepting connections to the just-released reservation port for tens of ms, so
the first connect bound a ghost socket instead of js-debug. Gate the connect on
the adapter announcing its listening port on stdout (as vscode-js-debug does
from inside its listen callback) so we only connect once the child owns the
port; this also drains stdout, which nothing else consumes in tcp mode.
Separately, the message reader only rejected pending requests from its catch, so
a clean stream end (the ghost socket dropped after we wrote initialize) left
requests and event waiters pending until their own timeout. Route both the
reader end and adapter exit through a single #failConnection that rejects
pending requests and wakes event waiters, turning every transport failure into
an immediate "DAP connection closed" error instead of a silent 30s timeout.
Fixes#6055
The TUI /usage matrix sorted each quota window's account columns
independently by used fraction, so the positional `account N` labels
denoted different credentials on each row. An account exhausted on one
window but light on another (e.g. a Kimi Code account's 5h limit)
rendered its exhausted bar under a sibling that still had quota, making
the section `% free` and capacity numbers look wrong.
Order account columns once per provider (worst-first) and hold that
order stable across every window row.
Fixes#6067
- Classified registered extension commands before automatic title generation while preserving normal and prompt-producing slash command titles.
- Added regression coverage for one-shot local handling, unnamed-session preservation, and forwarded prompt eligibility.
Fixes#6061
The flat single-spawn task wire schema carries arktype `"+": "delete"`, so a
batch `{ context, tasks[] }` payload sent while `task.batch` is disabled has
those keys stripped and is then rejected as `task must be a string (was
missing)` in the agent loop. That preempts the tool's own actionable checks
(validateShapeParams / validateSpawnParams), so the model only ever saw the
misleading arktype error instead of "task.batch is disabled…".
Mark TaskTool with lenientArgValidation so the agent loop forwards the raw
args to execute() on any arktype failure, letting the tool's shape checks
surface the real reason. Valid calls still normalize through arktype; the
success path is unchanged. Mirrors the existing yield-tool pattern.
Fixes#6039
prewalk.enabled resolved the hand-off target (default @smol) and threw
"No API key for <provider>/<id>" inside buildSessionOptions when the
target had no configured auth, aborting startup and locking the user out
of the app. Prewalk is an optional, off-by-default optimization, so an
unresolvable or unauthorized target now warns and leaves prewalk unarmed
instead of crashing.
Fixes#6064
Persisted the retained user-turn cursor in transcript metadata and restored it when sessions resume, including legacy rows from before the cursor existed.
Made forced lifecycle retention slice from that cursor so enqueue and shutdown no longer add cumulative snapshots beside incremental windows.
Fixes#6058
- Intercepted the configured smart-paste shortcut while a paste-capable modal owns focus.
- Added regression coverage for Ctrl+V API-key entry and documented the fix.
Fixes#6057
A stream that stalls or aborts mid-tool-call ends the assistant turn with
stopReason error/aborted, then appends a synthetic tool_result per un-run
tool call to keep the provider's tool_use/tool_result pairing intact. That
placeholder trailed the failed turn, so AgentSession.retry() — which only
inspected the last message and required role assistant — short-circuited to
false and /retry printed 'Nothing to retry'.
retry() now walks back over trailing synthetic tool results (details
__synthetic true) before the assistant + stopReason check, stripping both
the placeholders and the failed turn. Only synthetic results are skipped, so
a turn whose tools actually ran stays non-retryable. Adds an exported
isSyntheticToolResultMessage guard in agent-loop.ts.
Fixes#6056
Preserved settled user and assistant component instances during compaction-only transcript rebuilds so warmed Markdown and layout caches remain valid.
Covered both manual and automatic compaction paths with focused regression tests.
Fixes#6033
- Stopped asynchronous tool-result handling from overwriting newer todo state.
- Added an AgentSession regression for six exclusive completion calls.
Fixes#6148
Merged provider compatibility configuration after dynamic discovery so explicit models.yml fields remain authoritative across refreshes.
Added a regression covering Anthropic unsigned-thinking replay through refreshProvider.
Fixes#6041