mirror of
https://mirror.skon.top/github.com/code-yeongyu/oh-my-opencode
synced 2026-05-01 03:59:23 +08:00
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:
@@ -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()
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user