refactor(background-task): unify background_result and background_status into background_output tool

- Merge background_status into background_output with block parameter
- Replace background_result references with background_output throughout codebase
- Update tool descriptions to reflect new unified API
- Remove background-tasks.json (memory-based only)
- Simplify notification messages and tool usage instructions
This commit is contained in:
YeonGyu-Kim
2025-12-12 10:54:24 +09:00
parent 550322cb0c
commit 01f935f074
8 changed files with 121 additions and 165 deletions

View File

@@ -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"
}
}
]

View File

@@ -232,7 +232,7 @@ export class BackgroundManager {
}).catch(() => {}) }).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() const mainSessionID = getMainSessionID()
if (!mainSessionID) { if (!mainSessionID) {

View File

@@ -40,7 +40,7 @@ function formatNotifications(tasks: BackgroundTask[]): string {
} }
if (tasks.length > 1) { 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) { for (const task of tasks) {
const duration = formatDuration(task.startedAt, task.completedAt) 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 += ` 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 return message
} }
@@ -59,14 +59,14 @@ function formatNotifications(tasks: BackgroundTask[]): string {
const duration = formatDuration(task.startedAt, task.completedAt) const duration = formatDuration(task.startedAt, task.completedAt)
const toolCalls = task.progress?.toolCalls ?? 0 const toolCalls = task.progress?.toolCalls ?? 0
return `**Background Task Complete** return `**Background Task Complete**
**Task ID:** ${task.id} **Task ID:** ${task.id}
**Description:** ${task.description} **Description:** ${task.description}
**Duration:** ${duration} **Duration:** ${duration}
**Tool calls:** ${toolCalls} **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) { export function createBackgroundNotificationHook(manager: BackgroundManager) {

View File

@@ -11,37 +11,24 @@ Arguments:
- description: Short task description (shown in status) - description: Short task description (shown in status)
- prompt: Full detailed prompt for the agent - prompt: Full detailed prompt for the agent
- agent: Agent type to use (any agent allowed) - 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. export const BACKGROUND_OUTPUT_DESCRIPTION = `Get output from a background task.
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
Arguments: 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. Use this to:
- Check task progress (block=false)
Returns the full assistant output from the background session, including: - Wait for and retrieve task result (block=true)
- Task description - Set custom timeout for long tasks`
- Duration
- Complete response content
- Session ID for reference
Arguments:
- taskId: Required task ID to retrieve result for.`
export const BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task. export const BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task.

View File

@@ -1,7 +1,6 @@
export { export {
createBackgroundTask, createBackgroundTask,
createBackgroundStatus, createBackgroundOutput,
createBackgroundResult,
createBackgroundCancel, createBackgroundCancel,
} from "./tools" } from "./tools"

View File

@@ -1,7 +1,7 @@
import { tool, type PluginInput } from "@opencode-ai/plugin" import { tool, type PluginInput } from "@opencode-ai/plugin"
import type { BackgroundManager } from "../../features/background-agent" import type { BackgroundManager, BackgroundTask } from "../../features/background-agent"
import type { BackgroundTaskArgs, BackgroundStatusArgs, BackgroundResultArgs, BackgroundCancelArgs } from "./types" import type { BackgroundTaskArgs, BackgroundOutputArgs, BackgroundCancelArgs } from "./types"
import { BACKGROUND_TASK_DESCRIPTION, BACKGROUND_STATUS_DESCRIPTION, BACKGROUND_RESULT_DESCRIPTION, BACKGROUND_CANCEL_DESCRIPTION } from "./constants" import { BACKGROUND_TASK_DESCRIPTION, BACKGROUND_OUTPUT_DESCRIPTION, BACKGROUND_CANCEL_DESCRIPTION } from "./constants"
type OpencodeClient = PluginInput["client"] type OpencodeClient = PluginInput["client"]
@@ -38,7 +38,7 @@ export function createBackgroundTask(manager: BackgroundManager) {
parentMessageID: toolContext.messageID, parentMessageID: toolContext.messageID,
}) })
return `Background task launched successfully! return `Background task launched successfully.
Task ID: ${task.id} Task ID: ${task.id}
Session ID: ${task.sessionID} Session ID: ${task.sessionID}
@@ -46,8 +46,9 @@ Description: ${task.description}
Agent: ${task.agent} Agent: ${task.agent}
Status: ${task.status} Status: ${task.status}
Use \`background_status\` tool to check progress. Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results.
Use \`background_result\` tool to retrieve results when complete.` - block=false: Check status without waiting
- block=true (default): Wait for completion and get result`
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const message = error instanceof Error ? error.message : String(error)
return `❌ Failed to launch background task: ${message}` 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) { function delay(ms: number): Promise<void> {
return tool({ return new Promise(resolve => setTimeout(resolve, ms))
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}`
}
const duration = formatDuration(task.startedAt, task.completedAt) function formatTaskStatus(task: BackgroundTask): string {
const progress = task.progress const duration = formatDuration(task.startedAt, task.completedAt)
? `\nTool calls: ${task.progress.toolCalls}\nLast tool: ${task.progress.lastTool ?? "N/A"}` 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} Task ID: ${task.id}
Description: ${task.description} Description: ${task.description}
@@ -84,83 +76,31 @@ Status: ${task.status}
Duration: ${duration}${progress} Duration: ${duration}${progress}
Session ID: ${task.sessionID}` 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) { async function formatTaskResult(task: BackgroundTask, client: OpencodeClient): Promise<string> {
return tool({ const messagesResult = await client.session.messages({
description: BACKGROUND_RESULT_DESCRIPTION, path: { id: task.sessionID },
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}`
}
if (task.status !== "completed") { if (messagesResult.error) {
return `⏳ Task is still ${task.status}. Wait for completion. 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({ const lastMessage = assistantMessages[assistantMessages.length - 1]
path: { id: task.sessionID }, const textParts = lastMessage?.parts?.filter(
}) (p: any) => p.type === "text"
) ?? []
const textContent = textParts.map((p: any) => p.text).join("\n")
if (messagesResult.error) { const duration = formatDuration(task.startedAt, task.completedAt)
return `❌ Error fetching messages: ${messagesResult.error}`
}
const messages = messagesResult.data return `Task Result
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
Task ID: ${task.id} Task ID: ${task.id}
Description: ${task.description} 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) { } catch (error) {
return `Error retrieving result: ${error instanceof Error ? error.message : String(error)}` return `Error getting output: ${error instanceof Error ? error.message : String(error)}`
} }
}, },
}) })

View File

@@ -4,12 +4,10 @@ export interface BackgroundTaskArgs {
agent: string agent: string
} }
export interface BackgroundStatusArgs { export interface BackgroundOutputArgs {
taskId?: string task_id: string
} block?: boolean
timeout?: number
export interface BackgroundResultArgs {
taskId: string
} }
export interface BackgroundCancelArgs { export interface BackgroundCancelArgs {

View File

@@ -24,8 +24,7 @@ import { skill } from "./skill"
import { import {
createBackgroundTask, createBackgroundTask,
createBackgroundStatus, createBackgroundOutput,
createBackgroundResult,
createBackgroundCancel, createBackgroundCancel,
} from "./background-task" } from "./background-task"
@@ -39,8 +38,7 @@ export { createOmoTask } from "./omo-task"
export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) { export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient) {
return { return {
background_task: createBackgroundTask(manager), background_task: createBackgroundTask(manager),
background_status: createBackgroundStatus(manager), background_output: createBackgroundOutput(manager, client),
background_result: createBackgroundResult(manager, client),
background_cancel: createBackgroundCancel(manager, client), background_cancel: createBackgroundCancel(manager, client),
} }
} }