mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
* fix(mobile): preserve long terminal and chat pastes Agents can keep only the last ~1 KiB read of unframed input (#13332). Frame long single-line chat messages like multi-line ones, and frame Live input appends over 1 KiB (an OS paste) with the terminal's paste rule, including held text flushed before a terminal click. Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org> * fix(mobile): end the live input session after a framed paste Agents such as Codex collapse a framed paste into one token, so later per-character erases from the field mirror desynced the line. A long append to a terminal in bracketed paste mode now commits the whole field in one frame and clears the field, recording it like other session ends so a late native echo cannot send the paste twice; without that mode the paste stays unframed and editable. Chat sends measure the sanitized bytes when deciding to frame. --------- Co-authored-by: Carlos Baraza Haro <carlos.baraza@ieee.org> Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
buildNativeChatImagePasteBytes,
|
|
buildNativeChatPasteBytes
|
|
} from './native-chat-paste-bytes'
|
|
|
|
const BEGIN = '\x1b[200~'
|
|
const END = '\x1b[201~'
|
|
|
|
describe('buildNativeChatPasteBytes', () => {
|
|
it('single-line text has no trailing submit (Enter is written separately)', () => {
|
|
expect(buildNativeChatPasteBytes('hello world')).toBe('hello world')
|
|
expect(buildNativeChatPasteBytes('hello world')).not.toContain('\r')
|
|
})
|
|
|
|
it('multi-line text is bracketed-paste wrapped with NO trailing submit', () => {
|
|
const text = 'line one\r\nline two\nline three'
|
|
expect(buildNativeChatPasteBytes(text)).toBe(`${BEGIN}line one\rline two\rline three${END}`)
|
|
})
|
|
|
|
it('treats a trailing newline as multi-line', () => {
|
|
expect(buildNativeChatPasteBytes('a\n')).toBe(`${BEGIN}a\r${END}`)
|
|
})
|
|
|
|
it('sanitizes an embedded bracketed-paste end and bare ESC before framing', () => {
|
|
const malicious = 'before\nmid\x1b[201~ rm -rf /\x1b tail'
|
|
const bytes = buildNativeChatPasteBytes(malicious)
|
|
expect(bytes.startsWith(BEGIN)).toBe(true)
|
|
expect(bytes.endsWith(END)).toBe(true)
|
|
const inner = bytes.slice(BEGIN.length, bytes.length - END.length)
|
|
expect(inner).not.toContain('\x1b')
|
|
expect(inner).toContain('␛[201~')
|
|
})
|
|
|
|
it('neutralizes a stray ESC in the single-line branch', () => {
|
|
expect(buildNativeChatPasteBytes('hi\x1b there')).toBe('hi␛ there')
|
|
})
|
|
|
|
it('frames single-line text over 1 KiB of UTF-8 so agents keep every read (#13332)', () => {
|
|
const atBoundary = 'é'.repeat(512)
|
|
const overBoundary = `${atBoundary}x`
|
|
expect(buildNativeChatPasteBytes(atBoundary)).toBe(atBoundary)
|
|
expect(buildNativeChatPasteBytes(overBoundary)).toBe(`${BEGIN}${overBoundary}${END}`)
|
|
// Each ESC becomes a 3-byte ␛, so the sent length, not the typed one, decides.
|
|
expect(buildNativeChatPasteBytes('\x1b'.repeat(400))).toBe(`${BEGIN}${'␛'.repeat(400)}${END}`)
|
|
})
|
|
})
|
|
|
|
describe('buildNativeChatImagePasteBytes', () => {
|
|
it('always bracket-pastes the image path so agent TUIs attach it as an image', () => {
|
|
expect(buildNativeChatImagePasteBytes('/tmp/orca-paste-image.png')).toBe(
|
|
`${BEGIN}/tmp/orca-paste-image.png${END}`
|
|
)
|
|
})
|
|
})
|