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.
- The regenerated bundle (merged with PR #8021) now ships a
responses-route github-copilot/grok-4.5, so the id legitimately
resurfaces from the bundle when the migration refresh fails; the
contract worth defending is that the stale cached completions route
never returns and the unbundled long-context variant stays dropped.
- The OpenCode Go gateway does not serve DSV4-Flash at
/zen/go/v1/chat/completions; /zen/go/v1/responses works (user-verified
against the live gateway). Added a per-id override in
OPENCODE_GO_API_RESOLUTION so both bundled generation and the runtime
/v1/models refresh route it to openai-responses; deepseek-v4-pro keeps
chat completions.
- Regenerated models.json from the resolver source.
- 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.
- Gate closer-spare boundary repairs on tree-sitter parse validation to prevent incorrect rewrites on unrecognized languages or pathless edits.
- Add a warning when a `+` body row matches a valid hunk header format to flag accidental literal text insertion.
Applied curated Alibaba Token Plan seeds after generic models.dev fallback so bundled capabilities cannot be overwritten by incomplete upstream metadata.
- 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
Share display-command arity with readArg and consume exactly that many
required arguments across whitespace. This keeps continuation newlines
collapsed to spaces without separating a nested command from its own
argument or letting it absorb the outer command's next argument.
Cover adjacent, spaced, and source-line-split sqrt and nested-fraction
numerators.
Fixes#7996
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
Track pending command arities as nested frames instead of replacing the
outer frame when an unbraced command supplies an argument. Match readArg
by treating each command and its attached groups as one outer atom, while
retaining only that command's own missing arguments.
This keeps fractions such as `\frac\sqrt{a}\n{b}` waiting for their
denominator without folding standalone braced rows.
Fixes#7996
The first pass suppressed the top-level newline split whenever the next
row opened with `{`, folding a genuine braced row (`a\n{b+c}`) into the
row above. Gate the continuation on bracesOwed(): a newline joins to the
next row only when the current row ends with a command still awaiting a
brace argument (`\frac{num}\n{den}`, `\frac\n{a}{b}`, `x^\n{2}`); a row
that merely opens with a braced group stays a real row break.
Fixes#7996
latexToBlock pre-split display math on top-level newlines to stack a
`lhs =` line above its block, but a newline between `\frac{num}` and
`{den}` falls at brace-depth 0 and was treated as a row break, severing
the fraction from its denominator and rendering it as fragmented text.
Treat a top-level newline whose next non-space token opens a `{…}`
argument group as an argument continuation rather than a row break, and
collapse those interior newlines to a space so the argument reader parses
both brace groups.
Fixes#7996
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.