From bb757514103989b3df52b53da33b7d773a4413bc Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 19 May 2026 13:48:55 +0900 Subject: [PATCH] fix(babysitter): avoid double prompt gate --- src/plugin/unstable-agent-babysitter.test.ts | 86 ++++++++++++++++++++ src/plugin/unstable-agent-babysitter.ts | 27 +----- src/shared/prompt-async-route-audit.test.ts | 4 + 3 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 src/plugin/unstable-agent-babysitter.test.ts diff --git a/src/plugin/unstable-agent-babysitter.test.ts b/src/plugin/unstable-agent-babysitter.test.ts new file mode 100644 index 000000000..69b8d23be --- /dev/null +++ b/src/plugin/unstable-agent-babysitter.test.ts @@ -0,0 +1,86 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { createUnstableAgentBabysitter } from "./unstable-agent-babysitter" +import type { BackgroundTask } from "../features/background-agent" +import { _resetForTesting, setMainSession } from "../features/claude-code-session-state" +import { releaseAllPromptAsyncReservationsForTesting } from "../hooks/shared/prompt-async-gate" +import { unsafeTestValue } from "../../test-support/unsafe-test-value" + +function createTask(): BackgroundTask { + return { + id: "task-1", + sessionId: "bg-1", + parentSessionId: "main-1", + parentMessageId: "msg-1", + description: "unstable task", + prompt: "run work", + agent: "test-agent", + status: "running", + progress: { + toolCalls: 1, + lastUpdate: new Date(Date.now() - 121000), + lastMessage: "still working", + lastMessageAt: new Date(Date.now() - 121000), + }, + model: { providerID: "google", modelID: "gemini-1.5" }, + } +} + +describe("createUnstableAgentBabysitter", () => { + afterEach(() => { + _resetForTesting() + releaseAllPromptAsyncReservationsForTesting() + }) + + test("#given wrapper-created babysitter hook #when injecting a stale-task reminder #then the real SDK promptAsync is called once", async () => { + // given + setMainSession("main-1") + const promptAsyncCalls: unknown[] = [] + const babysitter = createUnstableAgentBabysitter({ + ctx: unsafeTestValue({ + directory: process.cwd(), + client: { + session: { + messages: async ({ path }: { path: { id: string } }) => ({ + data: path.id === "main-1" + ? [ + { + info: { + role: "assistant", + agent: "sisyphus", + model: { providerID: "openai", modelID: "gpt-4" }, + }, + }, + ] + : [ + { + info: { role: "assistant" }, + parts: [{ type: "thinking", thinking: "deep thought" }], + }, + ], + }), + status: async () => ({ data: { "main-1": { type: "idle" } } }), + promptAsync: async (input: unknown) => { + promptAsyncCalls.push(input) + return {} + }, + }, + }, + }), + backgroundManager: unsafeTestValue({ + getTasksByParentSession: () => [createTask()], + }), + pluginConfig: unsafeTestValue({ babysitting: { timeout_ms: 120000 } }), + }) + + // when + await babysitter.event({ + event: { + type: "session.idle", + properties: { sessionID: "main-1" }, + }, + }) + + // then + expect(promptAsyncCalls).toHaveLength(1) + }) +}) diff --git a/src/plugin/unstable-agent-babysitter.ts b/src/plugin/unstable-agent-babysitter.ts index 5cfe2d555..ee1f32ca1 100644 --- a/src/plugin/unstable-agent-babysitter.ts +++ b/src/plugin/unstable-agent-babysitter.ts @@ -3,7 +3,6 @@ import type { PluginContext } from "./types" import { createUnstableAgentBabysitterHook } from "../hooks" import type { BackgroundManager } from "../features/background-agent" -import { dispatchInternalPrompt } from "../hooks/shared/prompt-async-gate" export function createUnstableAgentBabysitter(args: { ctx: PluginContext @@ -26,30 +25,8 @@ export function createUnstableAgentBabysitter(args: { return [] }, status: async () => ctx.client.session.status(), - prompt: async (promptArgs) => { - const promptResult = await dispatchInternalPrompt({ - mode: "async", - client: ctx.client, - sessionID: promptArgs.path.id, - source: "unstable-agent-babysitter", - input: promptArgs, - }) - if (promptResult.status === "failed") { - throw promptResult.error - } - }, - promptAsync: async (promptArgs) => { - const promptResult = await dispatchInternalPrompt({ - mode: "async", - client: ctx.client, - sessionID: promptArgs.path.id, - source: "unstable-agent-babysitter", - input: promptArgs, - }) - if (promptResult.status === "failed") { - throw promptResult.error - } - }, + prompt: async (promptArgs) => ctx.client.session.prompt(promptArgs), + promptAsync: async (promptArgs) => ctx.client.session.promptAsync(promptArgs), }, }, }, diff --git a/src/shared/prompt-async-route-audit.test.ts b/src/shared/prompt-async-route-audit.test.ts index 933f29197..dab7324bd 100644 --- a/src/shared/prompt-async-route-audit.test.ts +++ b/src/shared/prompt-async-route-audit.test.ts @@ -14,6 +14,10 @@ const RAW_PROMPT_ALLOWLIST = new Map([ path.join(SOURCE_ROOT, "plugin", "build-team-idle-wake-hint-client.ts"), "binds SDK Session.promptAsync/.status into a narrow facade consumed only by gate-routed team-idle-wake-hint dispatch; performs no direct dispatch itself", ], + [ + path.join(SOURCE_ROOT, "plugin", "unstable-agent-babysitter.ts"), + "binds SDK Session.prompt/.promptAsync into a narrow facade consumed only by gate-routed unstable-agent-babysitter dispatch; performs no direct dispatch itself", + ], [ path.join(SOURCE_ROOT, "hooks", "session-recovery", "recover-unavailable-tool.ts"), "runtime type guard checks promptAsync presence before gate-routed dispatchInternalPrompt",