fix(atlas,todo-continuation): strip ZWSP sort prefix before promptAsync agent
Both injectors call resolveRegisteredAgentName, which returns the registered alias verbatim. OpenCode TUI registers agent names with leading zero-width characters (U+200B) for sort ordering, so that alias can be e.g. "\u200B\u200BAtlas - Plan Executor". Passing it directly to promptAsync produces "Agent not found" because the OpenCode SDK does an exact match against its canonical display name registry. Strip the ZWSP sort prefix on the resolved name before sending it to promptAsync in: - src/hooks/atlas/boulder-continuation-injector.ts - src/hooks/todo-continuation-enforcer/continuation-injection.ts Add regression tests asserting promptAsync receives the canonical display name (no \u200B) even when the registered alias carries a ZWSP sort prefix. Same root cause class as #3494 / #3547. Tests were RED on dev before the fix and GREEN after.
This commit is contained in:
@@ -43,6 +43,45 @@ describe("injectContinuation", () => {
|
||||
expect(capturedAgent).toBe("Sisyphus - Ultraworker")
|
||||
})
|
||||
|
||||
test("#given resolved agent name still carries a ZWSP sort prefix #when continuation is injected #then promptAsync receives the agent name without the ZWSP prefix", async () => {
|
||||
// given
|
||||
let capturedAgent: string | undefined
|
||||
const ctx = {
|
||||
directory: "/tmp/test",
|
||||
client: {
|
||||
session: {
|
||||
todo: async () => ({ data: [{ id: "1", content: "todo", status: "pending", priority: "high" }] }),
|
||||
promptAsync: async (input: {
|
||||
body: {
|
||||
agent?: string
|
||||
}
|
||||
}) => {
|
||||
capturedAgent = input.body.agent
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const sessionStateStore = {
|
||||
getExistingState: () => ({ inFlight: false, lastInjectedAt: 0, consecutiveFailures: 0 }),
|
||||
}
|
||||
|
||||
// when
|
||||
await injectContinuation({
|
||||
ctx: ctx as never,
|
||||
sessionID: "ses_zwsp_agent",
|
||||
resolvedInfo: {
|
||||
agent: "\u200B\u200BSisyphus - Ultraworker",
|
||||
model: { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" },
|
||||
},
|
||||
sessionStateStore: sessionStateStore as never,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(capturedAgent).toBe("Sisyphus - Ultraworker")
|
||||
expect(capturedAgent).not.toContain("\u200B")
|
||||
})
|
||||
|
||||
test("inherits tools from resolved message info when reinjecting", async () => {
|
||||
// given
|
||||
let capturedTools: Record<string, boolean> | undefined
|
||||
|
||||
@@ -20,6 +20,7 @@ import { isSqliteBackend } from "../../shared/opencode-storage-detection"
|
||||
import {
|
||||
getAgentConfigKey,
|
||||
normalizeAgentForPromptKey,
|
||||
stripAgentListSortPrefix,
|
||||
} from "../../shared/agent-display-names"
|
||||
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
|
||||
|
||||
@@ -131,7 +132,8 @@ export async function injectContinuation(args: {
|
||||
}
|
||||
|
||||
const promptAgent = normalizeAgentForPromptKey(agentName)
|
||||
const launchAgent = resolveRegisteredAgentName(agentName)
|
||||
const resolvedAgent = resolveRegisteredAgentName(agentName)
|
||||
const launchAgent = resolvedAgent ? stripAgentListSortPrefix(resolvedAgent) : resolvedAgent
|
||||
|
||||
if (promptAgent && skipAgents.some(s => getAgentConfigKey(s) === getAgentConfigKey(promptAgent))) {
|
||||
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName })
|
||||
|
||||
Reference in New Issue
Block a user