From 2cb057fcd90fe1a0cbc1e63923846c705cee4820 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 03:21:50 +0100 Subject: [PATCH] fix: harden async media completion delivery --- src/agents/tools/media-generate-background-shared.ts | 5 +++-- src/agents/tools/music-generate-background.test.ts | 6 +++--- src/agents/tools/video-generate-background.test.ts | 6 +++--- .../reply/dispatch-from-config.reply-dispatch.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/agents/tools/media-generate-background-shared.ts b/src/agents/tools/media-generate-background-shared.ts index b0a273694c0..959db3b78fe 100644 --- a/src/agents/tools/media-generate-background-shared.ts +++ b/src/agents/tools/media-generate-background-shared.ts @@ -1,5 +1,6 @@ import crypto from "node:crypto"; import { parseReplyDirectives } from "../../auto-reply/reply/reply-directives.js"; +import { SILENT_REPLY_TOKEN } from "../../auto-reply/tokens.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { parseAgentSessionKey } from "../../sessions/session-key-utils.js"; import { @@ -147,8 +148,8 @@ function buildMediaGenerationReplyInstruction(params: { if (params.status === "ok") { return [ `A completed ${params.completionLabel} generation task is ready for user delivery.`, - `Reply in your normal assistant voice and post the finished ${params.completionLabel} to the original message channel now.`, - "If the result includes MEDIA: lines, include those exact MEDIA: lines in your reply so OpenClaw attaches the generated media.", + `Prefer the message tool for delivery: use action="send" to the current/original chat, put your user-facing caption in message, attach each generated file with path/filePath using the exact path from the result, then reply ONLY: ${SILENT_REPLY_TOKEN}.`, + `If you cannot use the message tool, reply in your normal assistant voice and include the exact MEDIA: lines from the result so OpenClaw attaches the finished ${params.completionLabel}.`, "Keep internal task/session details private and do not copy the internal event text verbatim.", ].join(" "); } diff --git a/src/agents/tools/music-generate-background.test.ts b/src/agents/tools/music-generate-background.test.ts index 6b9eb7ce5c5..3ce97c6630f 100644 --- a/src/agents/tools/music-generate-background.test.ts +++ b/src/agents/tools/music-generate-background.test.ts @@ -148,16 +148,16 @@ describe("music generate background helpers", () => { to: "channel:1", }), expectsCompletionMessage: true, - internalEvents: [ + internalEvents: expect.arrayContaining([ expect.objectContaining({ source: "music_generation", announceType: "music generation task", status: "ok", result: expect.stringContaining("MEDIA:/tmp/generated-night-drive.mp3"), mediaUrls: ["/tmp/generated-night-drive.mp3"], - replyInstruction: expect.stringContaining("include those exact MEDIA: lines"), + replyInstruction: expect.stringContaining("Prefer the message tool for delivery"), }), - ], + ]), }), ); }); diff --git a/src/agents/tools/video-generate-background.test.ts b/src/agents/tools/video-generate-background.test.ts index d03ff6c5d0d..e078aaf92b1 100644 --- a/src/agents/tools/video-generate-background.test.ts +++ b/src/agents/tools/video-generate-background.test.ts @@ -148,16 +148,16 @@ describe("video generate background helpers", () => { to: "channel:1", }), expectsCompletionMessage: true, - internalEvents: [ + internalEvents: expect.arrayContaining([ expect.objectContaining({ source: "video_generation", announceType: "video generation task", status: "ok", result: expect.stringContaining("MEDIA:/tmp/generated-lobster.mp4"), mediaUrls: ["/tmp/generated-lobster.mp4"], - replyInstruction: expect.stringContaining("include those exact MEDIA: lines"), + replyInstruction: expect.stringContaining("Prefer the message tool for delivery"), }), - ], + ]), }), ); }); diff --git a/src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts b/src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts index f09ee73c92c..d4e2e55396e 100644 --- a/src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts @@ -400,7 +400,7 @@ describe("dispatchReplyFromConfig reply_dispatch hook", () => { ctx: createHookCtx(), cfg: emptyConfig, dispatcher: createDispatcher(), - fastAbortResolver: () => ({ handled: false, aborted: false }), + fastAbortResolver: async () => ({ handled: false, aborted: false }), formatAbortReplyTextResolver: () => "⚙️ Agent was aborted.", replyResolver: async () => ({ text: "model reply" }), }); @@ -425,7 +425,7 @@ describe("dispatchReplyFromConfig reply_dispatch hook", () => { handled: false, queuedFinal: false, counts: { tool: 0, block: 0, final: 0 }, - }); + } satisfies PluginHookReplyDispatchResult); const result = await dispatchReplyFromConfig({ ctx: createHookCtx(),