Commit Graph

1834 Commits

Author SHA1 Message Date
can1357 301d145301 Merge remote-tracking branch 'origin/farm/c42beb4b/fix-duplicate-todo-panels' 2026-06-26 05:15:39 +02:00
roboomp ce41a47b99 fix(tui): preserved live todo snapshot across mid-turn rebuild
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
2026-06-26 02:50:30 +00:00
can1357 f4339d1c43 Merge remote-tracking branch 'origin/farm/c42beb4b/fix-duplicate-todo-panels' 2026-06-26 04:39:33 +02:00
can1357 cf2f25de1e Merge remote-tracking branch 'origin/farm/e69418eb/macos-image-paste-keybind' 2026-06-26 04:38:42 +02:00
roboomp bd6710bd02 fix(tui): deferred todo displacement to successful result
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
2026-06-26 02:21:37 +00:00
roboomp ba3a6079be fix(tui): collapsed multi-todo rebuild snapshots
Resolved any tracked todo snapshot before storing a fresh one in the rebuild paths so an assistant message replaying multiple todo tool calls collapses to the final snapshot.

Added a renderSessionContext regression test for two todo tool calls in one rebuilt assistant message.

Fixes #3516
2026-06-26 02:17:12 +00:00
roboomp 58373fdee9 fix(tui): handled streamed todo replacement
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
2026-06-26 02:12:43 +00:00
roboomp a2230b3dd0 fix(tui): collapsed repeated todo snapshots
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
2026-06-26 02:04:19 +00:00
roboomp 257b515353 fix(coding-agent): attach every image in a multi-file Finder selection
Reviewer caught: the macOS file-URL loop returned after the first
image-shaped path, silently dropping the rest of a multi-image
selection. The bracketed-paste handler in `CustomEditor.handleInput`
already iterates every extracted image path; the keybind path now does
the same. Mixed selections (one .pdf + two images) still attach all
images and skip the non-images.

Tests cover (a) multi-image selection (3 attached), (b) mixed selection
with the file-URL fallback owning the outcome (text fallback MUST NOT
run when at least one file URL was an image).

Refs #3506
2026-06-25 23:35:31 +00:00
roboomp 8bb6fd76ba fix(coding-agent): recover image paths with unescaped spaces in keybind fallback
Reviewer caught: `extractImagePathFromText` reused the bracketed-paste
splitter, which treats unescaped spaces as separators. macOS screenshot
filenames default to names like
`/Users/me/Desktop/Screenshot 2026-06-25 at 1.23.45 PM.png` — the
splitter shredded those into 5 segments, the second segment failed the
explicit-path check, and the helper returned undefined, so the keybind
fallback pasted the path verbatim instead of attaching the image.

Add a whole-text-as-path stage gated on a new ABSOLUTE_PATH_PREFIX_REGEX
(matches `/`, `~/`, `file://`, `\\`, or a drive letter), used
only when the splitter found nothing (otherwise multi-path text like
`/tmp/a.png /tmp/b.png` would be mis-joined). Prose containing a
path-shaped fragment ("see /tmp/x.png") fails both passes and still
pastes as text.

Tests cover (a) macOS screenshot names with spaces, (b) ~/Pictures and
Windows paths with spaces, (c) anchored prose fragments not hijacking
the fallback, and (d) end-to-end real-file integration via
handleImagePaste.

Refs #3506
2026-06-25 23:30:12 +00:00
roboomp b3f99dc634 fix(coding-agent): reach macOS public.file-url pasteboard via osascript
Reviewer caught (correctly) that the #3506 text fallback relied on
`clipboard.readText()`, which on Darwin shells out to `pbpaste(1)` —
pbpaste only surfaces plain text / RTF / EPS, so a Finder Cmd+C
pasteboard (`public.file-url` only, no plain text, no raw image bytes)
made readText() return empty and the new path-detection never ran.

Add a Darwin-only `readMacFileUrlsFromClipboard` helper that pipes a
small AppleScript through `osascript -` to coerce the pasteboard via
`«class furl»` and emit POSIX paths one per line. Wire it into
`InputController.handleImagePaste` between the readImage and readText
calls; the first image-shaped path routes through
`handleImagePathPaste`, non-image file URLs (e.g. a copied .pdf) fall
through to the existing text fallback. The clipboard interface field is
optional so existing test fixtures keep working without changes.

