fix(babysitter): avoid double prompt gate
This commit is contained in:
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -3,7 +3,6 @@ import type { PluginContext } from "./types"
|
|||||||
|
|
||||||
import { createUnstableAgentBabysitterHook } from "../hooks"
|
import { createUnstableAgentBabysitterHook } from "../hooks"
|
||||||
import type { BackgroundManager } from "../features/background-agent"
|
import type { BackgroundManager } from "../features/background-agent"
|
||||||
import { dispatchInternalPrompt } from "../hooks/shared/prompt-async-gate"
|
|
||||||
|
|
||||||
export function createUnstableAgentBabysitter(args: {
|
export function createUnstableAgentBabysitter(args: {
|
||||||
ctx: PluginContext
|
ctx: PluginContext
|
||||||
@@ -26,30 +25,8 @@ export function createUnstableAgentBabysitter(args: {
|
|||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
status: async () => ctx.client.session.status(),
|
status: async () => ctx.client.session.status(),
|
||||||
prompt: async (promptArgs) => {
|
prompt: async (promptArgs) => ctx.client.session.prompt(promptArgs),
|
||||||
const promptResult = await dispatchInternalPrompt({
|
promptAsync: async (promptArgs) => ctx.client.session.promptAsync(promptArgs),
|
||||||
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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const RAW_PROMPT_ALLOWLIST = new Map<string, string>([
|
|||||||
path.join(SOURCE_ROOT, "plugin", "build-team-idle-wake-hint-client.ts"),
|
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",
|
"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"),
|
path.join(SOURCE_ROOT, "hooks", "session-recovery", "recover-unavailable-tool.ts"),
|
||||||
"runtime type guard checks promptAsync presence before gate-routed dispatchInternalPrompt",
|
"runtime type guard checks promptAsync presence before gate-routed dispatchInternalPrompt",
|
||||||
|
|||||||
Reference in New Issue
Block a user