mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 16:03:45 +00:00
refactor(native-chat): keep image separator fix focused
This commit is contained in:
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user