Tests: covers (a) Darwin file-URL pasteboard with empty pbpaste,
(b) non-image file URLs falling through to text, (c) the helper itself
on darwin/non-darwin and when osascript fails.

Refs #3506
2026-06-25 23:25:00 +00:00
roboomp 509eed817c fix(coding-agent): decode file:// URLs in pasted image paths
When the macOS pasteboard's text representation forwards a
`file:///Users/.../img.png` URL (Ghostty/iTerm2/etc. forwarding the
`public.file-url` representation after a Finder copy), the smart
bracketed-paste / keybind fallback recognized it as a path but
`loadImageInput` then tried to read a literal `file://` path and
failed. `normalizePastedPath` now decodes `file://` URLs via
`node:url.fileURLToPath` before the explicit-path check, mirroring
Codex's `normalize_pasted_path` in
`codex-rs/tui/src/clipboard_paste.rs`. Both the bracketed-paste path
and the new `extractImagePathFromText` keybind path benefit.

Refs #3506
2026-06-25 23:17:53 +00:00
roboomp e1dc21e0b5 fix(coding-agent): attach image on clipboard image-file paste
When the clipboard exposes only a file URL for an image (e.g. Finder
`Cmd+C` on a `.png`, certain screenshot tools), arboard's
`get_image()` returns `ContentNotAvailable`. `handleImagePaste` then
fell through to the #1628 smart-paste text fallback and pasted the path
verbatim, while the terminal-mediated paste round-tripped through
bracketed-paste's `extractBracketedImagePastePaths` and attached the
image — producing the asymmetric "for image I need control+v which is
very odd" symptom on macOS.

Refactor `custom-editor.ts` to share the bracketed-paste path-detection
logic via a new `extractImagePathFromText` export, then route the text
fallback through `handleImagePathPaste` whenever the clipboard text is
exactly one explicit image file path. Both keybind- and terminal-mediated
paste now agree.

Fixes #3506
2026-06-25 23:06:39 +00:00
roboomp 80c0beb84a fix(mcp/oauth): preserve advertised path-scoped resource indicators
Review on PR #3503 caught that the broad same-origin filter broke valid
protected-resource discovery shapes such as
`https://gateway.example.com/my-service/mcp`: same host as the authorization
server, but a distinct MCP service identified by path. Those advertised
resources must be preserved for audience selection.

- Replaced the unconditional same-origin filter with a provenance-aware policy: exact auth-server-origin resources are always stripped, path-scoped same-origin resources are preserved by default, and only OMP-synthesized fallback resources opt into same-origin path stripping.
- Added `stripSameOriginResource` to `MCPOAuthConfig` and `RefreshMCPOAuthTokenOptions`; quick-add/reauth set it only when the resource came from `config.url` / `runtimeBaseConfig.url` fallback rather than `oauth.resource` or an existing auth resource.
- Refresh uses the same flag when `MCPManager.prepareConfig` falls back to `config.url`, and no longer persists fallback resources into the credential as if they were provider-advertised material.
- Updated RFC 8707 tests to cover both sides: gateway path resource preserved, Plane-style fallback `/http/mcp` stripped, refresh path mirrors the same distinction.

Fixes #3502
2026-06-25 21:48:14 +00:00
roboomp 11c10640f2 fix(mcp/oauth): persist authorization-server origin so refresh filters against it
Review on PR #3503 flagged that the prior fix anchored the initial-grant filter
on `authorizationUrl` but the refresh filter on `tokenUrl`. RFC 8414 lets the
authorize and token endpoints sit on different origins, so when they do, a
`config.url` fallback equal to the auth-server origin survives the refresh
filter — the credential works until expiry, then refresh resurrects the same
self-referential `resource` the authorize/token exchange intentionally
omitted.

