- Added session-domain modules and exports for session-entries, context, listing, loader, and migrations.
- Changed persistence to async append writes plus writeTextAtomic, removing sync line APIs.
- Added compaction-aware session context rebuild with dangling tool-call cleanup.
- Added resumable session resolution with status inference, id/stem/suffix matching, and backup recovery.
- Added a SessionFocusController to switch transcript and input context between main and subagent sessions.
- Added agent-hub Enter activation and double-left return behavior for focused local agents.
- Added view-session-based event and render logic to avoid stale focus-session state.
- Added status-line focused agent display with ghost icon and focused-mode border dimming.
The session-observer overlay is gone. The Agent Hub (ctrl+s, alt+a, or double-tap left arrow on an empty editor) presents one overlay with two views: a live registry table (status, unread irc count, current task, last activity; j/k to navigate, r to revive, x to abort/release) and per-agent chat (transcript + input line) — submitting revives a parked agent and steers it via the normal prompt path. Main is the ambient chat and stays out of the table.\n\nrenderInitialMessages no longer takes a prebuilt context: every redraw now reaches for AgentSession.buildTranscriptSessionContext() (full-history transcript with each compaction emitted inline at the point it fired, snapcompact frames re-attached on rebuild). UiHelpers drops the deferred-compaction render and the IRC autoreply branch that the new mailbox bus deprecated. CompactionSummaryMessage renders as a slim divider (── 📷 compacted · ctrl+o ──), expanding to the summary + snapcompact frame count. session-manager.buildSessionContext gains a { transcript: true } mode; an exported buildSessionContextFromFile() reads a session file without taking the writer lock so the hub chat view can tail any agent (parked or live). Theme picks up icon.camera + tool.irc symbol entries.
- Added ChatBlock and ChatBlockHost with mount, finish, and dispose lifecycle callbacks.
- Added InteractiveModeContext.present and resetTranscript APIs and used them to mount/repaint blocks.
- Reworked controller rendering paths to emit command, event, extension, and selector outputs via ctx.present.
- Implemented component and container dispose hooks in tui so loaders and child blocks cleanup timers/effects.
- Replaced regex code-block parser with line scanner masking fences.
- Exposed `>`-quoted runs as drillable copy targets per message.
- Added combined "all quotes" target when multiple quotes exist.
- Emitted per-command cmd:N targets after the assistant message that issued them.
- Carried commands from text-less messages to the next visible message.
- Replaced the single trailing "last command" leaf.
- Added a fullscreen /copy tree of recent assistant messages with nested code blocks and a live preview pane.
- Removed the /copy last|code|all|cmd subcommands in favor of tree selection.
- Extracted copy-target assembly into a testable util.
- Cleared native scrollback on `omp`/`omp -c` so the resumed transcript no longer stacks on the prior run's welcome screen.
- Tracked assistant block finalization so aborted streaming messages stay repaintable when status rows land beneath them on ED3-risk terminals.
AGENTS.md L51: NEVER use ReturnType<> — use the actual type name.
bun:test exports Mock<T> directly; use that instead.
Note: framework is bun:test, not vitest as Codex stated.
Before this change, every navigateTree → renderInitialMessages call path
performed two independent O(N) session-tree walks:
1. agent-session.ts:6586 buildDisplaySessionContext() [inside navigateTree]
2. ui-helpers.ts:402 sessionManager.buildSessionContext() [inside renderInitialMessages]
Changes:
- agent-session.ts: navigateTree() now calls sessionManager.buildSessionContext()
once, derives the display (deobfuscated) context from the raw result, and
returns the raw SessionContext in the result object.
- ui-helpers.ts: renderInitialMessages() accepts an optional prebuiltContext
parameter; reuses it when provided, falls back to buildSessionContext() otherwise.
- interactive-mode.ts: forwards prebuiltContext through the wrapper.
- modes/types.ts: updates InteractiveModeContext interface to match.
- selector-controller.ts: passes result.sessionContext from navigateTree into
renderInitialMessages(), closing the deduplication loop.
Bench (100-msg session, 200 iterations):
two walks [BEFORE]: 0.0702ms/op
one walk [AFTER]: 0.0298ms/op
Saved: 0.0404ms/navigation (57.5% reduction per navigate)
Tests: render-initial-messages-dedupe.test.ts asserts buildSessionContext is
called 0 times when a prebuilt context is passed, 1 time as fallback.