fix(ralph-loop): send registered agent display name on continue

Continuation injector previously called normalizeAgentForPromptKey,
which collapsed agent inputs like "Sisyphus - Ultraworker" or
"sisyphus" down to the lowercase config key (e.g. "sisyphus").
OpenCode's promptAsync rejects that with "Agent not found", so the
ralph-loop continue prompt silently failed to dispatch on parent
sessions whose inherited message used a known display name.

Switch to normalizeAgentForPrompt and add a small wrapper that:
- preserves any agent string already in canonical " - " display form
  verbatim (covers ZWSP-prefixed inheritance and user-defined custom
  agents)
- otherwise normalizes config keys / legacy parenthesized names to the
  registered display name OpenCode expects.

Update the existing regression tests so that ZWSP-prefixed and clean
inherited agents both assert the registered display name reaches
promptAsync.
This commit is contained in:
YeonGyu-Kim
2026-05-15 18:19:58 +09:00
parent b3b2da89c9
commit f1fb1e08eb
2 changed files with 23 additions and 6 deletions
@@ -59,7 +59,7 @@ describe("ralph-loop continuation prompt injector", () => {
}
})
test("#given inherited message agent has ZWSP prefix #when injecting continuation prompt #then promptAsync receives normalized agent", async () => {
test("#given inherited message agent has ZWSP prefix #when injecting continuation prompt #then promptAsync receives registered display agent", async () => {
// given
let promptBody: { agent?: string; noReply?: boolean } | undefined
let promptPart:
@@ -103,14 +103,14 @@ describe("ralph-loop continuation prompt injector", () => {
})
// then
expect(promptBody?.agent).toBe("sisyphus")
expect(promptBody?.agent).toBe("Sisyphus - Ultraworker")
expect(promptBody?.agent).not.toContain("\u200b")
expect(promptBody?.noReply).toBeUndefined()
expect(promptPart?.synthetic).toBe(true)
expect(promptPart?.metadata?.compaction_continue).toBe(true)
})
test("#given inherited message agent has no ZWSP prefix #when injecting continuation prompt #then promptAsync receives normalized agent", async () => {
test("#given inherited message agent has no ZWSP prefix #when injecting continuation prompt #then promptAsync receives registered display agent", async () => {
// given
let promptBody: { agent?: string } | undefined
const ctx = {
@@ -136,7 +136,7 @@ describe("ralph-loop continuation prompt injector", () => {
})
// then
expect(promptBody?.agent).toBe("sisyphus")
expect(promptBody?.agent).toBe("Sisyphus - Ultraworker")
})
test("#given inherited message model includes variant #when injecting continuation prompt #then promptAsync receives variant as a top-level field", async () => {
@@ -9,7 +9,7 @@ import {
normalizeSDKResponse,
resolveInheritedPromptTools,
} from "../../shared"
import { normalizeAgentForPromptKey } from "../../shared/agent-display-names"
import { normalizeAgentForPrompt, stripAgentListSortPrefix } from "../../shared/agent-display-names"
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
type MessageInfo = {
@@ -60,6 +60,23 @@ function createPromptAsyncError(prefix: string, error: unknown): Error {
return new Error(`${prefix}: ${describePromptAsyncError(error)}`)
}
function normalizeInheritedAgentForPrompt(agent: string | undefined): string | undefined {
if (typeof agent !== "string") {
return undefined
}
const inheritedAgent = stripAgentListSortPrefix(agent).trim()
if (!inheritedAgent) {
return undefined
}
if (inheritedAgent.includes(" - ")) {
return inheritedAgent
}
return normalizeAgentForPrompt(inheritedAgent)
}
export async function injectContinuationPrompt(
ctx: PluginInput,
options: {
@@ -113,7 +130,7 @@ export async function injectContinuationPrompt(
}
const inheritedTools = resolveInheritedPromptTools(sourceSessionID, tools)
const cleanAgent = normalizeAgentForPromptKey(agent)
const cleanAgent = normalizeInheritedAgentForPrompt(agent)
const launchModel = model
? { providerID: model.providerID, modelID: model.modelID }