mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-23 05:31:23 +00:00
Some checks failed
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
Docker Build and Publish / build-amd64 (push) Waiting to run
Docker Build and Publish / build-arm64 (push) Waiting to run
Docker Build and Publish / merge (push) Blocked by required conditions
Docker Build and Publish / move-latest (push) Blocked by required conditions
Lint (ruff + ty) / ruff + ty diff (push) Waiting to run
Lint (ruff + ty) / ruff enforcement (blocking) (push) Waiting to run
Lint (ruff + ty) / Windows footguns (blocking) (push) Waiting to run
Nix / nix (macos-latest) (push) Waiting to run
Nix / nix (ubuntu-latest) (push) Waiting to run
OSV-Scanner / Scan lockfiles (push) Waiting to run
Tests / test (push) Waiting to run
Tests / e2e (push) Waiting to run
uv.lock check / uv lock --check (push) Waiting to run
Nix Lockfile Fix / auto-fix-main (push) Has been cancelled
Nix Lockfile Fix / fix (push) Has been cancelled
Build Skills Index / build-index (push) Has been cancelled
Build Skills Index / deploy-with-index (push) Has been cancelled
* fix(tui-clipboard): skip native safety net on OSC52-capable terminals
On terminals with first-class OSC 52 support (Ghostty, kitty, WezTerm,
Windows Terminal, VS Code), setClipboard() currently fires both OSC 52
AND a parallel native-tool write (wl-copy / xclip / pbcopy). On Wayland
+ wl-copy this corrupts the clipboard: probeLinuxCopy() runs wl-copy
with empty stdin as an existence check (destructive — wipes clipboard
to empty string), and the subsequent real wl-copy invocation races
OSC 52 plus its own daemon's previous SIGTERM.
Symptom: user on Arch + Ghostty + wl-copy (Wayland, no tmux, no SSH)
had to press Ctrl+Shift+C three times before a selection landed.
env -u WAYLAND_DISPLAY -u DISPLAY HERMES_TUI_FORCE_OSC52=1 (which
short-circuits copyNative via the DISPLAY-absent early-return) made
every copy work instantly — proving OSC 52 alone is sufficient on
Ghostty and that copyNative() is actively destructive there.
Add OSC52_CAPABLE_TERMINALS allowlist to terminal.ts (same pattern as
the existing EXTENDED_KEYS_TERMINALS), and gate copyNative() on the
terminal NOT being on it. The native safety net continues to fire on
unrecognised terminals (xterm, GNOME Terminal, Konsole, Terminal.app,
etc.) where OSC 52 is less reliable.
* fix(tui-clipboard): address Copilot review feedback
- Move OSC52_CAPABLE_TERMINALS + supportsOsc52Clipboard() from
ink/terminal.ts to utils/env.ts. ink/terminal.ts already imports
link from ink/termio/osc.ts; importing back into termio/osc.ts
introduced a circular dependency. utils/env.ts has no deps on
either file and already owns terminal detection (detectTerminal()),
so the helper sits naturally next to it.
- Replace the inline gating (!SSH_CONNECTION && !supportsOsc52Clipboard())
with a pure shouldUseNativeClipboard(env, terminal) helper. The old
expression skipped native on allowlisted terminals even when
setClipboard() wouldn't actually emit OSC 52 (e.g. inside
TMUX/STY where we use tmux load-buffer instead, or when the user
has set HERMES_TUI_FORCE_OSC52=0). That made the clipboard write
a no-op in those configurations. The new helper:
1. SSH_CONNECTION set -> false (existing behaviour)
2. TMUX or STY set -> true (we go through load-buffer, no race)
3. shouldEmitClipboardSequence() false -> true (native is the
only path left when OSC 52 is suppressed)
4. Otherwise: skip native iff terminal is allowlisted.
- Add 11 tests for shouldUseNativeClipboard covering the SSH guard,
TMUX/STY tmux-inside-Ghostty case, HERMES_TUI_FORCE_OSC52=0
override, allowlisted vs non-allowlisted terminals, precedence,
and default-args smoke. Tests follow the package's existing
parameterised-helper style (no vi.mock; helpers accept env and
terminal as arguments).
- Update test imports to the new utils/env.js path.
* fix(tui-clipboard): address Copilot round 2 feedback
* fix(tui-clipboard): address Copilot round 3 feedback
* fix(tui-clipboard): address Copilot round 4 feedback
66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
type TerminalName = string | null
|
|
|
|
function detectTerminal(): TerminalName {
|
|
if (process.env.CURSOR_TRACE_ID) {
|
|
return 'cursor'
|
|
}
|
|
|
|
if (process.env.TERM === 'xterm-ghostty') {
|
|
return 'ghostty'
|
|
}
|
|
|
|
if (process.env.TERM?.includes('kitty')) {
|
|
return 'kitty'
|
|
}
|
|
|
|
if (process.env.TERM_PROGRAM) {
|
|
return process.env.TERM_PROGRAM
|
|
}
|
|
|
|
if (process.env.TMUX) {
|
|
return 'tmux'
|
|
}
|
|
|
|
if (process.env.STY) {
|
|
return 'screen'
|
|
}
|
|
|
|
if (process.env.KITTY_WINDOW_ID) {
|
|
return 'kitty'
|
|
}
|
|
|
|
if (process.env.WT_SESSION) {
|
|
return 'windows-terminal'
|
|
}
|
|
|
|
return process.env.TERM ?? null
|
|
}
|
|
|
|
export const env = {
|
|
terminal: detectTerminal()
|
|
}
|
|
|
|
// Terminals known to correctly implement OSC 52 clipboard writes
|
|
// (ESC ] 52 ; c ; <b64> BEL/ST — osc() in ink/termio/osc.ts emits BEL
|
|
// for most terminals and ST for kitty). When detected, setClipboard() skips the
|
|
// native-tool safety net entirely — running wl-copy/xclip/pbcopy in
|
|
// parallel with OSC 52 races the terminal's own clipboard write and can
|
|
// corrupt it (e.g. wl-copy on Wayland holds the selection in a background
|
|
// daemon; stacking two writes within ~30ms triggers a SIGTERM race).
|
|
// Intentionally conservative: terminals with known flaky or disabled-by-
|
|
// default OSC 52 (iTerm2 disables OSC 52 by default; Alacritty detection
|
|
// is unreliable) are not on this list. Users on those terminals keep the
|
|
// existing behaviour (native safety net fires alongside OSC 52).
|
|
//
|
|
// Lives here in utils/env.ts (rather than ink/terminal.ts) so that
|
|
// ink/termio/osc.ts can import it without creating a circular dependency:
|
|
// ink/terminal.ts already imports `link` from ink/termio/osc.ts.
|
|
const OSC52_CAPABLE_TERMINALS = ['ghostty', 'kitty', 'WezTerm', 'windows-terminal', 'vscode']
|
|
|
|
/** True if this terminal is known to correctly handle OSC 52 clipboard
|
|
* writes, so setClipboard() can skip the native-tool safety net.
|
|
* Accepts an optional terminal name for testability; defaults to the
|
|
* module-level `env.terminal` detected at startup. */
|
|
export function supportsOsc52Clipboard(terminal: string | null = env.terminal): boolean {
|
|
return OSC52_CAPABLE_TERMINALS.includes(terminal ?? '')
|
|
}
|