From 4eb4b87c8ede4209608f55a6eff94efc45e727c2 Mon Sep 17 00:00:00 2001 From: ai-hpc Date: Fri, 5 Jun 2026 15:38:53 +0200 Subject: [PATCH] fix(cron): recover no-deliver tool warnings --- .../channel-output-policy.test.ts | 13 ++++++ .../isolated-agent/channel-output-policy.ts | 7 ++- src/cron/isolated-agent/run-executor.ts | 7 ++- .../run.message-tool-policy.test.ts | 46 +++++++++++++++++++ src/cron/isolated-agent/run.ts | 5 +- 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/cron/isolated-agent/channel-output-policy.test.ts b/src/cron/isolated-agent/channel-output-policy.test.ts index 452878509eab..f715a5838b01 100644 --- a/src/cron/isolated-agent/channel-output-policy.test.ts +++ b/src/cron/isolated-agent/channel-output-policy.test.ts @@ -45,6 +45,19 @@ describe("cron channel output policy", () => { }); }); + it("prefers final visible text only for unresolved no-delivery runs", async () => { + await expect( + resolveCronChannelOutputPolicy(undefined, { deliveryRequested: false }), + ).resolves.toEqual({ + preferFinalAssistantVisibleText: true, + }); + await expect( + resolveCronChannelOutputPolicy(undefined, { deliveryRequested: true }), + ).resolves.toEqual({ + preferFinalAssistantVisibleText: false, + }); + }); + it("lets channel plugins format current tool context targets", async () => { await expect( resolveCurrentChannelTarget({ diff --git a/src/cron/isolated-agent/channel-output-policy.ts b/src/cron/isolated-agent/channel-output-policy.ts index e108142c8ea9..cfcba842a2e3 100644 --- a/src/cron/isolated-agent/channel-output-policy.ts +++ b/src/cron/isolated-agent/channel-output-policy.ts @@ -13,12 +13,15 @@ async function loadChannelPluginRuntime() { } /** Resolves channel-specific cron output preferences from loaded channel plugins. */ -export async function resolveCronChannelOutputPolicy(channel: string | undefined): Promise<{ +export async function resolveCronChannelOutputPolicy( + channel: string | undefined, + opts?: { deliveryRequested?: boolean }, +): Promise<{ preferFinalAssistantVisibleText: boolean; }> { const channelId = normalizeOptionalLowercaseString(channel); if (!channelId) { - return { preferFinalAssistantVisibleText: false }; + return { preferFinalAssistantVisibleText: opts?.deliveryRequested === false }; } const { getChannelPlugin } = await loadChannelPluginRuntime(); return { diff --git a/src/cron/isolated-agent/run-executor.ts b/src/cron/isolated-agent/run-executor.ts index 7bceaab2835c..92768caca559 100644 --- a/src/cron/isolated-agent/run-executor.ts +++ b/src/cron/isolated-agent/run-executor.ts @@ -141,6 +141,7 @@ export function createCronPromptExecutor(params: { to?: string; threadId?: string | number; }; + deliveryRequested?: boolean; sourceDelivery: SourceDeliveryPlan; skillsSnapshot: SkillSnapshot; agentPayload: AgentTurnPayload; @@ -378,6 +379,7 @@ export async function executeCronRun(params: { to?: string; threadId?: string | number; }; + deliveryRequested?: boolean; sourceDelivery: SourceDeliveryPlan; skillsSnapshot: SkillSnapshot; agentPayload: AgentTurnPayload; @@ -429,6 +431,7 @@ export async function executeCronRun(params: { runTimeoutOverrideMs: params.runTimeoutOverrideMs, suppressExecNotifyOnExit: params.suppressExecNotifyOnExit, resolvedDelivery: params.resolvedDelivery, + deliveryRequested: params.deliveryRequested, sourceDelivery: params.sourceDelivery, skillsSnapshot: params.skillsSnapshot, agentPayload: params.agentPayload, @@ -501,7 +504,9 @@ export async function executeCronRun(params: { failureSignal: runResult.meta?.failureSignal, finalAssistantVisibleText: runResult.meta?.finalAssistantVisibleText, preferFinalAssistantVisibleText: ( - await resolveCronChannelOutputPolicy(params.resolvedDelivery.channel) + await resolveCronChannelOutputPolicy(params.resolvedDelivery.channel, { + deliveryRequested: params.deliveryRequested, + }) ).preferFinalAssistantVisibleText, }); const interimText = interimOutputText?.trim() ?? ""; diff --git a/src/cron/isolated-agent/run.message-tool-policy.test.ts b/src/cron/isolated-agent/run.message-tool-policy.test.ts index 20e91a226a54..252793eea368 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -395,6 +395,52 @@ describe("runCronIsolatedAgentTurn message tool policy", () => { expect(embeddedRun.messageTo).toBeUndefined(); }); + it("uses final assistant text to recover tool warnings for bare no-deliver runs", async () => { + mockRunCronFallbackPassthrough(); + resolveCronDeliveryPlanMock.mockReturnValue({ + requested: false, + mode: "none", + }); + resolveCronPayloadOutcomeMock.mockReturnValue({ + summary: "Final cron report from the agent.", + outputText: "Final cron report from the agent.", + synthesizedText: "Final cron report from the agent.", + deliveryPayload: { text: "Final cron report from the agent." }, + deliveryPayloads: [{ text: "Final cron report from the agent." }], + deliveryPayloadHasStructuredContent: false, + hasFatalErrorPayload: false, + hasFatalStructuredErrorPayload: false, + embeddedRunError: undefined, + }); + runEmbeddedAgentMock.mockResolvedValue({ + payloads: [{ text: "⚠️ 🛠️ show > (agent) failed", isError: true }], + meta: { + finalAssistantVisibleText: "Final cron report from the agent.", + agentMeta: { usage: { input: 10, output: 20 } }, + }, + }); + + const result = await runCronIsolatedAgentTurn({ + ...makeParams(), + job: makeMessageToolPolicyJob({ mode: "none" }), + }); + + expect(result.status).toBe("ok"); + expect(result.error).toBeUndefined(); + expect(result.summary).toBe("Final cron report from the agent."); + expect(result.outputText).toBe("Final cron report from the agent."); + expect(resolveCronPayloadOutcomeMock).toHaveBeenCalledWith( + expect.objectContaining({ + finalAssistantVisibleText: "Final cron report from the agent.", + preferFinalAssistantVisibleText: true, + }), + ); + expectDispatchFields({ + deliveryRequested: false, + deliveryPayloads: [{ text: "Final cron report from the agent." }], + }); + }); + it('suppresses automatic exec completion notifications when delivery.mode is "none"', async () => { mockRunCronFallbackPassthrough(); resolveCronDeliveryPlanMock.mockReturnValue({ diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 89a6010f4d06..133cd9ddba12 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -1049,7 +1049,9 @@ async function finalizeCronRun(params: { failureSignal: finalRunResult.meta?.failureSignal, finalAssistantVisibleText: finalRunResult.meta?.finalAssistantVisibleText, preferFinalAssistantVisibleText: ( - await resolveCronChannelOutputPolicy(prepared.resolvedDelivery.channel) + await resolveCronChannelOutputPolicy(prepared.resolvedDelivery.channel, { + deliveryRequested: prepared.deliveryRequested, + }) ).preferFinalAssistantVisibleText, }); const { @@ -1323,6 +1325,7 @@ export async function runCronIsolatedAgentTurn(params: { accountId: prepared.context.resolvedDelivery.accountId, threadId: prepared.context.resolvedDelivery.threadId, }, + deliveryRequested: prepared.context.deliveryRequested, sourceDelivery: prepared.context.sourceDelivery, skillsSnapshot: prepared.context.skillsSnapshot, agentPayload: prepared.context.agentPayload,