mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(dashboard): harden PTY input tracker against escape sequences
Two review fixes on the mobile input normalization path: - updatePtyInputLine appended the printable payload of escape sequences (the '[D' of a left-arrow) to the tracked line, and after any cursor movement the flat tracker no longer matched the visual line — the DELETE-repeat replacement could then be computed against a stale snapshot. Any chunk containing ESC now resets the tracker, disarming replacement normalization until a cleanly-tracked line starts. - Move the SGR mouse-report filter ahead of the blocked-input check so scrolling a disconnected terminal doesn't print the reconnect notice.
This commit is contained in:
parent
0b2b08d54c
commit
3e88cae243
3 changed files with 37 additions and 4 deletions
|
|
@ -67,4 +67,25 @@ describe("updatePtyInputLine", () => {
|
|||
expect(updatePtyInputLine("abc", "\x7f")).toBe("ab");
|
||||
expect(updatePtyInputLine("abc", "\r")).toBe("");
|
||||
});
|
||||
|
||||
it("resets tracking on escape sequences instead of appending their payload", () => {
|
||||
// Left-arrow arrives as one CSI chunk; the tracker cannot model cursor
|
||||
// moves, so it must disarm rather than record "hello[D".
|
||||
expect(updatePtyInputLine("hello", "\x1b[D")).toBe("");
|
||||
expect(updatePtyInputLine("hello", "\x1b[H")).toBe("");
|
||||
expect(updatePtyInputLine("hello", "\x1bOP")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizePtyMobileInput after cursor movement", () => {
|
||||
it("does not emit a replacement against a tracker reset by arrow keys", () => {
|
||||
// Simulate: type "hello my name is kain", press left-arrow, then a
|
||||
// Gboard suggestion arrives. The tracker reset means no replacement
|
||||
// heuristic can fire against a stale line snapshot.
|
||||
const afterArrow = updatePtyInputLine("hello my name is kain", "\x1b[D");
|
||||
const result = normalizePtyMobileInput("Kain", afterArrow, true);
|
||||
|
||||
expect(result.normalized).toBe(false);
|
||||
expect(result.data).toBe("Kain");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,6 +76,15 @@ export function shouldTreatInputAsMobileReplacement(
|
|||
}
|
||||
|
||||
export function updatePtyInputLine(currentLine: string, data: string): string {
|
||||
// Escape sequences (arrow keys, home/end, function keys, paste guards)
|
||||
// move the cursor or edit the line in ways this flat tracker cannot
|
||||
// model — and the per-char loop below would append their printable
|
||||
// payload (e.g. the "[D" of a left-arrow) as if it were typed text.
|
||||
// Reset instead: an unknown cursor position must disarm replacement
|
||||
// normalization until the user starts a fresh, cleanly-tracked line.
|
||||
if (data.includes("\x1b")) {
|
||||
return "";
|
||||
}
|
||||
let next = currentLine;
|
||||
for (const ch of chars(data)) {
|
||||
if (ch === "\r" || ch === "\n") {
|
||||
|
|
|
|||
|
|
@ -940,6 +940,13 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
// eslint-disable-next-line no-control-regex -- intentional ESC byte in xterm SGR mouse report parser
|
||||
const SGR_MOUSE_RE = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/;
|
||||
onDataDisposable = term.onData((data) => {
|
||||
// Mouse reports (scroll wheel etc.) are not typed input — swallow
|
||||
// them before the blocked-input check so scrolling a disconnected
|
||||
// terminal doesn't trip the "reconnecting" notice.
|
||||
if (SGR_MOUSE_RE.test(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
ws.readyState !== WebSocket.OPEN ||
|
||||
shouldBlockPtyInput(ptyStateRef.current)
|
||||
|
|
@ -953,10 +960,6 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (SGR_MOUSE_RE.test(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizePtyMobileInput(
|
||||
data,
|
||||
ptyInputLineRef.current,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue