Files
orca/src/shared/native-chat-paste-bytes.ts
T

48 lines
2.0 KiB
TypeScript

// Pure: turn raw composer text into the exact PTY bytes to write. Shared so the
// desktop and phone composers deliver a message as the same bytes.
import {
sanitizeBracketedPasteText,
wrapTerminalBracketedPasteText
} from './terminal-bracketed-paste-text'
// Why: carriage return (not \n) is what xterm/agent composers treat as the
// submit/Enter key over a PTY.
const SUBMIT = '\r'
/** True when the draft spans more than one line (so it needs bracketed-paste
* wrapping). A trailing newline alone still counts as multi-line. */
export function isMultilineDraft(text: string): boolean {
return /[\r\n]/.test(text)
}
/** The carriage-return submit byte, exported so send paths can write Enter as a
* SEPARATE pty write after the framed body (see buildNativeChatPasteBytes). */
export const NATIVE_CHAT_SUBMIT = SUBMIT
/**
* Compute the bytes for `text` WITHOUT the trailing submit:
* - single-line → `text`
* - multi-line → `\x1b[200~…\x1b[201~` (bracketed-paste wrapped, no submit)
*
* Why split the submit out: agent TUIs treat a framed paste that carries a
* trailing `\r` in the SAME pty write as part of the paste body rather than an
* Enter, so the text lands in the input box but never sends. Callers write this
* body first, then write `NATIVE_CHAT_SUBMIT` as a separate, slightly-delayed
* write (mirrors orca-runtime's writeTerminalAction Enter handling).
*/
export function buildNativeChatPasteBytes(text: string): string {
if (isMultilineDraft(text)) {
return wrapTerminalBracketedPasteText(text)
}
// Why: sanitize even unframed text so pasted scrollback cannot carry a raw
// terminal escape into the agent composer.
return sanitizeBracketedPasteText(text)
}
/** Image attachments must look like a real terminal image paste to Claude/Codex
* TUIs. A plain typed path (or @file mention) is treated as text/file-read. */
export function buildNativeChatImagePasteBytes(filePath: string): string {
return wrapTerminalBracketedPasteText(filePath)
}