- Added tracking for expected cache invalidations during model changes, compactions, and plan-mode transitions.
- Included `cacheMissExplainedAt` metadata in session context to prevent displaying misleading cache miss warnings in the transcript.
- Updated controller logic to reset assistant usage markers when mode-switching or performing actions that invalidate the prompt cache.
- Implemented `detectCacheInvalidation` to identify when model requests lose their prompt cache.
- Added `CacheInvalidationMarkerComponent` to display a slim notice above affected assistant turns.
- Updated `ChatTranscriptBuilder` and `EventController` to track session usage and inject markers dynamically.
- Included comprehensive test coverage for invalidation detection logic and UI rendering.
- Added a `mode` property to `CompactOptions` to allow fine-grained control over compaction strategies.
- Implemented `soft`, `remote`, and `snapcompact` submode overrides for the `/compact` command.
- Integrated `parseCompactArgs` to enable robust subcommand routing and validation, including focus instruction rejection for specific modes.
- Established a `CompactMode` registry to manage compaction strategies and verify remote availability.
- Added `#disposed` flags and guards to `StatusLineComponent` to stop async callbacks running post-dispose.
- Updated `onThemeChange` to return an unsubscribe function and registered it in `InteractiveMode`.
- Fixed schema validation for `spinnerFramesSchema` to narrower types instead of dynamic or-validation.
- Resolved settings leaks by saving and restoring `tuiTight` state in tests.
- Added regression tests verifying that pending async microtasks do not trigger updates after disposal.
- Added new `tui.tight` setting with default `false` and documented behavior.
- Added global tight-mode APIs and `setIgnoreTight` propagation across Box, Text, and Markdown.
- Handled `tui.tight` updates to refresh UI borders and trigger rerendering.
- Updated key message components to ignore tight mode selectively and preserve intended spacing.
- Added context snapshot metadata to AssistantMessage for prompt and non-message token history.
- Anchored context usage calculations on assistant snapshots and computed percent numerically.
- Updated status-line, /context, selector, and interactive mode flows to share session usage totals.
- Extended status-line cache fingerprinting and invalidation for assistant usage and prompt/tool/skill changes.
- Added LaTeX math and Mermaid allowances in terminal and final-chat prompts.
- Added inline math tokenization in TUI for $, $$, \(\), and \[\].
- Added LaTeX-to-Unicode conversion helpers and exports for math rendering.
- Fixed inline math detection to skip escaped dollars and currency-like spans.
- Removed render_mermaid from tool discovery, task definitions, and registries.
- Removed renderMermaid setting and prompt/docs references tied to the deleted tool.
- Added maxWidth and theme color options to Mermaid ASCII resolution in markdown flow.
- Re-rendered Mermaid ASCII in both directions and clipped output to available width.
- Rewrote `formatSessionDumpText` in `session-dump-format.ts` to emit the pre-16.x full dump: system-prompt prelude, model/thinking config, tool inventory with parameters, and the transcript as markdown role headings (`## User`, `## Assistant`, `### Tool Call`/`### Tool Result`), reusing `renderDelimitedThinking` for `<thinking>` blocks.
- Dropped the compact default and the `[raw]` flag from `/dump`: removed the `isRaw` parameter from `handleDumpCommand` in `command-controller.ts`, `interactive-mode.ts`, and `types.ts`, and removed the `inlineHint: "[raw]"`/`compact` plumbing in `builtin-registry.ts`.
- Updated the `formatSessionAsText` doc comment in `agent-session.ts` to describe the verbose dump shape.
- Removed the obsolete `formatSessionDumpText raw thinking` suite from `advisor.test.ts` and refreshed `session-dump-format.test.ts` to assert the verbose dump output.
- Recorded the revert in the coding-agent changelog and trimmed `/dump` from the compact transcript tool-intent-prefix entry.
- Created AdvisorRuntime and AdviseTool to drive a read-only advisor agent that delivers severity-tagged advice (nit, concern, blocker) with interruption policy and transcript delta rendering.
- Added /advisor slash command with on/off/status/dump subcommands to control advisor lifecycle and inspect advisor metrics (model, messages, tokens, cost).
- Added advisor.enabled and advisor.subagents settings to enable passive advisor review on main agent and spawned task/eval subagents.
- Implemented advisor message rendering with severity-color badges (blocker=error, concern=warning, nit=muted) in chat log and status line indicator (++ badge).
- Extended yield-queue and session-history-format to support advisor batching and optional thinking block inclusion.
- Suppressed MCP connecting and LSP startup event renders when startup.quiet is enabled.\n- Added regression coverage for quiet MCP and LSP startup event handling.\n\nFixes #2639
- Added a streamingBehavior field to submitted user inputs and threaded it through interactive mode types and controller start-up.
- Updated interactive submission dispatch to default to followUp queueing while preserving explicit steer intent when provided.
- Changed tests to verify followUp and steer queueing behavior, preventing AgentBusyError from race-window busy sessions.
When a running session changes projects via /move or resumes a session
from another cwd, applyCwdChange() reloads project settings but did not
reapply the module-level provider preferences (providers.webSearchExclude,
providers.webSearch, providers.image). The previous project's exclusions
could leak and newly-excluded providers were still used by web_search.
Reapply all three provider preferences after settings.reloadForCwd(),
mirroring the initialization logic in createAgentSession.
Closescan1357/oh-my-pi#2611 (discussion_r3411061509)
ExtensionRunner.emit shared the generic 30s EXTENSION_HANDLER_TIMEOUT_MS budget with every event, including the fire-and-forget session_shutdown teardown event extensions cannot observe. A hung third-party handler — observed on Windows with omp-discord-presence 0.1.2 waiting on a stuck Discord IPC pipe — held AgentSession.dispose() for the full window, making Ctrl+C look ignored for 30s.
session_shutdown now uses a dedicated 2s SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS cap routed through a per-event handlerTimeoutForEvent() lookup so generic and shutdown budgets are independently configurable. The interactive-mode Ctrl+C path adds a defence-in-depth hard-exit: when isShuttingDown is true a fresh Ctrl+C exits with code 130 (the session JSONL has already been sync-flushed by the first press) instead of stacking another no-op shutdown() call.
Fixes#2600
Deferred MCP discovery wrote 'Connecting to MCP servers: …' straight to process.stderr while the TUI owned the terminal, overdrawing the chat input box border. onMCPConnecting now emits McpConnectingEvent on the mcp:connecting channel; InteractiveMode subscribes and renders it via showStatus (status container), mirroring the LSP-startup pattern. New mcp/startup-events.ts holds the channel, type, and formatMCPConnectingMessage.
- Added unified `omp setup speech` flow with JSON/check modes and model picker.
- Added local STT pipeline with sherpa workers, recorder/download flow, and streaming inference.
- Added local TTS pipeline with `omp say`, backend selection, and streaming vocalization.
- Replaced legacy speech settings with unified `speech`/`speechgen` configuration keys.
Reviewer flagged a race left open by the streaming guard added in #2455:
getUserInput() arms onInputCallback and schedules an 800 ms goal
continuation timer; when /goal set takes the streaming branch (or any
extension/hook starts a turn inside that window), the timer fired
unchecked. The downstream onInputCallback resolved the main waiter with
a goal-continuation submission, submitInteractiveInput called
session.promptCustomMessage without a streamingBehavior, and the same
AgentBusyError the PR set out to fix resurfaced.
Make the continuation timer streaming-aware: at fire time, bail out
when session.isStreaming || isCompacting || hasPostPromptWork is true.
Reuses the auto-submit busy check loop mode already relies on (renamed
#isLoopAutoSubmitBlocked -> #isAutoSubmitBlocked since both flows have
the same notion of 'agent is busy, do not submit'). The next agent_end
in #handleGoalSessionEvent reschedules normally.
Fixes#2454
Kept /plan <prompt> from paused plan mode on the prompted entry path while retaining the no-arg third-toggle exit.
Added regression coverage for paused plan mode resuming and submitting the prompt.
Fixes#2510
- Added an `isEmpty` getter on `AgentHubOverlayComponent` to report whether no subagent rows were loaded.
- Extended `showAgentHub` with an optional `requireContent` flag so the overlay is disposed early when empty under the double-<- path.
- Added tests for double-<- gating behavior with no subagents, with subagents, and explicit hub-open behavior.
- Moved the ctrl+p model-role cycle rendering from `showStatus` to a dedicated anchored cycle container above the editor.
- Updated InteractiveMode to rebuild the cycle container in place and auto-clear the track after 4 seconds.
- Added tests that validate no scrollback stacking, in-place replacement, and timer-based clearing behavior.
- 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.
Addresses Copilot + Codex review on #2521:
- computeEditorMaxHeight returned 1 on terminals too small to host both the
editor and the chrome reserve, but the bordered editor never renders fewer
than 3 rows (2 border + 1 content). The cap now floors at that real minimum
(EDITOR_MIN_RENDERED_ROWS) so it no longer misreports the rows the editor
occupies; rendering is unchanged, and the contract is documented honestly
(reserve holds once terminalRows >= 7).
- #resolveOverlayLayout now always resolves maxHeight (?? availHeight), so the
maxHeight !== undefined branch in effectiveHeight, the composite slice guard,
and the number | undefined return type were dead. Tightened all three.
- Mirrored the maxHeight-default contract in the render stress oracle
(resolveExpectedOverlayLayout + compositeExpectedOverlays) so the randomized
sweep validates the real clipped geometry instead of the obsolete unclipped
one; updated the oracle helper test expectation accordingly.
Editor-height tests rewritten to assert the real contract (reserve when the
terminal can host both; pinned to the bordered minimum below that).
Addresses Codex review on #2520:
- Cancel path: `#approvePlan` returned on `compactOutcome === "cancelled"`
without restoring the deferred pre-plan model, stranding the next turn on
the plan model and leaking `#planModePreviousModelState`. The model
transition now runs for the cancelled outcome too (the operator aborted
only the compaction, not the approval) before the early return.
- Queue-flush ordering: `executeCompaction` flushes input queued during
compaction before returning, so the post-return model switch landed after
the queued turn began streaming (deferred one turn via #pendingModelSwitch).
Added a `beforeFlush(outcome)` hook to `executeCompaction`/`handleCompactCommand`;
`#approvePlan` runs the transition through it (and idempotently re-runs it
afterward to cover the message-count short-circuit).
Tests cover the cancel restore and the before-flush ordering.
Plan approval dispatched the executor's first synthetic prompt without
checking whether the agent was still streaming the post-resolve
continuation (or a turn started by the approve-time compaction/clear),
surfacing "Failed to finalize approved plan: ... Agent is already
processing". Loop auto-submit and goal continuations hit the same throw
via submitInteractiveInput, which always called prompt/promptCustomMessage
without a streamingBehavior.
#approvePlan now aborts any in-flight turn before the synthetic prompt,
and submitInteractiveInput routes submissions through the steer/follow-up
queue (streamingBehavior: "followUp") when the session is streaming.
Non-streaming call shapes are unchanged. Extends the manual-/goal fix
(#2454) to the continuation and plan-approval paths.
Two bounded TUI overlap fixes:
- Editor max-height (coding-agent): the [6,18] clamp's floor of 6 exceeded
available space on terminals <=18 rows, letting the editor crowd the
transcript/status. Extracted a pure computeEditorMaxHeight(rows) with an
EDITOR_MIN_CHROME_ROWS=4 upper bound; identical for rows >=18.
- Overlay overflow (tui): #resolveOverlayLayout left maxHeight undefined when
the option was unset, so a tall overlay's bottom rows were dropped
off-screen. maxHeight now defaults to availHeight so every overlay is
sliced to fit and re-clamps on resize.
When a plan is approved via "Approve and compact context", #exitPlanMode
restored the pre-plan model before the compaction summarizer ran, so the
request cold-missed the plan model's warm prompt cache. The model switch is
now deferred: compaction runs on the plan model, and the switch to the
execution (slider) or pre-plan model happens only after a successful
compaction. A failed compaction stays on the plan model; cancellation is
unchanged. Also clears any queued plan-role model switch when deferring the
restore so it cannot later clobber the restored model.
handlePlanModeCommand had branches for entering and pausing but fell
through to #enterPlanMode() when planModePaused was true, so once a
session entered plan mode it cycled forever between plan and plan_paused.
/goal and other mode-gated commands were permanently blocked because
they refuse to run while planModeEnabled || planModePaused is true.
Add a paused branch that clears the paused flag, resets the reentry
marker (so the next /plan logs as a fresh entry, not a resume), and
appends a mode_change to "none". Tools, model, and plan state were
already restored by the prior #exitPlanMode({ paused: true }), so no
extra cleanup is needed.
Fixes#2510
The whole-line editor decorate ran on `displayText` after the editor appended
the zero-width CURSOR_MARKER and cursor glyph; both start with ESC, so the
magic-keyword regex's right-boundary `(?!\S)` rejected `ultrathink` glued to
the marker and dropped the gradient until a trailing character was typed.
- `Editor.#decorate` (pi-tui) now splits around CURSOR_MARKER and decorates
each user-text segment independently, so word-boundary lookarounds resolve
correctly on both sides; the matching comment block is corrected.
- `KeywordHighlighter` / `highlightMagicKeywords` gain an optional `phase` in
[0, 1) that cyclically rotates the gradient stops. `0` (default) yields
the static palette, so sent-bubble rendering is unaffected.
- `CustomEditor.decorateText` derives `phase` from `Date.now()` and chains
`setTimeout(SHIMMER_FRAME_MS)` ticks while focused, the buffer holds a
magic keyword, and `magicKeywords.enabled` is on — the render itself
schedules the next frame, so losing focus, deleting the keyword, or
flipping the setting stops the animation on its own. `interactive-mode`
wires the repaint hook to `requestComponentRender(editor)` on construction
and after `setEditorComponent`.
- Adds `hasMagicKeyword(text)` (cheap prose-aware probe) and tests for the
seam fix, the phase cycle, the gating, and timer cleanup.
Fixes#2475
InteractiveMode.refreshSlashCommandState() built the autocomplete
provider from builtins, hook/custom/skill commands, and file-based
slash commands but never read session.promptTemplates, so templates
loaded from cwd/.omp/prompts/ and the agent prompts directory expanded
when typed manually yet never appeared in the / picker. Pass them
through alongside file commands; filter out templates whose names
collide with an existing command so the picker mirrors the runtime
expansion order (expandSlashCommand precedes expandPromptTemplate in
AgentSession.prompt).
Added a regression test that constructs InteractiveMode with a session
that carries a prompt template, spies on editor.setAutocompleteProvider,
and asserts the captured provider returns the template for both the
empty / menu and a fuzzy /rev prefix, plus the collision-dedup contract
against the builtin /exit.
Fixes#2462
- Replaced queued-message interrupt flow with session abort calls on empty submit and escape.
- Removed interrupting state and notifyInterrupting teardown paths from abort handling.
- Updated AgentSession queue operations to use shared steering and follow-up queue views.
- Propagated isAborting through session state and collab payloads to suppress late updates.
Stopped goal objective commands from resolving the interactive input waiter while the agent is already streaming. The goal context still uses steer immediately, and the next idle goal continuation submits the objective work without AgentBusyError spam.
Added goal-mode integration coverage for both initial and replacement objective commands during streaming.
Fixes#2454