diff --git a/.opencode/background-tasks.json b/.opencode/background-tasks.json deleted file mode 100644 index 92e412a..0000000 --- a/.opencode/background-tasks.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "id": "bg_wzsdt60b", - "sessionID": "ses_4f3e89f0dffeooeXNVx5QCifse", - "parentSessionID": "ses_4f3e8d141ffeyfJ1taVVOdQTzx", - "parentMessageID": "msg_b0c172ee1001w2B52VSZrP08PJ", - "description": "Explore opencode in codebase", - "agent": "explore", - "status": "completed", - "startedAt": "2025-12-11T06:26:57.395Z", - "completedAt": "2025-12-11T06:27:36.778Z" - }, - { - "id": "bg_392b9c9b", - "sessionID": "ses_4f38ebf4fffeJZBocIn3UVv7vE", - "parentSessionID": "ses_4f38eefa0ffeKV0pVNnwT37P5L", - "parentMessageID": "msg_b0c7110d2001TMBlPeEYIrByvs", - "description": "Test explore agent", - "agent": "explore", - "status": "running", - "startedAt": "2025-12-11T08:05:07.378Z", - "progress": { - "toolCalls": 0, - "lastUpdate": "2025-12-11T08:05:07.378Z" - } - } -] \ No newline at end of file diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 2ad856b..075c5ff 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -232,7 +232,7 @@ export class BackgroundManager { }).catch(() => {}) } - const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration} (${toolCalls} tool calls). Use background_result with taskId="${task.id}" to get results.` + const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration} (${toolCalls} tool calls). Use background_output with task_id="${task.id}" to get results.` const mainSessionID = getMainSessionID() if (!mainSessionID) { diff --git a/src/hooks/background-notification/index.ts b/src/hooks/background-notification/index.ts index 1a5d26b..397d28d 100644 --- a/src/hooks/background-notification/index.ts +++ b/src/hooks/background-notification/index.ts @@ -40,7 +40,7 @@ function formatNotifications(tasks: BackgroundTask[]): string { } if (tasks.length > 1) { - let message = `✅ **Background Tasks Complete (${tasks.length})**\n\n` + let message = `**Background Tasks Complete (${tasks.length})**\n\n` for (const task of tasks) { const duration = formatDuration(task.startedAt, task.completedAt) @@ -50,7 +50,7 @@ function formatNotifications(tasks: BackgroundTask[]): string { message += ` Duration: ${duration} | Tool calls: ${toolCalls}\n\n` } - message += `Use \`background_result\` tool to retrieve results.` + message += `Use \`background_output\` tool to retrieve results.` return message } @@ -59,14 +59,14 @@ function formatNotifications(tasks: BackgroundTask[]): string { const duration = formatDuration(task.startedAt, task.completedAt) const toolCalls = task.progress?.toolCalls ?? 0 - return `✅ **Background Task Complete** + return `**Background Task Complete** **Task ID:** ${task.id} **Description:** ${task.description} **Duration:** ${duration} **Tool calls:** ${toolCalls} -The background task has finished. Use \`background_result\` tool with task ID \`${task.id}\` to retrieve the full result.` +Use \`background_output\` tool with task_id="${task.id}" to retrieve the full result.` } export function createBackgroundNotificationHook(manager: BackgroundManager) { diff --git a/src/tools/background-task/constants.ts b/src/tools/background-task/constants.ts index f1e692e..019775b 100644 --- a/src/tools/background-task/constants.ts +++ b/src/tools/background-task/constants.ts @@ -11,37 +11,24 @@ Arguments: - description: Short task description (shown in status) - prompt: Full detailed prompt for the agent - agent: Agent type to use (any agent allowed) -- session_id: Optional parent session ID (auto-detected if omitted) -Returns immediately with task ID and session info. Use \`background_status\` to check progress.` +Returns immediately with task ID and session info. Use \`background_output\` to check progress or retrieve results.` -export const BACKGROUND_STATUS_DESCRIPTION = `Check the status of background tasks. - -If taskId is provided, returns status for that specific task. -If taskId is omitted, returns status for all tasks in the current session. - -Status includes: -- Task description -- Current status (pending/running/completed/error/cancelled) -- Duration -- Number of tool calls made -- Last tool used +export const BACKGROUND_OUTPUT_DESCRIPTION = `Get output from a background task. Arguments: -- taskId: Optional task ID. If omitted, lists all tasks for current session.` +- task_id: Required task ID to get output from +- block: If true (default), wait for task completion. If false, return current status immediately. +- timeout: Max wait time in ms when blocking (default: 60000, max: 600000) -export const BACKGROUND_RESULT_DESCRIPTION = `Retrieve the result of a completed background task. +Returns: +- When blocking: Waits for completion, then returns full result +- When not blocking: Returns current status and progress -Only works for tasks with status "completed". For running tasks, use \`background_status\` to check progress. - -Returns the full assistant output from the background session, including: -- Task description -- Duration -- Complete response content -- Session ID for reference - -Arguments: -- taskId: Required task ID to retrieve result for.` +Use this to: +- Check task progress (block=false) +- Wait for and retrieve task result (block=true) +- Set custom timeout for long tasks` export const BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task. diff --git a/src/tools/background-task/index.ts b/src/tools/background-task/index.ts index 5c869a4..22324f8 100644 --- a/src/tools/background-task/index.ts +++ b/src/tools/background-task/index.ts @@ -1,7 +1,6 @@ export { createBackgroundTask, - createBackgroundStatus, - createBackgroundResult, + createBackgroundOutput, createBackgroundCancel, } from "./tools" diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts index 837e967..52b978e 100644 --- a/src/tools/background-task/tools.ts +++ b/src/tools/background-task/tools.ts @@ -1,7 +1,7 @@ import { tool, type PluginInput } from "@opencode-ai/plugin" -import type { BackgroundManager } from "../../features/background-agent" -import type { BackgroundTaskArgs, BackgroundStatusArgs, BackgroundResultArgs, BackgroundCancelArgs } from "./types" -import { BACKGROUND_TASK_DESCRIPTION, BACKGROUND_STATUS_DESCRIPTION, BACKGROUND_RESULT_DESCRIPTION, BACKGROUND_CANCEL_DESCRIPTION } from "./constants" +import type { BackgroundManager, BackgroundTask } from "../../features/background-agent" +import type { BackgroundTaskArgs, BackgroundOutputArgs, BackgroundCancelArgs } from "./types" +import { BACKGROUND_TASK_DESCRIPTION, BACKGROUND_OUTPUT_DESCRIPTION, BACKGROUND_CANCEL_DESCRIPTION } from "./constants" type OpencodeClient = PluginInput["client"] @@ -38,7 +38,7 @@ export function createBackgroundTask(manager: BackgroundManager) { parentMessageID: toolContext.messageID, }) - return `✅ Background task launched successfully! + return `Background task launched successfully. Task ID: ${task.id} Session ID: ${task.sessionID} @@ -46,8 +46,9 @@ Description: ${task.description} Agent: ${task.agent} Status: ${task.status} -Use \`background_status\` tool to check progress. -Use \`background_result\` tool to retrieve results when complete.` +Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results. +- block=false: Check status without waiting +- block=true (default): Wait for completion and get result` } catch (error) { const message = error instanceof Error ? error.message : String(error) return `❌ Failed to launch background task: ${message}` @@ -56,26 +57,17 @@ Use \`background_result\` tool to retrieve results when complete.` }) } -export function createBackgroundStatus(manager: BackgroundManager) { - return tool({ - description: BACKGROUND_STATUS_DESCRIPTION, - args: { - taskId: tool.schema.string().optional().describe("Task ID to check. If omitted, lists all tasks for current session."), - }, - async execute(args: BackgroundStatusArgs, toolContext) { - try { - if (args.taskId) { - const task = manager.getTask(args.taskId) - if (!task) { - return `❌ Task not found: ${args.taskId}` - } +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} - const duration = formatDuration(task.startedAt, task.completedAt) - const progress = task.progress - ? `\nTool calls: ${task.progress.toolCalls}\nLast tool: ${task.progress.lastTool ?? "N/A"}` - : "" +function formatTaskStatus(task: BackgroundTask): string { + const duration = formatDuration(task.startedAt, task.completedAt) + const progress = task.progress + ? `\nTool calls: ${task.progress.toolCalls}\nLast tool: ${task.progress.lastTool ?? "N/A"}` + : "" - return `📊 Task Status + return `Task Status Task ID: ${task.id} Description: ${task.description} @@ -84,83 +76,31 @@ Status: ${task.status} Duration: ${duration}${progress} Session ID: ${task.sessionID}` - } else { - const tasks = manager.getTasksByParentSession(toolContext.sessionID) - - if (tasks.length === 0) { - return "No background tasks found for this session." - } - - let output = `📊 Background Tasks (${tasks.length})\n\n` - - for (const task of tasks) { - const duration = formatDuration(task.startedAt, task.completedAt) - const progress = task.progress ? ` | ${task.progress.toolCalls} tools` : "" - - output += `• ${task.id} - ${task.status} (${duration}${progress})\n` - output += ` ${task.description}\n\n` - } - - return output - } - } catch (error) { - return `❌ Error checking status: ${error instanceof Error ? error.message : String(error)}` - } - }, - }) } -export function createBackgroundResult(manager: BackgroundManager, client: OpencodeClient) { - return tool({ - description: BACKGROUND_RESULT_DESCRIPTION, - args: { - taskId: tool.schema.string().describe("Task ID to retrieve result from"), - }, - async execute(args: BackgroundResultArgs) { - try { - const task = manager.getTask(args.taskId) - if (!task) { - return `❌ Task not found: ${args.taskId}` - } +async function formatTaskResult(task: BackgroundTask, client: OpencodeClient): Promise { + const messagesResult = await client.session.messages({ + path: { id: task.sessionID }, + }) - if (task.status !== "completed") { - return `⏳ Task is still ${task.status}. Wait for completion. + if (messagesResult.error) { + return `Error fetching messages: ${messagesResult.error}` + } -Use \`background_status\` tool to check progress.` - } + const messages = messagesResult.data + const assistantMessages = messages.filter( + (m: any) => m.info?.role === "assistant" + ) - const messagesResult = await client.session.messages({ - path: { id: task.sessionID }, - }) + const lastMessage = assistantMessages[assistantMessages.length - 1] + const textParts = lastMessage?.parts?.filter( + (p: any) => p.type === "text" + ) ?? [] + const textContent = textParts.map((p: any) => p.text).join("\n") - if (messagesResult.error) { - return `❌ Error fetching messages: ${messagesResult.error}` - } + const duration = formatDuration(task.startedAt, task.completedAt) - const messages = messagesResult.data - - const assistantMessages = messages.filter( - (m: any) => m.info?.role === "assistant" - ) - - if (assistantMessages.length === 0) { - return `⚠️ Task completed but no output found. - -Task ID: ${task.id} -Session ID: ${task.sessionID}` - } - - const lastMessage = assistantMessages[assistantMessages.length - 1] - - const textParts = lastMessage.parts?.filter( - (p: any) => p.type === "text" - ) ?? [] - - const textContent = textParts.map((p: any) => p.text).join("\n") - - const duration = formatDuration(task.startedAt, task.completedAt) - - return `✅ Task Result + return `Task Result Task ID: ${task.id} Description: ${task.description} @@ -169,9 +109,70 @@ Session ID: ${task.sessionID} --- -${textContent}` +${textContent || "(No output)"}` +} + +export function createBackgroundOutput(manager: BackgroundManager, client: OpencodeClient) { + return tool({ + description: BACKGROUND_OUTPUT_DESCRIPTION, + args: { + task_id: tool.schema.string().describe("Task ID to get output from"), + block: tool.schema.boolean().optional().describe("Wait for completion (default: true)"), + timeout: tool.schema.number().optional().describe("Max wait time in ms (default: 60000, max: 600000)"), + }, + async execute(args: BackgroundOutputArgs) { + try { + const task = manager.getTask(args.task_id) + if (!task) { + return `Task not found: ${args.task_id}` + } + + const shouldBlock = args.block !== false + const timeoutMs = Math.min(args.timeout ?? 60000, 600000) + + // Non-blocking: return status immediately + if (!shouldBlock) { + return formatTaskStatus(task) + } + + // Already completed: return result immediately + if (task.status === "completed") { + return await formatTaskResult(task, client) + } + + // Error or cancelled: return status immediately + if (task.status === "error" || task.status === "cancelled") { + return formatTaskStatus(task) + } + + // Blocking: poll until completion or timeout + const startTime = Date.now() + + while (Date.now() - startTime < timeoutMs) { + await delay(1000) + + const currentTask = manager.getTask(args.task_id) + if (!currentTask) { + return `Task was deleted: ${args.task_id}` + } + + if (currentTask.status === "completed") { + return await formatTaskResult(currentTask, client) + } + + if (currentTask.status === "error" || currentTask.status === "cancelled") { + return formatTaskStatus(currentTask) + } + } + + // Timeout exceeded: return current status + const finalTask = manager.getTask(args.task_id) + if (!finalTask) { + return `Task was deleted: ${args.task_id}` + } + return `Timeout exceeded (${timeoutMs}ms). Task still ${finalTask.status}.\n\n${formatTaskStatus(finalTask)}` } catch (error) { - return `❌ Error retrieving result: ${error instanceof Error ? error.message : String(error)}` + return `Error getting output: ${error instanceof Error ? error.message : String(error)}` } }, }) diff --git a/src/tools/background-task/types.ts b/src/tools/background-task/types.ts index 84aefba..3b346ee 100644 --- a/src/tools/background-task/types.ts +++ b/src/tools/background-task/types.ts @@ -4,12 +4,10 @@ export interface BackgroundTaskArgs { agent: string } -export interface BackgroundStatusArgs { - taskId?: string -} - -export interface BackgroundResultArgs { - taskId: string +export interface BackgroundOutputArgs { + task_id: string + block?: boolean + timeout?: number } export interface BackgroundCancelArgs { diff --git a/src/tools/index.ts b/src/tools/index.ts index cf01b03..906df3f 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -24,8 +24,7 @@ import { skill } from "./skill" import { createBackgroundTask, - createBackgroundStatus, - createBackgroundResult, + createBackgroundOutput, createBackgroundCancel, } from "./background-task" @@ -39,8 +38,7 @@ export { createOmoTask } from "./omo-task" export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) { return { background_task: createBackgroundTask(manager), - background_status: createBackgroundStatus(manager), - background_result: createBackgroundResult(manager, client), + background_output: createBackgroundOutput(manager, client), background_cancel: createBackgroundCancel(manager, client), } }