From 4dc5b0e44fa6a05e5bf3c2ac00daf79ab9b70ffe Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 4 Mar 2026 08:52:19 -0500 Subject: [PATCH] test(process): cover attach and input-wait hints --- src/agents/bash-tools.process.attach.test.ts | 127 +++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/agents/bash-tools.process.attach.test.ts diff --git a/src/agents/bash-tools.process.attach.test.ts b/src/agents/bash-tools.process.attach.test.ts new file mode 100644 index 00000000000..0c90e046e81 --- /dev/null +++ b/src/agents/bash-tools.process.attach.test.ts @@ -0,0 +1,127 @@ +import { afterEach, expect, test, vi } from "vitest"; +import { addSession, appendOutput, resetProcessRegistryForTests } from "./bash-process-registry.js"; +import { createProcessSessionFixture } from "./bash-process-registry.test-helpers.js"; +import { createProcessTool } from "./bash-tools.process.js"; + +afterEach(() => { + resetProcessRegistryForTests(); +}); + +function readText(result: Awaited["execute"]>>) { + return result.content.find((part) => part.type === "text")?.text ?? ""; +} + +test("process attach surfaces input-wait hints for idle interactive sessions", async () => { + vi.useFakeTimers(); + try { + const now = new Date("2026-01-01T00:00:00.000Z").getTime(); + vi.setSystemTime(now); + + const processTool = createProcessTool({ inputWaitIdleMs: 2_000 }); + const session = createProcessSessionFixture({ + id: "sess-attach", + command: "expo login", + backgrounded: true, + startedAt: now - 10_000, + }); + session.stdin = { write: () => {}, end: () => {}, destroyed: false }; + addSession(session); + appendOutput(session, "stdout", "Enter 2FA code:\n"); + + const result = await processTool.execute("toolcall", { + action: "attach", + sessionId: session.id, + }); + const details = result.details as { + status?: string; + waitingForInput?: boolean; + stdinWritable?: boolean; + idleMs?: number; + }; + const text = readText(result); + + expect(details.status).toBe("running"); + expect(details.waitingForInput).toBe(true); + expect(details.stdinWritable).toBe(true); + expect(details.idleMs).toBeGreaterThanOrEqual(2_000); + expect(text).toContain("Enter 2FA code:"); + expect(text).toContain("may be waiting for input"); + expect(text).toContain("process write"); + } finally { + vi.useRealTimers(); + } +}); + +test("process poll returns waiting-for-input metadata when no new output arrives", async () => { + vi.useFakeTimers(); + try { + const now = new Date("2026-01-01T00:00:00.000Z").getTime(); + vi.setSystemTime(now); + + const processTool = createProcessTool({ inputWaitIdleMs: 2_000 }); + const session = createProcessSessionFixture({ + id: "sess-poll-wait", + command: "expo login", + backgrounded: true, + startedAt: now - 10_000, + }); + session.stdin = { write: () => {}, end: () => {}, destroyed: false }; + addSession(session); + + const poll = await processTool.execute("toolcall", { + action: "poll", + sessionId: session.id, + }); + const details = poll.details as { + status?: string; + waitingForInput?: boolean; + stdinWritable?: boolean; + idleMs?: number; + }; + const text = readText(poll); + + expect(details.status).toBe("running"); + expect(details.waitingForInput).toBe(true); + expect(details.stdinWritable).toBe(true); + expect(details.idleMs).toBeGreaterThanOrEqual(2_000); + expect(text).toContain("(no new output)"); + expect(text).toContain("may be waiting for input"); + } finally { + vi.useRealTimers(); + } +}); + +test("process list marks idle interactive sessions as input-wait", async () => { + vi.useFakeTimers(); + try { + const now = new Date("2026-01-01T00:00:00.000Z").getTime(); + vi.setSystemTime(now); + + const processTool = createProcessTool({ inputWaitIdleMs: 2_000 }); + const session = createProcessSessionFixture({ + id: "sess-list-wait", + command: "expo login", + backgrounded: true, + startedAt: now - 10_000, + }); + session.stdin = { write: () => {}, end: () => {}, destroyed: false }; + addSession(session); + + const list = await processTool.execute("toolcall", { action: "list" }); + const details = list.details as { + sessions?: Array<{ + sessionId: string; + waitingForInput?: boolean; + stdinWritable?: boolean; + }>; + }; + const text = readText(list); + const listed = details.sessions?.find((item) => item.sessionId === session.id); + + expect(listed?.waitingForInput).toBe(true); + expect(listed?.stdinWritable).toBe(true); + expect(text).toContain("[input-wait]"); + } finally { + vi.useRealTimers(); + } +});