runRootCommand called discoverAuthStorage without a try/catch, so a
configured-but-unreachable broker with no cached snapshot re-threw
AuthBrokerError as a raw uncaught exception at startup, unlike the other
startup paths that print a clean stderr message and exit non-zero.
Wrap the startup auth discovery: broker failures now report an actionable
message naming the broker URL and the recovery options (start it with
`omp auth-broker serve`, or reset `auth.broker.url`/`auth.broker.token`)
and exit 1. Unrelated errors still propagate. The broker still replaces
the local store when configured; no silent fallback to local credentials.
Fixes#8096
dispose({ timeoutMs }) legitimately detaches the consolidate pass when
the shutdown budget expires mid-flight (#3641), so asserting the shared
bank flush synchronously after dispose raced the detached pass on slow
CI runners (0 calls observed on run 31287979000). Signal the flush call
through a deferred and await it after releasing the lock; the bounded
<500ms return assertion is unchanged.
- seal() now runs before the final dispose close() and bumps the disk
epoch: work an event handler enqueues while dispose awaits the closing
tail is superseded, and an already-running fenced or authoritative
rewrite fails its commit guard at the rename fence instead of
publishing over a file a revival reopened.
- The authoritative repair path resets the disk tail itself, escaping the
close() serialization, and would atomically publish the emptied entry
list; it now no-ops once sealed and commit guards also check the seal.
- Execution-time gates cover the queued title persist and fenced rewrite
callbacks; setSessionName/appendCustomEntry attempted by a handler that
outlives dispose are dropped and covered by the seal regression, and a
failed atomic batch across the seal can no longer truncate the file.
- Replaced count-only assertions with the exact ordered message set:
revival sees only the seed, the reread holds seed + post-revive, and
the sealed manager's late persist text appears nowhere in the file.
- AgentLifecycleManager.park() resolves as soon as dispose() returns, so
ensureLive() may reopen the same JSONL through a new manager while a
timed-out event handler still holds the old one; a late append reopened
a second writer on that file and the deferred finalize then closed it.
- A post-release rewrite was worse: it persisted the emptied entry list,
truncating the transcript on disk.
- releaseRetainedEntries() now seals the manager: appends, title changes,
and rewrites become dropped no-ops and the append writer is closed, so
the deferred dispose pass is in-memory only and can never touch the file.
- File-backed regression: dispose on the drain deadline, revive the JSONL
immediately, unpark the late handler, and prove the file is byte-stable
and the revival writer owns it exclusively.
- The dispose drain deadline does not cancel in-flight event handlers: one
parked in a slow extension hook resumed after close/release, reopened the
append writer for its late persist, and repopulated the released state.
- Track whether the drain settled; on deadline, redo the final close +
release once the pipeline genuinely settles (hook runtime is bounded by
the extension runner).
- Expose drainTimeoutMs on AgentSessionDisposeOptions for bounded teardown
paths and deterministic coverage of the deadline branch.
- PR #8004 dispose() now releases the session manager in-memory transcript;
snapcompact-budget rebuilt a session over the closed manager and the exit
diagnostics read released entries.
- Snapcompact reopens the persisted file for its replacement session; exit
diagnostics move to disk-backed managers and assert the exit marker from a
reopened manager, proving actual durability.
- PR #8004 made dispose() release the session manager in-memory transcript;
three handoff tests reused the closed manager for a replacement session.
- Reopen the persisted session file via SessionManager.open, matching
production revival paths.
- Added account-scoped policy error detection to correctly identify Codex cyber-policy rejections.
- Updated credential storage and retry logic to route denied accounts through sibling rotation instead of bypassing it.
- Ensured coding-agent sessions exhaust all sibling accounts before falling back on cyber denials.
- Added comprehensive test coverage for credential rotation and retry behavior on policy errors.
- Add minimum character threshold to bypass minimization for short outputs.
- Add preserve-if-empty configuration and pipeline support for filters.
- Update test fixtures and integration tests to meet length thresholds.
- Add a tree-sitter syntax probe and parser veto to prevent syntax-unaware boundary repairs.
- Reject span pastes from empty named registers instead of deleting ranges.
- Reject duplicate top-level snapshot row line numbers during parsing.
- Export new syntax module symbols and add associated tests and changelog updates.
- Removed the Loader backpressure cap so slow ConPTY paints retain the documented proportional duty cycle.
- Made WSL terminal-title working state static to avoid a second periodic OSC write loop.
Fixes#8012
agent-core dispatches the session's event subscriber fire-and-forget (agent.ts #emit), so a message_end/agent_end handler can still be awaiting extension/subscriber/maintenance work — and its sessionManager/agent.state append — after agent.waitForIdle() resolves. The earlier settle waited only on the core run, so a late handler could append the finished message/entries back into the disposed session and re-pin the transcript.
Track every #handleAgentEvent dispatch in #inFlightEventHandlers and drain it (alongside agent.waitForIdle) inside the bounded settle before reset/clear/release. Added a regression test using a real extension whose message_end hook blocks before persistence, asserting dispose does not release memory until the in-flight handler settles.
dispose() only *signalled* the agent loop via abort(); it never awaited the run, so a mid-turn dispose (Ctrl-C/timeout/hard-killed subagent) could let the loop unwind after the release ran — its response/SSE interceptors re-recording wire frames into rawSseDebugBuffer and its terminal message re-appending to agent.state.messages, repopulating the disposed session with exactly the retained state the release drops.
Detach the response/SSE interceptors and await a bounded agent.waitForIdle() before the reset/clear so it lands on a quiescent session. Added a deterministic regression test that gates the active turn and asserts dispose blocks on it before clearing.
Disposed sessions remained reachable through lifecycle reviver closures. Agent.reset() cleared the live message array but left AppendOnlyContextManager attached, retaining its normalized provider transcript and stable prompt/tool prefix.
Detach the append-only manager during terminal disposal and extend the memory-release regression test to cover that second transcript copy.
Keep-alive subagents are handed to AgentLifecycleManager.adopt, which stores
their reviver closure in the process-global #adopted map. The closure is
defined inside runSubagent's scope, which also captures the live AgentSession
(extension-runner callbacks, buildSubagentSessionOptions), so its lexical
environment pins the whole session graph. park() disposes and detaches the
session but leaves the adoption record indefinitely, and #doDispose never
dropped the in-memory transcript, session-manager entries, or the raw-SSE
debug buffer (whose trimmed records retain slice() views of full wire frames),
so every completed subagent's heavy state leaked for the process lifetime.
dispose() is terminal and every revival path reopens from disk, so #doDispose
now sheds retained conversation memory via agent.reset(),
RawSseDebugBuffer.clear(), and SessionManager.releaseRetainedEntries(). The
adoption record can still reference the session, but only as a husk.
Fixes#8003
Decoded the extra JSON string layer returned by Z.AI MCP search and kept structured payloads out of answer text.
Added regression coverage for source extraction and plain prose preservation.
Fixes#8000
Harness-initiated session aborts previously cancelled compaction before the handoff reason was recorded. The handoff catch then saw only an aborted signal and replaced the harness reason with "Handoff cancelled".
Abort the handoff first with the session reason, forward caller-signal reasons, and reserve "Handoff cancelled" for direct or unreasoned cancellation. Add a regression test for an in-flight handoff aborted through AgentSession.abort.
Fixes#7993
The #7904 fix stopped masking provider errors as "Handoff cancelled", but
an empty or whitespace-only generation still fell through: whitespace-only
text passed the `!handoffText` guard and produced a bogus handoff, while
empty text returned undefined which the interactive /handoff caller mapped
to "Handoff cancelled" with no detail and no log entry.
Treat empty/whitespace-only output as a real failure: a user-initiated
handoff throws "Handoff generation produced no content" (surfaced as
"Handoff failed: ...") and logs it; auto-handoff keeps returning undefined
so maintenance falls back to context-full compaction. Also log genuine
handoff failures in the command controller so they persist for debugging.
Fixes#7993
- 28 symbols across discovery, mcp header policy, agent-hub projection and
rendering, the agent registry, shell tokenizing and changelog comparison
were exported but referenced only inside their own module; they are now
module-private, shrinking the deep-import surface.
- Kept AGENT_PLUGIN_MANIFEST_SCHEMA, AGENT_PLUGIN_MCP_SCHEMA,
parseAgentPluginManifest, clearAgentPluginRootCache and mergeMCPHeaders
exported: each is a seam for tests that defend real parsing or header
precedence behavior.
- Nothing reachable from an explicit exports entry or public barrel changed.
- src/lsp/index.ts is the explicit ./lsp package entry, yet held 2821 lines of
warmup, config caching, diagnostics, external build-command workspace
diagnostics, the writethrough batching subsystem and the LspTool class.
- Those are now servers, diagnostics, workspace-diagnostics, writethrough and
tool modules; index.ts is 22 lines and re-exports the same public surface.
- configCache and writethroughBatches remain single instances and every tuned
diagnostics timing constant moved verbatim.
- Moved the module-level machinery that sat in front of the ModelRegistry
class into model-config-values, model-patch, custom-models and
model-provider-discovery; model-registry.ts drops 646 lines.
- commandValueCache and its negative-cache TTL stay single instances, so the
execSync storm the cache exists to prevent cannot return.
- The setCodexAttestationProvider import-time side effect stays in
model-registry.ts. The class itself was left alone: its private state is
shared across the methods, so splitting it is not a straight move.
- theme.ts mixed symbol presets, JSON schema, color math, the Theme class,
loading, global state, appearance handling and TUI adapters in 3171 lines.
- Symbols, schema, color, theme-class, loader and tui-adapters are now
siblings; theme.ts keeps global state, the watcher, appearance handling and
HTML export at 745 lines, with all 44 exports intact.
- Left appearance and export-colors in place: both read private mutable
auto-theme state, so extracting them would have required new exported
internals or DI rather than a straight move.
- Separated deterministic replacement generation, placeholder derivation,
placeholder-range scanning and message-tree transforms out of the 2647-line
module; obfuscator.ts now holds the types and SecretObfuscator.
- ephemeralPlaceholderKey stays a single instance and both global regexes stay
beside the code that resets their lastIndex, so placeholder stability and
the security argument in the moved comments are preserved verbatim.
- Repointed every importer at the real modules rather than leaving a re-export
shim; the public ./secrets barrel exports the same 15 names as before.
- gh.ts held wire types, search, Actions run-watch, PR checkout/push/create,
PR diff parsing and view fetch/format in 3958 lines.
- Split into gh-types, gh-search, gh-run-watch, gh-pr-checkout, gh-pr-diff,
gh-view and a gh-common module holding the shared primitives and the single
process-lifetime default-repo memo pair; gh.ts is now 246 lines.
- All 22 exports stay on gh.ts because tools/index.ts star-exports ./gh, so
the issue:// and pr:// protocol handlers needed no edits.
- ReadTool mixed plain-file reading with archive, sqlite, pdf-image, summary,
selector, formatting and renderer concerns in one 3763-line module.
- Each now owns a sibling module; read.ts drops to 2020 lines and keeps its
public exports, including the readToolRenderer re-export required because
tools/index.ts star-exports ./read through the explicit ./tools entry.
- The pdfImageExtractions map and summaryParseCaches WeakMap stay single
instances; execute() was deliberately left intact.
- builtin-registry.ts held a 2341-line array of every command spec with its
handler inlined, plus the autocomplete builders and the TUI dispatcher.
- Specs moved verbatim into modes, collaboration, session, lifecycle,
marketplace and control modules; completions moved to builtin-completions.
- builtin-registry.ts is now a 174-line composition/dispatch module and keeps
all 16 exports. Command order is unchanged, which matters because it drives
autocomplete ordering and BUILTIN_SLASH_COMMAND_DEFS.
- Python, Ruby and Julia each carried their own copy of the same session
maps, acquire/reset/replace/dispose lifecycle and executeOnSession; one
generic registry now owns it, parameterized by a per-language descriptor.
- Julia additionally re-implemented seven executor-base helpers locally; those
copies are gone and executor-base gained sparse managed-env and timeout
resolver hooks so Julia's differing behavior survives unchanged.
- All twelve exported entry points keep their names and signatures.
- zune-jpeg 0.5.15 (image 0.25's JPEG decoder) cannot compile with its
non-default log feature off: zune-core's no-log warn! stub is not
expression-safe. A feature-activation-only workspace dep on
zune-jpeg { features = ["log"] } fixes the cold build; log stays 0.4.33.
- model-registry-default-config's local ModelSnapshot type gains the optional
streamIdleTimeoutMs the Bedrock watchdog compat now emits.
- A provider-supplied retry-after now bypasses the transient rate/concurrency
heuristic window instead of being overridden by it (regression from the
subscription-cap retry change).
- Updated event-controller/ui-helpers test doubles for provenance-gated
renderer selection (hasBuiltInTool), aggregated retryErrors on
auto_retry_end, and Bedrock override compat gaining streamIdleTimeoutMs.
Preserved zero-width readiness and wait matches across the daemon wire protocol, and isolated malformed completion events from unrelated pending RPCs.
Fixes#7908
Added the upstream unregisterProvider lifecycle to queued and initialized extension runtimes. Provider removal now clears runtime model/auth state before replacement, while failed factories restore the prior registration queue.
Fixes#7914
Published per-cwd discovery snapshots to existing task tools and refreshed them from TUI, ACP, and Agent Control Center reload paths.
Added regressions for existing and future task tools across TUI and ACP reloads.
Fixes#7940
A cooldown-expiry model revert runs at a turn boundary. The user-prompt
path reverts then re-checks accumulated context against the restored
model via runPrePromptCompactionIfNeeded, but the automatic
agent.continue() path (#scheduleAgentContinue) reverted and issued the
next request with no such check. When a transient failure had fallen
back to a larger-window model and the conversation then grew past the
original model's window, restoring the primary once its cooldown expired
sent a predictably oversized request to the smaller model.
maybeRestoreRetryFallbackPrimary now reports whether it actually
switched, and the auto-continue path runs the same post-revert
context-fit maintenance (compaction/promotion) the prompt path already
runs, but only when a revert occurred.
Fixes#7952
- Runtime error now just says to download vscode-js-debug from its GitHub repo;
tarball recipe, extract path, env var, and Mason detail stay in docs/tools/debug.md.
The handoff catch in session-handoff.ts and the /handoff handler in
command-controller.ts mapped any error named AbortError to "Handoff
cancelled" regardless of whether the handoff signal was actually
aborted. Providers throw name-AbortError errors on non-user conditions
(stalls, idle timeouts, nested resolution failures), so a genuine
generation failure surfaced as a user cancellation and hid the cause.
Only report "Handoff cancelled" when handoffSignal.aborted is set;
re-throw the real error otherwise. The controller now trusts the
normalized "Handoff cancelled" message and drops its own AbortError
check so re-thrown provider failures render as "Handoff failed: ...".
Fixes#7903