mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
fix(agents): fold non-Latin scripts + close echo-verify test holes
Echo fold kept only ASCII a-z/0-9, so a prompt in Cyrillic/CJK/Arabic/etc. folded to nothing, hit the unmatchable branch, and fired echo on any render — a blind submit onto a swallow screen for non-English users, the same #7466 failure. Fold on the Unicode letter/number classes instead; box borders and punctuation still drop, so Latin folding is unchanged. Also strengthen two vacuous FIX-2 guards proven by a revert experiment: the snapshot-exclusion assertion matched only the first callback arg (the watcher receives a second meta arg, so not.toHaveBeenCalledWith never matched), and the echo watch's includeSnapshot:false wiring was unasserted. Both now fail when the fix is reverted. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -107,6 +107,15 @@ describe('watchForPromptEcho', () => {
|
||||
)
|
||||
const watch = watchForPromptEcho('remote-pty', PROMPT, 10_000, {})
|
||||
await flushMicrotasks()
|
||||
// The echo watch must opt out of the pre-paste buffer snapshot (#7466),
|
||||
// or a swallow screen already on the terminal could falsely verify.
|
||||
expect(mocks.subscribeToRuntimeTerminalData).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'remote-pty',
|
||||
expect.any(String),
|
||||
expect.any(Function),
|
||||
{ includeSnapshot: false }
|
||||
)
|
||||
remoteObserver?.(ECHO)
|
||||
await expect(watch.result).resolves.toBe(true)
|
||||
expect(remoteUnsubscribe).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -189,8 +189,10 @@ describe('remote runtime terminal data subscriptions', () => {
|
||||
// Live output rendered after the subscription still reaches the watcher.
|
||||
deliverOutput(streamId, 'live')
|
||||
|
||||
expect(watcher).not.toHaveBeenCalledWith('PRE-PASTE SCREEN')
|
||||
expect(watcher).toHaveBeenCalledWith('live')
|
||||
// onSnapshot passes a second meta arg, so match on the first arg only —
|
||||
// a bare not.toHaveBeenCalledWith(...) would vacuously pass either way.
|
||||
expect(watcher.mock.calls.map((call) => call[0])).not.toContain('PRE-PASTE SCREEN')
|
||||
expect(watcher.mock.calls.map((call) => call[0])).toContain('live')
|
||||
})
|
||||
|
||||
it('keeps the shared terminal multiplexer until the last watcher closes', async () => {
|
||||
|
||||
@@ -103,4 +103,13 @@ describe('createPromptEchoScanner', () => {
|
||||
const scanner = createPromptEchoScanner('fix ci')
|
||||
expect(scanner.observe('\x1b[11;3Hfix\x1b[11;7Hci')).toBe(true)
|
||||
})
|
||||
|
||||
it('verifies a non-Latin-script prompt instead of failing open on any render', () => {
|
||||
// #7466: Cyrillic folds to letters (not away), so it matches on its render
|
||||
// and correctly withholds the submit on a swallow screen.
|
||||
const swallowed = createPromptEchoScanner('Разреши конфликты слияния')
|
||||
expect(swallowed.observe(CODEX_TRUST_SWALLOW_REDRAW)).toBe(false)
|
||||
const rendered = createPromptEchoScanner('Разреши конфликты слияния')
|
||||
expect(rendered.observe('\x1b[11;3HРазреши\x1b[11;15Hконфликты\x1b[11;25Hслияния')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
/**
|
||||
* Fold terminal output down to comparable text:
|
||||
* - strip OSC (title/hyperlink) and CSI/charset escape sequences
|
||||
* - drop every non-alphanumeric character (TUIs repaint with cursor moves
|
||||
* instead of literal spaces, wrap lines at pane width, and draw box
|
||||
* borders through the text, so only the letters/digits survive intact)
|
||||
* - drop every non-letter/non-number character (TUIs repaint with cursor
|
||||
* moves instead of literal spaces, wrap lines at pane width, and draw box
|
||||
* borders through the text, so only the letters/digits survive intact).
|
||||
* Uses the Unicode letter/number classes rather than ASCII so non-Latin
|
||||
* prompts (Cyrillic, CJK, Arabic, ...) still fold to matchable content
|
||||
* instead of vanishing to nothing.
|
||||
* - lowercase for case-stable matching
|
||||
*/
|
||||
export function foldTerminalTextForEchoMatch(raw: string): string {
|
||||
@@ -23,16 +26,7 @@ export function foldTerminalTextForEchoMatch(raw: string): string {
|
||||
.replace(/\x1b[()#][0-9A-Za-z]/g, '')
|
||||
.replace(/\x1b[@-_]/g, '')
|
||||
/* oxlint-enable no-control-regex */
|
||||
let folded = ''
|
||||
for (const ch of withoutEscapes.toLowerCase()) {
|
||||
const code = ch.codePointAt(0) ?? 0
|
||||
const isDigit = code >= 48 && code <= 57
|
||||
const isLower = code >= 97 && code <= 122
|
||||
if (isDigit || isLower) {
|
||||
folded += ch
|
||||
}
|
||||
}
|
||||
return folded
|
||||
return withoutEscapes.toLowerCase().replace(/[^\p{L}\p{N}]/gu, '')
|
||||
}
|
||||
|
||||
// Why: large pastes never render verbatim — codex collapses to
|
||||
|
||||
Reference in New Issue
Block a user