fix(babysitter): avoid double prompt gate

This commit is contained in:
YeonGyu-Kim
2026-05-19 13:48:55 +09:00
parent 44ef5dec1d
commit bb75751410
3 changed files with 92 additions and 25 deletions
@@ -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)
})
})
+2 -25
View File
@@ -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),
},
},
},
@@ -14,6 +14,10 @@ const RAW_PROMPT_ALLOWLIST = new Map<string, string>([
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",