2026-02-01 16:47:50 +09:00
import { tool , type ToolDefinition } from "@opencode-ai/plugin"
import type { DelegateTaskArgs , ToolContextWithMetadata , DelegateTaskToolOptions } from "./types"
import { DEFAULT_CATEGORIES , CATEGORY_DESCRIPTIONS } from "./constants"
import { log } from "../../shared"
import { buildSystemContent } from "./prompt-builder"
import {
resolveSkillContent ,
resolveParentContext ,
executeBackgroundContinuation ,
executeSyncContinuation ,
resolveCategoryExecution ,
resolveSubagentExecution ,
executeUnstableAgentTask ,
executeBackgroundTask ,
executeSyncTask ,
} from "./executor"
export { resolveCategoryConfig } from "./categories"
export type { SyncSessionCreatedEvent , DelegateTaskToolOptions , BuildSystemContentInput } from "./types"
export { buildSystemContent } from "./prompt-builder"
2026-01-09 02:24:43 +09:00
2026-01-16 17:34:40 +09:00
export function createDelegateTask ( options : DelegateTaskToolOptions ) : ToolDefinition {
2026-02-01 16:47:50 +09:00
const { userCategories } = options
2026-01-09 02:24:43 +09:00
2026-01-22 22:46:23 +09:00
const allCategories = { . . . DEFAULT_CATEGORIES , . . . userCategories }
const categoryNames = Object . keys ( allCategories )
const categoryExamples = categoryNames . map ( k = > ` ' ${ k } ' ` ) . join ( ", " )
const categoryList = categoryNames . map ( name = > {
const userDesc = userCategories ? . [ name ] ? . description
const builtinDesc = CATEGORY_DESCRIPTIONS [ name ]
const desc = userDesc || builtinDesc
return desc ? ` - ${ name } : ${ desc } ` : ` - ${ name } `
} ) . join ( "\n" )
const description = ` Spawn agent task with category-based or direct agent selection.
2026-01-25 13:28:44 +09:00
MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless continuing a session).
2026-01-22 22:46:23 +09:00
- load_skills: ALWAYS REQUIRED. Pass at least one skill name (e.g., ["playwright"], ["git-master", "frontend-ui-ux"]).
- category: Use predefined category → Spawns Sisyphus-Junior with category config
Available categories:
${ categoryList }
- subagent_type: Use specific agent directly (e.g., "oracle", "explore")
- run_in_background: true=async (returns task_id), false=sync (waits for result). Default: false. Use background=true ONLY for parallel exploration with 5+ independent queries.
2026-01-25 13:28:44 +09:00
- session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
- command: The command that triggered this task (optional, for slash command tracking).
2026-01-22 22:46:23 +09:00
2026-01-25 13:28:44 +09:00
**WHEN TO USE session_id:**
- Task failed/incomplete → session_id with "fix: [specific issue]"
- Need follow-up on previous result → session_id with additional question
- Multi-turn conversation with same agent → always session_id instead of new task
2026-01-22 22:46:23 +09:00
Prompts MUST be in English. `
2026-01-09 02:24:43 +09:00
return tool ( {
2026-01-22 22:46:23 +09:00
description ,
2026-01-09 02:24:43 +09:00
args : {
2026-01-22 22:46:23 +09:00
load_skills : tool.schema.array ( tool . schema . string ( ) ) . describe ( "Skill names to inject. REQUIRED - pass [] if no skills needed, but IT IS HIGHLY RECOMMENDED to pass proper skills like [\"playwright\"], [\"git-master\"] for best results." ) ,
description : tool.schema.string ( ) . describe ( "Short task description (3-5 words)" ) ,
2026-01-09 02:24:43 +09:00
prompt : tool.schema.string ( ) . describe ( "Full detailed prompt for the agent" ) ,
2026-01-22 22:46:23 +09:00
run_in_background : tool.schema.boolean ( ) . describe ( "true=async (returns task_id), false=sync (waits). Default: false" ) ,
category : tool.schema.string ( ) . optional ( ) . describe ( ` Category (e.g., ${ categoryExamples } ). Mutually exclusive with subagent_type. ` ) ,
subagent_type : tool.schema.string ( ) . optional ( ) . describe ( "Agent name (e.g., 'oracle', 'explore'). Mutually exclusive with category." ) ,
2026-01-25 13:28:44 +09:00
session_id : tool.schema.string ( ) . optional ( ) . describe ( "Existing Task session to continue" ) ,
command : tool.schema.string ( ) . optional ( ) . describe ( "The command that triggered this task" ) ,
2026-01-09 02:24:43 +09:00
} ,
2026-01-16 17:34:40 +09:00
async execute ( args : DelegateTaskArgs , toolContext ) {
2026-01-09 02:24:43 +09:00
const ctx = toolContext as ToolContextWithMetadata
2026-02-01 16:47:50 +09:00
2026-01-09 02:24:43 +09:00
if ( args . run_in_background === undefined ) {
2026-01-22 22:46:23 +09:00
throw new Error ( ` Invalid arguments: 'run_in_background' parameter is REQUIRED. Use run_in_background=false for task delegation, run_in_background=true only for parallel exploration. ` )
2026-01-09 02:24:43 +09:00
}
2026-01-22 22:46:23 +09:00
if ( args . load_skills === undefined ) {
throw new Error ( ` Invalid arguments: 'load_skills' parameter is REQUIRED. Pass [] if no skills needed, but IT IS HIGHLY RECOMMENDED to pass proper skills like ["playwright"], ["git-master"] for best results. ` )
2026-01-16 13:12:48 +09:00
}
2026-01-22 22:46:23 +09:00
if ( args . load_skills === null ) {
throw new Error ( ` Invalid arguments: load_skills=null is not allowed. Pass [] if no skills needed, but IT IS HIGHLY RECOMMENDED to pass proper skills. ` )
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
2026-01-09 02:24:43 +09:00
const runInBackground = args . run_in_background === true
2026-02-01 16:47:50 +09:00
const { content : skillContent , error : skillError } = await resolveSkillContent ( args . load_skills , {
gitMasterConfig : options.gitMasterConfig ,
browserProvider : options.browserProvider ,
2026-02-01 23:54:32 +07:00
disabledSkills : options.disabledSkills ,
2026-02-01 16:47:50 +09:00
} )
if ( skillError ) {
return skillError
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
const parentContext = resolveParentContext ( ctx )
2026-01-09 02:24:43 +09:00
2026-01-25 13:28:44 +09:00
if ( args . session_id ) {
2026-01-09 02:24:43 +09:00
if ( runInBackground ) {
2026-02-01 16:47:50 +09:00
return executeBackgroundContinuation ( args , ctx , options , parentContext )
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
return executeSyncContinuation ( args , ctx , options )
2026-01-09 02:24:43 +09:00
}
if ( args . category && args . subagent_type ) {
2026-01-17 20:40:35 +09:00
return ` Invalid arguments: Provide EITHER category OR subagent_type, not both. `
2026-01-09 02:24:43 +09:00
}
if ( ! args . category && ! args . subagent_type ) {
2026-01-17 20:40:35 +09:00
return ` Invalid arguments: Must provide either category or subagent_type. `
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
let systemDefaultModel : string | undefined
try {
const openCodeConfig = await options . client . config . get ( )
systemDefaultModel = ( openCodeConfig as { data ? : { model? : string } } ) ? . data ? . model
} catch {
systemDefaultModel = undefined
}
2026-01-27 09:28:40 +09:00
2026-02-01 16:47:50 +09:00
const inheritedModel = parentContext . model
? ` ${ parentContext . model . providerID } / ${ parentContext . model . modelID } `
: undefined
2026-01-27 09:28:40 +09:00
2026-02-01 16:47:50 +09:00
let agentToUse : string
let categoryModel : { providerID : string ; modelID : string ; variant? : string } | undefined
let categoryPromptAppend : string | undefined
let modelInfo : import ( "../../features/task-toast-manager/types" ) . ModelFallbackInfo | undefined
let actualModel : string | undefined
let isUnstableAgent = false
if ( args . category ) {
const resolution = await resolveCategoryExecution ( args , 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
2026-01-14 14:45:01 -05:00
2026-01-22 22:46:23 +09:00
const isRunInBackgroundExplicitlyFalse = args . run_in_background === false || args . run_in_background === "false" as unknown as boolean
2026-01-14 14:45:01 -05:00
2026-01-22 22:46:23 +09:00
log ( "[delegate_task] unstable agent detection" , {
category : args.category ,
actualModel ,
isUnstableAgent ,
run_in_background_value : args.run_in_background ,
run_in_background_type : typeof args . run_in_background ,
isRunInBackgroundExplicitlyFalse ,
willForceBackground : isUnstableAgent && isRunInBackgroundExplicitlyFalse ,
} )
2026-01-13 08:58:47 +07:00
2026-01-22 22:46:23 +09:00
if ( isUnstableAgent && isRunInBackgroundExplicitlyFalse ) {
2026-01-26 17:00:06 +09:00
const systemContent = buildSystemContent ( { skillContent , categoryPromptAppend , agentName : agentToUse } )
2026-02-01 16:47:50 +09:00
return executeUnstableAgentTask ( args , ctx , options , parentContext , agentToUse , categoryModel , systemContent , actualModel )
2026-01-20 18:48:13 +09:00
}
2026-01-09 02:24:43 +09:00
} else {
2026-02-01 16:47:50 +09:00
const resolution = await resolveSubagentExecution ( args , options , parentContext . agent , categoryExamples )
if ( resolution . error ) {
return resolution . error
2026-01-22 22:46:23 +09:00
}
2026-02-01 16:47:50 +09:00
agentToUse = resolution . agentToUse
categoryModel = resolution . categoryModel
2026-01-09 02:24:43 +09:00
}
2026-01-26 17:00:06 +09:00
const systemContent = buildSystemContent ( { skillContent , categoryPromptAppend , agentName : agentToUse } )
2026-01-09 02:24:43 +09:00
if ( runInBackground ) {
2026-02-01 16:47:50 +09:00
return executeBackgroundTask ( args , ctx , options , parentContext , agentToUse , categoryModel , systemContent )
2026-01-09 02:24:43 +09:00
}
2026-02-01 16:47:50 +09:00
return executeSyncTask ( args , ctx , options , parentContext , agentToUse , categoryModel , systemContent , modelInfo )
2026-01-09 02:24:43 +09:00
} ,
} )
}