From f1fb1e08eb995c140611f3b594bf22ce37ca1f66 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 18:19:58 +0900 Subject: [PATCH] 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. --- .../continuation-prompt-injector.test.ts | 8 +++---- .../continuation-prompt-injector.ts | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/hooks/ralph-loop/continuation-prompt-injector.test.ts b/src/hooks/ralph-loop/continuation-prompt-injector.test.ts index bc15f4734..05d025aad 100644 --- a/src/hooks/ralph-loop/continuation-prompt-injector.test.ts +++ b/src/hooks/ralph-loop/continuation-prompt-injector.test.ts @@ -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 () => { diff --git a/src/hooks/ralph-loop/continuation-prompt-injector.ts b/src/hooks/ralph-loop/continuation-prompt-injector.ts index 62dc150e2..fd9c133e2 100644 --- a/src/hooks/ralph-loop/continuation-prompt-injector.ts +++ b/src/hooks/ralph-loop/continuation-prompt-injector.ts @@ -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 }