From 885c13fceeed5bf9991904f74b90132c94cb5f0f Mon Sep 17 00:00:00 2001
From: Kit Langton <7587245+kitlangton@users.noreply.github.com>
Date: Sun, 12 Jul 2026 12:40:39 +0000
Subject: [PATCH] fix(tui): show background subagent completion
---
packages/core/src/tool/subagent.ts | 21 ++++++++++---
packages/core/test/tool-subagent.test.ts | 10 ++++++
packages/tui/src/routes/session/index.tsx | 38 +++++++++++++++++++++--
3 files changed, 61 insertions(+), 8 deletions(-)
diff --git a/packages/core/src/tool/subagent.ts b/packages/core/src/tool/subagent.ts
index f73efd0dc7..78be0df690 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: `Background agent ${state}: ${description}`,
+ metadata: { source: "subagent", childID, agent, state, description },
})
})
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..2596e3058d 100644
--- a/packages/core/test/tool-subagent.test.ts
+++ b/packages/core/test/tool-subagent.test.ts
@@ -288,6 +288,16 @@ 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..262dc35877 100644
--- a/packages/tui/src/routes/session/index.tsx
+++ b/packages/tui/src/routes/session/index.tsx
@@ -1245,15 +1245,47 @@ function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) {
function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
const { theme } = useTheme()
+ const completion = () => props.message.type === "synthetic" && props.message.metadata?.source === "subagent"
+ const state = () => {
+ if (props.message.type !== "synthetic") return
+ return typeof props.message.metadata?.state === "string" ? props.message.metadata.state : undefined
+ }
+ const agent = () => {
+ if (props.message.type !== "synthetic") return "Subagent"
+ return typeof props.message.metadata?.agent === "string"
+ ? Locale.titlecase(props.message.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()}
+
+
+
)
}