From a4178e4062c9d0173b6dd9fb370e44a5750c2e31 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 17 Jan 2026 06:32:24 +0000 Subject: [PATCH] fix: stabilize pty send-keys tests --- src/agents/bash-tools.process.send-keys.test.ts | 5 +++-- src/agents/pty-keys.ts | 9 +++++++-- src/agents/session-slug.test.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/agents/bash-tools.process.send-keys.test.ts b/src/agents/bash-tools.process.send-keys.test.ts index eac885d5f..1f4ab44a9 100644 --- a/src/agents/bash-tools.process.send-keys.test.ts +++ b/src/agents/bash-tools.process.send-keys.test.ts @@ -15,7 +15,7 @@ test("process send-keys encodes Enter for pty sessions", async () => { const processTool = createProcessTool(); const result = await execTool.execute("toolcall", { command: - "node -e \"process.stdin.on('data', d => { process.stdout.write(d); if (d.includes(13)) process.exit(0); });\"", + "node -e \"process.stdin.on('data', d => { process.stdout.write(d); if (d.includes(10) || d.includes(13)) process.exit(0); });\"", pty: true, background: true, }); @@ -30,7 +30,8 @@ test("process send-keys encodes Enter for pty sessions", async () => { keys: ["h", "i", "Enter"], }); - for (let i = 0; i < 10; i += 1) { + const deadline = Date.now() + (process.platform === "win32" ? 4000 : 2000); + while (Date.now() < deadline) { await wait(50); const poll = await processTool.execute("toolcall", { action: "poll", sessionId }); const details = poll.details as { status?: string; aggregated?: string }; diff --git a/src/agents/pty-keys.ts b/src/agents/pty-keys.ts index a3bc9561e..445b50076 100644 --- a/src/agents/pty-keys.ts +++ b/src/agents/pty-keys.ts @@ -12,6 +12,10 @@ type Modifiers = { shift: boolean; }; +function escapeRegExp(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + const namedKeyMap = new Map([ ["enter", CR], ["return", CR], @@ -231,8 +235,9 @@ function xtermModifier(mods: Modifiers): number { } function applyXtermModifier(sequence: string, modifier: number): string | null { - const csiNumber = /^\x1b\[(\d+)([~A-Z])$/; - const csiArrow = /^\x1b\[(A|B|C|D|H|F)$/; + const escPattern = escapeRegExp(ESC); + const csiNumber = new RegExp(`^${escPattern}\\[(\\d+)([~A-Z])$`); + const csiArrow = new RegExp(`^${escPattern}\\[(A|B|C|D|H|F)$`); const numberMatch = sequence.match(csiNumber); if (numberMatch) { diff --git a/src/agents/session-slug.test.ts b/src/agents/session-slug.test.ts index 22170e5fd..917ea8ab4 100644 --- a/src/agents/session-slug.test.ts +++ b/src/agents/session-slug.test.ts @@ -20,7 +20,7 @@ describe("session slug", () => { it("falls back to three words when collisions persist", () => { vi.spyOn(Math, "random").mockReturnValue(0); - const slug = createSessionSlug((id) => id === "amber-atlas" || id === "amber-atlas-2"); + const slug = createSessionSlug((id) => /^amber-atlas(-\d+)?$/.test(id)); expect(slug).toBe("amber-atlas-atlas"); }); });