Address PR #3602 review feedback from chatgpt-codex-connector:
when a stdin read carries the empty bracketed paste followed by
a trailing keystroke (a user pressing Enter right after Cmd+V),
the pre-fix paste path was fire-and-forget. The trailing byte
processed synchronously while the clipboard image read was still
pending, so submit ran against an empty pendingImages and the
image landed on the next draft instead.
CustomEditor now tracks in-flight pastes with #pasteInFlight and
buffers subsequent input into #pendingInput. #trackAsyncPaste
increments the counter, awaits the paste promise, decrements, and
drains the queue through handleInput (so requeueing still works
if a drained chunk triggers another async paste).
For an assembled paste whose remaining bytes are present in the
same call, those bytes are pushed onto #pendingInput before the
async paste starts, so they always run AFTER it settles. The
text-paste branch stays sync and drains its own queue inline.
New repro test asserts the call ordering: paste:start fires, the
queued Enter does NOT, and only after the paste promise settles
does Enter dispatch.
Address PR #3602 review feedback from chatgpt-codex-connector:
when the terminal fragments a bracketed paste across stdin chunks
(\x1b[200~ in one read, \x1b[201~ in the next — Windows Terminal
under load, certain SSH muxes, tmux extended-keys passthrough),
the previous single-chunk `isEmptyBracketedPaste` /
`extractBracketedImagePastePaths` guards never saw both markers
in the same `handleInput` call. The inherited
`BracketedPasteHandler` then buffered the run as a zero-length
text paste and Cmd+V still disappeared.
CustomEditor now owns its own BracketedPasteHandler that runs
ahead of `super.handleInput`, so split bracketed pastes resolve
to a single assembled payload before any routing decision:
- empty payload -> onPasteImage (Cmd+V macOS image-only screenshot)
- image-file paths -> onPasteImagePath (#3506 also gains split-chunk
coverage as a bonus)
- everything else -> base editor's public `pasteText` so the
`[Paste #N]` markers, autocomplete, and undo state stay intact
Removed the now-redundant single-chunk `isEmptyBracketedPaste`
helper. New repro tests cover the split-chunk empty paste, the
split-chunk image-file path, and a split-chunk text paste
forwarding exactly once to the base editor.
Address PR #3602 review feedback from chatgpt-codex-connector:
a whitespace-only bracketed paste carries real user content
(indentation, blank-line padding) and must reach the editor as
literal whitespace. The prior 'trim().length === 0' guard treated
whitespace pastes the same as empty pastes and routed them to the
clipboard-image reader, which in SSH/headless sessions silently
replaced the whitespace with a 'Clipboard is empty' diagnostic.
isEmptyBracketedPaste now matches only strict zero-length payloads
('\x1b[200~\x1b[201~'). Whitespace pastes flow through to the
normal text-paste path; empty pastes still route to onPasteImage so
Cmd+V on an image-only macOS clipboard keeps working.
The whitespace-only test case is flipped to assert the preservation
contract instead of the hijack path.
macOS terminals (iTerm2, Terminal.app, Warp, Ghostty without OSC 5522,
…) intercept Cmd+V and read NSPasteboardTypeString first. For an
image-only clipboard (Cmd+Shift+5 screenshot saved to clipboard, Chrome
image copy, …) that read returns empty, so the terminal forwards a
complete-but-empty bracketed paste — '\x1b[200~\x1b[201~' — to the app.
CustomEditor.handleInput inserted that empty payload and the keystroke
disappeared, forcing users back to Ctrl+V (which is never intercepted
and already routes through handleImagePaste).
Add isEmptyBracketedPaste and route a complete, empty-or-whitespace-only
bracketed paste through the same onPasteImage smart reader the
app.clipboard.pasteImage keybind uses, so Cmd+V attaches the clipboard
image (or falls back to the #1628 smart text paste / 'clipboard is
empty' diagnostic) instead of silent nothing. Bracketed pastes carrying
any text (including the explicit image-file path branch from #3506)
keep their existing routing.
Fixes#3601
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
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
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
- 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.
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
Require bracketed image-path paste detection to see an explicit local path separator or file URI before routing .png-like text to image attachment handling.\n\nFixes #3253
- 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.
- Replaced usage of `ReturnType<typeof setTimeout>` and `ReturnType<typeof setInterval>` with the explicit `Timer` type across the codebase.
- Updated several type definitions and function signatures to use concrete types instead of inferred return types for improved clarity and maintainability.
fix(coding-agent): avoid placeholder crash before theme init
Resolved CHANGELOG conflict: 15.13.0 was re-opened into [Unreleased] on
main, so the fix entry goes under the existing [Unreleased] Fixed section
rather than resurrecting the released 15.13.0 heading.
Two CustomEditor/streaming-preview tests timed out under bun test --parallel
because they reset+reinitialised the process-global Settings singleton in
beforeEach. 50+ other test files do the same dance, so under parallelism the
proxy points at whichever instance won the latest race rather than the one
under test (issue #2582).
- Added CustomEditor#magicKeywordsEnabledOverride: an instance-level test/host
injection that short-circuits the global lookup at the read site.
Production wiring still reads from Settings.
- Switched the magic-keyword shimmer-disabled test to set
magicKeywordsEnabledOverride directly instead of mutating the singleton.
- Removed the gratuitous reset+init from the 'streaming tool call preview
height (bounded across renderers)' describe -- bash/ssh/eval pending
previews never read settings.*; only initTheme() (idempotent) is kept.
Fixes#2582
- Added a space-hold gesture state machine in CustomEditor, tracking repeated spaces, detecting holds beyond SPACE_HOLD_THRESHOLD, and firing start/end callbacks via a release timer.
- Hooked editor space-hold callbacks in InputController so STT toggles on hold start and again on release when STT is enabled.
- Added tests for space-hold start/stop behavior and updated keybinding docs to describe the hold-to-record STT workflow.
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
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.
- Updated bracketed-image parsing to recognize multiple image paths in a single paste, including quoted values and shell-escaped spaces.
- Changed the editor image-path handler to process each matched path in order, awaiting async handlers.
- Added tests for multi-path routing/normalization and recorded the fix in the coding-agent changelog entry.
- Added atomicTokenPattern support to treat image and paste markers as indivisible tokens, enabling atomic deletion on backspace and forward-delete instead of character-by-character removal.
- Extended image marker format to include pixel dimensions [Image #N, WxH] and updated paste marker format to [Paste #N, +X lines] or [Paste #N, Y chars] with consistent comma-separated metadata.
- Implemented accent styling for paste markers in editor rendering while preserving hyperlinks for image markers, improving visual distinction between marker types.
- Refactored image reference handling to unified placeholder system supporting both image and paste markers with kind-aware rendering callbacks and regex patterns.
- Built canonical and alias key maps for action/custom handlers and rebuilt them on changes.
- Preserved the first handler when custom key alias collisions occurred during rebuilds.
- Exported canonicalKeyId and addKeyAliases from keybindings for external use.
- Added OSC5522 parsing and a payload controller that prefers image MIME and reassembles chunks.
- Added bracketed image-path detection and `onPasteImagePath` callback wiring in the editor.
- Added local image-path paste insertion with fallback to text paste on image-size errors.
- Added `Editor.pasteText()` and OSC5522 disable sequencing on terminal cleanup.
- Added `TUI.resetDisplay()` to force an immediate full-frame replay including native scrollback.
- Moved the persistent model selector default from Ctrl+L to Alt+M, preserving existing user remaps.
- Reserved Alt+M so extensions cannot shadow the model selector shortcut.
- Added image-reference rendering to make `[Image #N]` placeholders clickable in chat.
- Added MIME-aware image blob materialization with extensioned sidecar paths.
- Added clickable path, line, and URL hyperlinks for read, search, and fetch outputs.
- Hardened OSC8 hyperlink emission with URI validation and control-byte/idempotency checks.
When the autocomplete popup is visible, ESC unconditionally falls through
to the editor base class so it can dismiss the popup. Only an ESC with no
popup visible reaches onEscape and routes to the global interrupt handler.
Removed the shouldBypassAutocompleteOnEscape callback that paved over the
popup-dismissal path whenever the agent was busy (streaming, bash, /btw,
auto-compaction, etc.) — that is precisely the moment the user is most
likely to hit ESC while the popup is open, and dismissing-then-aborting in
one keystroke is a footgun, not a feature. Two-press semantics now match
the standard TUI/IDE pattern: first ESC closes the popup, second ESC
aborts.
Tests cover both branches in custom-editor-keybindings.test.ts and the
input-controller escape suite no longer asserts the dead callback.
Fixes#1655
- Add new keybinding `app.clipboard.pasteTextRaw` with Ctrl+Shift+V / Alt+Shift+V defaults
- Implement `readTextFromClipboard()` utility supporting macOS, Windows, Linux (Wayland/X11), and Termux
- Integrate raw paste handler into CustomEditor and InputController
- Text is inserted without formatting collapse to preserve whitespace and newlines
- Replaced composed keyword decorators in `CustomEditor.decorateText` with `highlightMagicKeywords`.
- Adjusted `UserMessageComponent` rendering to use `highlightMagicKeywords` with `keywordReset` for consistent foreground.
- Dropped `onShowHotkeys` callback and its binding from `CustomEditor` and `InputController`.
- `?` now inserts a literal question mark regardless of editor state; use `/hotkeys` explicitly.
- Added regression test confirming `?` is treated as plain input when the editor is empty.
- Added orchestrate keyword detection and notice handling for non-synthetic prompts.
- Added orchestrate notice handling in session output paths, including streaming and append delivery.
- Added a system orchestrate notice specifying task-subagent delegation, phase workflow, and validation gates.
- Added shared gradient-highlighter utilities and switched ultrathink highlighting to use cached palettes.
- Removed embedded orchestrate prompt artifacts and updated usage tips for orchestration, ultrathink, and /login behavior.
- Added a new ultrathink mode module with standalone, case-insensitive detection, rainbow editor highlighting, and a hidden notice payload.
- Updated `CustomEditor` and the shared TUI `Editor` to support optional zero-width text decoration and apply the `ultrathink` styling during input rendering.
- Extended `AgentSession` prompt handling to append the hidden ultrathink notice after user turns in both streaming and non-streaming message flows, excluding synthetic messages.
- Added session-draft persistence methods in SessionManager to write unsent editor text to an artifacts-sidecar draft file and delete it after single-shot consumption.
- Persisted editor text during interactive shutdown and restored that draft on resume when the editor was empty, enabling Ctrl+D draft recovery.
- Updated Ctrl+D handling in the editor/controller path and added tests covering draft round-trip, artifact cleanup, stale-draft eviction, and in-memory no-op behavior.
- Added abort signal support to LSP file operations (ensureFileOpen, refreshFile) enabling cancellation of long-running file operations.
- Added abort signal propagation through LSP request handlers (definition, references, hover, symbols, rename) with $/cancelRequest notification support.
- Added shouldBypassAutocompleteOnEscape callback to custom editor allowing parent components to override escape key handling behavior.
- Changed escape key handling in custom editor to respect bypass callback, enabling global escape handling during active operations.
- Fixed LSP operations to properly handle abort signals and throw ToolAbortError when operations are cancelled, with input controller bypassing autocomplete during streaming/loading states.