Repository navigation
Conversation
…utput Strips CSI, OSC, and stray ESC sequences from PTY output before it is returned in pty_read results or embedded in \u003cpty_exited\u003e notifications. The raw RingBuffer is left untouched so the Web UI / xterm.js stream continues to receive original terminal output. Adds a PTY_SANITIZE_OUTPUT environment variable (defaults to true) so users can opt out and keep raw escape sequences when needed. Fixes shekohex#75
|
@NAnD71 I understand, there is a env PTY_SANITIZE_OUTPUT, but is the also a config's |
Honestly I think this is a pretty nasty bug in most cases — raw escape sequences leaking into the session can wreck your terminal (on Windows it remaps keyboard input and wipes the screen on every session resume). So sanitizing by default feels right. But yeah, if you're developing a TUI app you might genuinely want the raw sequences in pty_read, which is why I kept the PTY_SANITIZE_OUTPUT escape hatch. Fair point that env vars are a bit clunky to work with. I'm open to better ideas — could also wire it as a plugin option in the opencode config (sanitizeOutput: false, taking precedence over the env var). Happy to hear what others prefer. |
|
@NAnD71 Ah I my bad, I wasn’t specific enough: I was referring to opencode‘s v2 plugin spec, where the openvodes config has a new ’plugins’ key, where the plugins can be loaded with additional config. Did you prepare for adding your config parameter To the ones already added by the pty plugin as well? |
…Y_SANITIZE_OUTPUT env var)
|
Ah, thanks for clarifying — yes, wired now: |
| return text | ||
| } | ||
|
|
||
| return Bun.stripANSI(text) |
There was a problem hiding this comment.
will that work on opencode v2? afaik opencode v2 is not using bun?
There was a problem hiding this comment.
will that work on opencode v2? afaik opencode v2 is not using bun?
I re-checked this against the current upstream state and went through the plugin again more carefully. I'm closing this PR and will open a new one with a better-scoped fix.
The problem actually is
The plugin hands PTY output to the host as plain text in two places: pty_read results and the <pty_exited> notification. On Windows, ConPTY starts every session with mode, clear-screen and window-title sequences (ESC[?9001h ESC[?1004h ESC[?25l ESC[2J ESC[H ESC]0;C:\Program Files\PowerShell\7\pwsh.EXE BEL). For short commands, that preamble often ends up as the notification's Last Line. The OpenCode TUI writes text content to the terminal verbatim, so the terminal executes these sequences. It isn't specific to V1 or to Bun:
- OpenTUI passes control characters through to the terminal: Text renderables pass control characters and ANSI escape sequences from content to the terminal verbatim anomalyco/opencode#47687 (open). I confirmed it with a minimal
TextRenderableon both 0.4.5 (V1) and 0.5.17 (V2). An injectedESC[?7777hshows up raw inside the rendered frame. - OpenCode only strips ANSI for its own shell output. User messages and generic/plugin tool output are rendered as-is:
- V1.18.35: user text, generic tool, vs shell with stripAnsi
- V2.0.26: user text, generic tool, vs shell with stripAnsi
- The upstream attempt to strip generic tool output (fix(tui): strip ANSI escape sequences from generic tool output anomalyco/opencode#16369, for #16193) was closed without merging, so a host-side fix isn't coming soon.
- In my local OpenCode database, 48 of 94
<pty_exited>messages contained escape sequences. In 21 of them, theLast Linewas nothing but the ConPTY preamble.
On V2 / Bun question
You were right. V2 ships a Bun-compiled binary, but it also builds a Node SEA (opencode2-node), and plugins run in-process. Under Node, Bun.stripANSI would throw. The new PR doesn't add any Bun dependency.
Changes in the new PR
- Use
node:utilstripVTControlCharacters, plus removal of OSC/DCS/APC strings and leftover C0/C1 control characters (e.g.\r, BEL). Tabs are kept. This works on both Bun and Node. - Sanitize only where text leaves the plugin:
pty_readand<pty_exited>. The raw buffer used by the Web UI is unchanged. pty_readwithpatternnow matches against the sanitized text. Previously a pattern could match bytes inside escape sequences, or fail to match the visible text.- The exit notification skips lines that are empty after sanitizing, so the ConPTY preamble is never reported as the last line.
- No new plugin options. There is a single
PTY_SANITIZE_OUTPUT=0escape hatch, following the existingPTY_*env vars.
Fixes #75
What changed
src/plugin/pty/sanitize.tswithsanitizeAnsi()(usesBun.stripANSI) andisAnsiSanitizationEnabled().pty_readoutput inOutputManager.read()andOutputManager.search().Last Linefield inbuildExitNotification()before it is sent as a\u003cpty_exited\u003enotification.RingBufferuntouched so the Web UI / xterm.js stream and the/buffer/rawendpoint still receive original terminal output.PTY_SANITIZE_OUTPUTenvironment variable that defaults totrue; set it tofalseto preserve raw escape sequences.Test coverage
test/sanitize.test.ts: CSI sequences, OSC sequences, lone/stray ESC, plain text, mixed real-world output, and the opt-out env var.test/output-manager.test.ts: read and search both strip escape sequences while leaving plain text alone.test/notification-manager.test.ts: exit notifications strip ANSI from the last line.Verification
bun lintpassesbun typecheckpassesbun build:pluginpassesbun test test/sanitize.test.ts test/output-manager.test.ts test/notification-manager.test.ts test/pty-tools.test.ts)