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 {