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:
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user