diff --git a/src/tools/delegate-task/skill-resolver.ts b/src/tools/delegate-task/skill-resolver.ts index e3bb89a50..2d9cba696 100644 --- a/src/tools/delegate-task/skill-resolver.ts +++ b/src/tools/delegate-task/skill-resolver.ts @@ -4,7 +4,13 @@ import { discoverSkills } from "../../features/opencode-skill-loader" export async function resolveSkillContent( skills: string[], - options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider, disabledSkills?: Set, directory?: string } + options: { + gitMasterConfig?: GitMasterConfig + browserProvider?: BrowserAutomationProvider + disabledSkills?: Set + teamModeEnabled?: boolean + directory?: string + } ): Promise<{ content: string | undefined; contents: string[]; error: string | null }> { if (skills.length === 0) { return { content: undefined, contents: [], error: null } diff --git a/src/tools/delegate-task/subagent-resolver.ts b/src/tools/delegate-task/subagent-resolver.ts index 1d67f24b4..44e789f70 100644 --- a/src/tools/delegate-task/subagent-resolver.ts +++ b/src/tools/delegate-task/subagent-resolver.ts @@ -26,11 +26,17 @@ import type { FallbackEntry } from "../../shared/model-requirements" import { resolveModelForDelegateTask } from "./model-selection" import { fuzzyMatchModel } from "../../shared/model-availability" +export interface ResolveSubagentExecutionOptions { + allowSisyphusJuniorDirect?: boolean + allowPrimaryAgentDelegation?: boolean +} + export async function resolveSubagentExecution( args: DelegateTaskArgs, executorCtx: ExecutorContext, parentAgent: string | undefined, - categoryExamples: string + categoryExamples: string, + options: ResolveSubagentExecutionOptions = {}, ): Promise<{ agentToUse: string; categoryModel: DelegatedModelConfig | undefined; fallbackChain?: FallbackEntry[]; error?: string }> { const { client, agentOverrides, userCategories } = executorCtx @@ -40,11 +46,17 @@ export async function resolveSubagentExecution( const agentName = sanitizeSubagentType(args.subagent_type) - if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) { + if ( + !options.allowSisyphusJuniorDirect && + agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase() + ) { + const exampleHint = categoryExamples.trim() !== "" + ? `Use category parameter instead (e.g., ${categoryExamples}).` + : `Use the category parameter instead (pick one of: quick, deep, ultrabrain, visual-engineering, artistry, writing).` return { agentToUse: "", categoryModel: undefined, - error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. Use category parameter instead (e.g., ${categoryExamples}). + error: `Cannot use subagent_type="${SISYPHUS_JUNIOR_AGENT}" directly. ${exampleHint} Sisyphus-Junior is spawned automatically when you specify a category. Pick the appropriate category for your task domain.`, } @@ -73,7 +85,7 @@ Create the work plan directly - that's your job as the planning agent.`, const mergedAgents = mergeWithClaudeCodeAgents(agents, executorCtx.directory) const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse) - if (matchedPrimaryAgent) { + if (matchedPrimaryAgent && !options.allowPrimaryAgentDelegation) { return { agentToUse: "", categoryModel: undefined, @@ -81,7 +93,11 @@ Create the work plan directly - that's your job as the planning agent.`, } } - const matchedAgent = findCallableAgentMatch(mergedAgents, agentToUse) + const usePrimary = options.allowPrimaryAgentDelegation && matchedPrimaryAgent !== undefined + const matchedAgent = usePrimary + ? matchedPrimaryAgent + : findCallableAgentMatch(mergedAgents, agentToUse) + if (!matchedAgent) { return { agentToUse: "", @@ -90,7 +106,9 @@ Create the work plan directly - that's your job as the planning agent.`, } } - agentToUse = stripAgentListSortPrefix(matchedAgent.name) + agentToUse = usePrimary + ? matchedAgent.name + : stripAgentListSortPrefix(matchedAgent.name) const agentConfigKey = getAgentConfigKey(agentToUse) const agentOverride = agentOverrides?.[agentConfigKey as keyof typeof agentOverrides] diff --git a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts index 5346f9eb8..05e988716 100644 --- a/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts +++ b/src/tools/delegate-task/zauc-mocks-subagent-resolver/subagent-resolver.test.ts @@ -168,6 +168,69 @@ describe("resolveSubagentExecution", () => { expect(result.error).toBe('Cannot delegate to primary agent "Prometheus - Plan Builder" via task. Select that agent directly instead.') }) + test("allows delegating to a primary agent when allowPrimaryAgentDelegation is enabled (team-mode path)", async () => { + //#given + readProviderModelsCacheMock.mockReturnValue({ + models: { anthropic: ["claude-opus-4-7"] }, + connected: ["anthropic"], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: "sisyphus" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "\u200BSisyphus - Ultraworker", mode: "primary", model: "anthropic/claude-opus-4-7" }, + { name: "oracle", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep", { + allowPrimaryAgentDelegation: true, + }) + + //#then + expect(result.error).toBeUndefined() + expect(result.agentToUse).toBe("\u200BSisyphus - Ultraworker") + }) + + test("allows delegating to Sisyphus-Junior when allowSisyphusJuniorDirect is enabled (team-mode path)", async () => { + //#given + readProviderModelsCacheMock.mockReturnValue({ + models: { anthropic: ["claude-sonnet-4-6"] }, + connected: ["anthropic"], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: "sisyphus-junior" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "Sisyphus-Junior", mode: "subagent", model: "anthropic/claude-sonnet-4-6" }, + { name: "oracle", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep", { + allowSisyphusJuniorDirect: true, + }) + + //#then + expect(result.error).toBeUndefined() + expect(result.agentToUse).toBe("Sisyphus-Junior") + }) + + test("renders a usable fallback hint when categoryExamples is empty for the default Sisyphus-Junior block", async () => { + //#given + const args = createBaseArgs({ subagent_type: "sisyphus-junior" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "Sisyphus-Junior", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "") + + //#then + expect(result.agentToUse).toBe("") + expect(result.error).toBeDefined() + expect(result.error).not.toContain("(e.g., )") + expect(result.error).toContain("pick one of: quick, deep, ultrabrain") + }) + test("requires explicit all or subagent mode for task-callable agents", async () => { //#given const args = createBaseArgs({ subagent_type: "custom-worker" })