diff --git a/packages/core/src/tool/subagent.ts b/packages/core/src/tool/subagent.ts index f73efd0dc7..9b7015935a 100644 --- a/packages/core/src/tool/subagent.ts +++ b/packages/core/src/tool/subagent.ts @@ -65,6 +65,7 @@ export const Plugin = { const injectCompletion = Effect.fn("SubagentTool.injectCompletion")(function* ( parentID: SessionSchema.ID, childID: SessionSchema.ID, + agent: string, description: string, state: "completed" | "error" | "cancelled", text: string, @@ -72,22 +73,32 @@ export const Plugin = { yield* runtime.session.synthetic({ sessionID: parentID, text: `\n${text}\n`, + description, + metadata: { source: "subagent", childID, agent, state }, }) }) const notifyWhenDone = Effect.fn("SubagentTool.notifyWhenDone")(function* ( parentID: SessionSchema.ID, childID: SessionSchema.ID, + agent: string, description: string, ) { yield* runtime.job.wait({ id: childID }).pipe( Effect.flatMap((result) => { if (result.info?.status === "completed") - return injectCompletion(parentID, childID, description, "completed", result.info.output ?? NO_TEXT) + return injectCompletion(parentID, childID, agent, description, "completed", result.info.output ?? NO_TEXT) if (result.info?.status === "error") - return injectCompletion(parentID, childID, description, "error", result.info.error ?? "Subagent failed") + return injectCompletion( + parentID, + childID, + agent, + description, + "error", + result.info.error ?? "Subagent failed", + ) if (result.info?.status === "cancelled") - return injectCompletion(parentID, childID, description, "cancelled", "Subagent cancelled") + return injectCompletion(parentID, childID, agent, description, "cancelled", "Subagent cancelled") return Effect.void }), Effect.forkIn(scope, { startImmediately: true }), @@ -167,7 +178,7 @@ export const Plugin = { if (background) { yield* runtime.job.background(info.id) - yield* notifyWhenDone(context.sessionID, child.id, input.description) + yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description) return { sessionID: child.id, status: "running" as const, @@ -183,7 +194,7 @@ export const Plugin = { ), ) if (result?.type === "backgrounded") { - yield* notifyWhenDone(context.sessionID, child.id, input.description) + yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description) return { sessionID: child.id, status: "running" as const, diff --git a/packages/core/test/tool-subagent.test.ts b/packages/core/test/tool-subagent.test.ts index 284d4e6fd0..e0250065c5 100644 --- a/packages/core/test/tool-subagent.test.ts +++ b/packages/core/test/tool-subagent.test.ts @@ -288,6 +288,15 @@ describe("SubagentTool", () => { const admission = Array.from(yield* Fiber.join(admitted))[0] expect(admission?.data.input.data.text).toContain(` message.type === "synthetic") diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx index b24526a8aa..5e6fc8eaf3 100644 --- a/packages/tui/src/routes/session/index.tsx +++ b/packages/tui/src/routes/session/index.tsx @@ -1245,15 +1245,43 @@ function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) { function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) { const { theme } = useTheme() + const metadata = () => (props.message.type === "synthetic" ? props.message.metadata : undefined) + const completion = () => metadata()?.source === "subagent" + const state = () => stringValue(metadata()?.state) + const agent = () => Locale.titlecase(stringValue(metadata()?.agent) ?? "Subagent") const text = () => { if (props.message.type === "system") return props.message.text if (props.message.type === "synthetic") return props.message.description ?? "" return "" } + const status = () => { + if (state() === "completed") return "finished" + if (state() === "error") return "failed" + return state() ?? "finished" + } + const color = () => { + if (state() === "error") return theme.error + if (state() === "cancelled") return theme.warning + return theme.info + } return ( - - {text()} - + + {text()} + + } + > + + + + {state() === "completed" ? "↳" : "!"} {agent()} {status()} + + · {text()} + + + ) }