- Add support for customizable composer shapes and editor border styles including box, claude, pi, and borderless.
- Introduce a setup wizard scene and settings selector integration for choosing and previewing composer shapes.
- Enhance status line rendering to support standalone placement and context usage percentage shading.
- Add comprehensive unit tests covering composer shape previews, editor border styles, and status line rendering.
showHookCustom hardcoded the overlay geometry and never read
overlayOptions/onHandle, which regressed the v0.45.6 API (PR
badlogic/pi-mono#667). Forward overlayOptions to showOverlay (keeping the
full-cover defaults as fallback), invoke onHandle with the returned
OverlayHandle, widen the options type via a shared ExtensionCustomOptions,
and re-export OverlayHandle/OverlayOptions from the extension API.
Key hints resolved modifier tokens through a static, platform-agnostic label map with no `super` entry, so on macOS the shipped `super+v` paste default rendered 'Super+V' (no such key on a Mac) and `alt` always rendered 'Alt' instead of 'Option'. The static /hotkeys navigation rows were hardcoded with macOS 'Option'/'Cmd' names on every platform, so Linux/Windows users saw 'Cmd+Left'.
Modifier labels are now platform-aware: on darwin `alt` renders 'Option' and `super` renders 'Cmd'; every other platform keeps 'Alt'/'Super'. The platform is resolved through a single seam (setKeyHintPlatform/keyHintPlatform) mirroring the TUI's setKittyProtocolActive, keeping hint output deterministic in tests without mutating process.platform. The static /hotkeys rows use the same convention and drop the macOS-only Cmd line-start/end fragments (which map to no binding) off darwin.
Fixes#8235
- Gated model-issued approval dialogs on the TUI tool preview lifecycle.
- Finalized edit arguments before waiting for the asynchronous diff.
- Added regression coverage for both the preview gate and approval ordering.
Fixes#7957
- A provider-supplied retry-after now bypasses the transient rate/concurrency
heuristic window instead of being overridden by it (regression from the
subscription-cap retry change).
- Updated event-controller/ui-helpers test doubles for provenance-gated
renderer selection (hasBuiltInTool), aggregated retryErrors on
auto_retry_end, and Bedrock override compat gaining streamIdleTimeoutMs.
The handoff catch in session-handoff.ts and the /handoff handler in
command-controller.ts mapped any error named AbortError to "Handoff
cancelled" regardless of whether the handoff signal was actually
aborted. Providers throw name-AbortError errors on non-user conditions
(stalls, idle timeouts, nested resolution failures), so a genuine
generation failure surfaced as a user cancellation and hid the cause.
Only report "Handoff cancelled" when handoffSignal.aborted is set;
re-throw the real error otherwise. The controller now trusts the
normalized "Handoff cancelled" message and drops its own AbortError
check so re-thrown provider failures render as "Handoff failed: ...".
Fixes#7903
After the fullscreen Plan Review closed on approve-and-execute, the
conversation view stayed blank while the plan ran. The propose write's
tool_execution_end handler runs inside EventController's serialized
dispatch chain and awaited handlePlanApproval, which awaits session.prompt
for the entire execution turn, so every later agent_start/message_start/
tool/message_update event queued behind it until the run finished.
Detach the approval dispatch so the dispatch link settles immediately and
the execution turn's events render live. Follow-up to #5688, which only
moved the overlay close before the still-blocking dispatch.
Fixes#7684
Reaching the `isTerminal === false` branch means the superseded-turn guard
above it already passed, so `session.isStreaming` is false: a command
issued from that point mounts immediately while panels queued earlier in
the turn stay in `#pendingCommandOutput` until some later terminal
agent_end. Newer output rendered ahead of older, and the queued panel
could strand for minutes on an async fan-out that keeps settling
non-terminally.
Flush there too. The transcript is quiescent at a settle, which is the
condition #4806 wanted, and the notice now says "until the agent pauses"
rather than promising the current turn.
Reserve the plain b shortcut only after /btw has a completed answer or a branch is already pending. Running, empty, aborted, and failed panels now leave the key for the composer, while completed-but-refused branches still consume it with an explanation.
Fixes#7474
Branched session files preserve entry ids, so leaf-id equality alone let a stale /btw answer promote into a different loaded session. Capture the originating session id at /btw start and require it to match at both the controller gate and every branchFromBtw checkpoint.
Fixes#7474
- Passed the authorized leaf through the branch executor and revalidated it before rewriting history.
- Refused promotion during active turns and bounded post-prompt drains.
- Consumed unavailable branch keys while showing pending and refusal state in the panel.
Fixes#7474
The isTerminal:false early-return skipped #finishAgentEnd, the only site
that flushes a deferred plan-mode model switch, so an automatic continuation
(async wake) ran on the old model/thinking level until the terminal settle.
Flush the pending switch on the non-terminal branch before returning; the
title/loader teardown stays deferred to the terminal agent_end.
EventController.#handleAgentEnd guarded only on session.isStreaming, so a
non-terminal agent_end (isTerminal:false, emitted while an async job will
re-wake the loop) flipped the terminal title to idle and tore down the
working loader while a /vibe worker or async bash job was still running.
Early-return on event.isTerminal === false, matching the guard every other
agent_end consumer already applies; the later terminal agent_end performs
the normal teardown.
Fixes#7386
/memory stats and /memory diagnose fall back to a generic
'Memory <action> is not available for the <backend.id> backend.'
message whenever the active backend's stats/diagnose hook is
undefined. For every real backend (hindsight, mnemopi, local) this
reads fine, but the off backend isn't a backend a user picked among
several stats-capable options - it's the no-op state memory falls
back to by default - so the same template renders as 'Memory stats
is not available for the off backend.', which reads as an odd,
almost self-contradictory warning.
Special-case backend.id === "off" with wording that matches the
phrasing offBackend.status() already uses elsewhere ('Memory backend
is off.'), and add a unit test covering both the off-backend wording
and the unchanged generic fallback for a real backend (local) that
simply has no stats hook.
The regression test only asserts on a mocked showWarning call and
never renders Markdown, so it doesn't need a real theme instance;
drop the global dark-theme setup/teardown to avoid leaving the
process-wide theme singleton mutated for later suites in the same
Bun process.
- Add the `app.live.toggle` keybinding defaulted to `Ctrl+L` to start or stop live voice mode.
- Remap the default display-reset action (`app.display.reset`) from `Ctrl+L` to `Alt+L`.
- Update the live visualizer to listen for stop keys so the toggle chord terminates active sessions.
Snapshot this.ctx.sessionManager.getSessionId() for the tan clone's local://
mapping instead of session.sessionId. The two diverge after /fresh or a
provider session override, and the Windows short-root fallback keys
%TEMP%/omp-local/<id> off the session-manager id used by the parent's
large-paste writes and '/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' reads, so the mismatched id left attachments
unreachable.
Diverge the mocked session id from the manager id in the regression test so
it pins the session-manager id.
Fixes#6971
(cherry picked from commit 1efcd22326d76fdb8b50c0977a836c892e80ab76)
Capture the parent artifacts directory and session ID when /tan dispatches
instead of resolving them through the mutable interactive SessionManager.
This keeps background tan '/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' reads pinned to the dispatching transcript
after the user switches or resumes another session.
Extend the regression test to switch the mocked interactive session before
the background job starts and assert the original local mapping is retained.
Fixes#6971
(cherry picked from commit e05db428eabd5087e4b2b4a462f47927c0628e72)
TanCommandController.start nests the tan clone at
<parent-artifacts>/Tan-<id>.jsonl, so the clone's session manager derived
its own artifacts dir and hence local root <parent-artifacts>/Tan-<id>/local.
Its sdk.createAgentSession call omitted localProtocolOptions, unlike the
task-subagent path which inherits the parent's mapping, so parent-session
'/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' attachments (pasted files, generated references) were unreadable.
Thread the parent session manager's localProtocolOptions into the tan clone
so local:// resolves against <parent-artifacts>/local.
Fixes#6971
(cherry picked from commit 1ded46e182fc24f9f57d8e9a907aaad58f790783)
/usage, /session, /advisor status, /jobs, /changelog, /context, and
/memory view mounted their finalized panel immediately via ctx.present()
instead of ctx.presentCommandOutput(), the streaming-deferral path added in
#5427 for /tools and /mcp. When invoked mid-turn, the panel landed above a
still-growing live block and the append-only scrollback contract recommitted
it lower down, so it appeared twice in native scrollback.
Route all six large command panels through presentCommandOutput() so they
defer until agent_end, matching /tools and /mcp.
Fixes#6767
Scheduling agent.continue() inside navigateTree started a post-prompt
task before the interactive /tree handler rebuilt its transcript, so a
fast provider's agent_start/turn_start could render against the stale
pre-rebuild UI and then be clobbered by renderInitialMessages.
navigateTree now reports the commit via askReanswerCommitted instead of
resuming, and SelectorController.showTreeSelector calls the new
AgentSession.resumeAfterAskReanswer() only after renderInitialMessages +
reloadTodos, so the resumed turn always renders against the rebuilt
transcript.
Addresses chatgpt-codex-connector review on #6484.
Fixes#6483
Moved vocalizer interruption from agent_start to user message_start so agentLoopContinue preserves queued speech.
Expanded regression coverage for continuation starts and user prompt boundaries.
Fixes#6375
Moved vocalizer clearing from each assistant/tool continuation boundary to the start of a new agent run.
Added regression coverage for uninterrupted internal turns and new-run interruption.
Fixes#6375