- `MCPStoredOAuthCredential.authorizationUrl?: string` — new field, the issuer the grant was minted against.
- `MCPOAuthFlow.authorizationUrl` getter exposes the value so the persistence site can write it (symmetric with `flow.resource`).
- `refreshMCPOAuthToken` accepts `{ authorizationUrl }` via the trailing options object; filters self-referential indicators against the supplied URL, falling back to `tokenUrl`'s origin for legacy credentials. New `RefreshMCPOAuthTokenOptions` interface keeps the positional resource form working.
- `mcp-command-controller.ts` persists `flow.authorizationUrl` on credential write; `manager.ts` extracts it from the embedded credential material (legacy `MCPAuthConfig` rows lack it and continue through the `tokenUrl` fallback) and threads it to `refreshMCPOAuthToken`.
- Tests: 3 new cross-origin refresh cases — stripped when resource equals auth-server origin with cross-origin token endpoint; preserved when resource points at a third origin; legacy `tokenUrl`-anchored fallback still works without `authorizationUrl`. Plus a `flow.authorizationUrl` getter test. Updated `mcp-manager-oauth-refresh.test.ts` to account for the new opts arg.

Fixes #3502
2026-06-25 21:04:58 +00:00
can1357 61da80d2ae Merge PR #3468 into sweep 2026-06-25 18:53:34 +02:00
roboomp 039c93be60 fix(tui): restore streaming steer image draft on prompt error
Wrap the streaming Enter steer dispatch in try/catch so prompt failures restore text plus pendingImages / pendingImageLinks / imageLinks and surface showError instead of losing an image-only draft.\n\nAlso remove a forbidden ReturnType<> from the follow-up image regression helper.\n\nFixes #3467
2026-06-25 13:53:00 +00:00
roboomp b0bbd872c4 fix(tui): restore followup image draft on prompt error
Snapshot pendingImageLinks alongside pendingImages at the top of handleFollowUp and wrap both the streaming and idle session.prompt dispatches in try/catch that restores text + pendingImages + pendingImageLinks + imageLinks and surfaces showError, mirroring the main submit and focused submit error paths so an image-only or text+image Ctrl+Enter draft survives dispatch rejection.\n\nFixes #3467
2026-06-25 13:47:06 +00:00
roboomp a2a217277c fix(tui): restore focused submit image draft on prompt error
Snapshot pendingImageLinks and re-seed editor.pendingImages / pendingImageLinks / imageLinks in the focused-session submit catch block so an image-only or text+image draft survives a viewSession.prompt rejection, mirroring the main controller error path.\n\nFixes #3467
2026-06-25 13:40:14 +00:00
roboomp e24b70c09a fix(tui): queued image-only streaming submits
Treat pending pasted images as submit content in the main and focused input controller paths so image-only Enter/Ctrl+Enter submissions queue instead of dropping or aborting.\n\nFixes #3467
2026-06-25 13:32:18 +00:00
roboomp 184f6dd809 style: bun run fix 2026-06-25 11:41:56 +00:00
roboomp 8506fbdf52 fix(coding-agent): switched ctrl-z handler to SIGSTOP-self to defeat brush tokio SIGTSTP hijack
brush-core's Process::wait calls tokio::signal::unix::signal(SIGTSTP) to
detect when its children get stopped. Per tokio's documented contract,
the first call for a SignalKind permanently replaces the kernel-default
handler for the lifetime of the process. So once omp has executed any
bash tool call — even /usr/bin/true — SIGTSTP's default "stop" action
is gone, and InputController.handleCtrlZ's process.kill(0, "SIGTSTP")
gets swallowed by tokio. The TUI tore down via ui.stop() but the process
kept running in Sl+ state, leaving the user with a dead terminal that
only kill -9 could recover.

Send SIGSTOP to our own PID instead. SIGSTOP can't be caught, blocked,
or ignored — it stops the process at the kernel regardless of installed
handlers. Targeting self (not pgid=0) also leaves long-lived children
(MCP stdio servers, the persistent brush native shell) running across
the suspend, so they no longer freeze mid-IPC during a quick fg/bg
detour.

