fix(team-session-events): pin subagent_type and model on wake-hint promptAsync

Same bug class as commit 93d99d5d but on the idle-wake-hint path. When a
team member session went idle with unread mailbox messages, the wake
hint was injected via promptAsync without agent/model/variant, so every
wake turn reverted the member to the default agent (sisyphus) and
default model - exactly the same symptom user reported on live delivery.

Reads the recipient member's persisted subagent_type and model (populated
at launch time by createTeamRun) from runtime state and forwards them in
the promptAsync body. Falls back to the previous text-only payload when
the member has nothing recorded (legacy runs, lead that reused the
caller session).
This commit is contained in:
YeonGyu-Kim
2026-04-20 11:51:39 +09:00
parent 8cbae4fe0c
commit 74c0ccb344
2 changed files with 107 additions and 11 deletions

View File

@@ -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<string> {
@@ -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<WakeHintPromptInput> = []
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<WakeHintPromptInput> = []
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<WakeHintPromptInput> = []
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()

View File

@@ -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 },