- Extracted decodeStreamedToolArgs into tool-args-reveal.ts and used it from both the live event path and transcript rebuilds, so mid-write theme/settings/focus replays no longer show stale streamed write/edit/eval content.
- Fixed the smoothing-off live path returning stale provider-parsed args.
- Documented the mandatory shared decode in the AGENTS.md streaming-preview hazard note; added changelog entries for this batch.
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
Registered `edit.input` and `eval.code` alongside `write.content` so the reveal controller decodes those top-level string arguments incrementally between throttled full-JSON parses.
Added a regression test covering multi-key extraction so the wire-up survives future additions.
Refs #4043
Added incremental decoding for streamed write content so preview args update below the full JSON parse throttle.
Added a regression test covering sub-throttle content growth in ToolArgsRevealController.
Fixes#4043
- Improved the leaked-thinking stream projector to clone and sync native tool-call blocks directly.
- Eliminated the need for placeholder IDs and complex rekeying logic in the event controller and argument reveal module.
- Simplified native tool-call validation in owned-stream processing by requiring only a non-empty name.
- Added comprehensive unit tests to ensure tool-call IDs and partial JSON parameters remain intact during healing.
- Added `#streamTurnNonce` to prevent aborted streaming turns from corrupting content indexes of subsequent messages.
- Implemented temporary stream-key generation using content position and turn nonces for previewing tool calls without native IDs.
- Added migration logic to key pending tool previews by their real ID and rekey `ToolArgsRevealController` once the real ID is parsed.
- 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.
- Skipped processing tool calls in the event controller streaming message when the tool ID is missing.
- Prevented creating orphaned empty placeholder cards caused by empty IDs during early Anthropic and OpenAI tool block streaming.
StdinBuffer held a bare `\x1b\x1b` chunk (or emitted it as one when followed by
a non-CSI byte). `parseKey("\x1b\x1b")` returns undefined, so CustomEditor
fell through to the base editor and never fired the configured `onEscape` —
the double-escape gesture and the second-press single-Esc handler both went
dead whenever the terminal batched the two presses into one stdin read.
Split a bare `\x1b\x1b` into two ESC events at the buffer layer, mirroring
the existing split for ESC + SGR mouse report. Meta-CSI/SS3 chords
(`\x1b\x1b[A`, `\x1b\x1bO…`) still emit as one combined sequence.
EventController.tool_execution_update re-armed the working loader when a
transient overlay (auto-compaction / auto-retry / handoff) had torn it down
mid-tool; tool_execution_end did not. A subagent (`task`) call only fires
_end, so a task result landing after such an overlay left the UI looking
idle even though the session was still streaming. Mirror the reconciler
call in #handleToolExecutionEnd.
Fixes#3857
- Optimized TUI tool argument previews by throttling JSON re-parsing to prevent frame starvation during high-frequency streaming.
- Suppressed redundant component updates for unchanged parsed fields while maintaining raw preview integrity for bash and patch renderers.
- Added adaptive parsing logic to `ToolArgsRevealController` that distinguishes between renderers requiring continuous raw JSON streams and those consuming parsed arguments.
- Updated `EventController` to dynamically determine exposure requirements based on tool type and wire-format metadata.
- Replace the static "Goal/Next" status line with an ephemeral LLM-generated summary triggered after idle periods.
- Hook the recap into the agent's side-channel pipeline, using live goal and task state as context anchors for meaningful recaps.
- Implement abort logic so that active user interactions immediately cancel pending recaps and discard late-arriving responses.
- Added idle recap functionality to EventController to display goals and next actions when the agent is inactive.
- Updated session parsing in gc-cli and memories to correctly support optional title entries in session files.
The time_spent segment rendered Date.now() - sessionStartTime, so an
idle session displayed hours of "time spent" while the agent did
nothing — the only inputs were wall-clock and the unmoving session
start.
Replace sessionStartTime with activeMs in SegmentContext and accumulate
inside StatusLineComponent across agent_start -> agent_end windows.
markActivityStart/markActivityEnd are idempotent (reentrant agent_start
events and superseded agent_end events never double-count); the segment
ticks live during an open window and freezes when the agent yields.
The session-boundary hook drops the now-meaningless wall-clock argument
and is renamed setSessionStartTime -> resetActiveTime; it zeroes the
accumulator and drops any in-flight window so /clear / fresh-session /
joined-collab paths start the meter at zero.
Fixes#3681
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
- Migrated internal streaming state from string-based properties to symbol-keyed properties for improved data isolation and safety.
- Replaced the deprecated `stripVariant` utility with centralized `clearStreamingPartialJson` and symbol-specific helper methods across all provider implementations.
- Implemented `stripStreamingBlockSymbols` and updated deep equality checks to ensure metadata does not interfere with content comparisons.
- Standardized streaming metadata access through a new `block-symbols` utility module.
- Migrated 288 lines of scattered error classification logic from `utils/error-id.ts` into a cohesive `packages/ai/src/error/` module with 13 specialized submodules covering flags, classes, OAuth, providers, rate-limiting, and finalization.
- Replaced 100+ generic `Error` throws across 60+ provider and registry files with semantic `AIError.*` classes (e.g., `AIError.MissingApiKeyError`, `AIError.OAuthError`, `AIError.ProviderResponseError`), improving error diagnostics and retry logic.
- Consolidated error utility imports from `pi-utils` and scattered classification functions into a single `AIError` namespace, reducing coupling and simplifying error handling across all packages.
- 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.
Mid-turn renderSessionContext (settings overlay close, focus attach during streaming) now hands the rebuilt todo snapshot back to the EventController via the new inheritDisplaceableTodo method instead of sealing it. Idle rebuilds keep the historic seal path.
Added a regression test that asserts the trailing todo snapshot is published to the controller and stays displaceable while session.isStreaming is true.
Fixes#3516
Dropped eager todo snapshot displacement from tool_execution_start, streaming message_update, and the rebuild assistant-iteration step. Displacement now runs only when the next todo's successful result lands, so a failed follow-up leaves the last-good todo panel on screen.
Added regression coverage for the failed follow-up case and updated the streamed-second-todo test to drive displacement from the success result.
Fixes#3516
Resolved existing todo components before tool execution so streamed tool-call previews can still displace stale todo snapshots.
Added regression coverage for pre-created todo calls replacing a prior todo panel after intervening tool output.
Fixes#3516
Kept successful todo result blocks live until a later todo update replaces them or the turn ends.
Added regression coverage for same-turn todo snapshot replacement after intervening tool output.
Fixes#3516
Address P2 review: auto-classification (e.g. high→medium) emits
thinking_level_changed without changing effectiveHideThinkingBlock.
The unconditional resetDisplay() turned routine auto-classification
updates into a full terminal replay on direct terminals.
Track #prevHideThinking and only call resetDisplay() when the effective
visibility actually changed. For effort-only changes, use a lightweight
requestRender() instead.
Address P2 review: StreamingRevealController caches #hideThinkingBlock
in begin()/setTarget(). When thinking level changes mid-stream, the
timer's #tick() kept rendering with the stale cached value, causing
provider-returned thinking to reappear after the off state.
Add resyncVisibility() method to StreamingRevealController that:
- Re-reads #hideThinkingBlock and #proseOnlyThinking from getters
- Recalculates visible units (hiding thinking reduces total)
- Clamps #revealed to new total (prevents over-revealing)
- Re-renders with #renderCurrent() using updated values
- Resyncs timer via #syncTimer()
Call resyncVisibility() in the thinking_level_changed handler instead
of streamingComponent.updateContent().
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
- 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.
Skipped optimistic replacement when a user message_start matches another recorded local submission, preserving the pending prompt bubble until its own expanded event arrives.
Added coverage for the queued-message drain race between startPendingSubmission and prompt dispatch.
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
- Synchronize tool arguments with the component state upon receipt of `tool_execution_start` to ensure visual consistency when final update events are missed.
- Terminate active argument reveal streams to prevent late ticks from overwriting valid, fully-materialized tool arguments with stale partial data.
- Add test coverage to verify that tool UI components render finalized arguments even in the absence of intermediate streaming updates.
- Added validation to scan for non-ASCII characters before performing snap-compaction, falling back to LLM-based summarization if the unrenderable ratio is too high.
- Updated event handling and status reporting to explicitly support snapcompact actions, including specific error warnings and cancellation states in the UI.
- Updated session logic to default to snapcompact strategy when auto-compaction is enabled.
- Stop the Esc key from aborting active background maintenance (compaction, handoff, or retry) while a subagent is focused.
- Remove "(esc to cancel)" hints from maintenance loaders when a subagent is active to avoid false affordance.
- Ensure that main-session maintenance remains cancellable via Esc when no subagent is focused.
Fixes#2819
- Added a `proseOnlyThinking` configuration setting to suppress raw code blocks in AI thinking traces.
- Implemented `formatThinkingForDisplay` utility to replace code blocks with ellipses in the UI.
- Integrated runtime toggling and live refreshing of message components via streaming reveal controllers.
- Added a live tokens-per-second indicator to the assistant thinking pulse.
- Verified logic with new unit and integration tests for thinking block presentation.
- Improved thinking loop detection logic by refining text normalization and treating stalls as retryable errors.
- Instrumented the agent session to recognize thinking loop markers within retryable error conditions.
- Automated the clearing of stale error banners upon successful auto-retry execution.
- Added comprehensive test coverage for chunked thinking loop errors and banner management.
- Renamed the global `INTENT_FIELD` constant from `_i` to `i`.
- Updated documentation strings, type annotations, and test expectations across packages to reflect the new field name.
- Ensured consistent usage of the constant in tool schema construction and intent serialization.
- 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.
- Added `display.cacheMissMarker` setting to enable visual indicators for prompt cache invalidation in assistant messages.
- Included updated theme symbols to represent cache misses across supported icon sets.
- Configured the selector controller to rebuild the chat display when the marker visibility setting is toggled.
- 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.
- Moved the `INTENT_FIELD` constant from `@oh-my-pi/pi-agent-core` to the specialized `@oh-my-pi/pi-wire` package to permit broader usage across the monorepo.
- Updated all references across `agent`, `ai`, `coding-agent`, `collab-web`, and `snapcompact` packages to import the constant from the new location.
- Added `@oh-my-pi/pi-wire` as a dependency to all affected packages.
- 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.
- Canonicalized assistant and thinking messages by trimming and collapsing dot text.
- Skipped rendering assistant and thinking blocks when canonicalized content was empty.
- Filtered ACP thinking notifications and session outputs to ignore placeholder content.
- Added canonicalizeMessage tests for undefined, blank, whitespace, and dot-only inputs.
- 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.