fix: respond to PTY cursor queries

This commit is contained in:
Peter Steinberger
2026-01-17 07:05:15 +00:00
parent a85ddf258c
commit 5dc87a2ed4
4 changed files with 42 additions and 1 deletions

View File

@@ -24,6 +24,7 @@
- Tools: default `exec` exit notifications and auto-migrate legacy `tools.bash` to `tools.exec`.
- Tools: add tmux-style `process send-keys` and bracketed paste helpers for PTY sessions.
- Tools: add `process submit` helper to send CR for PTY sessions.
- Tools: respond to PTY cursor position queries to unblock interactive TUIs.
- Status: trim `/status` to current-provider usage only and drop the OAuth/token block.
- Directory: unify `clawdbot directory` across channels and plugin channels.
- UI: allow deleting sessions from the Control UI.

View File

@@ -29,6 +29,7 @@ import {
truncateMiddle,
} from "./bash-tools.shared.js";
import { getShellConfig, sanitizeBinaryOutput } from "./shell-utils.js";
import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
const DEFAULT_MAX_OUTPUT = clampNumber(
readEnvInt("PI_BASH_MAX_OUTPUT_CHARS"),
@@ -451,7 +452,17 @@ export function createExecTool(
};
if (pty) {
pty.onData(handleStdout);
const cursorResponse = buildCursorPositionResponse();
pty.onData((data) => {
const raw = data.toString();
const { cleaned, requests } = stripDsrRequests(raw);
if (requests > 0) {
for (let i = 0; i < requests; i += 1) {
pty.write(cursorResponse);
}
}
handleStdout(cleaned);
});
} else if (child) {
child.stdout.on("data", handleStdout);
child.stderr.on("data", handleStderr);

View File

@@ -0,0 +1,15 @@
import { expect, test } from "vitest";
import { buildCursorPositionResponse, stripDsrRequests } from "./pty-dsr.js";
test("stripDsrRequests removes cursor queries and counts them", () => {
const input = "hi\x1b[6nthere\x1b[?6n";
const { cleaned, requests } = stripDsrRequests(input);
expect(cleaned).toBe("hithere");
expect(requests).toBe(2);
});
test("buildCursorPositionResponse returns CPR sequence", () => {
expect(buildCursorPositionResponse()).toBe("\x1b[1;1R");
expect(buildCursorPositionResponse(12, 34)).toBe("\x1b[12;34R");
});

14
src/agents/pty-dsr.ts Normal file
View File

@@ -0,0 +1,14 @@
const DSR_PATTERN = /\x1b\[\??6n/g;
export function stripDsrRequests(input: string): { cleaned: string; requests: number } {
let requests = 0;
const cleaned = input.replace(DSR_PATTERN, () => {
requests += 1;
return "";
});
return { cleaned, requests };
}
export function buildCursorPositionResponse(row = 1, col = 1): string {
return `\x1b[${row};${col}R`;
}