From f8398dbec3acdeadbebe71f9109efd50a05c9ec0 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 19:33:11 +0900 Subject: [PATCH] fix(delegate-task): restrict task-callable agent modes --- .../delegate-task/subagent-resolver.test.ts | 35 +++++++++++++++++++ src/tools/delegate-task/subagent-resolver.ts | 21 ++++------- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/tools/delegate-task/subagent-resolver.test.ts b/src/tools/delegate-task/subagent-resolver.test.ts index 4205a6b66..7f87f8531 100644 --- a/src/tools/delegate-task/subagent-resolver.test.ts +++ b/src/tools/delegate-task/subagent-resolver.test.ts @@ -85,6 +85,41 @@ describe("resolveSubagentExecution", () => { }) }) + test("hides primary agents from task delegation lookups", async () => { + //#given + const args = createBaseArgs({ subagent_type: "sisyphus" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "sisyphus", mode: "primary" }, + { name: "oracle", mode: "subagent" }, + { name: "metis", mode: "all" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.agentToUse).toBe("") + expect(result.categoryModel).toBeUndefined() + expect(result.error).toBe('Unknown agent: "sisyphus". Available agents: metis, oracle') + }) + + test("requires explicit all or subagent mode for task-callable agents", async () => { + //#given + const args = createBaseArgs({ subagent_type: "custom-worker" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "custom-worker" }, + { name: "oracle", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.agentToUse).toBe("") + expect(result.categoryModel).toBeUndefined() + expect(result.error).toBe('Unknown agent: "custom-worker". Available agents: oracle') + }) + test("normalizes matched agent model string before returning categoryModel", async () => { //#given const cacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ diff --git a/src/tools/delegate-task/subagent-resolver.ts b/src/tools/delegate-task/subagent-resolver.ts index 242bcdac1..f5a255c70 100644 --- a/src/tools/delegate-task/subagent-resolver.ts +++ b/src/tools/delegate-task/subagent-resolver.ts @@ -16,6 +16,8 @@ import { resolveModelForDelegateTask } from "./model-selection" import { fuzzyMatchModel } from "../../shared/model-availability" import type { CategoryConfig } from "../../config/schema" +type AgentMode = "subagent" | "primary" | "all" | undefined + function applyCategoryParams( base: DelegatedModelConfig, config: CategoryConfig | undefined, @@ -85,7 +87,7 @@ Create the work plan directly - that's your job as the planning agent.`, preferResponseOnMissingData: true, }) - const callableAgents = agents.filter((a) => a.mode !== "primary") + const callableAgents = agents.filter((agent) => isTaskCallableAgentMode(agent.mode)) const resolvedDisplayName = getAgentDisplayName(agentToUse) const matchedAgent = callableAgents.find( @@ -93,19 +95,6 @@ Create the work plan directly - that's your job as the planning agent.`, || agent.name.toLowerCase() === resolvedDisplayName.toLowerCase() ) if (!matchedAgent) { - const isPrimaryAgent = agents - .filter((a) => a.mode === "primary") - .find((agent) => agent.name.toLowerCase() === agentToUse.toLowerCase() - || agent.name.toLowerCase() === resolvedDisplayName.toLowerCase()) - - if (isPrimaryAgent) { - return { - agentToUse: "", - categoryModel: undefined, - error: `Cannot call primary agent "${isPrimaryAgent.name}" via task. Primary agents are top-level orchestrators.`, - } - } - const availableAgents = callableAgents .map((a) => a.name) .sort() @@ -239,3 +228,7 @@ Create the work plan directly - that's your job as the planning agent.`, return { agentToUse, categoryModel, fallbackChain } } + +function isTaskCallableAgentMode(mode: AgentMode): boolean { + return mode === "all" || mode === "subagent" +}