From ed06428ba36986b097c77715a7e6d7eaec94775b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 3 Apr 2026 16:41:59 +0900 Subject: [PATCH] fix(delegate-task): strip wrapping chars from subagent_type before lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLMs sometimes wrap agent names in backslashes, quotes, or slashes (e.g. \hephaestus\ instead of hephaestus). The trim() call only removed whitespace, causing "Agent not found" errors during delegation. Now strips leading/trailing backslashes, quotes, and slashes before the case-insensitive agent lookup. Adds regression tests for backslash-wrapped, double-quoted, and single-quoted agent names. Fixes: release blocker — delegate_task to hephaestus failing in pre-publish review sessions. --- .../delegate-task/subagent-resolver.test.ts | 75 +++++++++++++++++++ src/tools/delegate-task/subagent-resolver.ts | 4 +- 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/tools/delegate-task/subagent-resolver.test.ts b/src/tools/delegate-task/subagent-resolver.test.ts index 53cf0f0a4..eaa4cbb7e 100644 --- a/src/tools/delegate-task/subagent-resolver.test.ts +++ b/src/tools/delegate-task/subagent-resolver.test.ts @@ -508,3 +508,78 @@ describe("resolveSubagentExecution", () => { connectedSpy.mockRestore() }) }) + +describe("resolveSubagentExecution - agent name sanitization", () => { + let logSpy: ReturnType | undefined + + beforeEach(() => { + logSpy = spyOn(logger, "log").mockImplementation(() => {}) + }) + + afterEach(() => { + logSpy?.mockRestore() + }) + + test("strips backslash-wrapped agent names like \\hephaestus\\", async () => { + //#given + const cacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ + models: {}, + connected: [], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: "\\hephaestus\\" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "Hephaestus (Deep Agent)", mode: "subagent", model: "openai/gpt-5.3-codex" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.error).toBeUndefined() + expect(result.agentToUse).toBe("Hephaestus (Deep Agent)") + cacheSpy.mockRestore() + }) + + test("strips double-quoted agent names", async () => { + //#given + const cacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ + models: {}, + connected: [], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: '"oracle"' }) + const executorCtx = createExecutorContext(async () => ([ + { name: "oracle", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.error).toBeUndefined() + expect(result.agentToUse).toBe("oracle") + cacheSpy.mockRestore() + }) + + test("strips single-quoted agent names", async () => { + //#given + const cacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ + models: {}, + connected: [], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: "'explore'" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "explore", mode: "subagent" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.error).toBeUndefined() + expect(result.agentToUse).toBe("explore") + cacheSpy.mockRestore() + }) +}) diff --git a/src/tools/delegate-task/subagent-resolver.ts b/src/tools/delegate-task/subagent-resolver.ts index 12e97f52a..0baedf552 100644 --- a/src/tools/delegate-task/subagent-resolver.ts +++ b/src/tools/delegate-task/subagent-resolver.ts @@ -27,7 +27,9 @@ export async function resolveSubagentExecution( return { agentToUse: "", categoryModel: undefined, error: `Agent name cannot be empty.` } } - const agentName = args.subagent_type.trim() + // Strip wrapping characters (backslashes, quotes) that LLMs sometimes add around agent names + // e.g. \hephaestus\ -> hephaestus, "oracle" -> oracle, 'explore' -> explore + const agentName = args.subagent_type.trim().replace(/^[\\\/"']+|[\\\/"']+$/g, "").trim() if (agentName.toLowerCase() === SISYPHUS_JUNIOR_AGENT.toLowerCase()) { return {