import { tool, type PluginInput, type ToolDefinition } from "@opencode-ai/plugin" import type { BackgroundManager } from "../../features/background-agent" import type { BackgroundTaskArgs } from "./types" import { BACKGROUND_TASK_DESCRIPTION } from "./constants" import { resolveMessageContext } from "../../features/hook-message-injector" import { getSessionAgent } from "../../features/claude-code-session-state" import { publishToolMetadata } from "../../features/tool-metadata-store" import { log } from "../../shared/logger" import { delay } from "./delay" import { getMessageDir } from "./message-dir" type ToolContextWithMetadata = { sessionID: string messageID: string agent: string abort: AbortSignal metadata?: (input: { title?: string; metadata?: Record }) => void callID?: string } export function createBackgroundTask( manager: BackgroundManager, client: PluginInput["client"] ): ToolDefinition { 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 registered agent)"), }, async execute(args: BackgroundTaskArgs, toolContext) { const ctx = toolContext as ToolContextWithMetadata if (!args.agent || args.agent.trim() === "") { return `[ERROR] Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)` } try { const messageDir = getMessageDir(ctx.sessionID) const { prevMessage, firstMessageAgent } = await resolveMessageContext( ctx.sessionID, client, messageDir ) const sessionAgent = getSessionAgent(ctx.sessionID) const parentAgent = ctx.agent ?? sessionAgent ?? firstMessageAgent ?? prevMessage?.agent log("[background_task] parentAgent resolution", { sessionID: ctx.sessionID, ctxAgent: ctx.agent, sessionAgent, firstMessageAgent, prevMessageAgent: prevMessage?.agent, resolvedParentAgent: parentAgent, }) const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID, ...(prevMessage.model.variant ? { variant: prevMessage.model.variant } : {}), } : undefined const task = await manager.launch({ description: args.description, prompt: args.prompt, agent: args.agent.trim(), parentSessionId: ctx.sessionID, parentMessageId: ctx.messageID, parentModel, parentAgent, }) const WAIT_FOR_SESSION_INTERVAL_MS = 50 const WAIT_FOR_SESSION_TIMEOUT_MS = 30000 const waitStart = Date.now() let sessionId = task.sessionId while (!sessionId && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) { const updated = manager.getTask(task.id) if (updated?.status === "error" || updated?.status === "cancelled" || updated?.status === "interrupt") { return `Task ${`entered error state`}\.\n\nTask ID: ${task.id}` } sessionId = updated?.sessionId if (sessionId) { break } if (ctx.abort?.aborted) { break } await delay(WAIT_FOR_SESSION_INTERVAL_MS) } const bgMeta = { title: args.description, metadata: { ...(sessionId ? { sessionId } : {}), }, } await publishToolMetadata(ctx, bgMeta) return `Background task launched successfully. Task ID: ${task.id} Session ID: ${sessionId ?? "(not yet assigned)"} Description: ${task.description} Agent: ${task.agent} Status: ${task.status} System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check. Do NOT call background_output now. Wait for notification first.` } catch (error) { const message = error instanceof Error ? error.message : String(error) return `[ERROR] Failed to launch background task: ${message}` } }, }) }