2026-02-08 13:57:26 +09:00
|
|
|
import type { DelegateTaskArgs } from "./types"
|
|
|
|
|
import type { ExecutorContext } from "./executor-types"
|
2026-03-18 14:21:27 +01:00
|
|
|
import type { DelegatedModelConfig } from "./types"
|
2026-02-08 17:34:47 +09:00
|
|
|
import { isPlanFamily } from "./constants"
|
2026-02-08 13:57:26 +09:00
|
|
|
import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent"
|
2026-04-13 15:52:44 +09:00
|
|
|
import { applyCategoryParams } from "./delegated-model-config"
|
|
|
|
|
import { resolveEffectiveFallbackEntry } from "./fallback-entry-resolution"
|
|
|
|
|
import { applyFallbackEntrySettings } from "./fallback-entry-settings"
|
|
|
|
|
import {
|
|
|
|
|
type AgentInfo,
|
|
|
|
|
sanitizeSubagentType,
|
|
|
|
|
mergeWithClaudeCodeAgents,
|
|
|
|
|
findPrimaryAgentMatch,
|
|
|
|
|
findCallableAgentMatch,
|
|
|
|
|
listCallableAgentNames,
|
|
|
|
|
} from "./subagent-discovery"
|
2026-02-23 17:42:53 +01:00
|
|
|
import { normalizeModelFormat } from "../../shared/model-format-normalizer"
|
2026-02-08 17:34:47 +09:00
|
|
|
import { AGENT_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
2026-03-18 14:21:27 +01:00
|
|
|
import { normalizeFallbackModels, flattenToFallbackModelStrings } from "../../shared/model-resolver"
|
2026-04-13 15:52:44 +09:00
|
|
|
import { buildFallbackChainFromModels } from "../../shared/fallback-chain-from-models"
|
|
|
|
|
import { getAgentConfigKey, stripAgentListSortPrefix } from "../../shared/agent-display-names"
|
2026-02-16 18:20:19 +09:00
|
|
|
import { normalizeSDKResponse } from "../../shared"
|
2026-02-17 01:26:58 +09:00
|
|
|
import { log } from "../../shared/logger"
|
2026-02-08 18:03:15 +09:00
|
|
|
import { getAvailableModelsForDelegateTask } from "./available-models"
|
2026-02-19 04:41:00 +02:00
|
|
|
import type { FallbackEntry } from "../../shared/model-requirements"
|
2026-02-08 18:03:15 +09:00
|
|
|
import { resolveModelForDelegateTask } from "./model-selection"
|
2026-03-27 15:50:16 +09:00
|
|
|
import { fuzzyMatchModel } from "../../shared/model-availability"
|
2026-04-12 06:13:52 +09:00
|
|
|
|
2026-02-08 13:57:26 +09:00
|
|
|
export async function resolveSubagentExecution(
|
|
|
|
|
args: DelegateTaskArgs,
|
|
|
|
|
executorCtx: ExecutorContext,
|
|
|
|
|
parentAgent: string | undefined,
|
2026-03-18 14:21:27 +01:00
|
|
|
categoryExamples: string
|
|
|
|
|
): Promise<{ agentToUse: string; categoryModel: DelegatedModelConfig | undefined; fallbackChain?: FallbackEntry[]; error?: string }> {
|
2026-03-04 20:43:55 +01:00
|
|
|
const { client, agentOverrides, userCategories } = executorCtx
|
2026-02-08 13:57:26 +09:00
|
|
|
|
|
|
|
|
if (!args.subagent_type?.trim()) {
|
|
|
|
|
return { agentToUse: "", categoryModel: undefined, error: `Agent name cannot be empty.` }
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 15:52:44 +09:00
|
|
|
const agentName = sanitizeSubagentType(args.subagent_type)
|
2026-02-08 13:57:26 +09:00
|
|
|
|
|
|
|
|
if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) {
|
|
|
|
|
return {
|
|
|
|
|
agentToUse: "",
|
|
|
|
|
categoryModel: undefined,
|
|
|
|
|
error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. Use category parameter instead (e.g., ${categoryExamples}).
|
|
|
|
|
|
|
|
|
|
Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 17:34:47 +09:00
|
|
|
if (isPlanFamily(agentName) && isPlanFamily(parentAgent)) {
|
2026-02-08 13:57:26 +09:00
|
|
|
return {
|
|
|
|
|
agentToUse: "",
|
|
|
|
|
categoryModel: undefined,
|
2026-02-08 17:34:47 +09:00
|
|
|
error: `You are a plan-family agent (plan/prometheus). You cannot delegate to other plan-family agents via task.
|
2026-02-08 13:57:26 +09:00
|
|
|
|
|
|
|
|
Create the work plan directly - that's your job as the planning agent.`,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let agentToUse = agentName
|
2026-03-18 14:21:27 +01:00
|
|
|
let categoryModel: DelegatedModelConfig | undefined
|
2026-02-19 04:41:00 +02:00
|
|
|
let fallbackChain: FallbackEntry[] | undefined = undefined
|
2026-02-08 13:57:26 +09:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const agentsResult = await client.app.agents()
|
2026-02-16 18:20:19 +09:00
|
|
|
const agents = normalizeSDKResponse(agentsResult, [] as AgentInfo[], {
|
|
|
|
|
preferResponseOnMissingData: true,
|
|
|
|
|
})
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-04-12 06:13:52 +09:00
|
|
|
const mergedAgents = mergeWithClaudeCodeAgents(agents, executorCtx.directory)
|
2026-04-13 15:52:44 +09:00
|
|
|
const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse)
|
2026-04-03 11:51:19 +10:00
|
|
|
|
2026-04-13 15:52:44 +09:00
|
|
|
if (matchedPrimaryAgent) {
|
|
|
|
|
return {
|
|
|
|
|
agentToUse: "",
|
|
|
|
|
categoryModel: undefined,
|
|
|
|
|
error: `Cannot delegate to primary agent "${stripAgentListSortPrefix(matchedPrimaryAgent.name)}" via task. Select that agent directly instead.`,
|
2026-04-11 14:53:01 +09:00
|
|
|
}
|
2026-04-13 15:52:44 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const matchedAgent = findCallableAgentMatch(mergedAgents, agentToUse)
|
2026-02-08 13:57:26 +09:00
|
|
|
if (!matchedAgent) {
|
|
|
|
|
return {
|
|
|
|
|
agentToUse: "",
|
|
|
|
|
categoryModel: undefined,
|
2026-04-13 15:52:44 +09:00
|
|
|
error: `Unknown agent: "${agentToUse}". Available agents: ${listCallableAgentNames(mergedAgents)}`,
|
2026-02-08 13:57:26 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 14:53:01 +09:00
|
|
|
agentToUse = stripAgentListSortPrefix(matchedAgent.name)
|
2026-02-08 13:57:26 +09:00
|
|
|
|
2026-02-16 20:43:09 +09:00
|
|
|
const agentConfigKey = getAgentConfigKey(agentToUse)
|
|
|
|
|
const agentOverride = agentOverrides?.[agentConfigKey as keyof typeof agentOverrides]
|
|
|
|
|
?? (agentOverrides ? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentConfigKey)?.[1] : undefined)
|
|
|
|
|
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentConfigKey]
|
2026-04-04 15:40:37 +09:00
|
|
|
const agentCategoryConfig = agentOverride?.category
|
|
|
|
|
? userCategories?.[agentOverride.category]
|
2026-03-27 13:59:06 +09:00
|
|
|
: undefined
|
2026-04-04 15:40:37 +09:00
|
|
|
const agentCategoryModel = agentCategoryConfig?.model
|
2026-03-04 20:43:55 +01:00
|
|
|
const normalizedAgentFallbackModels = normalizeFallbackModels(
|
|
|
|
|
agentOverride?.fallback_models
|
2026-04-04 15:40:37 +09:00
|
|
|
?? agentCategoryConfig?.fallback_models
|
2026-03-04 20:43:55 +01:00
|
|
|
)
|
2026-02-08 17:34:47 +09:00
|
|
|
|
2026-03-27 15:50:16 +09:00
|
|
|
const availableModels = await getAvailableModelsForDelegateTask(client)
|
|
|
|
|
|
2026-03-27 13:59:06 +09:00
|
|
|
if (agentOverride?.model || agentCategoryModel || agentRequirement || matchedAgent.model) {
|
2026-02-08 17:34:47 +09:00
|
|
|
|
2026-03-03 00:31:12 +09:00
|
|
|
const normalizedMatchedModel = matchedAgent.model
|
|
|
|
|
? normalizeModelFormat(matchedAgent.model)
|
|
|
|
|
: undefined
|
2026-02-23 17:42:53 +01:00
|
|
|
const matchedAgentModelStr = normalizedMatchedModel
|
|
|
|
|
? `${normalizedMatchedModel.providerID}/${normalizedMatchedModel.modelID}`
|
2026-02-08 17:34:47 +09:00
|
|
|
: undefined
|
|
|
|
|
|
2026-02-08 18:03:15 +09:00
|
|
|
const resolution = resolveModelForDelegateTask({
|
2026-03-27 13:59:06 +09:00
|
|
|
userModel: agentOverride?.model ?? agentCategoryModel,
|
2026-03-18 14:21:27 +01:00
|
|
|
userFallbackModels: flattenToFallbackModelStrings(normalizedAgentFallbackModels),
|
2026-02-08 18:03:15 +09:00
|
|
|
categoryDefaultModel: matchedAgentModelStr,
|
|
|
|
|
fallbackChain: agentRequirement?.fallbackChain,
|
|
|
|
|
availableModels,
|
|
|
|
|
systemDefaultModel: undefined,
|
2026-02-08 17:34:47 +09:00
|
|
|
})
|
|
|
|
|
|
2026-03-26 16:54:04 +09:00
|
|
|
const resolutionSkipped = resolution && 'skipped' in resolution
|
|
|
|
|
|
|
|
|
|
if (resolution && !resolutionSkipped) {
|
2026-02-23 17:42:53 +01:00
|
|
|
const normalized = normalizeModelFormat(resolution.model)
|
|
|
|
|
if (normalized) {
|
2026-04-04 15:40:37 +09:00
|
|
|
const variantToUse = agentOverride?.variant ?? resolution.variant ?? agentCategoryConfig?.variant
|
|
|
|
|
const resolvedModel = variantToUse ? { ...normalized, variant: variantToUse } : normalized
|
|
|
|
|
categoryModel = applyCategoryParams(resolvedModel, agentCategoryConfig)
|
2026-02-08 17:34:47 +09:00
|
|
|
}
|
2026-03-27 13:59:06 +09:00
|
|
|
} else if (resolutionSkipped && (agentOverride?.model ?? agentCategoryModel)) {
|
|
|
|
|
const normalized = normalizeModelFormat((agentOverride?.model ?? agentCategoryModel)!)
|
2026-03-26 16:54:04 +09:00
|
|
|
if (normalized) {
|
2026-04-04 15:40:37 +09:00
|
|
|
const variantToUse = agentOverride?.variant ?? agentCategoryConfig?.variant
|
|
|
|
|
const resolvedModel = variantToUse ? { ...normalized, variant: variantToUse } : normalized
|
|
|
|
|
categoryModel = applyCategoryParams(resolvedModel, agentCategoryConfig)
|
2026-03-26 16:54:04 +09:00
|
|
|
log("[delegate-task] Cold cache: using explicit user override for subagent", {
|
|
|
|
|
agent: agentToUse,
|
2026-03-27 13:59:06 +09:00
|
|
|
model: agentOverride?.model ?? agentCategoryModel,
|
2026-03-26 16:54:04 +09:00
|
|
|
})
|
|
|
|
|
}
|
2026-02-08 17:34:47 +09:00
|
|
|
}
|
2026-03-04 20:43:55 +01:00
|
|
|
|
|
|
|
|
const defaultProviderID = categoryModel?.providerID
|
|
|
|
|
?? normalizedMatchedModel?.providerID
|
|
|
|
|
?? "opencode"
|
|
|
|
|
const configuredFallbackChain = buildFallbackChainFromModels(
|
|
|
|
|
normalizedAgentFallbackModels,
|
|
|
|
|
defaultProviderID,
|
|
|
|
|
)
|
2026-03-26 16:54:04 +09:00
|
|
|
fallbackChain = configuredFallbackChain ?? (resolutionSkipped ? undefined : agentRequirement?.fallbackChain)
|
2026-04-13 15:52:44 +09:00
|
|
|
const effectiveEntry = resolveEffectiveFallbackEntry({
|
|
|
|
|
categoryModel,
|
|
|
|
|
configuredFallbackChain,
|
|
|
|
|
resolution,
|
|
|
|
|
})
|
2026-03-18 14:21:27 +01:00
|
|
|
|
|
|
|
|
if (categoryModel && effectiveEntry) {
|
2026-04-13 15:52:44 +09:00
|
|
|
categoryModel = applyFallbackEntrySettings({
|
|
|
|
|
categoryModel,
|
|
|
|
|
effectiveEntry,
|
|
|
|
|
variantOverride: agentOverride?.variant,
|
|
|
|
|
})
|
2026-03-18 14:21:27 +01:00
|
|
|
}
|
2026-02-08 18:03:15 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!categoryModel && matchedAgent.model) {
|
2026-03-03 00:31:12 +09:00
|
|
|
const normalizedMatchedModel = normalizeModelFormat(matchedAgent.model)
|
|
|
|
|
if (normalizedMatchedModel) {
|
2026-03-27 15:50:16 +09:00
|
|
|
const fullModel = `${normalizedMatchedModel.providerID}/${normalizedMatchedModel.modelID}`
|
|
|
|
|
if (availableModels.size === 0 || fuzzyMatchModel(fullModel, availableModels, [normalizedMatchedModel.providerID])) {
|
|
|
|
|
categoryModel = normalizedMatchedModel
|
|
|
|
|
} else {
|
|
|
|
|
log("[delegate-task] Skipping unavailable agent default model", {
|
|
|
|
|
agent: agentToUse,
|
|
|
|
|
model: fullModel,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-03 00:31:12 +09:00
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
}
|
2026-02-17 01:26:58 +09:00
|
|
|
} catch (error) {
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
|
|
|
log("[delegate-task] Failed to resolve subagent execution", {
|
|
|
|
|
requestedAgent: agentToUse,
|
|
|
|
|
parentAgent,
|
|
|
|
|
error: errorMessage,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
agentToUse: "",
|
|
|
|
|
categoryModel: undefined,
|
|
|
|
|
error: `Failed to delegate to agent "${agentToUse}": ${errorMessage}`,
|
|
|
|
|
}
|
2026-02-08 13:57:26 +09:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 04:41:00 +02:00
|
|
|
return { agentToUse, categoryModel, fallbackChain }
|
2026-02-08 13:57:26 +09:00
|
|
|
}
|