From e6222529e7f695c8ac55e520996048d8c51e2878 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 4 Mar 2026 10:48:30 +0530 Subject: [PATCH] refine background task ux and auto-resume --- .../src/cli/cmd/tui/routes/session/index.tsx | 9 +-- packages/opencode/src/tool/task.ts | 70 +++++++++++++++++-- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 4cfa1d0bf1..56e6b46cab 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -1967,11 +1967,7 @@ function Task(props: ToolProps) { 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 ( @@ -1989,9 +1985,6 @@ function Task(props: ToolProps) { {props.input.description} ({toolLabel()}) - - ยท background - diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 087c71df3c..87ff4ade9d 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -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,