- Add 'Enabled' toggle field to detail editor (● on / ○ off)
- Show ●/○ markers in roster list for enabled/disabled advisors
- Show enabled status in advisor preview panel
- Add overlay test verifying disabled advisors render with ○ marker
- Add 'enabled' field to AdvisorConfig (default true, persisted in WATCHDOG.yml)
- Filter disabled advisors in #resolveAdvisorRuntimeDescriptors, keep in status map
- Classify quota/rate-limit errors separately from transient server errors
- Auto-pause advisor on quota exhaustion, auto-resume after 5min cooldown
- Add AdvisorRuntimeStatus enum (running/paused/no_model/quota_exhausted/error)
- Render per-advisor status dots in status line: ●○✕ with truncation to 4+ '+'
- Include disabled/no-model advisors in PerAdvisorStat with status field
- Add notifyQuotaExhausted host callback distinct from notifyFailure
- Tests: config round-trip for enabled field, quota classification, overloaded path
computeNonMessageTokens / computeNonMessageBreakdown re-tokenize the system
prompt and every tool's wire schema (per-tool JSON.stringify) on each call,
but the per-turn compaction and context-threshold paths call them several
times (getContextBreakdown twice, #estimateStoredContextTokens once) over
inputs that change at most once per turn. Memoize on the identity of
(systemPrompt, tools, skills) -- the same stable refs the StatusLineComponent
cache already trusts -- so the expensive parts run at most once per input
change instead of per call.
Stopped new-session and session-switch UI paths from detaching active loader/render components without running their disposal hooks.
Added container and loader coverage for disposing children before destructive transcript/status replacement.
Fixes#4686
- Detected copied shell-prompt transcripts before the Python shortcut router.
- Forwarded OMP terminal chrome pastes through normal prompt submission.
- Added regression coverage for the #4678 transcript shape.
Fixes#4678
- Moved emergency terminal restore registration to the TUI terminal initialization logic.
- Ensured terminal restoration triggers correctly on fatal exits by moving registration out of a side-effect-heavy barrel module.
- Added a registration guard to prevent redundant postmortem handler attachments.
- Added throttling and debouncing to HUD data rendering and observer UI synchronization to coalesce update bursts.
- Constrained the subagent HUD display to a maximum of 8 rows with a truncation notice for hidden sessions.
- Enhanced the session observer registry to categorize update types, enabling more granular UI reconciliation.
- Verified render coalescing and display truncation behavior with comprehensive integration tests using fake timers.
sendErrorNotification now reads the settled turn from event.messages,
but sendCompletionNotification still read viewSession.getLastAssistantMessage().
For a classifier-refusal turn that stale/undefined lookup no longer
matched 'aborted'/'error', so with completion.notify=on the same
failed turn fired both the error toast and a misleading 'Complete'
toast.
Thread the same agent_end event into sendCompletionNotification so
both gates read one consistent source of truth.
This branch tracked main forward through many merge commits over its
long life; four files carried stale fixups for intermediate states of
main that current main never needed (a test-title rename, retimed
pi-native stream fixtures, a mermaid-cache type refactor, and a
multi-path test rewrite). None relate to error.notify, and current
upstream/main's own versions of these files already pass. Restore them
to keep this PR scoped to the error-notification feature.
Classifier-refusal failures end a turn with stopReason === "error" but
get pruned from the active context (agent-session.ts's
#removeAssistantMessageFromActiveContext) before agent_end fires.
sendErrorNotification() read viewSession.getLastAssistantMessage(),
which reflects that mutated context and silently missed the
notification for exactly the turns it should fire on.
Thread the agent_end event through #handleAgentEnd -> #finishAgentEnd
so sendErrorNotification reads the turn's own outcome from
agent_end.messages instead.
Resolves the two Codex P2s raised on #4420 that merged unaddressed:
- wrapUrlRows indented every continuation chunk. A multi-row terminal
selection includes the newline plus that indent; address bars strip
newlines but preserve or percent-encode embedded spaces, so the
reassembled URL was corrupted at every chunk boundary - silently,
when the damage landed inside a query value. Chunk rows now carry
zero leading bytes (label rows keep their indent), and the test
reassembly helper concatenates chunks raw instead of stripping the
indent that previously masked exactly this defect.
- #launchUrlIfSafe advertised a localhost /launch copy target for
flows whose redirectUri never returns to the loopback server. Its
catch-comment assumed custom-scheme URIs are non-parseable, but
new URL('vscode://gitlab.gitlab-workflow/authentication') parses
fine and sailed through the pathname check. The guard now requires
an http(s) loopback redirectUri (localhost / 127.0.0.1 / [::1]);
custom schemes, non-loopback hosts, and unparseable URIs all
suppress the launch URL. Regression tests cover the GitLab Duo
vscode:// shape and a fixed non-loopback HTTPS redirect.
Refs #4418
- Propagated builtin allowArgs metadata into TUI autocomplete entries.
- Let no-arg slash-looking prompts fall through to prompt-composer completions while keeping argument-capable commands scoped.
- Added regressions for /settings @ and /settings #copy.
- Stopped prompt-composer # actions and @ file references from claiming submitted slash-command argument text without explicit argument completions.
- Added provider regression tests for /rename title arguments and explicit command argument completions.
Fixes#4600
macOS laptops have no dedicated Forward Delete key. Fn+Backspace is the
only way to send \e[3~, and many macOS terminals (Terminal.app, some
iTerm2 profiles) deliver \x7f for that combo instead — so the keystroke
landed in the search box, not the delete handler, making session deletion
unreachable for those users.
Add a Backspace-on-empty-search handler alongside the existing Delete
check. With a typed query, Backspace stays bound to the search Input so
users can still edit their filter text. The existing confirmation dialog
guards against accidents.
Footer hint updated: [Del delete] -> [Del/⌫ delete].
Rendered the status-line token rate as an explicit tok/s unit so Ghostty no longer auto-detects the numeric value as a URL.
Added a regression test for the token_rate segment rendering contract.
Fixes#4541
Replace the visible-anchor suppression with a billed-usage predicate so live and resume paths agree on rendering the badge whenever the turn actually consumed tokens. Only genuinely free turns (no input, output, cache, or premium requests) drop the row, so hidden automated turns keep cost transparency.
Fixes#4532
- Exposed retry fallback chains in the model settings panel.
- Added a /model action that assigns the selected model as the default retry fallback.
- Cleared retry cooldown suppression when users manually switch models.
Fixes#4533
Suppress token-usage rows for assistant turns that have no visible text, tool call, or terminal error anchor. Share the same decision across live rendering and transcript rebuilds so resume matches live output.
Fixes#4532
`#filterModels` in `packages/coding-agent/src/modes/components/model-selector.ts`
used to auto-switch to the ALL tab on any non-empty query. Typing a search from
a provider tab silently escaped the scope, so selecting the apparent match could
persist a same-named model from a different provider (e.g. a custom-proxy
`glm-5.2` while the user was on the openrouter tab wanting
`z-ai/glm-5.2`) under the default role.
Keep the search scoped to the active provider tab; empty results now name the
active tab and point at ALL as the explicit escape. Regression tests in
`test/model-selector-provider-search-scope.test.ts` cover the scoped search,
the still-global ALL-tab search, and the empty-state hint.
Fixes#4522
Once the assistant reply stops streaming, `vocalizer.clear()` was only invoked from the aborted-stream cascade in EventController. Escaping after the model finished fell through InputController to the empty-editor double-Esc gesture while StreamingAudioPlayer kept draining buffered Kokoro PCM.
Add `Vocalizer.isSpeaking()` (true while any live player, stream handle, or in-flight abort is around) and consult it in the Esc handler before the double-Esc branch: if speech is still audible, a single Esc calls `vocalizer.clear()` and resets `lastEscapeTime` so tree/branch stays reachable via the next press.
Fixes#4521
Reverted the defensive typeof guard; the assistant component contract guarantees the method, and test doubles now mock it. Keeping the production call strict avoids masking broken mocks or silently skipping persistence-key recovery.