Compare commits

...

3 Commits

Author SHA1 Message Date
Kit Langton 482dae02aa fix(tui): keep completion task label concise 2026-07-12 12:54:12 +00:00
Kit Langton 41b51e1824 fix(tui): label completed subagent task 2026-07-12 12:44:22 +00:00
Kit Langton 885c13fcee fix(tui): show background subagent completion 2026-07-12 12:40:39 +00:00
3 changed files with 62 additions and 8 deletions
+16 -5
View File
@@ -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: `<subagent id="${childID}" state="${state}" description="${description}">\n${text}\n</subagent>`,
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,
+10
View File
@@ -288,6 +288,16 @@ describe("SubagentTool", () => {
const admission = Array.from(yield* Fiber.join(admitted))[0]
expect(admission?.data.input.data.text).toContain(`<subagent id="${childID}" state="completed"`)
expect(admission?.data.input.data).toMatchObject({
description: "background review",
metadata: {
source: "subagent",
childID,
agent: "reviewer",
state: "completed",
description: "background review",
},
})
const database = yield* Database.Service
yield* SessionPending.promoteSteers(database.db, events, parent.id)
const synthetic = (yield* sessions.context(parent.id)).filter((message) => message.type === "synthetic")
+36 -3
View File
@@ -1245,15 +1245,48 @@ 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 (
<InlineToolRow icon="◈" color={theme.textMuted} pending="Notice" complete={true}>
{text()}
</InlineToolRow>
<Show
when={completion()}
fallback={
<InlineToolRow icon="◈" color={theme.textMuted} pending="Notice" complete={true}>
{text()}
</InlineToolRow>
}
>
<box marginLeft={3} flexDirection="row">
<text fg={color()}>
{state() === "completed" ? "↳" : "!"} {agent()} {status()}
</text>
<text fg={theme.textMuted}> · {text()}</text>
</box>
</Show>
)
}