EventController.handleEvent rebuilt the editor's status-line top border
synchronously on every session event via updateEditorTopBorder(). During
a long-running eval that fires 5-10 events/s, each rebuild ran
StatusLine.getTopBorder → #buildSegmentContext → getCachedContextBreakdown
→ session.getContextUsage → estimateTokens (with JSON.stringify per
toolCall block) — the render pipeline is throttled to ~30 fps, so most
rebuilds were dropped before painting. Combined with a scheduler that
collapsed cadenceDelay to zero whenever a frame overran the 33ms budget,
the TUI busy-looped at ~40-50% CPU.
Fix:
- Editor gains setTopBorderProvider(): a lazy builder invoked once per
editor render. InteractiveMode installs it in the constructor and on
setEditorComponent, so the rebuild coalesces to the render tempo
regardless of event rate.
- Delete updateEditorTopBorder wrapper (now equivalent to
ui.requestRender) and inline every call site.
- Add adaptive render backpressure: a frame that exceeds
MIN_RENDER_INTERVAL_MS inflates the next scheduling delay to
2 * last_frame_cost, capped at 200 ms, targeting a 50% render duty
cycle instead of pinning the CPU at t=0.
New regression tests:
- editor-top-border-provider.test.ts: provider fires exactly once per
render, wins over eager setTopBorder, falls back when cleared, gets
the correct availableWidth.
- adaptive-render-backpressure.test.ts: cheap frames keep the 33 ms
cadence, a slow frame idles proportionally, pathological frames are
capped at 200 ms.
Verified with bun test packages/tui/test (all 246 relevant tests pass)
and bun test packages/coding-agent/test/modes (455 tests pass). Three
pre-existing agent-session-handoff snapcompact failures on main are
unrelated (snapcompactSupportedChars binding).
Fixes#4145
The postmortem SIGTERM/SIGHUP/uncaughtException handlers only ran the registered
cleanup callback list before process.exit, and the only session-related callback
was session-manager-flush. So a real kernel signal (terminal close, process
manager killing omp, IDE stop) skipped saveDraft, session.dispose (session_shutdown
emit, owned async job disposal, kernel disposal, MCP disconnect, browser tab
release), and violated the SessionShutdownEvent docstring contract that promises
delivery on SIGINT/SIGTERM. The LSP client also owned its own SIGINT/SIGTERM
handlers that called shutdownAll then process.exit(0), which could race postmortem's
async runCleanup and short-circuit the session teardown.
- Extracted a promise-memoized createSessionTeardown helper (modes/session-teardown.ts)
that snapshots the editor draft, persists it via sessionManager.saveDraft, then
invokes session.dispose. A saveDraft failure is logged but never aborts disposal.
- Memoized AgentSession.dispose so the keypress path and the signal path share one
settled promise and cannot double-emit session_shutdown or double-drain the owned
AsyncJobManager.
- Registered the teardown on postmortem as "session-teardown" in InteractiveMode.init,
replacing the narrower session-manager-flush callback. InteractiveMode.shutdown
now delegates the draft+dispose steps to the same helper.
- Replaced the LSP client's SIGINT/SIGTERM handlers with a "lsp-shutdown" postmortem
callback so LSP cleanup runs alongside every other session teardown instead of
racing them via process.exit(0). beforeExit is unchanged.
- Added session-teardown.test.ts covering: draft-then-dispose ordering, disposal
after saveDraft rejects, empty-string clears stale sidecar, promise memoization
under concurrent invocation, and snapshot-at-first-call semantics.
Fixes#4080
- Anchors the incomplete-todo reminder block inside the scrollback transcript instead of a floating live container.
- Eliminates duplicate reminder copies piling up in terminal scrollback during terminal reflows.
- Removes the dedicated `todoReminderContainer` and simplifies state synchronization on todo reload.
- Updates tests to verify sequential reminders commit as separate blocks and are left intact when tools succeed.
Captured the configured thinking selector when entering plan mode so approving a plan restores auto instead of the provisional concrete effort. Reloaded DEFAULT(auto) badges from defaultThinkingLevel and covered the plan-approval handoff plus /model display.
Fixes#3901
The replan-driven title refresh (title.refreshOnReplan, fired after a
`todo init`) called `generateSessionTitle()` without the user's
`TITLE_SYSTEM.md` override, silently falling back to the bundled
`prompts/system/title-system.md` and overwriting auto titles with the
default policy. The override was only ever discovered by main.ts and
passed into the first-input title path on InteractiveMode, never into
`AgentSession.#refreshTitleAfterReplan`. Most visible in Plan Mode,
which initializes todos early.
`AgentSession` now owns the resolved title prompt:
- New `CreateAgentSessionOptions.titleSystemPrompt` threaded by
`createAgentSession()` into the constructor.
- New `AgentSessionConfig.titleSystemPrompt` stored on
`#titleSystemPrompt` with a `get titleSystemPrompt` /
`setTitleSystemPrompt(...)` pair.
- `#refreshTitleAfterReplan` passes `#titleSystemPrompt` as
`customSystemPrompt` to `generateSessionTitle()`.
- `input-controller.ts` reads from `session.titleSystemPrompt`, and
the duplicate `InteractiveMode.titleSystemPrompt` field /
constructor arg / `InteractiveModeContext` field / `runInteractiveMode`
parameter are removed. `InteractiveMode.refreshTitleSystemPrompt`
now calls `session.setTitleSystemPrompt(...)` so a `/move`-style cwd
change keeps the override in sync.
Regression test asserts the prompt handed to `completeSimple()` from
`#refreshTitleAfterReplan` is the configured override, not the bundled
`title-system.md`.
Fixes#3734
- Introduced comprehensive support for multiple concurrent, independently-configured advisors via `WATCHDOG.yml` files.
- Implemented a full-screen TUI overlay for managing advisor rosters, models, tools, and instructions.
- Added session-wide advisor initialization, telemetry aggregation, and named transcript isolation.
- Enhanced advisor security and observability with secret redaction in tool results and secure XML attribute encoding.
- Added `statusLine.compactThinkingLevel` setting to render the thinking level as a leading icon.
- Replaced the verbose ` · <level>` suffix with a single glyph when compact mode is enabled.
- Updated the status line controller and component to resolve and propagate the new configuration.
Propagated the first observed reasoning-content unlock to the active streaming assistant component before the reveal controller re-renders it. Added a regression that starts a hidden thinking-off stream and verifies the first reasoning delta becomes visible.
Tracked received thinking content per interactive session so OpenAI-compatible providers that omit reasoning metadata can still reveal streamed reasoning blocks. Added a Ctrl+T regression covering the unlocked visibility path.
Fixes#3669
`handleShakeCommand` calls `rebuildChatFromMessages()`, which clears
`chatContainer` and replays only committed `state.messages`. The agent's
in-flight `streamMessage` and its still-pending tool calls live OUTSIDE
`state.messages` until `message_end`, so the live `streamingComponent`
and `pendingTools` entries were detached while their references stayed
live — every subsequent `message_update`/`message_end` event then
updated orphaned components that never re-rendered, and the in-flight
LLM output disappeared from the chat. Other mid-stream rebuild paths
(setting toggles such as `display.cacheMissMarker` and
`tui.renderMermaid`) had the same flaw.
Snapshot the live `streamingComponent` and `pendingTools` (in their
original chat-container order) before clear, re-append after the
historical replay, and restore the `pendingTools` map so the next
streamed tool-call delta routes back into the preserved component
instead of stacking a duplicate ToolExecutionComponent below it. Idle
rebuilds are unchanged.
Fixes#3656
- Redesigned the Todo HUD as a connector tree with fixed-budget stage previews.
- Anchored status and HUD containers to prevent redundant UI elements in terminal scrollback.
- Implemented tree-based rendering for project phases and tasks while removing dynamic border rules.
- Upgraded `sherpa-onnx` and related packages to support current infrastructure.
- Added a check to restore the live "Working..." loader when streaming events occur after a transient status overlay clears the UI.
- Updated `ensureLoadingAnimation` to re-attach the animation to the status container if it is missing.
Hidden slider means the operator made no choice; a singleton cycle built around the active plan model must not be pinned as executionModel, otherwise approval re-applies the plan model after #exitPlanMode restored the pre-plan one.
Added regression coverage for the plan-only role configuration.
Refs #3554
Same-model role with an explicit thinking suffix that differs from the pre-plan thinking now passes through applyRoleModel instead of being treated as an implicit match.
Added regression coverage for the sonnet:off vs pre-plan thinking-high case.
Refs #3554
Compared the selected approval tier against the model restored after plan mode instead of the active plan-mode tier.
Added regression coverage for keeping the active planning model selected on approval.
Fixes#3554
Replaces the old /move (which relocated the current session file) with a
new flow that starts a fresh empty session in the target directory, leaving
the previous session resumable via /resume. With no argument, /move opens
a path autocomplete overlay (type to filter, Tab to accept, Enter to
confirm). If the target directory does not exist, a confirmation prompt
offers to create it. Empty move sessions are cleaned up on shutdown.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Address 4 P2 review comments on PR #3314:
1. effectiveHideThinkingBlock now uses viewSession.thinkingLevel
instead of session.thinkingLevel — in focused-agent mode, the
viewed transcript may have a different thinking level than the
main session.
2. thinking_level_changed handler now iterates existing
AssistantMessageComponent children and calls setHideThinkingBlock
with the new effective value, then resetDisplay() to repaint.
Previously only new/streaming messages got the updated visibility.
3. Changed AssistantMessageComponent import from type-only to value
import for instanceof check.
4. Agent Hub callback already uses effectiveHideThinkingBlock which
now reflects viewSession — no separate fix needed.
Some providers (MiniMax, GLM, DeepSeek) return thinking blocks in
their responses even when reasoning is disabled — the model generates
thinking content regardless of the reasoning_effort parameter.
When the user sets thinking level to "off", they expect no thinking
content to be visible. Previously, thinking blocks would still appear
because the hideThinkingBlock setting was independent of the thinking
level and defaulted to false (show).
Fix: add effectiveHideThinkingBlock computed property that returns
true when hideThinkingBlock is true OR the session thinking level is
"off". All render paths (streaming, transcript rebuild, component
construction) now read the effective value instead of the raw setting.
The toggle (Ctrl+T) is guarded: when thinking is off, it shows a
status message ("Thinking is off — enable thinking to show blocks")
instead of silently no-op'ing or corrupting the persisted setting.
Fixes#626
Tail appended transcript JSONL instead of rebuilding rendered history on every poll, collapse compacted history for live chat rendering, and replace synchronous session rewrites so tailers detect historical changes.
Fixes#3258
- Centralized draft state and image management by migrating fields from context to the CustomEditor component.
- Standardized transcript row construction by introducing shared helpers for background jobs, IRC traffic, and file mentions.
- Refactored redundant UI logic and helper functions into reusable utility modules to streamline message submission and component rendering.
- Standardized event handler types by consolidating lifecycle definitions into a shared module while maintaining public API stability.
The sticky panel above the editor rendered as ambient text:
Todos
└ I. Foundation
└ ☐ ...
so users perceived the bordered tool-result block in chat as the
only todo display. Once that result scrolled into history they
concluded the list 'isn't anchored'.
Bracket the panel with dim horizontal rules (matching BtwPanel /
OmfgPanel) and inline progress + active-phase pointer in the header
(`Todos · 2/7 done · I/III Foundation`) so the persistent HUD reads
as a real panel and stays self-describing without scrolling back.
Fixes#3213
Updated optimistic replay to track the replacement component handles created during transcript rebuilds, so expanded slash prompts still replace the raw replayed message.
Extended the regression test to cover the rebuild window called out in review.
Fixes#3199
Replaced raw optimistic slash-command transcript entries with the canonical user message emitted by AgentSession when prompt expansion changes the text.
Added coverage for prompt-template expansion reconciliation so the transcript keeps one expanded user message.
Fixes#3199