- InputController now clears typed editor text and triggers a render when Esc is pressed with content, then resets the double-Esc timer.
- The double-Esc selector path was updated to reset the terminal display before opening tree or branch message selectors.
- Escape handling tests were expanded with settings setup/teardown to verify default, branch, and draft-clearing Esc scenarios.
- Added a focused-agent left-key input listener that consumes double left taps when the input is empty.
- Routed focused session left-tap behavior through #handleFocusedLeftTap so it matches the Esc-style unfocus timing.
- Updated tests/fixtures for the new listener flow and refreshed a hashline block-edit error assertion.
- Added a SessionFocusController to switch transcript and input context between main and subagent sessions.
- Added agent-hub Enter activation and double-left return behavior for focused local agents.
- Added view-session-based event and render logic to avoid stale focus-session state.
- Added status-line focused agent display with ghost icon and focused-mode border dimming.
- Added `src/export/share.ts`: `/share` now snapshots the session JSON, gzips and seals it with a fresh AES-256-GCM key, and pushes the blob to a secret gist or the share server (1 MB cap with image/string/entry truncation via `sealToFit`); links are `<serverUrl>/<id>#<key>` with the key only in the fragment.
- Added `share-loader.js` and `scripts/generate-share-viewer.ts` building the static viewer the relay serves at `GET /s/<id>`: it fetches the sealed blob, decrypts in-browser, and hands the JSON to the export template via `window.__OMP_SESSION_DATA__`.
- Reworked the `/share` command in `command-controller.ts`/`builtin-registry.ts` off the plaintext-gist HTML upload, exported `LoadedCustomShare`, and exposed the session `SecretObfuscator` getter on `AgentSession` for redaction.
- Added `share.serverUrl` and `share.redactSecrets` settings backed by `DEFAULT_SHARE_URL` from pi-wire.
- HTML exports now embed subagent transcripts: `collectSubSessions` walks `<session>/<AgentId>.jsonl` recursively into `SessionData.subSessions`, with `includeSubSessions` opt-out and the exported `buildSessionData` reused by share snapshots.
- Added `share.test.ts` (snapshot/seal/server-url contracts) and `export-subsessions.test.ts`.
- Added write-token generation and validation to distinguish writable and read-only guests.
- Added deep-link support using `https://<relay>/#<link>` with 32/48-byte collab secrets.
- Added full-link and key-only semantics where full links grant writes and key-only links are view-only.
- Added /collab view/status/stop command updates with read-only participant status and join hints.
Reviewer flagged that the bracketed-paste path is untrusted terminal input —
ANSI escapes, control chars, newlines/tabs, or a multi-hundred-char path
would corrupt the status line (per AGENTS.md TUI sanitization rules) and
leak the absolute home-dir path.
The new ENOENT diagnostic now feeds the path through sanitizeText (strip
ANSI/C0/C1 controls), collapses CR/LF/TAB to single spaces, runs it
through shortenPath (collapse home → '~'), and truncateToWidth-clamps it
to TRUNCATE_LENGTHS.CONTENT (80) before interpolating into either the SSH
or local status string. Added a third assertion to the repro test
defending the contract: ANSI/control bytes never reach the status, and
the displayed path is bounded below the input length.
Refs PR #2376
When a local terminal forwards a bracketed-paste containing an image-file
path and the omp process is itself running on the remote end of an SSH
session, the path is on the user's local machine and unreachable from the
remote filesystem. The path-as-text fallback in handleImagePathPaste then
made it look like the image was attached when in fact only a useless
absolute path entered the editor and the bytes never crossed.
Distinguish ENOENT from other read failures: drop the misleading
path-as-text degrade, and surface a clear status that names the file and
— when SSH_CONNECTION/SSH_TTY/SSH_CLIENT indicate the remote end of an
SSH session — points the user at the clipboard image-paste shortcut so
the bytes actually cross. The ImageInputTooLargeError and unknown-error
branches keep their existing fallback so genuine type issues still
yield the user's input back to them.
Fixes#2375
Dynamically assigns segment colors by sweeping across the full HSV hue spectrum based on their track position. This ensures each segment has a distinct, predictable hue and removes the need for explicit `color` assignments.
Updated thinking visibility toggles to refresh assistant blocks in place instead of rebuilding the transcript, preserving pending user submissions and loaders before streaming starts. Added a regression test for the Ctrl+T pre-stream gap.\n\nFixes #2370
- Updated snapcompact selectors and previews to pass `ShapeTarget` into `resolveShape` so `auto` is model-tuned.
- Applied `providerFrameBudget` in session compaction so generated archives stay within provider image caps.
- Replaced inline hard-coded image limits with `providerImageBudget` and skipped rasterization at cap.
- Added OpenRouter inline-transformer tests for cap exhaustion and existing-image budget exhaustion behavior.
- Added `SnapcompactShapePreview` (`snapcompact-shape-preview.ts`), rendering the highlighted shape variant over the sample text in `snapcompact-shape-preview-doc.md`; `auto` resolves to the active model's winning shape.
- Wired the preview as a footer component below the interactive rows in `settings-selector.ts`.
- Passed `modelApi`, `imageBudget`, and `requestRender` through `SelectorController` so the preview can rasterize against the session model and request repaints.
- Added `handleUsageResetCommand` support to list and redeem usage reset credits.
- Refactored `/usage` into `show` and `reset` subcommands and removed `/reset-usage`.
- Handled ACP/TUI `/usage` flows so `show` reports usage and `reset` redeems credits.
- Kept selected theme setting values dirty-colored while selected in the UI list.
- Added AuthStorage.listResetCredits to query each stored Codex account from the reset-credits endpoint and return live availability plus active or error state.
- Exposed the new status fetch through AgentSession and switched reset-usage selectors and commands to consume it via toResetUsageAccounts.
- Updated reset-usage UI and slash-command output to show per-account errors and updated empty-state messaging when resets could not be loaded.
- Extracted `limitMatchesActiveAccount`/`reportMatchesActiveAccount` into `slash-commands/helpers/active-oauth-account.ts` as the single definition of the report-to-account matching rules, including projectId matching against `limit.scope.projectId`/metadata.
- Dropped the duplicated `ActiveAccountIdentity`/`OAuthAccessResolver` shims, `as unknown` session casts, the dead `getOAuthAccountId` fallback, and the email-vs-scope-accountId comparison from `command-controller.ts` and `usage-report.ts`.
- Replaced the async per-provider `resolveActiveAccountsForReports` map with one synchronous typed `authStorage.getOAuthAccountIdentity()` call per render, gated to the session's current provider.
- Re-exported `OAuthAccountIdentity` from `session/auth-storage.ts` and added `active-oauth-account.test.ts` covering the matching rules.
- Stored queued steering/follow-up attachments in `QueuedDisplayEntry` so restore APIs retain image data.
- Changed `AgentSession.clearQueue` and `popLastQueuedMessage` to return `{ text, images }` payloads.
- Restored queued text and images into editor image buffers when steer submission fails or queue items are restored.
- Added optional `hasSteeringMessages` config hook and limited steering checks to boundaries.
- Fixed interrupted tool-batch steering by keeping queued messages until boundary handling.
- Added idle text and image submissions to steer queueing when no input waiter exists.
- Auto-continued resumable sessions after queued steering and preserved submit metadata.
- Added a `personality` enum to `settings-schema.ts` and new `Personality` type alias.
- Replaced inline reply guidelines with a templated `<personality>` block in `system-prompt.md`.
- Added markdown personality specs and wired them into `system-prompt.ts` rendering.
- Updated `sdk.ts` and `selector-controller.ts` to apply personality changes and refresh prompts.
- Set sub-agent sessions to use `personality: none` to suppress personality rendering.
- Added mouse hover, wheel, and click routing for `/settings` rows and tabs.
- Changed `/settings` to open as a fullscreen alternate-screen overlay.
- Added hover, hit-testing, and wheel APIs across TabBar, SelectList, and SettingsList.
- Added SGR mouse parsing plus `routeMouse` dispatch and exported it from the `tui` entrypoint.
- Added smooth streaming reveal of streamed tool-call args and `{ input }` partial previews.
- Integrated ToolArgsRevealController in EventController to route partial and final args.
- Implemented UTF-16-safe slicing to prevent surrogate-pair splits during reveal boundaries.
- Implemented reveal lifecycle controls to flush or stop streamed arg frames at message boundaries.
- Added tests for monotonic reveal progression, non-smooth immediate output, and flush/stop behavior.
Status line painted theme.statusLineBg across the bar, which renders as
visible dark/black blocks on terminals (Ghostty, transparent themes,
custom palettes) whose background does not match the theme's hardcoded
status-line color. Powerline end caps inherited the same fill so they
also stood out against the surrounding terminal.
The new statusLine.transparent appearance setting (default off) routes
the bar to the terminal's default background via the existing empty-bg
sentinel (\x1b[49m) and drops the powerline end caps, which need a
contrasting fill to bridge into the terminal.
Theme authors can opt in per-theme by setting statusLineBg to '' — the
existing empty-string sentinel already resolves to the same ANSI escape
the new toggle uses.
Fixes#2306
Store MCP OAuth credentials under deterministic mcp_oauth:<url> ids in each
profile's agent.db with refresh material embedded, so a definition-only entry
in a shared project mcp.json resolves each profile's own credential instead
of profiles clobbering each other's auth.credentialId pointer.
- Refresh material is single-source: embedded credential fields win over the
config auth block (which may belong to another profile); legacy rows fall
back to the auth block wholesale
- Wire the 401 refresh hook off the resolvable credential, not the auth
block, so definition-only bindings refresh mid-session too
- The url-keyed fallback never overrides a pinned Authorization header
- Send prompt=consent by default (oauth.prompt to override, "" to omit) so
reauth can switch accounts past an active browser session
- /mcp reauth fails fast on stdio transports (with an mcp-remote ~/.mcp-auth
hint), probes http/sse without OAuth injection, GCs the superseded legacy
row only after the flow succeeds, and leaves definition-only entries
untouched on disk
- DCR-issued client secrets stay embedded in the stored credential and are
never written into config files; user-supplied secrets survive reauth
A job poll whose watched jobs are all still running and nothing was cancelled is pure 'still waiting' noise once a newer block exists. ToolExecutionComponent now detects isWaitingPollDetails on the result, leaves the block displaceable, and keeps its rows out of scrollback. EventController tracks the most recent displaceable poll and resolves it (sealing the block) on every event that proves another tool now owns the live region: a new tool call/result, a streaming arg start, an end-of-turn signal, or another job result that itself isn't displaceable. UiHelpers' transcript rebuild applies the same rule. Docs and the new job-poll-displacement test pin the behavior.
The session-observer overlay is gone. The Agent Hub (ctrl+s, alt+a, or double-tap left arrow on an empty editor) presents one overlay with two views: a live registry table (status, unread irc count, current task, last activity; j/k to navigate, r to revive, x to abort/release) and per-agent chat (transcript + input line) — submitting revives a parked agent and steers it via the normal prompt path. Main is the ambient chat and stays out of the table.\n\nrenderInitialMessages no longer takes a prebuilt context: every redraw now reaches for AgentSession.buildTranscriptSessionContext() (full-history transcript with each compaction emitted inline at the point it fired, snapcompact frames re-attached on rebuild). UiHelpers drops the deferred-compaction render and the IRC autoreply branch that the new mailbox bus deprecated. CompactionSummaryMessage renders as a slim divider (── 📷 compacted · ctrl+o ──), expanding to the summary + snapcompact frame count. session-manager.buildSessionContext gains a { transcript: true } mode; an exported buildSessionContextFromFile() reads a session file without taking the writer lock so the hub chat view can tail any agent (parked or live). Theme picks up icon.camera + tool.irc symbol entries.
Address review notes on #2248:
- readTextFromClipboard's WSL branch now mirrors readImageViaPowerShell
(Bun.spawn + kill timer sharing POWERSHELL_TIMEOUT_MS) instead of
execSync, so a cold powershell.exe start cannot block the TUI event
loop on the first smart-paste miss.
- The smart-paste text fallback routes through ui.getFocused() +
hasPasteText like the enhanced-paste text path, so the payload lands
in focused modal Input prompts instead of the hidden main editor
(#2127 contract). Covered by a new routing test.
app.clipboard.pasteImage dead-ended with a status message when the
clipboard held text, which made the chord useless on hosts that only
deliver that one keypress (VS Code's integrated terminal forwarding
Ctrl+V, Windows clipboard history via Win+V simulating Ctrl+V).
- handleImagePaste now pastes clipboard text through the editor's
paste semantics when no image is available; an empty clipboard
reports 'Clipboard is empty'.
- readTextFromClipboard reaches the Windows clipboard through host
PowerShell under WSL (mirroring readImageFromClipboard), with CRLF
normalization and UTF-8 output encoding.
- handleClipboardTextRawPaste's empty-clipboard status message moved
to the actual empty branch (was shown after successful pastes).
- Clipboard reads are constructor-injectable for tests.
Implements proposal 1 of #1628.