- Restored CustomInputRow.priority field dropped in 3b80dc01d ask row budgeting.
- Narrowed dereferenced schema properties via isRecord in yield-assembly and output-schema-validator instead of untyped object access.
- Renamed stale advisorReadOnlyTools to advisorTools in advisor parity test.
- Narrowed AgentMessage content access in session-loader-stream test.
- Reformatted browser-schema test to satisfy biome.
- Removed a duplicated branch.reverse() left by the PR #3862 merge in
SessionEntryIndex.pathTo(), which returned branches leaf-to-root and made
getLastModelChangeRole() read the oldest model change instead of the
newest — pinning the ctrl+p cycle to one slot and breaking session model
restore.
- Hardened getRoleModelCycle() to trust the recorded role only while its
resolved model still equals the active model, falling back to matching by
model after switches through alt+m, /model, or retry fallback.
- Added mutation-verified regression tests for branch ordering and the
stale-role fallback.
- Consolidated `browserOpenSchema`, `browserCloseSchema`, and `browserRunSchema` into a single `browserSchema`.
- Simplified the `action` type definition to accept `'open' | 'close' | 'run'`.
- Updated schema validation tests to reflect the unified schema definition.
Ports only the thinking double-format fix: resolveThinkingDisplay reuses block.thinking when rawThinking is set (buildDisplayMessage already formatted it), plus a single-entry memo in formatThinkingForDisplay and a rawThinking regression test. The PR's incremental reveal slicing is superseded by the already-merged #3848 (memoized grapheme slicing).
Cherry-pick of the reserve-budget clamp only (resolveBudgetReserveTokens + no-op compaction guard): applies compaction.ts + agent-session.ts + compaction/shake/progress-guard tests. Excludes unrelated Julia prelude timeout and ai/test churn from the PR head.
/quit and /exit hung for many seconds because AgentSession.dispose()
awaited MnemopiSessionState.dispose() unconditionally, and that path
runs consolidate() (state.ts:421) which fires a fresh LLM fact
extraction for the just-retained transcript and then awaits
flushExtractions() per owned bank. One LLM round-trip per shutdown,
no upper bound, no visible status.
- Add a timeoutMs option to MnemopiSessionState.dispose. When the cap
fires the in-flight consolidate is detached to the background and the
SQLite handles close once it settles, so writes never race a closed
handle.
- AgentSession.dispose passes SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1_500 on
the user-visible shutdown path. Per-turn maybeRetainOnAgentEnd has
already retained earlier turns, so the worst case is losing episodic
promotion for the last few turns. State-replacement disposes
(mnemopiBackend.start) stay unbounded.
- InteractiveMode.shutdown surfaces a 'Closing session…' status before
dispose runs so the brief pause is explained rather than mysterious.
Two regression tests in memory-tools.test.ts cover (1) dispose returns
within the budget when flushExtractions stalls and the deferred close
still runs once consolidate settles, and (2) unbounded dispose still
runs the full #2320 consolidate-then-close pipeline.
Fixes#3641