diff --git a/src/hooks/team-session-events/team-idle-wake-hint.test.ts b/src/hooks/team-session-events/team-idle-wake-hint.test.ts index 0a2d14601..80fd1a4f0 100644 --- a/src/hooks/team-session-events/team-idle-wake-hint.test.ts +++ b/src/hooks/team-session-events/team-idle-wake-hint.test.ts @@ -15,6 +15,17 @@ import { loadRuntimeState, saveRuntimeState } from "../../features/team-mode/tea import type { RuntimeState } from "../../features/team-mode/types" import { createTeamIdleWakeHint } from "./team-idle-wake-hint" +type WakeHintPromptInput = { + path: { id: string } + body: { + parts: Array<{ type: "text"; text: string }> + agent?: string + model?: { providerID: string; modelID: string } + variant?: string + } + query: { directory: string } +} + const temporaryDirectories: string[] = [] async function createTemporaryBaseDir(): Promise { @@ -95,16 +106,8 @@ describe("createTeamIdleWakeHint", () => { await seedUnreadMessage(teamRunId, config, randomUUID(), "first message body", 100) await seedUnreadMessage(teamRunId, config, randomUUID(), "second message body", 200) - const promptInputs: Array<{ - path: { id: string } - body: { parts: Array<{ type: "text"; text: string }> } - query: { directory: string } - }> = [] - const promptAsyncSpy = mock(async (input: { - path: { id: string } - body: { parts: Array<{ type: "text"; text: string }> } - query: { directory: string } - }) => { + const promptInputs: Array = [] + const promptAsyncSpy = mock(async (input: WakeHintPromptInput) => { promptInputs.push(input) return {} }) @@ -133,6 +136,85 @@ describe("createTeamIdleWakeHint", () => { expect(promptInput.body.parts[0]?.text).not.toContain("second message body") }) + test("pins the recipient's resolved subagent_type and model on the wake-hint promptAsync", async () => { + // given + const baseDir = await createTemporaryBaseDir() + const config = createConfig(baseDir) + const teamRunId = randomUUID() + const runtimeState = createRuntimeState(teamRunId) + const worker = runtimeState.members[0] + if (!worker) throw new Error("worker member missing from fixture") + worker.subagent_type = "atlas" + worker.model = { providerID: "anthropic", modelID: "claude-opus-4-7", variant: "high" } + await seedRuntimeState(runtimeState, config) + await seedUnreadMessage(teamRunId, config, randomUUID(), "hello", 100) + + const promptInputs: Array = [] + const promptAsyncSpy = mock(async (input: WakeHintPromptInput) => { + promptInputs.push(input) + return {} + }) + const handler = createTeamIdleWakeHint({ + directory: "/tmp/project", + client: { session: { promptAsync: promptAsyncSpy } }, + }, config) + + // when + await handler({ + event: { + type: "session.idle", + properties: { sessionID: "member-session" }, + }, + }) + + // then + expect(promptAsyncSpy).toHaveBeenCalledTimes(1) + const promptInput = promptInputs[0] + if (promptInput === undefined) { + throw new Error("expected wake hint prompt input") + } + expect(promptInput.body.agent).toBe("atlas") + expect(promptInput.body.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" }) + expect(promptInput.body.variant).toBe("high") + }) + + test("omits agent and model on the wake-hint promptAsync when the member has none recorded", async () => { + // given + const baseDir = await createTemporaryBaseDir() + const config = createConfig(baseDir) + const teamRunId = randomUUID() + await seedRuntimeState(createRuntimeState(teamRunId), config) + await seedUnreadMessage(teamRunId, config, randomUUID(), "hello", 100) + + const promptInputs: Array = [] + const promptAsyncSpy = mock(async (input: WakeHintPromptInput) => { + promptInputs.push(input) + return {} + }) + const handler = createTeamIdleWakeHint({ + directory: "/tmp/project", + client: { session: { promptAsync: promptAsyncSpy } }, + }, config) + + // when + await handler({ + event: { + type: "session.idle", + properties: { sessionID: "member-session" }, + }, + }) + + // then + expect(promptAsyncSpy).toHaveBeenCalledTimes(1) + const promptInput = promptInputs[0] + if (promptInput === undefined) { + throw new Error("expected wake hint prompt input") + } + expect(promptInput.body.agent).toBeUndefined() + expect(promptInput.body.model).toBeUndefined() + expect(promptInput.body.variant).toBeUndefined() + }) + test("acks pending messages on idle, moves files to processed, and clears pending ids", async () => { // given const baseDir = await createTemporaryBaseDir() diff --git a/src/hooks/team-session-events/team-idle-wake-hint.ts b/src/hooks/team-session-events/team-idle-wake-hint.ts index 7db603803..5e44b0ce6 100644 --- a/src/hooks/team-session-events/team-idle-wake-hint.ts +++ b/src/hooks/team-session-events/team-idle-wake-hint.ts @@ -6,7 +6,12 @@ import { log } from "../../shared/logger" type PromptAsyncInput = { path: { id: string } - body: { parts: Array<{ type: "text"; text: string }> } + body: { + parts: Array<{ type: "text"; text: string }> + agent?: string + model?: { providerID: string; modelID: string } + variant?: string + } query: { directory: string } } @@ -83,9 +88,18 @@ export function createTeamIdleWakeHint(ctx: TeamIdleWakeHintContext, config: Tea return } + const memberAgent = memberEntry.subagent_type + const memberModel = memberEntry.model + ? { providerID: memberEntry.model.providerID, modelID: memberEntry.model.modelID } + : undefined + const memberVariant = memberEntry.model?.variant + await ctx.client.session.promptAsync({ path: { id: sessionID }, body: { + ...(memberAgent ? { agent: memberAgent } : {}), + ...(memberModel ? { model: memberModel } : {}), + ...(memberVariant ? { variant: memberVariant } : {}), parts: [{ type: "text", text: buildWakeHint(unreadMessages.length) }], }, query: { directory: ctx.directory },