From ef26e8dfce77cee19aab66a123b00be74aeeb5c0 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 1 Jun 2026 19:04:05 +0200 Subject: [PATCH] fix(repro): clean webchat tts proof artifacts --- scripts/repro/webchat-auto-tts-live-proof.mjs | 125 ++++++++++-------- .../webchat-auto-tts-live-proof.test.ts | 24 ++++ 2 files changed, 91 insertions(+), 58 deletions(-) create mode 100644 test/scripts/webchat-auto-tts-live-proof.test.ts diff --git a/scripts/repro/webchat-auto-tts-live-proof.mjs b/scripts/repro/webchat-auto-tts-live-proof.mjs index 8e70a08696ed..1addfa8dc23f 100644 --- a/scripts/repro/webchat-auto-tts-live-proof.mjs +++ b/scripts/repro/webchat-auto-tts-live-proof.mjs @@ -6,6 +6,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { pathToFileURL } from "node:url"; import { maybeApplyTtsToPayload } from "../../packages/speech-core/src/tts.ts"; import { buildWebchatAudioContentBlocksFromReplyPayloads } from "../../src/gateway/server-methods/chat-webchat-media.ts"; import { createPluginRecord } from "../../src/plugins/loader-records.ts"; @@ -22,8 +23,16 @@ const noopLogger = { debug() {}, }; +export function cleanupProofArtifacts({ mediaPath, prefsPath }) { + if (mediaPath) { + fs.rmSync(path.dirname(mediaPath), { recursive: true, force: true }); + } + fs.rmSync(prefsPath, { force: true }); +} + async function main() { resetPluginRuntimeStateForTest(); + let mediaPath; const pluginRegistry = createPluginRegistry({ logger: noopLogger, runtime: {}, @@ -62,65 +71,65 @@ async function main() { }, }; - const accumulatedBlockText = - "WebChat streams block text; dispatch synthesizes one TTS tail with kind final."; - const blockResult = await maybeApplyTtsToPayload({ - payload: { text: accumulatedBlockText }, - cfg, - channel: "webchat", - kind: "block", - }); - console.log("maybeApplyTtsToPayload(kind=block).mediaUrl =", blockResult.mediaUrl ?? "(none)"); - - const tailResult = await maybeApplyTtsToPayload({ - payload: { text: accumulatedBlockText }, - cfg, - channel: "webchat", - kind: "final", - }); - console.log("maybeApplyTtsToPayload(kind=final).mediaUrl =", tailResult.mediaUrl ?? "(none)"); - console.log( - "maybeApplyTtsToPayload(kind=final).trustedLocalMedia =", - tailResult.trustedLocalMedia ?? false, - ); - - const mediaPath = tailResult.mediaUrl; - if (!mediaPath || !fs.existsSync(mediaPath)) { - throw new Error("expected final-mode tail TTS to write a local media file"); - } - - // Same shape as dispatch-from-config accumulated block TTS-only final payload. - const ttsOnlyPayload = { - mediaUrl: tailResult.mediaUrl, - audioAsVoice: tailResult.audioAsVoice, - spokenText: accumulatedBlockText, - trustedLocalMedia: true, - }; - console.log("dispatch ttsOnlyPayload.trustedLocalMedia =", ttsOnlyPayload.trustedLocalMedia); - - const localRoots = [path.dirname(mediaPath)]; - const trustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads([ttsOnlyPayload], { - localRoots, - }); - const untrustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads( - [{ mediaUrl: mediaPath }], - { localRoots }, - ); - console.log( - "buildWebchatAudioContentBlocksFromReplyPayloads(ttsOnlyPayload).length =", - trustedBlocks.length, - ); - console.log( - "buildWebchatAudioContentBlocksFromReplyPayloads(untrusted).length =", - untrustedBlocks.length, - ); - - fs.rmSync(path.dirname(mediaPath), { recursive: true, force: true }); try { - fs.unlinkSync(prefsPath); - } catch { - // optional prefs file + const accumulatedBlockText = + "WebChat streams block text; dispatch synthesizes one TTS tail with kind final."; + const blockResult = await maybeApplyTtsToPayload({ + payload: { text: accumulatedBlockText }, + cfg, + channel: "webchat", + kind: "block", + }); + console.log("maybeApplyTtsToPayload(kind=block).mediaUrl =", blockResult.mediaUrl ?? "(none)"); + + const tailResult = await maybeApplyTtsToPayload({ + payload: { text: accumulatedBlockText }, + cfg, + channel: "webchat", + kind: "final", + }); + console.log("maybeApplyTtsToPayload(kind=final).mediaUrl =", tailResult.mediaUrl ?? "(none)"); + console.log( + "maybeApplyTtsToPayload(kind=final).trustedLocalMedia =", + tailResult.trustedLocalMedia ?? false, + ); + + mediaPath = tailResult.mediaUrl; + if (!mediaPath || !fs.existsSync(mediaPath)) { + throw new Error("expected final-mode tail TTS to write a local media file"); + } + + // Same shape as dispatch-from-config accumulated block TTS-only final payload. + const ttsOnlyPayload = { + mediaUrl: tailResult.mediaUrl, + audioAsVoice: tailResult.audioAsVoice, + spokenText: accumulatedBlockText, + trustedLocalMedia: true, + }; + console.log("dispatch ttsOnlyPayload.trustedLocalMedia =", ttsOnlyPayload.trustedLocalMedia); + + const localRoots = [path.dirname(mediaPath)]; + const trustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads([ttsOnlyPayload], { + localRoots, + }); + const untrustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads( + [{ mediaUrl: mediaPath }], + { localRoots }, + ); + console.log( + "buildWebchatAudioContentBlocksFromReplyPayloads(ttsOnlyPayload).length =", + trustedBlocks.length, + ); + console.log( + "buildWebchatAudioContentBlocksFromReplyPayloads(untrusted).length =", + untrustedBlocks.length, + ); + } finally { + cleanupProofArtifacts({ mediaPath, prefsPath }); + resetPluginRuntimeStateForTest(); } } -await main(); +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + await main(); +} diff --git a/test/scripts/webchat-auto-tts-live-proof.test.ts b/test/scripts/webchat-auto-tts-live-proof.test.ts new file mode 100644 index 000000000000..33ad06b0198f --- /dev/null +++ b/test/scripts/webchat-auto-tts-live-proof.test.ts @@ -0,0 +1,24 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { cleanupProofArtifacts } from "../../scripts/repro/webchat-auto-tts-live-proof.mjs"; + +describe("webchat auto TTS live proof", () => { + it("cleans generated media and prefs artifacts", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-webchat-tts-proof-test-")); + const mediaDir = path.join(root, "media"); + const mediaPath = path.join(mediaDir, "voice.ogg"); + const prefsPath = path.join(root, "prefs.json"); + fs.mkdirSync(mediaDir, { recursive: true }); + fs.writeFileSync(mediaPath, "voice"); + fs.writeFileSync(prefsPath, "{}\n"); + + cleanupProofArtifacts({ mediaPath, prefsPath }); + + expect(fs.existsSync(mediaDir)).toBe(false); + expect(fs.existsSync(prefsPath)).toBe(false); + cleanupProofArtifacts({ mediaPath, prefsPath }); + fs.rmSync(root, { force: true, recursive: true }); + }); +});