mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 16:02:37 +00:00
* fix(grok): restore clipboard and native-chat parity Grok CLI already supports argv prompts, OSC 52 copy, and image paste chips. Orca was blocking those paths: stdin-after-start keystroke injection, OSC 52 writes default-off, image-attachment denylist, and native-chat allowlist. - Launch Grok with positional argv prompts - Default OSC 52 TUI clipboard writes on (still user-toggleable) - Treat Grok as image-attachment capable - Parse ~/.grok/.../chat_history.jsonl for native chat OSC 52 clipboard *query* remains ignored by design (host clipboard exfil risk); xAI docs only require OSC 52 write for remote copy. * fix(grok): sync OSC 52 docs and locale catalog with default-on Update terminalAllowOsc52Clipboard type docs for the true default, and refresh locale strings so settings UI mentions Grok alongside other TUIs. * fix(grok): tool hook matcher, StopFailure, previews, AskUser waiting Grok tool-event matchers are real regexes; bare `*` failed as match-all. Install `.*` for Pre/Post tool hooks, add StopFailure for API-error ends, recognize Grok-native tool input keys, and map ask_user_question PreToolUse to waiting with interactivePrompt (Kimi-style live card path). * fix(grok): resolve chat_history under GROK_HOME and long-cwd layouts Centralize Grok session path helpers so hooks and native-chat honor GROK_HOME and find chat_history.jsonl by session id when the cwd group is slug-encoded (encoded name > 255 bytes) instead of only encodeURIComponent(cwd). * fix(terminal): keep Kitty keyboard for Grok on Windows ConPTY Local Windows ConPTY withholds KKP so CSI-u-blind CLIs (e.g. Antigravity) keep Enter/nav working (#2434). Grok needs KKP for Ctrl+Enter interject and modified-Enter newline chords; blanking the advertisement for Orca-launched Grok left those actions broken. - Prefer KKP when tuiAgent is grok despite ConPTY withhold - Wire launchAgent from tab/startup into keyboard protocol options * fix(grok): restore OSC52 default-off, split decoders, honor GROK_HOME hooks - Keep terminalAllowOsc52Clipboard default false (clipboard exfil risk) - Split transcript-line-decoders under max-lines without suppressions - Install local Grok hooks under resolveGrokHomeDir() / GROK_HOME * refactor(grok): share CLI home resolution * fix(grok): harden terminal and native chat integration * test(grok): align CI coverage with native chat support --------- Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
100 lines
5.1 KiB
TypeScript
100 lines
5.1 KiB
TypeScript
import { Terminal } from '@xterm/headless'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { isTerminalQueryReply } from './terminal-query-reply'
|
|
|
|
describe('isTerminalQueryReply', () => {
|
|
it('matches synthetic query replies that must be sent immediately', () => {
|
|
// CPR cursor position report (answer to CSI 6n) — the #7329 culprit.
|
|
expect(isTerminalQueryReply('\x1b[3;1R')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[22;1R')).toBe(true)
|
|
// DSR device status.
|
|
expect(isTerminalQueryReply('\x1b[0n')).toBe(true)
|
|
// DA1/DA2/DA3 device attributes.
|
|
expect(isTerminalQueryReply('\x1b[?1;2c')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[?61;4c')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[>0;276;0c')).toBe(true)
|
|
// Window/cell pixel-size reports.
|
|
expect(isTerminalQueryReply('\x1b[6;16;8t')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[4;384;640t')).toBe(true)
|
|
// DECRPM mode report — private (with ?) and ANSI (without ?).
|
|
expect(isTerminalQueryReply('\x1b[?2026;2$y')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[4;1$y')).toBe(true)
|
|
// OSC 10/11 color responses (the #7329 culprit) — BEL and ST terminated.
|
|
expect(isTerminalQueryReply('\x1b]11;rgb:2828/2c2c/3434\x1b\\')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b]10;rgb:c0c0/c0c0/c0c0\x07')).toBe(true)
|
|
// DECXCPR extended cursor position report (answer to CSI ? 6n).
|
|
expect(isTerminalQueryReply('\x1b[?12;5R')).toBe(true)
|
|
// Text-area size in characters (answer to CSI 18t).
|
|
expect(isTerminalQueryReply('\x1b[8;24;80t')).toBe(true)
|
|
// Kitty keyboard flags report (answer to CSI ? u) — crossterm probes this
|
|
// at startup, so a debounced reply corrupts the same way CPR did.
|
|
expect(isTerminalQueryReply('\x1b[?0u')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1b[?31u')).toBe(true)
|
|
// DCS DECRQSS reports (vim queries cursor style via DCS $ q) + XTVERSION.
|
|
expect(isTerminalQueryReply('\x1bP1$r2 q\x1b\\')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1bP1$r0m\x1b\\')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1bP0$r\x1b\\')).toBe(true)
|
|
expect(isTerminalQueryReply('\x1bP>|xterm.js(5.6.0)\x1b\\')).toBe(true)
|
|
})
|
|
|
|
it('classifies the fully framed XTVERSION reply emitted by real xterm', async () => {
|
|
const terminal = new Terminal()
|
|
const replies: string[] = []
|
|
const disposable = terminal.onData((data) => replies.push(data))
|
|
try {
|
|
await new Promise<void>((resolve) => terminal.write('\x1b[>q', resolve))
|
|
expect(replies).toHaveLength(1)
|
|
const reply = replies[0]
|
|
expect(reply.startsWith('\x1bP>|xterm.js(')).toBe(true)
|
|
expect(reply.endsWith(')\x1b\\')).toBe(true)
|
|
expect(isTerminalQueryReply(reply)).toBe(true)
|
|
} finally {
|
|
disposable.dispose()
|
|
terminal.dispose()
|
|
}
|
|
})
|
|
|
|
it('documents the accepted modified-F3/CPR collision', () => {
|
|
// xterm.js encodes Shift+F3 as CSI 1;2R — byte-identical to a CPR report.
|
|
// Classified as a reply on purpose: order is still preserved (the immediate
|
|
// path flushes pending input first); see the comment in terminal-query-reply.ts.
|
|
expect(isTerminalQueryReply('\x1b[1;2R')).toBe(true)
|
|
})
|
|
|
|
it('does NOT match ordinary typed input or navigation sequences', () => {
|
|
// Plain text.
|
|
expect(isTerminalQueryReply('yes')).toBe(false)
|
|
expect(isTerminalQueryReply('y')).toBe(false)
|
|
expect(isTerminalQueryReply('\r')).toBe(false)
|
|
expect(isTerminalQueryReply('\x03')).toBe(false) // Ctrl-C
|
|
// Arrow keys / navigation — must stay batched (coalesced auto-repeat).
|
|
expect(isTerminalQueryReply('\x1b[A')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[B')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[C')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[D')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[H')).toBe(false) // Home
|
|
expect(isTerminalQueryReply('\x1b[F')).toBe(false) // End
|
|
// Function keys (end in ~).
|
|
expect(isTerminalQueryReply('\x1b[15~')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[3~')).toBe(false) // Delete
|
|
// Bare Escape key.
|
|
expect(isTerminalQueryReply('\x1b')).toBe(false)
|
|
// Alt+key (including Alt+Shift+P, whose bytes prefix the DCS grammar).
|
|
expect(isTerminalQueryReply('\x1bb')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1bP')).toBe(false)
|
|
// Kitty-protocol KEYSTROKES (CSI code;mods u, no "?") must stay batched.
|
|
expect(isTerminalQueryReply('\x1b[97;5u')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[13u')).toBe(false)
|
|
// Modified F1/F2/F4 (CSI 1;<mod> P/Q/S) are keystrokes, not replies.
|
|
expect(isTerminalQueryReply('\x1b[1;2P')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[1;2Q')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[1;2S')).toBe(false)
|
|
// Bracketed paste markers are input framing, not replies.
|
|
expect(isTerminalQueryReply('\x1b[200~')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1b[201~')).toBe(false)
|
|
// Incomplete / non-terminated OSC and DCS must not match.
|
|
expect(isTerminalQueryReply('\x1b]11;rgb:2828/2c2c/3434')).toBe(false)
|
|
expect(isTerminalQueryReply('\x1bP1$r2 q')).toBe(false)
|
|
})
|
|
})
|