Skip to content

fix(host): keep replayed terminal queries out of the shell's input - #60

Merged
iamnbutler merged 7 commits into
mainfrom
glad-ridge/terminal-replay-input
Oct 5, 2026
Merged

iamnbutler merged 7 commits into
mainfrom
glad-ridge/terminal-replay-input

Conversation

@iamnbutler

Copy link
Copy Markdown
Contributor

Fixes #48.

When a terminal is reattached, the host replays its scrollback, and xterm answers queries in that old output as if they were live. A replayed cursor-position query (CSI 6n) made xterm send CSI row;col R to the current shell, and that ended up in front of whatever was pasted next. This is where the 1R prefix came from.

  • Host: sends the scrollback as one frame marked replay: true (TerminalFrame in apps/host/src/protocol.ts), so the client knows where the replay ends.
  • App: while xterm is parsing a replay frame, the terminal panel drops what xterm's onData emits. xterm calls the write callback right after it parses that chunk and before any live output queued behind it. So the window closes exactly where the replay ends, and live queries are still answered. A counter tracks pending replays, so a stale replay still in xterm's write buffer during a quick reattach can't end the window early.

Smoke test

I ran a real bun ace serve with an isolated ACE_HOME and a real zsh, connected through GatewayClient, and used @xterm/headless@6.0.0 (the same core as the app's @xterm/xterm@6.0.0) with the panel's input logic copied in:

  1. Client A opens a terminal and runs printf '\033[6n'; read -rs -t 2 -d R x; echo LIVE:${#x}. The live query is answered (LIVE:5).
  2. Client A disconnects. Client B reattaches and the scrollback replays. Then it types echo PASTED-OK.
    • Without the fix: B sends \e[5;1R during the replay, and zsh prints zsh: command not found: 1Recho. This reproduces the issue.
    • With the fix: B sends nothing during the replay, and PASTED-OK prints.
  3. After the replay, B runs the live query again. It is still answered (LIVE-B:6).

bun types and bun run ci pass. I didn't run this check again in the desktop app's webview.

Built in Ace

Built in an Ace channel (agent with shell in a lane). The smoke test used @xterm/headless, installed temporarily in /tmp and not committed, because the agent can't type into the desktop app's terminal. It also used a throwaway ace serve on port 4199 instead of the running canary host.

Refs #23. Show full rolling summaries in the details sidebar, with brief dashboard and channel previews. Preserve existing channel history and lane prefixes.
* feat(desktop): embed native window inspection in Ace

* fix(desktop): keep inspection responsive during folder selection

* chore(desktop): prepare canary 0.0.10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pasted command gets a stray 1R prefix after returning to a terminal

1 participant