diff --git a/src/hooks/runtime-fallback/auto-retry.ts b/src/hooks/runtime-fallback/auto-retry.ts index f69754622..74d7932b5 100644 --- a/src/hooks/runtime-fallback/auto-retry.ts +++ b/src/hooks/runtime-fallback/auto-retry.ts @@ -8,7 +8,7 @@ import { prepareFallback } from "./fallback-state" import { SessionCategoryRegistry } from "../../shared/session-category-registry" import { clearDelegatedChildSessionBootstrap } from "../../shared/delegated-child-session-bootstrap" import { buildRetryModelPayload } from "./retry-model-payload" -import { getLastUserRetryParts } from "./last-user-retry-parts" +import { getLastUserRetryPayload } from "./last-user-retry-parts" import { extractSessionMessages } from "./session-messages" import { resolveRegisteredAgentName } from "../../features/claude-code-session-state" import { @@ -144,7 +144,8 @@ export function createAutoRetryHelpers(deps: HookDeps) { path: { id: sessionID }, query: { directory: ctx.directory }, }) - const retryParts = getLastUserRetryParts(messagesResp, sessionID) + const retryPayload = getLastUserRetryPayload(messagesResp, sessionID) + const retryParts = retryPayload.retryParts if (retryParts.length > 0) { log(`[${HOOK_NAME}] Auto-retrying with fallback model (${source})`, { sessionID, @@ -166,6 +167,8 @@ export function createAutoRetryHelpers(deps: HookDeps) { body: { ...(launchAgent ? { agent: launchAgent } : {}), ...retryModelPayload, + ...(retryPayload.system ? { system: retryPayload.system } : {}), + ...(retryPayload.tools ? { tools: retryPayload.tools } : {}), parts: retryParts, }, query: { directory: ctx.directory }, diff --git a/src/hooks/runtime-fallback/index.test.ts b/src/hooks/runtime-fallback/index.test.ts index 2d3196cdf..2f1949781 100644 --- a/src/hooks/runtime-fallback/index.test.ts +++ b/src/hooks/runtime-fallback/index.test.ts @@ -8,6 +8,7 @@ import { } from "../../shared/delegated-child-session-bootstrap" import * as loggerModule from "../../shared/logger" import { SessionCategoryRegistry } from "../../shared/session-category-registry" +import type { RuntimeFallbackPluginInput } from "./types" type RuntimeFallbackModule = typeof import("./hook") @@ -49,8 +50,8 @@ describe("runtime-fallback", () => { abort?: (args: unknown) => Promise status?: () => Promise } - }) { - return unsafeTestValue({ + }): RuntimeFallbackPluginInput { + return unsafeTestValue({ client: { tui: { showToast: async (opts: { body: { title: string; message: string; variant: string; duration: number } }) => { @@ -522,6 +523,8 @@ describe("runtime-fallback", () => { sessionID, promptText: "inspect src/tools/delegate-task and report the issue", category: "quick", + system: "delegated child system prompt", + tools: { call_omo_agent: true, question: false, task: false }, }) await hook.event({ @@ -538,14 +541,19 @@ describe("runtime-fallback", () => { const promptBody = promptCalls[0]?.body as { model?: { providerID?: string; modelID?: string } parts?: Array<{ type?: string; text?: string }> + system?: string + tools?: Record variant?: string } | undefined expect(promptBody?.model).toEqual({ providerID: "openai", modelID: "gpt-5.4" }) expect(promptBody?.variant).toBe("high") + expect(promptBody?.system).toBe("delegated child system prompt") + expect(promptBody?.tools?.question).toBe(false) + expect(promptBody?.tools?.call_omo_agent).toBe(true) expect(promptBody?.parts?.[0]?.text).toContain("inspect src/tools/delegate-task") }) - test("should discard delegated bootstrap once persisted user prompt exists", async () => { + test("should use persisted user prompt while preserving delegated bootstrap launch context", async () => { const promptCalls: Array> = [] const sessionID = "test-delegated-history-prefers-persisted-user" const hook = createRuntimeFallbackHook( @@ -577,6 +585,8 @@ describe("runtime-fallback", () => { registerDelegatedChildSessionBootstrap({ sessionID, promptText: "bootstrap copy should not be reused", + system: "persisted delegated child system prompt", + tools: { call_omo_agent: true, question: false, task: false }, }) SessionCategoryRegistry.register(sessionID, "test") @@ -593,8 +603,13 @@ describe("runtime-fallback", () => { expect(promptCalls).toHaveLength(1) const promptBody = promptCalls[0]?.body as { parts?: Array<{ type?: string; text?: string }> + system?: string + tools?: Record } | undefined expect(promptBody?.parts?.[0]?.text).toBe("persisted child task prompt") + expect(promptBody?.system).toBe("persisted delegated child system prompt") + expect(promptBody?.tools?.question).toBe(false) + expect(promptBody?.tools?.call_omo_agent).toBe(true) expect(getDelegatedChildSessionBootstrap(sessionID)).toBeUndefined() }) diff --git a/src/hooks/runtime-fallback/last-user-retry-parts.ts b/src/hooks/runtime-fallback/last-user-retry-parts.ts index 7705188a6..38a73eeec 100644 --- a/src/hooks/runtime-fallback/last-user-retry-parts.ts +++ b/src/hooks/runtime-fallback/last-user-retry-parts.ts @@ -4,10 +4,26 @@ import { getDelegatedChildSessionBootstrap, } from "../../shared/delegated-child-session-bootstrap" +type RetryPart = { type: "text"; text: string } + +export type LastUserRetryPayload = { + retryParts: RetryPart[] + system?: string + tools?: Record +} + export function getLastUserRetryParts( messagesResponse: unknown, sessionID?: string, -): Array<{ type: "text"; text: string }> { +): RetryPart[] { + return getLastUserRetryPayload(messagesResponse, sessionID).retryParts +} + +export function getLastUserRetryPayload( + messagesResponse: unknown, + sessionID?: string, +): LastUserRetryPayload { + const bootstrap = sessionID ? getDelegatedChildSessionBootstrap(sessionID) : undefined const messages = extractSessionMessages(messagesResponse) const lastUserMessage = messages?.filter((message) => message.info?.role === "user").pop() const lastUserParts = @@ -27,12 +43,20 @@ export function getLastUserRetryParts( if (sessionID) { clearDelegatedChildSessionBootstrap(sessionID) } - return retryParts + return { + retryParts, + ...(bootstrap?.system ? { system: bootstrap.system } : {}), + ...(bootstrap?.tools ? { tools: bootstrap.tools } : {}), + } } if (!sessionID) { - return retryParts + return { retryParts } } - return getDelegatedChildSessionBootstrap(sessionID)?.retryParts ?? [] + return { + retryParts: bootstrap?.retryParts ?? [], + ...(bootstrap?.system ? { system: bootstrap.system } : {}), + ...(bootstrap?.tools ? { tools: bootstrap.tools } : {}), + } } diff --git a/src/hooks/runtime-fallback/types.ts b/src/hooks/runtime-fallback/types.ts index c05bfe952..aaa6436e1 100644 --- a/src/hooks/runtime-fallback/types.ts +++ b/src/hooks/runtime-fallback/types.ts @@ -16,6 +16,8 @@ export interface RuntimeFallbackPluginInput { body: { agent?: string model: { providerID: string; modelID: string } + system?: string + tools?: Record parts: Array<{ type: "text"; text: string }> } query: { directory: string }