- Separate system prompt for local vs cloud models via isLocal flag (isLocalBackendURL, buildSystemPrompt(..., isLocal), propagate through createSession/createSessionFromRecord/compactSession/spawnSubAgent) - Increase MAX_EMPTY_RESPONSE_RETRIES from 1 to 3 for cloud model resilience - Upgrade mouse input: full SGR-1006 parsing with col/row/pressed/modifiers, logicalButton() helper, and copyToClipboard() via OSC 52 - Add temporary mouse debug logging in App.tsx - Increase DEFAULT_MAX_ITERATIONS from 100 to 300 - Update README mouse/scrollback docs, tweak diff-remove color
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
// xterm SGR-1006 mouse sequences (enabled in App.tsx via `\x1b[?1002h\x1b[?1006h`)
|
|
// arrive on stdin as a single ink `useInput` event with the leading ESC stripped,
|
|
// e.g. "[<0;12;5M" (left button press at col 12, row 5) or "[<64;12;5M" (wheel up).
|
|
//
|
|
// Mode 1002 (button-event tracking) reports: button press, button release, and
|
|
// drag (motion while a button is held). Every active `useInput` hook in the tree
|
|
// receives the same raw event — ink has no concept of one handler "consuming" it
|
|
// before others see it — so any text-input component that doesn't recognize the
|
|
// sequence falls through to its regular-character handling and types the raw
|
|
// escape text into the field. Shared so every `useInput` consumer can ignore it
|
|
// the same way App.tsx's own handler already does.
|
|
//
|
|
// SGR-1006 format: [<button;col;rowM (press/drag) or [<button;col;rowm (release)
|
|
// button encodes button number + modifier keys:
|
|
// bit 0: shift
|
|
// bit 1: meta (alt)
|
|
// bit 2: ctrl
|
|
// bits 4-5: 00=left, 01=middle, 10=right
|
|
// Special: 64=wheel up, 65=wheel down
|
|
// Drag reports button 3 (all buttons released) during motion.
|
|
|
|
const MOUSE_SEQUENCE_RE = /^\[<(\d+);(\d+);(\d+)([Mm])$/;
|
|
|
|
export interface MouseEvent {
|
|
/** Raw button code (includes modifier bits in bits 0-2, button in bits 4-5). */
|
|
button: number;
|
|
/** 1-based column. */
|
|
col: number;
|
|
/** 1-based row. */
|
|
row: number;
|
|
/** True on press/drag (trailing 'M'), false on release (trailing 'm'). */
|
|
pressed: boolean;
|
|
/** True if shift was held. */
|
|
shift: boolean;
|
|
/** True if meta (alt) was held. */
|
|
meta: boolean;
|
|
/** True if ctrl was held. */
|
|
ctrl: boolean;
|
|
}
|
|
|
|
/** Which logical button is pressed (ignoring modifiers). */
|
|
export function logicalButton(e: MouseEvent): "left" | "middle" | "right" | "wheel-up" | "wheel-down" | "release" | "other" {
|
|
const raw = e.button & ~0x07; // strip modifier bits
|
|
if (raw === 0) return "left";
|
|
if (raw === 1) return "middle";
|
|
if (raw === 2) return "right";
|
|
if (raw === 64) return "wheel-up";
|
|
if (raw === 65) return "wheel-down";
|
|
if (raw === 3 || (!e.pressed && raw === 0)) return "release";
|
|
return "other";
|
|
}
|
|
|
|
export function matchMouseSequence(input: string): MouseEvent | null {
|
|
const match = MOUSE_SEQUENCE_RE.exec(input);
|
|
if (!match) return null;
|
|
const button = Number(match[1]);
|
|
const col = Number(match[2]);
|
|
const row = Number(match[3]);
|
|
const pressed = match[4] === "M";
|
|
return {
|
|
button,
|
|
col,
|
|
row,
|
|
pressed,
|
|
shift: !!(button & 4),
|
|
meta: !!(button & 8),
|
|
ctrl: !!(button & 16),
|
|
};
|
|
}
|
|
|
|
/** Write text to the system clipboard using OSC 52 (works over SSH, tmux, etc.).
|
|
* Falls back to platform-specific commands if OSC 52 doesn't work. */
|
|
export function copyToClipboard(text: string, stdout: NodeJS.WriteStream): void {
|
|
// OSC 52: \x1b]52;c;<base64>\x07 (target 'c' = system clipboard)
|
|
const base64 = Buffer.from(text, "utf-8").toString("base64");
|
|
// Write in chunks to avoid overflowing terminal input buffers (some terminals
|
|
// have limits around 4096 bytes for a single escape sequence).
|
|
const CHUNK = 300;
|
|
for (let i = 0; i < base64.length; i += CHUNK) {
|
|
const chunk = base64.slice(i, i + CHUNK);
|
|
// For the first chunk, start the sequence; for subsequent chunks, just append.
|
|
if (i === 0) {
|
|
stdout.write(`\x1b]52;c;${chunk}`);
|
|
} else {
|
|
stdout.write(chunk);
|
|
}
|
|
}
|
|
stdout.write("\x07"); // BEL terminates the sequence
|
|
} |