From 2fad28d552ef04f0a67217859c07df13e6eae736 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 11 Dec 2025 16:04:27 +0900 Subject: [PATCH] feat(background-task): add 4 background task tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) --- src/tools/background-task/constants.ts | 51 ++++++ src/tools/background-task/index.ts | 9 + src/tools/background-task/tools.ts | 223 +++++++++++++++++++++++++ src/tools/background-task/types.ts | 18 ++ 4 files changed, 301 insertions(+) create mode 100644 src/tools/background-task/constants.ts create mode 100644 src/tools/background-task/index.ts create mode 100644 src/tools/background-task/tools.ts create mode 100644 src/tools/background-task/types.ts diff --git a/src/tools/background-task/constants.ts b/src/tools/background-task/constants.ts new file mode 100644 index 0000000..f1e692e --- /dev/null +++ b/src/tools/background-task/constants.ts @@ -0,0 +1,51 @@ +export const BACKGROUND_TASK_DESCRIPTION = `Launch a background agent task that runs asynchronously. + +The task runs in a separate session while you continue with other work. The system will notify you when the task completes. + +Use this for: +- Long-running research tasks +- Complex analysis that doesn't need immediate results +- Parallel workloads to maximize throughput + +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.` + +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 + +Arguments: +- taskId: Optional task ID. If omitted, lists all tasks for current session.` + +export const BACKGROUND_RESULT_DESCRIPTION = `Retrieve the result of a completed background task. + +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.` + +export const BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task. + +Only works for tasks with status "running". Aborts the background session and marks the task as cancelled. + +Arguments: +- taskId: Required task ID to cancel.` diff --git a/src/tools/background-task/index.ts b/src/tools/background-task/index.ts new file mode 100644 index 0000000..5c869a4 --- /dev/null +++ b/src/tools/background-task/index.ts @@ -0,0 +1,9 @@ +export { + createBackgroundTask, + createBackgroundStatus, + createBackgroundResult, + createBackgroundCancel, +} from "./tools" + +export type * from "./types" +export * from "./constants" diff --git a/src/tools/background-task/tools.ts b/src/tools/background-task/tools.ts new file mode 100644 index 0000000..2a0e1f4 --- /dev/null +++ b/src/tools/background-task/tools.ts @@ -0,0 +1,223 @@ +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" + +type OpencodeClient = PluginInput["client"] + +function formatDuration(start: Date, end?: Date): string { + const duration = (end ?? new Date()).getTime() - start.getTime() + const seconds = Math.floor(duration / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + + if (hours > 0) { + return `${hours}h ${minutes % 60}m ${seconds % 60}s` + } else if (minutes > 0) { + return `${minutes}m ${seconds % 60}s` + } else { + return `${seconds}s` + } +} + +export function createBackgroundTask(manager: BackgroundManager) { + return tool({ + description: BACKGROUND_TASK_DESCRIPTION, + args: { + description: tool.schema.string().describe("Short task description (shown in status)"), + prompt: tool.schema.string().describe("Full detailed prompt for the agent"), + agent: tool.schema.string().describe("Agent type to use (any agent allowed)"), + session_id: tool.schema.string().describe("Parent session ID (auto-detected if omitted)").optional(), + }, + async execute(args: BackgroundTaskArgs, toolContext) { + try { + const task = await manager.launch({ + description: args.description, + prompt: args.prompt, + agent: args.agent, + parentSessionID: args.session_id ?? toolContext.sessionID, + parentMessageID: toolContext.messageID ?? "unknown", + }) + + return `✅ Background task launched successfully! + +Task ID: ${task.id} +Session ID: ${task.sessionID} +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.` + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + return `❌ Failed to launch background task: ${message}` + } + }, + }) +} + +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}` + } + + 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 + +Task ID: ${task.id} +Description: ${task.description} +Agent: ${task.agent} +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}` + } + + if (task.status !== "completed") { + return `⏳ Task is still ${task.status}. Wait for completion. + +Use \`background_status\` tool to check progress.` + } + + const messagesResult = await client.session.messages({ + path: { id: task.sessionID }, + }) + + if (messagesResult.error) { + return `❌ Error fetching messages: ${messagesResult.error}` + } + + 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 + +Task ID: ${task.id} +Description: ${task.description} +Duration: ${duration} +Session ID: ${task.sessionID} + +--- + +${textContent}` + } catch (error) { + return `❌ Error retrieving result: ${error instanceof Error ? error.message : String(error)}` + } + }, + }) +} + +export function createBackgroundCancel(manager: BackgroundManager, client: OpencodeClient) { + return tool({ + description: BACKGROUND_CANCEL_DESCRIPTION, + args: { + taskId: tool.schema.string().describe("Task ID to cancel"), + }, + async execute(args: BackgroundCancelArgs) { + try { + const task = manager.getTask(args.taskId) + if (!task) { + return `❌ Task not found: ${args.taskId}` + } + + if (task.status !== "running") { + return `❌ Cannot cancel task: current status is "${task.status}". +Only running tasks can be cancelled.` + } + + const abortResult = await client.session.abort({ + path: { id: task.sessionID }, + }) + + if (abortResult.error) { + return `❌ Failed to abort session: ${(abortResult.error as any).message || String(abortResult.error)}` + } + + task.status = "cancelled" + task.completedAt = new Date() + + await manager.persist() + + return `✅ Task cancelled successfully + +Task ID: ${task.id} +Description: ${task.description} +Session ID: ${task.sessionID} +Status: ${task.status}` + } catch (error) { + return `❌ Error cancelling task: ${error instanceof Error ? error.message : String(error)}` + } + }, + }) +} diff --git a/src/tools/background-task/types.ts b/src/tools/background-task/types.ts new file mode 100644 index 0000000..5d2f051 --- /dev/null +++ b/src/tools/background-task/types.ts @@ -0,0 +1,18 @@ +export interface BackgroundTaskArgs { + description: string + prompt: string + agent: string + session_id?: string +} + +export interface BackgroundStatusArgs { + taskId?: string +} + +export interface BackgroundResultArgs { + taskId: string +} + +export interface BackgroundCancelArgs { + taskId: string +}