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 // given
let promptBody: { agent?: string; noReply?: boolean } | undefined let promptBody: { agent?: string; noReply?: boolean } | undefined
let promptPart: let promptPart:
@@ -103,14 +103,14 @@ describe("ralph-loop continuation prompt injector", () => {
}) })
// then // then
expect(promptBody?.agent).toBe("sisyphus") expect(promptBody?.agent).toBe("Sisyphus - Ultraworker")
expect(promptBody?.agent).not.toContain("\u200b") expect(promptBody?.agent).not.toContain("\u200b")
expect(promptBody?.noReply).toBeUndefined() expect(promptBody?.noReply).toBeUndefined()
expect(promptPart?.synthetic).toBe(true) expect(promptPart?.synthetic).toBe(true)
expect(promptPart?.metadata?.compaction_continue).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 // given
let promptBody: { agent?: string } | undefined let promptBody: { agent?: string } | undefined
const ctx = { const ctx = {
@@ -136,7 +136,7 @@ describe("ralph-loop continuation prompt injector", () => {
}) })
// then // 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 () => { 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, normalizeSDKResponse,
resolveInheritedPromptTools, resolveInheritedPromptTools,
} from "../../shared" } from "../../shared"
import { normalizeAgentForPromptKey } from "../../shared/agent-display-names" import { normalizeAgentForPrompt, stripAgentListSortPrefix } from "../../shared/agent-display-names"
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate" import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
type MessageInfo = { type MessageInfo = {
@@ -60,6 +60,23 @@ function createPromptAsyncError(prefix: string, error: unknown): Error {
return new Error(`${prefix}: ${describePromptAsyncError(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( export async function injectContinuationPrompt(
ctx: PluginInput, ctx: PluginInput,
options: { options: {
@@ -113,7 +130,7 @@ export async function injectContinuationPrompt(
} }
const inheritedTools = resolveInheritedPromptTools(sourceSessionID, tools) const inheritedTools = resolveInheritedPromptTools(sourceSessionID, tools)
const cleanAgent = normalizeAgentForPromptKey(agent) const cleanAgent = normalizeInheritedAgentForPrompt(agent)
const launchModel = model const launchModel = model
? { providerID: model.providerID, modelID: model.modelID } ? { providerID: model.providerID, modelID: model.modelID }