Session entries keep AgentMessage objects strongly reachable for the whole
session, so WeakMap entries for compacted-away history pinned their
components' rendered layout caches forever. Rebuild now retains only
components for messages the new transcript context actually renders.
Pre-fix resumed sessions wrote cumulative transcript rows under the
incremental ${sessionId}-<ts> source_id shape, so summing every prefixed
legacy row could overshoot the real retained prefix and permanently skip
unseen turns. Use per-row max instead: it can only under-count, which at
worst re-stores one suffix before an explicit cursor row takes over.
Adds a regression seeding a legacy bank with two incremental rows plus a
cumulative resumed row and asserting turns 7-8 still get retained.
hasAuth() consults $HOME/.env via getEnvApiKey, so a CEREBRAS_API_KEY in the
runner's home .env legitimately armed prewalk and failed the no-auth case.
Force the condition with a hasConfiguredAuth spy.
Flat aggregator ids whose prefix collides with a provider slug (e.g.
openai/gpt-oss-120b hosted on OpenRouter) bypassed the authenticated-model
preference: the explicit-provider branch searched the full catalog only.
Keep provider/id exact references authoritative, but let the flat-id
fallback prefer authenticated providers before catalog order.
A retry-chain entry without its own :level suffix now inherits the
unavailable primary's configured thinking level, matching runtime
fallback-chain semantics. Regression test asserts a level that differs
from the fallback model's default.
Cold-opening a large read-only Advisor transcript froze the TUI for tens
of seconds: AgentTranscriptViewer.render() called the full
container.render() before ScrollView clipped, laying out every synthetic
`Session update` input as full Markdown. A 6.5 MiB __advisor.jsonl
blocked the first frame ~27s in a repro.
Synthetic (agent-attributed) inputs now render as a CollapsedSyntheticMessageComponent:
one dim summary row (heading, size, line count, ctrl+o hint) that builds
the heavy UserMessageComponent Markdown only on expand. Blocks above the
viewport never pay layout on cold open; the raw observability data in
__advisor.jsonl is untouched. Real user prompts stay fully rendered.
Fixes#6308
- 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
The default install path only checked whether bun existed, so an
x86_64 bun running under Rosetta installed an x86_64 omp on Apple
Silicon (AVX warning, ~11s startup). Compare bun's process.arch to
the host, detecting Apple Silicon via `sysctl -in hw.optional.arm64`
so Rosetta can't spoof it, and fall back to the prebuilt native
binary on mismatch; `--source` now errors actionably. install_binary
also derives the arch from the real host instead of the
Rosetta-translated `uname -m`.
Fixes#6268
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
Codex catalog discovery resolved a single access token, mapped it to one
chatgpt-account-id, and made one authoritative fetchCodexModels call whose
result pruned bundled entries. With multiple ChatGPT/Codex OAuth accounts,
the visible catalog depended on whichever account the discovery preflight
selected, hiding models available only through a sibling account.
openaiCodexModelManagerOptions now takes a resolveAccounts callback, fetches
each configured account's /models catalog independently, and unions them by
id before the authoritative merge. Bundled models are retained when every
account fetch fails. The runtime wiring resolves every stored openai-codex
OAuth account via AuthStorage.getOAuthAccesses.
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
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 agent_end handler only recorded stopReason/provider/model at debug and dropped errorMessage/errorStatus/errorId, so a session dying repeatedly on provider stream failures left no actionable trace in the main log. Extract logProviderTurnError and emit one warn-level entry carrying provider, model, errorMessage, errorStatus, and errorId when a turn ends in stopReason:error.
Fixes#6177