- Stopped and cleared the working loader before auto-compaction and auto-retry.
- Ensured stale loadingAnimation is removed so agent_start recreates the Working loader.
- Set catalog model input/output costs to 0.09/0.18 and reduced maxTokens to 65536.
- Added `paste.largeMenuThreshold` setting with default 100 and values 0/100/250/500/1000.
- Added `Editor.onLargePaste` and `Editor.insertPaste` to intercept oversized pastes and expand markers.
- Added threshold-based large-paste routing to show the menu and wrap oversized content for code/XML or attachment fallback.
- Added tests for large-paste interception, fallback behavior, short-paste handling, and marker expansion.
- 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.
- Routed `customType: "handoff"` messages to the compact divider path in Agent Hub and UI helpers.
- Added handoff summary expansion that extracts context text and strips `<handoff-context>` wrappers.
- Refactored shared divider rendering into `SummaryDividerComponent` used by compaction and handoff messages.
- Hardened left-arrow double-tap handling with a new detector that only fires on a second tap in a 40--500ms window.
- Reused that detector for both Agent Hub opening and focused-subagent return so terminal-synthesized burst taps no longer trigger navigation.
- Added gesture tests for deliberate doubles, burst suppression, minimum-gap filtering, and focused-subagent unfocus behavior.
`restoreQueuedMessagesToEditor` prepended queued text but appended queued
images to `pendingImages`. Positional `[Image #N]` lookup at submit time
therefore broke whenever the editor draft already held pending image(s):
queued markers (numbered 1..K against their own image list) collided with
draft markers (1..M) and resolved to the wrong images; queued images
landing past slot M were orphaned.
Add `shiftImageMarkers(text, offset)` to `image-references.ts` and have
`restoreQueuedMessagesToEditor` walk each queued message in order,
shifting its markers by the running pending-image count (existing draft
images plus images already pulled in from earlier queued messages). Draft
markers stay untouched because draft images keep their original slots.
Paste markers are left alone — those are owned by the editor's paste store,
not the pending-image buffer, and queued message text never carries
unmaterialized `[Paste #N]` because the editor expands paste markers in
`getExpandedText()` before `onSubmit` fires.
Regression test seeds a draft image + a queued image-message in
`input-controller-compaction-image.test.ts` (per acceptance) and locks the
marker -> image mapping after restore. Unit tests for
`shiftImageMarkers` cover the WxH tail, Paste-marker passthrough, and
the zero-offset no-op.
Fixes#2531
- 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.
Add an optional `role` field to the task spawn contract, threaded end to
end through resolveSpawnItems/spawnParamsFor into the executor. A role
injects a specialization preamble into the subagent system prompt and
becomes the subagent's display name and telemetry identity (label
normalized, length capped), so delegated trees stop being clones of one
generic worker. Empty/absent roles fall back to the agent type name.
Refs #2467
Op: extend
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).
restoreQueuedMessagesToEditor only drained the agent steering/follow-up
queue via session.clearQueue(), but the "Alt+Up to edit" pending-bar
hint is rendered for both that queue and ctx.compactionQueuedMessages.
Messages typed while the session was compacting -- including /skill:*
follow-ups, which the follow-up path routes to the compaction queue
before its skill check -- were advertised by the hint yet unreachable,
so Alt+Up reported "No queued messages to restore".
Drain compactionQueuedMessages alongside the agent queue, merged in the
same order the pending bar renders (session-steer, compaction-steer,
session-follow-up, compaction-follow-up). The existing text-join, image
hand-back, and abort paths operate on the merged list unchanged.
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.
- Added a changelog entry describing that submitted user messages no longer pair OSC 133 command-start markers with missing end markers.
- Annotated the user-message component with guidance to avoid emitting OSC 133 command-start markers for submitted prompts.
- Removed the unused OSC133 final zone marker constant from the user message component.
- Stopped appending the final OSC133 marker so rendered messages now end with only the shell integration close code.
executeCompaction added a transcript Spacer(1) the handoff path never adds; it leaked as an orphan blank line on cancelled/failed compaction (only the OK branch cleared it via rebuildChatFromMessages). Removed it, and on the OK branch the loader is stopped + statusContainer cleared before the rebuild so the live loader no longer composites over the reconciled transcript. The finally block stays as the idempotent cancel/fail safety net.
ToolExecutionComponent.#updateDisplay() re-ran renderResult (O(result-size)) on every invalidate - spinner ticks, stream chunks, resizes, keystrokes - so large results blocked the loop for seconds and typing lagged. It now early-returns on an unchanged dirty key (result version, expanded, partial, spinner frame, image visibility, theme epoch), and theme.ts exposes getThemeEpoch() bumped on every theme swap so cached blocks re-render on theme change.
With display.showTokenUsage on, the usage row was rendered inside the
assistant block above the turn's tool blocks. Finalizing the assistant
block was therefore deferred, and the late append recommitted the
already-committed tool rows, duplicating them in scrollback (worst with
parallel tool calls).
The assistant block now always finalizes as soon as a tool-call appears,
and the usage row is emitted as a standalone finalized block below the
turn's tool blocks across all three render paths (live event-controller,
transcript rebuild, agent-hub). The now-dead setUsageInfo/#usageInfo path
on AssistantMessageComponent is removed.
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.
Each live ToolExecutionComponent advanced its spinner glyph from its own
per-instance start time, so concurrent tool spinners showed different
frames and read as janky. Derive the glyph from a single shared monotonic
clock (sharedSpinnerFrame) so every live block animates in lockstep; the
per-instance interval still drives requestRender. The unused
#lastSpinnerAdvanceAt anchor is removed; the todo-strike counter is
untouched.
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.
The HTML session export rendered empty text tokens (text, userMessageText,
customMessageText, toolTitle) as the dark-theme grey #e5e5e7 on every theme
whose name was not literally 'light'. getResolvedThemeColors picked
defaultText off name === 'light', and the standalone isLightTheme helper
classified on userMessageBg — both diverged from Theme.isLight, which has
classified on statusLineBg since porcelain proved userMessageBg unreliable.
Factor the status-line luminance into isLightThemeJson(themeJson) and route
both helpers through it. defaultText now falls back to #000000 on every
light theme (sandstone, limestone, porcelain, light, …) and to #e5e5e7
elsewhere, matching Theme.getColorHex.
Regression test extends test/theme-islight.test.ts with the standalone
helper and getResolvedThemeColors HTML-default coverage for sandstone +
dark.
Fixes#2516
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
- Added tail-only transcript rendering via `renderViewportTail`, stopping at `maxRows` and returning `EMPTY_TAIL`.
- Added `ViewportTailProvider`/`asViewportTailProvider` API and resize getters for viewport state.
- Implemented viewport-only painting during non-mux resize with deferred full repaint after settle.
- Added settle-resize helpers and coverage for deferred repaint timing and cancel paths.
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
- Refactored status-line context caching to be keyed by message, tail, and window.
- Updated context usage flow to pass breakdown tokens and expose null usage when unknown.
- Adjusted context percentage handling so zero or unknown windows yield nullable values.
- Expanded status-line cache tests for usage provenance, memoization, and invalidation.
- 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.
- Added a credential-picker `/logout` flow for selecting one OAuth account.
- Added optional `/logout` provider argument and unknown-provider error handling.
- Changed logout handling to delete only the chosen credential and keep others.
- Added AuthStorage APIs to list and remove credentials by id, with remote deletion hook.
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
- Filtered dot-only or blank thinking blocks so they no longer render as assistant thought.
- Adjusted assistant-message and streaming-reveal logic to use visible-thinking helpers for consistency.