import { tool, type ToolDefinition } from "@opencode-ai/plugin" import type { DelegatedModelConfig, ToolContextWithMetadata, DelegateTaskToolOptions } from "./types" import { log } from "../../shared/logger" import { buildSystemContent } from "./prompt-builder" import { resolveSkillContent, resolveParentContext, executeBackgroundContinuation, executeSyncContinuation, resolveCategoryExecution, resolveSubagentExecution, executeUnstableAgentTask, executeBackgroundTask, executeSyncTask, } from "./executor" import { prepareDelegateTaskArgs } from "./tool-argument-preparation" import { createDelegateTaskPresentation } from "./tool-description" export { resolveCategoryConfig } from "./categories" export type { SyncSessionCreatedEvent, DelegateTaskToolOptions, BuildSystemContentInput } from "./types" export { buildSystemContent, buildTaskPrompt } from "./prompt-builder" const delegateTaskArgsSchema = { load_skills: tool.schema.array(tool.schema.string()).describe("Skill names to inject. REQUIRED - pass [] if no skills needed."), description: tool.schema.string().optional().describe("Short task description (3-5 words). Auto-generated from prompt if omitted."), prompt: tool.schema.string().describe("Full detailed prompt for the agent"), run_in_background: tool.schema.boolean().describe("REQUIRED. true=async (returns task_id), false=sync (waits). Use false for task delegation, true ONLY for parallel exploration."), category: tool.schema.string().optional().describe("REQUIRED if subagent_type not provided. Do NOT provide both category and subagent_type."), subagent_type: tool.schema.string().optional().describe("REQUIRED if category not provided. Do NOT provide both category and subagent_type."), task_id: tool.schema.string().optional().describe("Existing task to continue. Canonical resume identifier."), command: tool.schema.string().optional().describe("The command that triggered this task"), } export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefinition { const { availableCategories, availableSkills, categoryExamples, description } = createDelegateTaskPresentation(options) return tool({ description, args: delegateTaskArgsSchema, async execute(args, toolContext) { const ctx = toolContext as ToolContextWithMetadata const delegateTaskArgs = await prepareDelegateTaskArgs(args, ctx) const runInBackground = delegateTaskArgs.run_in_background === true const { content: skillContent, contents: skillContents, error: skillError } = await resolveSkillContent(delegateTaskArgs.load_skills, { gitMasterConfig: options.gitMasterConfig, browserProvider: options.browserProvider, disabledSkills: options.disabledSkills, directory: options.directory, }) if (skillError) { return skillError } const parentContext = await resolveParentContext(ctx, options.client) if (delegateTaskArgs.task_id) { if (runInBackground) { return executeBackgroundContinuation(delegateTaskArgs, ctx, options, parentContext) } return executeSyncContinuation(delegateTaskArgs, ctx, options, parentContext) } if (!delegateTaskArgs.category && !delegateTaskArgs.subagent_type) { return `Invalid arguments: Must provide either category or subagent_type.` } let systemDefaultModel: string | undefined try { const openCodeConfig = await options.client.config.get() systemDefaultModel = (openCodeConfig as { data?: { model?: string } })?.data?.model } catch { systemDefaultModel = undefined } const inheritedModel = parentContext.model ? `${parentContext.model.providerID}/${parentContext.model.modelID}` : undefined let agentToUse: string let categoryModel: DelegatedModelConfig | undefined let categoryPromptAppend: string | undefined let modelInfo: import("../../features/task-toast-manager/types").ModelFallbackInfo | undefined let actualModel: string | undefined let isUnstableAgent = false let fallbackChain: import("../../shared/model-requirements").FallbackEntry[] | undefined let maxPromptTokens: number | undefined if (delegateTaskArgs.category) { const resolution = await resolveCategoryExecution(delegateTaskArgs, options, inheritedModel, systemDefaultModel) if (resolution.error) { return resolution.error } agentToUse = resolution.agentToUse categoryModel = resolution.categoryModel categoryPromptAppend = resolution.categoryPromptAppend modelInfo = resolution.modelInfo actualModel = resolution.actualModel isUnstableAgent = resolution.isUnstableAgent fallbackChain = resolution.fallbackChain maxPromptTokens = resolution.maxPromptTokens const isRunInBackgroundExplicitlyFalse = isExplicitSyncRun(delegateTaskArgs.run_in_background) log("[task] unstable agent detection", { category: delegateTaskArgs.category, actualModel, isUnstableAgent, run_in_background_value: delegateTaskArgs.run_in_background, run_in_background_type: typeof delegateTaskArgs.run_in_background, isRunInBackgroundExplicitlyFalse, willForceBackground: isUnstableAgent && isRunInBackgroundExplicitlyFalse, }) if (isUnstableAgent && isRunInBackgroundExplicitlyFalse) { const systemContent = buildSystemContent({ skillContent, skillContents, categoryPromptAppend, agentName: agentToUse, maxPromptTokens, model: categoryModel, availableCategories, availableSkills, }) return executeUnstableAgentTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, actualModel) } } else { const resolution = await resolveSubagentExecution(delegateTaskArgs, options, parentContext.agent, categoryExamples) if (resolution.error) { return resolution.error } agentToUse = resolution.agentToUse categoryModel = resolution.categoryModel fallbackChain = resolution.fallbackChain } const systemContent = buildSystemContent({ skillContent, skillContents, categoryPromptAppend, agentName: agentToUse, maxPromptTokens, model: categoryModel, availableCategories, availableSkills, }) if (runInBackground) { return executeBackgroundTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain) } return executeSyncTask(delegateTaskArgs, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo, fallbackChain) }, }) } function isExplicitSyncRun(runInBackground: unknown): boolean { return runInBackground === false || runInBackground === "false" }