refactor(native-chat): keep image separator fix focused

This commit is contained in:
Brennan Benson
2026-08-27 00:52:46 -07:00
parent ee7d6ec62a
commit efda0e5c49
10 changed files with 17 additions and 126 deletions
@@ -343,10 +343,6 @@ describe('sendNativeChatMessageWithImageAttachments', () => {
vi.advanceTimersByTime(NATIVE_CHAT_IMAGE_ATTACHMENT_SETTLE_MS)
expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, 'what do you see?')
expect(`${framedImageWithSeparator}what do you see?`).toBe(
'\x1b[200~/tmp/orca-paste-image.png\x1b[201~ what do you see?'
)
expect(`${framedImageWithSeparator}what do you see?`).not.toContain('png\x1b[201~what')
vi.advanceTimersByTime(NATIVE_CHAT_SUBMIT_DELAY_MS)
expect(sendRuntimePtyInput).toHaveBeenLastCalledWith(SETTINGS, PTY, NATIVE_CHAT_SUBMIT)
@@ -1981,7 +1981,6 @@ function TerminalPane(
forceBracketedPaste: options?.forceBracketedPaste,
forceBracketedPasteForMultiline: options?.forceBracketedPasteForMultiline,
windowsInputRecordNewline: options?.windowsInputRecordNewline,
followedByNonImageInput: options?.followedByNonImageInput,
terminalBracketedPasteMode: pane.terminal.modes.bracketedPasteMode
})
const execution = await executeTerminalPastePlan(plan, {
@@ -1,5 +1,4 @@
import type { Terminal } from '@xterm/xterm'
import { separateImagePasteFromFollowingText } from '../../../../shared/image-paste-following-text'
import type { WindowsInputRecordNewline } from './terminal-paste-model'
type BracketedPasteTerminal = {
@@ -17,7 +16,6 @@ type PasteTerminal = BracketedPasteTerminal & {
type PasteTerminalTextOptions = {
forceBracketedPaste?: boolean
windowsInputRecordNewline?: WindowsInputRecordNewline
followedByNonImageInput?: boolean
}
const interruptedBracketedPasteTerminals = new WeakSet<object>()
@@ -103,6 +101,12 @@ export function encodeWindowsInputRecordPasteText(
return encoded
}
function forceBracketedPaste(terminal: PasteTerminal, text: string): void {
// Why: forced callers already built the exact paste protocol bytes. Send
// them as PTY input so xterm's DOM/native paste machinery cannot defer them.
terminal.input(wrapTerminalBracketedPasteText(text))
}
export function markTerminalBracketedPasteInterrupted(terminal: BracketedPasteTerminal): void {
if (terminal.modes.bracketedPasteMode) {
interruptedBracketedPasteTerminals.add(terminal)
@@ -139,14 +143,7 @@ export function pasteTerminalText(
if (options?.forceBracketedPaste) {
// Why: generated image paths are paste payloads, even when they are a
// single line, so they must bypass stale Ctrl+C plain-text suppression.
// Trailing space is keyed off followedByNonImageInput only — never off
// forceBracketedPaste, which also wraps multiline text.
terminal.input(
separateImagePasteFromFollowingText(
wrapTerminalBracketedPasteText(text),
options.followedByNonImageInput === true
)
)
forceBracketedPaste(terminal, text)
return
}
if (!interruptedBracketedPasteTerminals.has(terminal)) {
@@ -22,11 +22,7 @@ describe('terminal clipboard paste', () => {
expect(pasteText).toHaveBeenCalledWith(
'/var/folders/3l/b7w02vh17tg5r5s3nhhdf3kh0000gn/T/orca-paste-1760000000000-id.png',
{
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
}
{ forceBracketedPaste: true, recoverImagePasteWebglAtlas: true }
)
})
@@ -52,10 +48,8 @@ describe('terminal clipboard paste', () => {
pasteText: (text, options) => pasteTerminalText(terminal, text, options)
})
const imageBytes = '\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~ '
expect(terminal.input).toHaveBeenCalledWith(imageBytes)
expect(`${imageBytes}describe this`).toBe(
'\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~ describe this'
expect(terminal.input).toHaveBeenCalledWith(
'\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~'
)
expect(terminal.paste).not.toHaveBeenCalled()
expect(observedIgnoreBracketedPasteMode).toEqual([false])
@@ -84,7 +78,7 @@ describe('terminal clipboard paste', () => {
})
expect(terminal.input).toHaveBeenCalledWith(
'\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~ '
'\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~'
)
expect(terminal.paste).not.toHaveBeenCalled()
expect(observedIgnoreBracketedPasteMode).toEqual([false])
@@ -110,8 +104,7 @@ describe('terminal clipboard paste', () => {
})
expect(pasteText).toHaveBeenCalledWith('/var/tmp/orca-paste-1760000000000-id.png', {
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
recoverImagePasteWebglAtlas: true
})
})
@@ -134,8 +127,7 @@ describe('terminal clipboard paste', () => {
})
expect(pasteText).toHaveBeenCalledWith('/tmp/orca-paste-1760000000000-runtime.png', {
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
recoverImagePasteWebglAtlas: true
})
})
@@ -152,8 +144,7 @@ describe('terminal clipboard paste', () => {
expect(pasteText).toHaveBeenCalledWith('/tmp/orca-paste-1760000000000-id.png', {
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
recoverImagePasteWebglAtlas: true
})
})
@@ -175,8 +166,7 @@ describe('terminal clipboard paste', () => {
})
expect(pasteText).toHaveBeenCalledWith('/tmp/orca-paste-1760000000000-id.png', {
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
recoverImagePasteWebglAtlas: true
})
})
@@ -86,10 +86,8 @@ export async function pasteTerminalClipboard({
const result = await pasteText(filePath, {
// Why: a generated clipboard-image path is terminal image injection, not
// ordinary one-line text. Keep it off the Ctrl+C stale-text paste path.
// Trailing space so later typed text cannot glue onto `.png`.
forceBracketedPaste: true,
recoverImagePasteWebglAtlas: true,
followedByNonImageInput: true
recoverImagePasteWebglAtlas: true
})
if (result === false) {
return { status: 'skipped', reason: 'image-paste-rejected' }
@@ -1,77 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { pasteTerminalText } from './terminal-bracketed-paste'
import {
executeTerminalPastePlan,
planTerminalPaste,
type TerminalPasteTarget
} from './terminal-paste-coordinator'
function terminalTarget(): TerminalPasteTarget {
return {
kind: 'terminal',
paneId: 1,
leafId: 'leaf-1',
ptyId: 'pty-1',
runtime: {
platform: 'darwin',
runtimeKey: 'local:darwin',
kind: 'local'
}
}
}
function createTerminal() {
return {
modes: { bracketedPasteMode: true },
options: { ignoreBracketedPasteMode: false },
input: vi.fn(),
paste: vi.fn()
}
}
describe('clipboard image paste through plan → execute → pasteTerminalText', () => {
it('writes a trailing space after the bracketed image path so later typing cannot glue', async () => {
const terminal = createTerminal()
const plan = planTerminalPaste({
text: '/tmp/orca-paste-1760000000000-id.png',
source: 'keyboard',
target: terminalTarget(),
forceBracketedPaste: true,
followedByNonImageInput: true
})
const result = await executeTerminalPastePlan(plan, {
pasteText: (text, options) => pasteTerminalText(terminal, text, options),
isTargetCurrent: () => true
})
expect(result.status).toBe('pasted')
const imageBytes = '\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~ '
expect(terminal.input).toHaveBeenCalledWith(imageBytes)
expect(`${imageBytes}describe this`).toBe(
'\x1b[200~/tmp/orca-paste-1760000000000-id.png\x1b[201~ describe this'
)
expect(terminal.paste).not.toHaveBeenCalled()
})
it('does not append a trailing space to forced-bracketed multiline text paste', async () => {
const terminal = createTerminal()
const plan = planTerminalPaste({
text: 'one\r\ntwo',
source: 'keyboard',
target: terminalTarget(),
forceBracketedPasteForMultiline: true
})
const result = await executeTerminalPastePlan(plan, {
pasteText: (text, options) => pasteTerminalText(terminal, text, options),
isTargetCurrent: () => true
})
expect(result.status).toBe('pasted')
expect(plan.mode).toBe('bracketed-terminal')
expect(terminal.input).toHaveBeenCalledWith('\x1b[200~one\rtwo\x1b[201~')
expect(terminal.paste).not.toHaveBeenCalled()
})
})
@@ -84,7 +84,6 @@ export const executeTerminalPaneMenuPasteText = async (
forceBracketedPaste: options?.forceBracketedPaste,
forceBracketedPasteForMultiline: options?.forceBracketedPasteForMultiline,
windowsInputRecordNewline: options?.windowsInputRecordNewline,
followedByNonImageInput: options?.followedByNonImageInput,
terminalBracketedPasteMode: pane.terminal.modes.bracketedPasteMode
})
const execution = await executeTerminalPastePlan(plan, {
@@ -44,7 +44,6 @@ type PlanTerminalPasteArgs = {
forceBracketedPaste?: boolean
forceBracketedPasteForMultiline?: boolean
windowsInputRecordNewline?: WindowsInputRecordNewline
followedByNonImageInput?: boolean
terminalBracketedPasteMode?: boolean
hasRichText?: boolean
maxDirectBytes?: number
@@ -87,7 +86,6 @@ export function planTerminalPaste({
forceBracketedPaste = false,
forceBracketedPasteForMultiline = false,
windowsInputRecordNewline,
followedByNonImageInput = false,
terminalBracketedPasteMode = false,
hasRichText = false,
maxDirectBytes = TERMINAL_PASTE_DIRECT_MAX_BYTES,
@@ -99,7 +97,6 @@ export function planTerminalPaste({
forceBracketedPaste,
forceBracketedPasteForMultiline,
windowsInputRecordNewline,
followedByNonImageInput,
maxBytes,
maxChunkBytes,
maxDirectBytes,
@@ -116,7 +113,6 @@ export async function planTerminalPasteWithYield({
forceBracketedPaste = false,
forceBracketedPasteForMultiline = false,
windowsInputRecordNewline,
followedByNonImageInput = false,
terminalBracketedPasteMode = false,
hasRichText = false,
maxDirectBytes = TERMINAL_PASTE_DIRECT_MAX_BYTES,
@@ -143,7 +139,6 @@ export async function planTerminalPasteWithYield({
forceBracketedPaste,
forceBracketedPasteForMultiline,
windowsInputRecordNewline,
followedByNonImageInput,
maxBytes,
maxChunkBytes,
maxDirectBytes,
@@ -159,7 +154,6 @@ function buildTerminalPastePlan({
forceBracketedPaste,
forceBracketedPasteForMultiline,
windowsInputRecordNewline,
followedByNonImageInput,
terminalBracketedPasteMode,
maxDirectBytes,
maxChunkBytes,
@@ -170,7 +164,6 @@ function buildTerminalPastePlan({
forceBracketedPaste: boolean
forceBracketedPasteForMultiline: boolean
windowsInputRecordNewline?: WindowsInputRecordNewline
followedByNonImageInput: boolean
terminalBracketedPasteMode: boolean
maxDirectBytes: number
maxChunkBytes: number
@@ -212,7 +205,6 @@ function buildTerminalPastePlan({
...(effectiveWindowsInputRecordNewline
? { windowsInputRecordNewline: effectiveWindowsInputRecordNewline }
: {}),
...(followedByNonImageInput ? { followedByNonImageInput: true } : {}),
runtimeKey: target.runtime.runtimeKey,
...(shouldChunk ? { maxChunkBytes } : {}),
bracketed: mode === 'bracketed-terminal' || (mode === 'chunked' && shouldBracketChunk),
@@ -65,8 +65,7 @@ async function executeTerminalPastePlanNow(
forceBracketedPaste: plan.mode === 'bracketed-terminal',
...(plan.windowsInputRecordNewline
? { windowsInputRecordNewline: plan.windowsInputRecordNewline }
: {}),
...(plan.followedByNonImageInput ? { followedByNonImageInput: true } : {})
: {})
})
}, operationTimeoutMs)
if (pasteResult.timedOut) {
@@ -40,7 +40,6 @@ export type TerminalPastePlan = {
// every write path applies xterm's native \r?\n -> \r before ConPTY sees LF.
newlinePolicy: 'preserve' | 'terminal-cr' | 'windows-input-record'
windowsInputRecordNewline?: WindowsInputRecordNewline
followedByNonImageInput?: boolean
runtimeKey: string
maxChunkBytes?: number
bracketed: boolean
@@ -53,7 +52,6 @@ export type TerminalPasteTextOptions = {
forceBracketedPasteForMultiline?: boolean
windowsInputRecordNewline?: WindowsInputRecordNewline
recoverImagePasteWebglAtlas?: boolean
followedByNonImageInput?: boolean
}
export type WindowsInputRecordNewline = 'alt-enter' | 'csi-u'