fix(delegate-task): strip wrapping chars from subagent_type before lookup

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.
This commit is contained in:
YeonGyu-Kim
2026-04-03 16:41:59 +09:00
parent c78a9e640a
commit ed06428ba3
2 changed files with 78 additions and 1 deletions
@@ -508,3 +508,78 @@ describe("resolveSubagentExecution", () => {
connectedSpy.mockRestore()
})
})
describe("resolveSubagentExecution - agent name sanitization", () => {
let logSpy: ReturnType<typeof spyOn> | 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()
})
})
+3 -1
View File
@@ -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 {