Fixes #3461
2026-06-25 11:33:35 +00:00
can1357 57e9848c8c refactor(coding-agent): removed automatic file attachment for non-image paste paths
- Removed `onPasteFilePath` handler to prevent automatic background file attachment when pasting paths.
- Updated `CustomEditor` to treat non-image paths as literal text input.
- Cleaned up unused file system utilities and paste path resolution logic.
2026-06-25 12:57:54 +02:00
can1357 27ed9f7af7 feat(coding-agent): enabled mouse navigation and fullscreen mode for extension dashboard
- Implemented SGR mouse event routing for dashboard interaction, including tab selection and pane scrolling.
- Added mouse-driven list manipulation in the extension viewer with selection highlighting, click toggling, and wheel navigation.
- Enabled fullscreen alternate-screen behavior and host terminal mouse tracking for the dashboard overlay.
- Integrated hit-testing and row selection logic into the extension list to support unified mouse and keyboard inputs.
2026-06-25 04:02:34 +02:00
can1357 9ad17a2b49 ux(coding-agent/modes): removed hotkeys tip from welcome component
- Removed the hotkeys tip from the welcome screen UI.
2026-06-24 21:01:55 +02:00
can1357 fa5024caa4 fix(tui): guarded transcript sentinel reads against mid-poll unlink
The append fast-path opened the session file for sentinel comparison and
recompute without a guard, so a file unlinked/rotated between #refresh's
statSync and the sentinel read threw out of the 250ms poll timer (no catch),
risking a TUI crash. Treat sentinel read/recompute failures as non-appendable
and fall back to the guarded full reload. Adds a fake-timer regression.
2026-06-24 18:26:20 +02:00
can1357 c04747c5ac Merge PR #3259: fix(tui): reduce large transcript stalls (@roboomp) 2026-06-24 18:26:19 +02:00
can1357 0f072cbcc1 Merge PR #3315: fix(welcome): replace stale ? shortcut with /hotkeys in tips panel (@oldschoola) 2026-06-24 18:26:18 +02:00
can1357 a7a17e8dc1 Merge PR #3376: fix(tui): theme-aware welcome tip line for light-theme legibility (@roboomp) 2026-06-24 18:26:18 +02:00
can1357 ee57c07371 Merge PR #3377: fix(settings): prevent numeric config values from crashing settings UI (@roboomp) 2026-06-24 18:26:18 +02:00
can1357 b56a7524af Merge PR #3385: fix(coding-agent): restore TUI focus to live editor-slot owner when a fullscreen overlay closes (@roboomp) 2026-06-24 18:26:18 +02:00
can1357 8c76b45c3b Merge PR #3381: fix(coding-agent): honor app.message.followUp chord in ask prompt-style editor (@roboomp) 2026-06-24 18:26:18 +02:00
can1357 d88d9bd6d8 Merge PR #3352: fix: store slash commands in input history (@oldschoola) 2026-06-24 18:26:17 +02:00
can1357 d4d7fed0cc Merge PR #3384: fix(tui): attach pasted file paths as local refs (@roboomp) 2026-06-24 18:26:17 +02:00
can1357 345bdc32e1 test(usage): cover TUI aggregate provider-notes-once and per-limit dedup
renderUsageReports (command-controller) carried the #3268 dedup contract
with no regression test; the PR's added CLI test asserts the opposite
(per-limit CLI rendering shows the note twice). Export renderUsageReports
and add a real regression through it: two accounts sharing one window group
render a provider-wide UsageReport.note once and an identical per-limit note
once. Verified failing on the pre-fix flatMap form (0 and 2 occurrences) and
passing on head (1 and 1).
2026-06-24 18:26:17 +02:00
can1357 01ef63fbd0 Merge PR #3312: fix(usage): dedup provider-wide notes and add report-level notes field (@oldschoola) 2026-06-24 18:26:17 +02:00
roboomp 1b24e0044a fix(coding-agent): restore focus to the live editor-slot owner when a fullscreen overlay closes
When /settings (or the Extensions/Agents dashboard) is open and a tool
approval prompt fires, ExtensionUiController.showHookSelector swaps the
editor out of editorContainer for the HookSelectorComponent. On exit,
the overlay's done() called overlayHandle.hide() + setFocus(editor),
both pointing at the editor captured as preFocus when the overlay
opened — now no longer mounted. The visible approval prompt then sat
unreachable: Up/Down/Enter/Esc routed to the unmounted editor and only
Ctrl+C escaped (issue #3349).

SelectorController now exposes focusActiveEditorArea(), which restores
focus to editorContainer.children[0] (the live slot owner) or falls
back to the editor. Wired into showSettingsSelector, showExtensionsDashboard,
and showAgentsDashboard close paths after overlay.hide().

Tests: unit test verifying focusActiveEditorArea picks the live slot
owner; TUI overlay-focus regression pinning the post-fix contract plus
a 'pre-fix snapshot' test pinning the broken pre-fix behavior so the
restore-from-preFocus assumption can't silently change.

Fixes #3349
2026-06-24 14:24:15 +00:00
roboomp 4f20d10454 fix(tui): attached pasted file paths
Converted bracketed non-image filesystem path pastes into session-local attachment references while preserving the existing image path flow.

Added regression coverage for editor routing and controller local file attachment behavior.

Fixes #3360
2026-06-24 14:21:45 +00:00
roboomp bd06c5dd4e fix(coding-agent): honor app.message.followUp chord in ask prompt-style editor
The ask tool's "Other (type your own)" free-text input is a prompt-style
HookEditorComponent. The hook-style branch already called matchesAppFollowUp
(Ctrl+Q / Ctrl+Enter) so Windows Terminal users — which can't deliver a
distinct Ctrl+Enter (#1903 / fixed by #1905) — had a working chord on the
main editor, hook editors, and the agent dashboard. The prompt-style branch
did not, so Ctrl+Q was unbound and Ctrl+Enter fell through to Editor as a
newline that WT silently swallowed: pressing Ctrl+Enter did nothing.

#handlePromptStyleInput now checks matchesAppFollowUp before the rest of
the dispatch (mirroring #handleHookStyleInput), so plain Enter remains the
primary submit and the chord is a secondary submit for cross-terminal
muscle memory. The prompt-style hint now reads "enter or ctrl+q submit"
so the fallback is discoverable.

Fixes #3353
2026-06-24 14:12:05 +00:00
roboomp e261848293 fix(settings): coerced rendered settings values
Coerced malformed YAML-derived settings values to strings before they reach settings row truncation. Added regression coverage for numeric enum display values.

Fixes #3338
2026-06-24 13:53:06 +00:00
roboomp de87923add fix(tui): theme-aware welcome tip line for light-theme legibility
The welcome 'Tip:' line hardcoded #b48cff (label) and #9ccfff (body)
with an additional \x1b[2m dim on the body, ignoring the active theme.
On any light theme this dropped the body to ~1.5:1 contrast on a white
background (WCAG AA needs >=4.5:1), making the line effectively
invisible. Switching between light variants did not help because the
colors were not theme-derived.

renderWelcomeTip in packages/coding-agent/src/modes/components/welcome.ts
now paints the label through theme.fg('customMessageLabel', ...) and the
body through theme.fg('muted', ...), drops the manual dim, and wraps the
whole line with theme.italic(...). Both tokens are tuned per theme, so
the line stays vivid on dark backgrounds and readable on light ones
(e.g. light theme's customMessageLabel #7e57c2 = 5.21:1 on white).

A regression test pins the contract: dark/light themes must produce
different bytes for the same tip, and no manual \x1b[2m may remain.

Fixes #3337
2026-06-24 13:44:21 +00:00
oldschoola 04c3199511 fix(usage): normalize newlines in provider notes before rendering
sanitizeText preserves newlines (\n) which break TUI line layout when
injected into a single rendered row. All notes rendering sites now
replace \r\n sequences with spaces before sanitization:

- command-controller.ts: provider-wide notes (line 1591) + per-group
  notes (line 1666)
- usage-report.ts: provider-wide notes (line 58) + per-limit notes
  (line 90, previously completely unsanitized)
- usage-cli.ts: provider-wide notes (line 455)
2026-06-23 16:02:26 -07:00
oldschoola 76bbd77eac fix(usage): sanitize report-level notes before TUI rendering
Address review feedback: provider notes could contain tabs, embedded
newlines, or control characters that break TUI rendering. Both note
rendering sites (provider-wide and per-group) now wrap the joined text
through sanitizeText → truncateToWidth → replaceTabs per AGENTS.md
TUI Sanitization rules.
2026-06-23 16:02:25 -07:00
oldschoola 6c3f35dfef fix(usage): dedup provider-wide notes and add report-level notes field
Provider-wide disclaimers (e.g. OpenCode Go's "OMP-observed spend
only") were duplicated onto every UsageLimit, then repeated N times
in the TUI aggregate renderer (once per account × window). With
2 accounts × 3 windows, the same disclaimer appeared 6 times
bullet-joined.

Structural fix:
- Add notes?: string[] to UsageReport (interface + both schema
  copies: usage.ts and auth-broker/wire-schemas.ts) so the field
  survives the broker client's "+": "reject" deserialization gate.
- Move opencode-go's disclaimer from per-limit notes to
  provider-level notes.

Defensive fix:
- Dedup identical per-limit notes in the TUI aggregate renderer
  (command-controller.ts) via [...new Set(...)].
- Render provider-level notes once above per-account sections in
  all three rendering paths: TUI (command-controller), CLI
  (usage-cli), and ACP (usage-report helper).

Regression tests:
- usage-cli.test.ts: provider-level notes render once, not
  duplicated per account or limit; positioned above per-account rows.
- usage-report-notes-schema.test.ts: wire-schema round-trip proving
  notes survives usageResponseSchema validation.

Fixes #3268
2026-06-23 16:02:25 -07:00
oldschoola eb56da7d18 fix(welcome): replace stale ? shortcut with /hotkeys in tips panel
The welcome panel advertised '? for keyboard shortcuts' even though
the ? shortcut was deliberately removed in commit dcf482c4c
('fix(editor): removed ? shortcut that opened hotkeys when input was
empty'). Users typing ? on an empty prompt got a literal question
mark submitted to the model instead of the help panel they expected.

Replace the stale claim with '/hotkeys for keyboard shortcuts',
matching the actual command that opens the shortcut reference.

Fixes #1614
2026-06-23 16:02:08 -07:00
oldschoola a6bfb8c0e1 fix: record slash commands with inline prompts to history
Address P2 review: when executeBuiltinSlashCommand returns a string
(e.g. /loop 10 fix bug → 'fix bug'), the original slash command text
was not recorded to history — only the extracted prompt was. Now the
original text is added to history before reassigning, so Up Arrow
recalls '/loop 10 fix bug' rather than just 'fix bug'.

Applied to both Enter and Ctrl+Enter submit paths.
2026-06-23 15:23:14 -07:00
oldschoola 00fd6f2263 fix: handle colon-separator bypass and /join secrets in history filter
Address three P1 code review comments on PR #3352:

1. Colon-separator bypass: parseSlashCommand() treats ':' as an argument
   separator, but shouldSkipHistory only split on whitespace. So
   /login:?code=abc&state=xyz bypassed the filter. Now uses the same
   earliest-whitespace-or-colon splitting as parseSlashCommand.

2. /join <link> secret: the collab join link carries a 32-byte room key
   and optional write token. Add /join to the denylist — skip any /join
   with arguments.

3. Added regression tests for colon-separator forms and /join denylist.
2026-06-23 15:01:07 -07:00
oldschoola c6c2c386bc fix: skip all /login args from history (P1 security review)
parseCallbackInput() accepts three forms: redirect URLs, query strings
(?code=...), and raw auth codes — all carry OAuth secrets. The previous
filter only skipped URL-like inputs, leaking query strings and raw codes.

Skip ALL /login commands with any argument. The minor convenience loss
(can't recall /login <provider>) is far less important than the risk of
persisting OAuth authorization codes.
2026-06-23 14:17:35 -07:00
oldschoola 715eb0792c fix: store slash commands in input history (#3148)
Previously only 4 commands (/plan, /goal, /mcp, /ssh) stored their text
in history via per-handler addToHistory calls. All other built-in slash
commands were silently skipped because executeBuiltinSlashCommand returned
true before the input controller's addToHistory was reached.

- Centralize history recording in the input controller after successful
  slash command dispatch, for both Enter and Ctrl+Enter submit paths.
- Remove all 10 per-command addToHistory calls from slash command handlers
  to prevent duplicates.
- Add shouldSkipHistory() security filter to exclude commands that may
  carry secrets: /login <url> (OAuth callback with code=/state= params)
  and /mcp add --token <token> (bearer token).
- Add regression tests for the security filter (8 cases).
- Update 7 existing test files to remove handler-level addToHistory
  assertions (now the input controller's responsibility).
2026-06-23 14:11:12 -07:00
Alexander Kirilin 881ae78010 fix(tui): include tiered Codex usage limits 2026-06-23 07:58:11 -04:00