fix(delegate-task): strip ZWSP from agent names on background launch path

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-08 17:17:26 +09:00
parent 917ae4dfc3
commit 359f74132a
4 changed files with 107 additions and 5 deletions
@@ -466,4 +466,58 @@ describe("background-agent spawner fallback model promotion", () => {
}) })
expect(promptCalls[0]?.body?.variant).toBe("medium") expect(promptCalls[0]?.body?.variant).toBe("medium")
}) })
test("strips leading zwsp from prompt body agent before promptAsync", async () => {
//#given
const promptCalls: Array<{ body?: { agent?: string } }> = []
const client = {
session: {
get: async () => ({ data: { directory: "/parent/dir" } }),
create: async () => ({ data: { id: "ses_child_clean_agent" } }),
promptAsync: async (args?: { body?: { agent?: string } }) => {
promptCalls.push(args ?? {})
return {}
},
},
}
const task = createTask({
description: "Test task",
prompt: "Do work",
agent: "\u200Bsisyphus-junior",
parentSessionID: "ses_parent",
parentMessageID: "msg_parent",
})
const item = {
task,
input: {
description: task.description,
prompt: task.prompt,
agent: task.agent,
parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID,
parentModel: task.parentModel,
parentAgent: task.parentAgent,
model: task.model,
},
}
const ctx = {
client,
directory: "/fallback",
concurrencyManager: { release: () => {} },
tmuxEnabled: false,
onTaskError: () => {},
}
//#when
await startTask(item as any, ctx as any)
await new Promise((resolve) => setTimeout(resolve, 0))
//#then
expect(promptCalls).toHaveLength(1)
expect(promptCalls[0]?.body?.agent).toBe("sisyphus-junior")
})
}) })
+4 -2
View File
@@ -6,6 +6,7 @@ import { applySessionPromptParams } from "../../shared/session-prompt-params-hel
import { subagentSessions } from "../claude-code-session-state" import { subagentSessions } from "../claude-code-session-state"
import { getTaskToastManager } from "../task-toast-manager" import { getTaskToastManager } from "../task-toast-manager"
import { isInsideTmux } from "../../shared/tmux" import { isInsideTmux } from "../../shared/tmux"
import { stripAgentListSortPrefix } from "../../shared/agent-display-names"
import type { ConcurrencyManager } from "./concurrency" import type { ConcurrencyManager } from "./concurrency"
export const FALLBACK_AGENT = "general" export const FALLBACK_AGENT = "general"
@@ -168,11 +169,12 @@ export async function startTask(
} }
: undefined : undefined
const launchVariant = input.model?.variant const launchVariant = input.model?.variant
const normalizedAgent = stripAgentListSortPrefix(input.agent)
applySessionPromptParams(sessionID, input.model) applySessionPromptParams(sessionID, input.model)
const promptBody = { const promptBody = {
agent: input.agent, agent: normalizedAgent,
...(launchModel ? { model: launchModel } : {}), ...(launchModel ? { model: launchModel } : {}),
...(launchVariant ? { variant: launchVariant } : {}), ...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent, system: input.skillContent,
@@ -180,7 +182,7 @@ export async function startTask(
task: false, task: false,
call_omo_agent: true, call_omo_agent: true,
question: false, question: false,
...getAgentToolRestrictions(input.agent), ...getAgentToolRestrictions(normalizedAgent),
}, },
parts: [createInternalAgentTextPart(input.prompt)], parts: [createInternalAgentTextPart(input.prompt)],
} }
@@ -204,6 +204,50 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
]) ])
}) })
testFn("strips leading zwsp from agent name before launching background task", async () => {
//#given - display-sorted agent names should be normalized before manager launch
const launchCalls: unknown[] = []
const manager = {
launch: async (input: unknown) => {
launchCalls.push(input)
return {
id: "bg_clean_agent",
sessionID: "ses_clean_agent",
description: "Clean agent",
agent: "sisyphus-junior",
status: "running",
}
},
getTask: () => ({ sessionID: "ses_clean_agent" }),
}
//#when
await executeBackgroundTask(
{
description: "Clean agent",
prompt: "check",
run_in_background: true,
load_skills: [],
},
{
sessionID: "ses_parent",
callID: "call_clean_agent",
metadata: async () => {},
abort: new AbortController().signal,
},
{ manager },
{ sessionID: "ses_parent", messageID: "msg_clean_agent" },
"\u200Bsisyphus-junior",
undefined,
undefined,
undefined,
)
//#then
expectFn(launchCalls).toHaveLength(1)
expectFn((launchCalls[0] as { agent: string }).agent).toBe("sisyphus-junior")
})
testFn("keeps launched background task alive when parent aborts before session id resolves", async () => { testFn("keeps launched background task alive when parent aborts before session id resolves", async () => {
//#given - parallel tool execution can abort the parent call after launch succeeds //#given - parallel tool execution can abort the parent call after launch succeeds
const metadataCalls: any[] = [] const metadataCalls: any[] = []
+5 -3
View File
@@ -10,6 +10,7 @@ import { getSessionTools } from "../../shared/session-tools-store"
import { SessionCategoryRegistry } from "../../shared/session-category-registry" import { SessionCategoryRegistry } from "../../shared/session-category-registry"
import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission" import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission"
import { setSessionFallbackChain } from "../../hooks/model-fallback/hook" import { setSessionFallbackChain } from "../../hooks/model-fallback/hook"
import { stripAgentListSortPrefix } from "../../shared/agent-display-names"
function continueSessionSetup(args: { function continueSessionSetup(args: {
taskID: string taskID: string
@@ -62,11 +63,12 @@ export async function executeBackgroundTask(
try { try {
const tddEnabled = executorCtx.sisyphusAgentConfig?.tdd const tddEnabled = executorCtx.sisyphusAgentConfig?.tdd
const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse, tddEnabled) const normalizedAgent = stripAgentListSortPrefix(agentToUse)
const effectivePrompt = buildTaskPrompt(args.prompt, normalizedAgent, tddEnabled)
const task = await manager.launch({ const task = await manager.launch({
description: args.description, description: args.description,
prompt: effectivePrompt, prompt: effectivePrompt,
agent: agentToUse, agent: normalizedAgent,
parentSessionID: parentContext.sessionID, parentSessionID: parentContext.sessionID,
parentMessageID: parentContext.messageID, parentMessageID: parentContext.messageID,
parentModel: parentContext.model, parentModel: parentContext.model,
@@ -156,7 +158,7 @@ Do NOT call background_output now. Wait for <system-reminder> notification first
return formatDetailedError(error, { return formatDetailedError(error, {
operation: "Launch background task", operation: "Launch background task",
args, args,
agent: agentToUse, agent: stripAgentListSortPrefix(agentToUse),
category: args.category, category: args.category,
}) })
} }