fix(tui): show background subagent completion
This commit is contained in:
committed by
opencode-agent[bot]
parent
02e2277057
commit
885c13fcee
@@ -65,6 +65,7 @@ export const Plugin = {
|
|||||||
const injectCompletion = Effect.fn("SubagentTool.injectCompletion")(function* (
|
const injectCompletion = Effect.fn("SubagentTool.injectCompletion")(function* (
|
||||||
parentID: SessionSchema.ID,
|
parentID: SessionSchema.ID,
|
||||||
childID: SessionSchema.ID,
|
childID: SessionSchema.ID,
|
||||||
|
agent: string,
|
||||||
description: string,
|
description: string,
|
||||||
state: "completed" | "error" | "cancelled",
|
state: "completed" | "error" | "cancelled",
|
||||||
text: string,
|
text: string,
|
||||||
@@ -72,22 +73,32 @@ export const Plugin = {
|
|||||||
yield* runtime.session.synthetic({
|
yield* runtime.session.synthetic({
|
||||||
sessionID: parentID,
|
sessionID: parentID,
|
||||||
text: `<subagent id="${childID}" state="${state}" description="${description}">\n${text}\n</subagent>`,
|
text: `<subagent id="${childID}" state="${state}" description="${description}">\n${text}\n</subagent>`,
|
||||||
|
description: `Background agent ${state}: ${description}`,
|
||||||
|
metadata: { source: "subagent", childID, agent, state, description },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const notifyWhenDone = Effect.fn("SubagentTool.notifyWhenDone")(function* (
|
const notifyWhenDone = Effect.fn("SubagentTool.notifyWhenDone")(function* (
|
||||||
parentID: SessionSchema.ID,
|
parentID: SessionSchema.ID,
|
||||||
childID: SessionSchema.ID,
|
childID: SessionSchema.ID,
|
||||||
|
agent: string,
|
||||||
description: string,
|
description: string,
|
||||||
) {
|
) {
|
||||||
yield* runtime.job.wait({ id: childID }).pipe(
|
yield* runtime.job.wait({ id: childID }).pipe(
|
||||||
Effect.flatMap((result) => {
|
Effect.flatMap((result) => {
|
||||||
if (result.info?.status === "completed")
|
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")
|
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")
|
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
|
return Effect.void
|
||||||
}),
|
}),
|
||||||
Effect.forkIn(scope, { startImmediately: true }),
|
Effect.forkIn(scope, { startImmediately: true }),
|
||||||
@@ -167,7 +178,7 @@ export const Plugin = {
|
|||||||
|
|
||||||
if (background) {
|
if (background) {
|
||||||
yield* runtime.job.background(info.id)
|
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 {
|
return {
|
||||||
sessionID: child.id,
|
sessionID: child.id,
|
||||||
status: "running" as const,
|
status: "running" as const,
|
||||||
@@ -183,7 +194,7 @@ export const Plugin = {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (result?.type === "backgrounded") {
|
if (result?.type === "backgrounded") {
|
||||||
yield* notifyWhenDone(context.sessionID, child.id, input.description)
|
yield* notifyWhenDone(context.sessionID, child.id, agent.name, input.description)
|
||||||
return {
|
return {
|
||||||
sessionID: child.id,
|
sessionID: child.id,
|
||||||
status: "running" as const,
|
status: "running" as const,
|
||||||
|
|||||||
@@ -288,6 +288,16 @@ describe("SubagentTool", () => {
|
|||||||
|
|
||||||
const admission = Array.from(yield* Fiber.join(admitted))[0]
|
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.text).toContain(`<subagent id="${childID}" state="completed"`)
|
||||||
|
expect(admission?.data.input.data).toMatchObject({
|
||||||
|
description: "Background agent completed: background review",
|
||||||
|
metadata: {
|
||||||
|
source: "subagent",
|
||||||
|
childID,
|
||||||
|
agent: "reviewer",
|
||||||
|
state: "completed",
|
||||||
|
description: "background review",
|
||||||
|
},
|
||||||
|
})
|
||||||
const database = yield* Database.Service
|
const database = yield* Database.Service
|
||||||
yield* SessionPending.promoteSteers(database.db, events, parent.id)
|
yield* SessionPending.promoteSteers(database.db, events, parent.id)
|
||||||
const synthetic = (yield* sessions.context(parent.id)).filter((message) => message.type === "synthetic")
|
const synthetic = (yield* sessions.context(parent.id)).filter((message) => message.type === "synthetic")
|
||||||
|
|||||||
@@ -1245,15 +1245,47 @@ function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) {
|
|||||||
|
|
||||||
function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
||||||
const { theme } = useTheme()
|
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 = () => {
|
const text = () => {
|
||||||
if (props.message.type === "system") return props.message.text
|
if (props.message.type === "system") return props.message.text
|
||||||
if (props.message.type === "synthetic") return props.message.description ?? ""
|
if (props.message.type === "synthetic") return props.message.description ?? ""
|
||||||
return ""
|
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 (
|
return (
|
||||||
<InlineToolRow icon="◈" color={theme.textMuted} pending="Notice" complete={true}>
|
<Show
|
||||||
{text()}
|
when={completion()}
|
||||||
</InlineToolRow>
|
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>
|
||||||
|
</box>
|
||||||
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user