diff --git a/src/tools/call-omo-agent/background-agent-executor.test.ts b/src/tools/call-omo-agent/background-agent-executor.test.ts index f6cda6b9e..181e8dd8a 100644 --- a/src/tools/call-omo-agent/background-agent-executor.test.ts +++ b/src/tools/call-omo-agent/background-agent-executor.test.ts @@ -152,4 +152,32 @@ describe("executeBackgroundAgent", () => { expect(secondResult).toContain("Task ID: task-2") expect(secondResult).not.toContain("interrupt") }) + + test("#given subagent_type is the lowercase config key 'hephaestus' #when executeBackgroundAgent runs #then BackgroundManager.launch receives the registered display name 'Hephaestus - Deep Agent'", async () => { + //#given + launchMock.mockClear() + launchMock.mockResolvedValueOnce({ + id: "task-heph", + sessionId: "ses-heph", + description: "task", + agent: "Hephaestus - Deep Agent", + status: "pending", + }) + getTaskMock.mockReturnValueOnce({ + id: "task-heph", + sessionId: "ses-heph", + description: "task", + agent: "Hephaestus - Deep Agent", + status: "pending", + }) + const args = { ...testArgs, subagent_type: "hephaestus" } + + //#when + await executeBackgroundAgent(args, testContext, mockManager, mockClient) + + //#then + const launchCall = launchMock.mock.calls.find(([input]) => (input as { agent: string }).agent !== undefined) + expect(launchCall).toBeDefined() + expect((launchCall![0] as { agent: string }).agent).toBe("Hephaestus - Deep Agent") + }) }) diff --git a/src/tools/call-omo-agent/background-agent-executor.ts b/src/tools/call-omo-agent/background-agent-executor.ts index 23cfbfc0d..196157e9a 100644 --- a/src/tools/call-omo-agent/background-agent-executor.ts +++ b/src/tools/call-omo-agent/background-agent-executor.ts @@ -7,6 +7,7 @@ import type { CallOmoAgentArgs } from "./types" import type { ToolContextWithMetadata } from "./tool-context-with-metadata" import { getMessageDir } from "./message-storage-directory" import { getSessionTools } from "../../shared/session-tools-store" +import { getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names" export async function executeBackgroundAgent( args: CallOmoAgentArgs, @@ -39,7 +40,7 @@ export async function executeBackgroundAgent( const task = await manager.launch({ description: args.description, prompt: args.prompt, - agent: args.subagent_type, + agent: getAgentDisplayName(stripAgentListSortPrefix(args.subagent_type)), parentSessionId: toolContext.sessionID, parentMessageId: toolContext.messageID, parentAgent, diff --git a/src/tools/call-omo-agent/background-executor.test.ts b/src/tools/call-omo-agent/background-executor.test.ts index d92722245..68603bf49 100644 --- a/src/tools/call-omo-agent/background-executor.test.ts +++ b/src/tools/call-omo-agent/background-executor.test.ts @@ -126,7 +126,7 @@ describe("executeBackground", () => { if (!launchArgs) { throw new Error("Expected launch arguments") } - expect(launchArgs.agent).toBe("hephaestus") + expect(launchArgs.agent).toBe("Hephaestus - Deep Agent") }) test("keeps launched background task alive when parent aborts before session id resolves", async () => { @@ -210,4 +210,48 @@ describe("executeBackground", () => { expect(secondResult).toContain("Task ID: task-2") expect(secondResult).not.toContain("interrupt") }) + + test("#given subagent_type is the lowercase config key 'hephaestus' #when executeBackground runs #then BackgroundManager.launch receives the registered display name", async () => { + //#given + launchMock.mockClear() + launchMock.mockResolvedValueOnce({ + id: "test-task-id", + sessionId: "sub-session", + description: "Test task", + agent: "Hephaestus - Deep Agent", + status: "pending", + }) + + //#when + await executeBackground({ ...testArgs, subagent_type: "hephaestus" }, testContext, mockManager, mockClient) + + //#then + const latestCall = [...launchMock.mock.calls].pop() + if (!latestCall) throw new Error("Expected background manager launch to be called") + const launchArgs = latestCall[0] + if (!launchArgs) throw new Error("Expected launch arguments") + expect(launchArgs.agent).toBe("Hephaestus - Deep Agent") + }) + + test("#given subagent_type is a same-keyed agent 'explore' #when executeBackground runs #then BackgroundManager.launch receives the unchanged key (regression guard)", async () => { + //#given + launchMock.mockClear() + launchMock.mockResolvedValueOnce({ + id: "test-task-id", + sessionId: "sub-session", + description: "Test task", + agent: "explore", + status: "pending", + }) + + //#when + await executeBackground({ ...testArgs, subagent_type: "explore" }, testContext, mockManager, mockClient) + + //#then + const latestCall = [...launchMock.mock.calls].pop() + if (!latestCall) throw new Error("Expected background manager launch to be called") + const launchArgs = latestCall[0] + if (!launchArgs) throw new Error("Expected launch arguments") + expect(launchArgs.agent).toBe("explore") + }) }) diff --git a/src/tools/call-omo-agent/background-executor.ts b/src/tools/call-omo-agent/background-executor.ts index cae87c986..ed0c841e6 100644 --- a/src/tools/call-omo-agent/background-executor.ts +++ b/src/tools/call-omo-agent/background-executor.ts @@ -9,6 +9,7 @@ import { getSessionAgent } from "../../features/claude-code-session-state" import { getMessageDir } from "./message-dir" import { getSessionTools } from "../../shared/session-tools-store" import { sanitizeSubagentType } from "../delegate-task/subagent-discovery" +import { getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names" export async function executeBackground( args: CallOmoAgentArgs, @@ -48,7 +49,7 @@ export async function executeBackground( const task = await manager.launch({ description: args.description, prompt: args.prompt, - agent: sanitizeSubagentType(args.subagent_type), + agent: getAgentDisplayName(stripAgentListSortPrefix(sanitizeSubagentType(args.subagent_type))), parentSessionId: toolContext.sessionID, parentMessageId: toolContext.messageID, parentAgent, diff --git a/src/tools/call-omo-agent/sync-executor.test.ts b/src/tools/call-omo-agent/sync-executor.test.ts index fdd6cff0b..3b0e0db5f 100644 --- a/src/tools/call-omo-agent/sync-executor.test.ts +++ b/src/tools/call-omo-agent/sync-executor.test.ts @@ -141,6 +141,69 @@ describe("executeSync", () => { expect(promptInput?.body.agent).toBe("Sisyphus - Ultraworker") }) + test("#given subagent_type is the lowercase config key 'hephaestus' #when executeSync runs #then promptAsync receives the registered display name 'Hephaestus - Deep Agent'", async () => { + //#given + const executeSync = await importExecuteSync() + const deps = createDependencies() + const toolContext = createToolContext() + const recorder = createPromptAsyncRecorder() + const args = { + subagent_type: "hephaestus", + description: "task", + prompt: "do the thing", + run_in_background: false, + } + + //#when + await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps) + + //#then — SDK rejects raw config keys with UnknownError; the dispatch must translate + const promptInput = recorder.getCapturedInput() + expect(promptInput?.body.agent).toBe("Hephaestus - Deep Agent") + }) + + test("#given subagent_type is the lowercase config key 'sisyphus-junior' #when executeSync runs #then promptAsync receives the registered display name 'Sisyphus-Junior'", async () => { + //#given + const executeSync = await importExecuteSync() + const deps = createDependencies() + const toolContext = createToolContext() + const recorder = createPromptAsyncRecorder() + const args = { + subagent_type: "sisyphus-junior", + description: "task", + prompt: "do the thing", + run_in_background: false, + } + + //#when + await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps) + + //#then + const promptInput = recorder.getCapturedInput() + expect(promptInput?.body.agent).toBe("Sisyphus-Junior") + }) + + test("#given subagent_type is already a display name like 'explore' (config key == display name) #when executeSync runs #then promptAsync receives 'explore' unchanged", async () => { + //#given a same-keyed agent must not be double-translated + const executeSync = await importExecuteSync() + const deps = createDependencies() + const toolContext = createToolContext() + const recorder = createPromptAsyncRecorder() + const args = { + subagent_type: "explore", + description: "task", + prompt: "do the thing", + run_in_background: false, + } + + //#when + await executeSync(args, toolContext, createContext(recorder.promptAsync) as never, deps) + + //#then + const promptInput = recorder.getCapturedInput() + expect(promptInput?.body.agent).toBe("explore") + }) + test("returns processed response with task metadata footer", async () => { //#given const executeSync = await importExecuteSync() diff --git a/src/tools/call-omo-agent/sync-executor.ts b/src/tools/call-omo-agent/sync-executor.ts index 640bdb15a..aec78b183 100644 --- a/src/tools/call-omo-agent/sync-executor.ts +++ b/src/tools/call-omo-agent/sync-executor.ts @@ -5,7 +5,7 @@ import { getAgentToolRestrictions, log } from "../../shared" import { applySessionPromptParams } from "../../shared/session-prompt-params-helpers" import type { DelegatedModelConfig } from "../../shared/model-resolution-types" import type { FallbackEntry } from "../../shared/model-requirements" -import { stripAgentListSortPrefix } from "../../shared/agent-display-names" +import { getAgentDisplayName, stripAgentListSortPrefix } from "../../shared/agent-display-names" import { promptAsyncAfterSessionIdle } from "../../hooks/shared/prompt-async-gate" import { waitForCompletion } from "./completion-poller" import { processMessages } from "./message-processor" @@ -119,7 +119,7 @@ export async function executeSync( input: { path: { id: sessionID }, body: { - agent: normalizedSubagentType, + agent: getAgentDisplayName(normalizedSubagentType), tools: { ...getAgentToolRestrictions(normalizedSubagentType), task: false,