Defer recentOutput line reconstruction from every text_delta to the
progress emit boundary. appendRecentOutputTail only extends the capped
raw tail and marks dirty; refreshRecentOutput runs the exact old
split/filter/slice(-8)/reverse algorithm as the first step of every
emitProgressNow snapshot (onProgress + event bus), including coalesced
and finalize/error/cancel flushes. Reset publishes [] immediately;
replace marks dirty; past snapshot arrays stay immutable via spread.
Before (base pool median-of-5):
w8_d3 61.55 cpu_ms/1k_events
w32_d3 44.16 cpu_ms/1k_events
After (stable final run on E+G, 7 episodes, trimmed CV gate pass):
w8_d3 55.78 cpu_ms/1k_events (1.103×) trimmed CV 15.1%
w32_d3 40.72 cpu_ms/1k_events (1.084×) trimmed CV 11.1%
Checksums match prior exactness baseline; retained_after_release_kb
1284 / 2864 (no regression vs prior concur).
Op: GConcurEmitBoundary emit-boundary dirty flag
Restores: none
Long sessions re-walked the full live AgentMessage[] every turn: convertToLlm
re-converted the unchanged prefix and estimateTokens re-tokenized settled tool
results and assistants, redoing work only the newest suffix can change.
- Added a per-message estimate cache in agent-core keyed by identity, with a
settle gate (assistants cache only with real usage + terminal non-error
stopReason; streaming partials bypass) and dual option-split WeakMaps for the
default vs compaction-floor estimates.
- Memoized convertToLlm per message identity + assistant interruptedNext flag,
with an exact-repeat outer-array reuse and slice-on-growth for append-only
turns, guarded by a boundary-identity check against interior splice-replaces.
- Invalidated both caches at the mutation seams: prune, shake, strip-images, and
the prewalk plan-nudge scrub, via invalidateMessageCache /
registerMessageCacheInvalidator across the package boundary.
- Added the llm-assembly bench (N=5000, robust MAD-noise gate): steady/append
convert and repeat estimate are all >10x faster with noise under 20%.
Fixes#5934
TranscriptContainer gated committed-prefix compaction on version === undefined, so version-tracked AssistantMessageComponent history never compacted and every stream tick re-walked all N sealed blocks (depth-linear compose).
Compact fully-committed finalized blocks regardless of post-finalize version tracking: their rows are immutable native scrollback the terminal owns. A post-commit mutation no longer recommits on ordinary frames (no duplication) and rehydrates on the next destructive full replay (no loss). Adds the permanent bench/transcript-compose.bench.ts fixture; ratio(N5000/N500) drops 2.30 -> 0.90.
Fixes#5930
Kept full-root rendering as the default after input and introduced an explicit host opt-in for stable-focus subtree repainting. The coding-agent default composer opts in, while extension-provided editors and other TUI consumers retain callback-safe full composition.
Bounded aborted post-prompt drains and ran independent subsystem cleanup under one barrier while preserving writers-before-close ordering.
Kept long interactive shutdowns visible with a delayed status refresh.
Fixes#5932
Scoped ordinary input frames to the focused component while retaining a full compose when input moves focus. Explicitly repainted the coding-agent pending-message sibling and covered stable focus, wrapped growth, focus movement, and queue clearing.
Fixes#5928
The interactive /tree selector's own no-op guard ("Selecting the current
leaf is a no-op") fired before ever reaching navigateTree()'s
allowAskReopen path added in 743c8ab7d, so that fix was unreachable from
the actual UI whenever the selected ask toolResult was already the
current leaf (interrupted right after answering, or navigated there by
another caller).
Let a current-leaf selection fall through to the reopen path when the
entry is an ask toolResult, mirroring the same targetIsAskResult check
navigateTree() uses.
Fixed in response to Codex review threads posted as PR review bodies
(not inline comments) on #5895 at 20:24:48Z and 21:54:30Z, which
predate 743c8ab7d and were never addressed.
Review follow-ups:
- The corruption retry now goes THROUGH the sidecar heal, so a cache
broken in both ways (truncated model blob AND stale/missing
config/tokenizer sidecars) recovers in one pass instead of the retry
escaping with a sidecar error.
- defaultLocalModelInitializer is exported from core as the shared
initializer and the coding-agent embed worker now uses it instead of
calling FlagEmbedding.init directly, so omp's subprocess embeddings
inherit both heals; fastembed/onnxruntime still load only in the
child address space.
resolveBlobRefsInEntries handed every non-session entry to the recursive
async resolvePersistedBlobRefs walk, allocating and awaiting child promises
even for plain-text entries with no blob:sha256: refs. On large text-heavy
histories this dominated the blob_resolve phase of session open.
Add a cheap synchronous containsBlobRef precheck that early-exits on the
first ref and allocates nothing. Interleave the precheck with per-entry
initiation so positive entries still start resolution at the same relative
point as the old filter+map schedule (a later entry that gains a ref during
an earlier BlobStore.get is still scanned after that mutation).
Blob-free N=5000 fixture: blob_resolve median 19.5ms -> 1.1ms, zero
BlobStore.get calls.
Fixes#5922
navigateTree()'s targetId === oldLeafId no-op short-circuit ran before
the ask re-answer probe/completion block, so selecting an ask
toolResult that is already the current leaf silently reported success
without returning reopenAsk or branching a new answer. This happens
when the user interrupts right after answering ask (before a follow-up
assistant message lands) or another caller navigates straight onto the
ask result. Exempt allowAskReopen probes/completions targeting an ask
toolResult from the short-circuit so the two-phase re-answer protocol
still runs.
The `/tree` ask re-answer recovery path (#recoverAskReanswerQuestions)
read the persisted ask toolCall's raw arguments directly, so when secret
obfuscation is active the recovered question text could still contain
`#HASH#` placeholders instead of the original secret. The live tool
path already deobfuscates via transformToolCallArguments before use;
apply the same deobfuscateToolArguments call to the recovery path.
Wires an optional obfuscator through TestSessionOptions/createTestSession
so tests can exercise sessions with secret obfuscation active, and adds
a regression test confirming the reopened ask picker shows deobfuscated
plaintext rather than the raw placeholder.
Review follow-up: a live subagent focused from the Agent Hub renders its
session name in the status line (session_name segment reads
sessionManager.getSessionName()), so the blanket agentKind === "sub" skip
made the user-enabled title.refreshOnReplan silently ineffective and left
focused subagents untitled after their first todo replan.
Focus only exists in an interactive host, and subagents run in-process, so
gate the skip on a process-global interactive-host flag: subagents skip the
replan title refresh only in non-interactive hosts (print/RPC/ACP/eval/SDK/
CI) where no session tree is focusable. The interactive entrypoint declares
the host via setInteractiveHost(isInteractive); the flag defaults false, so
bun test and headless embedders keep the optimization without leaking state.
Fixes#5910
Subagent sessions run `todo init` per the eager-todo prelude, which
triggered `#scheduleReplanTitleRefresh()` and a tiny-model title
generation call. The result is written to JSONL but never displayed —
subagents surface their registry id and generated task label, not a
session title.
Short-circuit `#scheduleReplanTitleRefresh()` when `#agentKind === "sub"`.
Uses the session-level subagent marker rather than `hasUI` so print/RPC
top-level sessions keep persisting their auto title for `--resume`.
Fixes#5910
- Probed Linux ffmpeg demuxers and fell back to ALSA when PulseAudio input is unavailable.
- Preserved recorder stderr so immediate capture failures report their real cause.
- Added regressions for ALSA selection and stderr diagnostics.
Fixes#5907
- Replaced the losing Bun.sleep with an unrefed timeout cleared in a finally block.
- Added regression coverage that verifies prompt wheel acknowledgements leave no timer behind.
Fixes#5905
- Released tab.scroll after two seconds when a queued wheel event waits on a busy renderer acknowledgement.
- Preserved immediate dispatch failures and added regression coverage for both outcomes.
Fixes#5905
Claimed Bun's singleton stdin reader before loading extensions and passed the owned stream into RPC and RPC-UI mode.
Added process-level regressions for both modes with a startup extension that attempts to lock stdin.
Fixes#5898
#recoverAskReanswerQuestions previously bailed on any non-message-typed
ancestor, but #recordToolExecutionStart() appends a custom
tool_execution_start entry between the assistant message and every
toolResult in real persisted sessions. The walk now generically skips
any ancestor that isn't the target assistant message (or a turn-
boundary user message), so the normal single-tool-call ask case
recovers correctly instead of always falling back to a plain leaf
move.
Cursor selects server-native tools (bash, grep, ...) outside the advisor's grant. Those exec-channel blocks are stamped kCursorExecResolved: they already ran server-side through the advisor-scoped CursorExecHandlers bridge, which rejects ungranted tools in-band. quarantineAdvisorUnsafeOutput was flagging them as pre-dispatch hazards and discarding the entire turn, dropping the legitimate advise emitted alongside them.
Skip exec-resolved native blocks in the unavailable-tool check so the scoped bridge stays the grant gate and the advisor can still deliver advice.
Fixes#5900
kill(pid, 0) succeeds for a zombie too: a grandchild whose parent (the
killed leader) is gone sits as <defunct> until whatever reaps orphans
gets around to it, which can lag on some hosts. processExists() now
reads the process's ps state and treats a zombie as already reaped
instead of still alive, so the group-kill regression tests assert what
they actually claim to test.
Kept top-level custom tool descriptors when a retained xd device uses the same name. Added compact and inline inventory coverage for mounted-only and dual-presentation tools.
- Gate navigateTree()'s ask toolResult reopenAsk protocol behind a new
allowAskReopen option, set only by the interactive /tree selector.
Every other navigateTree() caller (extensions, hooks, ACP,
session-extension actions) now falls through to the pre-#5642 plain
leaf move instead of reporting a successful no-op navigation.
- Anchor the branch-summary entry collection on targetEntry.parentId
for an ask re-answer completion so the replaced (abandoned) answer
is included in the summary instead of silently dropped.
- #recoverAskReanswerQuestions now walks the ancestor chain past
interleaved sibling toolResults to find the assistant entry that
actually emitted the ask toolCall, instead of assuming it's the
toolResult's direct parent.
- Replace the fabricated `as unknown as AgentToolContext` standalone
tool context in SelectorController#reanswerAsk with
AgentSession#buildAskReanswerContext(), a fully-typed context
backed by real session state.
- #reanswerAsk now rejects a chatRedirect ("Chat about this") result
instead of silently completing the navigation with it.
- Fix the CHANGELOG entry's external-contribution attribution format.
- Fixed xd:// mount notices forcing their own model turn by deferring them until the next user prompt instead.
- Added `#pendingXdevMountDelta` field and `#takePendingXdevMountNotice()` to coalesce mount/unmount events and ride along with prompts.
- Mount and unmount events that cancel each other out before the next prompt are now dropped from the coalesced delta.
- Notices remain buffered during quiet startup mode (`startup.quiet`) and are delivered on the subsequent user prompt.