refine background task ux and auto-resume

This commit is contained in:
Shoubhit Dash 2026-03-04 10:48:30 +05:30
commit e6222529e7
2 changed files with 65 additions and 14 deletions

View file

@ -1967,11 +1967,7 @@ function Task(props: ToolProps<typeof TaskTool>) {
return !!msgs().findLast((msg) => msg.role === "assistant")?.error
})
const isRunning = createMemo(() => props.part.state.status === "running" || childRunning())
const toolLabel = createMemo(() => {
const total = counts().all
if (!childRunning()) return `${total} toolcalls`
return `${counts().done}/${total} toolcalls`
})
const toolLabel = createMemo(() => `${childRunning() ? counts().done : counts().all} toolcalls`)
return (
<Switch>
@ -1989,9 +1985,6 @@ function Task(props: ToolProps<typeof TaskTool>) {
<box>
<text style={{ fg: theme.textMuted }}>
{props.input.description} ({toolLabel()})
<Show when={background()}>
<span> · background</span>
</Show>
</text>
<Show when={background()}>
<text style={{ fg: failed() ? theme.error : backgroundRunning() ? theme.warning : theme.textMuted }}>

View file

@ -6,6 +6,7 @@ import { MessageV2 } from "../session/message-v2"
import { Identifier } from "../id/id"
import { Agent } from "../agent/agent"
import { SessionPrompt } from "../session/prompt"
import { SessionStatus } from "../session/status"
import { iife } from "@/util/iife"
import { defer } from "@/util/defer"
import { Config } from "../config/config"
@ -68,6 +69,47 @@ function errorText(error: unknown) {
return String(error)
}
function resultTaskID(input: unknown) {
if (!input || typeof input !== "object") return
const taskID = Reflect.get(input, "task_id")
if (typeof taskID === "string") return taskID
}
function polled(input: { message: MessageV2.WithParts; taskID: string }) {
if (input.message.info.role !== "assistant") return false
return input.message.parts.some((part) => {
if (part.type !== "tool") return false
if (part.tool !== "task_status") return false
if (part.state.status !== "completed") return false
return resultTaskID(part.state.input) === input.taskID
})
}
async function latestUser(sessionID: string) {
const [message] = await Session.messages({
sessionID,
limit: 1,
})
if (!message) return
if (message.info.role !== "user") return
return message.info.id
}
async function continueParent(input: { parentID: string; userID: string; taskID: string }) {
const message =
SessionStatus.get(input.parentID).type === "idle"
? undefined
: await SessionPrompt.loop({
sessionID: input.parentID,
}).catch(() => undefined)
if (message && polled({ message, taskID: input.taskID })) return
if (SessionStatus.get(input.parentID).type !== "idle") return
if ((await latestUser(input.parentID)) !== input.userID) return
await SessionPrompt.loop({
sessionID: input.parentID,
})
}
export const TaskTool = Tool.define("task", async (ctx) => {
const agents = await Agent.list().then((x) => x.filter((a) => a.mode !== "primary"))
@ -210,12 +252,28 @@ export const TaskTool = Tool.define("task", async (ctx) => {
})
void run()
.then((text) => {
void inject("completed", text).catch(() => {})
})
.catch((error) => {
void inject("error", errorText(error)).catch(() => {})
})
.then((text) =>
inject("completed", text)
.then((message) =>
continueParent({
parentID: ctx.sessionID,
userID: message.info.id,
taskID: session.id,
}),
)
.catch(() => {}),
)
.catch((error) =>
inject("error", errorText(error))
.then((message) =>
continueParent({
parentID: ctx.sessionID,
userID: message.info.id,
taskID: session.id,
}),
)
.catch(() => {}),
)
return {
title: params.description,