

























@@ -0,0 +1,118 @@
1+#!/usr/bin/env node
2+/**
3+ * Live repro for WebChat auto-TTS fix (PR #82701).
4+ * Run: pnpm exec tsx scripts/repro/webchat-auto-tts-live-proof.mjs
5+ */
6+import fs from "node:fs";
7+import os from "node:os";
8+import path from "node:path";
9+import { maybeApplyTtsToPayload } from "../../extensions/speech-core/src/tts.ts";
10+import { buildWebchatAudioContentBlocksFromReplyPayloads } from "../../src/gateway/server-methods/chat-webchat-media.ts";
11+import { createPluginRecord } from "../../src/plugins/loader-records.ts";
12+import { createPluginRegistry } from "../../src/plugins/registry.ts";
13+import {
14+resetPluginRuntimeStateForTest,
15+setActivePluginRegistry,
16+} from "../../src/plugins/runtime.ts";
17+18+const noopLogger = {
19+info() {},
20+warn() {},
21+error() {},
22+debug() {},
23+};
24+25+async function main() {
26+resetPluginRuntimeStateForTest();
27+const pluginRegistry = createPluginRegistry({
28+logger: noopLogger,
29+runtime: {},
30+activateGlobalSideEffects: false,
31+});
32+const record = createPluginRecord({
33+id: "repro-mock-tts",
34+name: "Repro Mock TTS",
35+source: "scripts/repro/webchat-auto-tts-live-proof.mjs",
36+origin: "global",
37+enabled: true,
38+configSchema: false,
39+});
40+pluginRegistry.registerSpeechProvider(record, {
41+id: "mock",
42+label: "Mock",
43+autoSelectOrder: 1,
44+isConfigured: () => true,
45+synthesize: async (request) => ({
46+audioBuffer: Buffer.from("voice"),
47+fileExtension: ".ogg",
48+outputFormat: "ogg",
49+voiceCompatible: request.target === "voice-note",
50+}),
51+});
52+setActivePluginRegistry(pluginRegistry.registry);
53+54+const prefsPath = path.join(os.tmpdir(), `openclaw-webchat-tts-proof-${process.pid}.json`);
55+const cfg = {
56+messages: {
57+tts: {
58+enabled: true,
59+provider: "mock",
60+ prefsPath,
61+},
62+},
63+};
64+65+const blockText = "WebChat block replies should synthesize audio for auto TTS.";
66+const blockResult = await maybeApplyTtsToPayload({
67+payload: { text: blockText },
68+ cfg,
69+channel: "webchat",
70+kind: "block",
71+});
72+console.log("maybeApplyTtsToPayload(kind=block).mediaUrl =", blockResult.mediaUrl ?? "(none)");
73+console.log(
74+"maybeApplyTtsToPayload(kind=block).trustedLocalMedia =",
75+blockResult.trustedLocalMedia ?? false,
76+);
77+78+const toolResult = await maybeApplyTtsToPayload({
79+payload: { text: "Intermediate tool output should not be spoken." },
80+ cfg,
81+channel: "webchat",
82+kind: "tool",
83+});
84+console.log("maybeApplyTtsToPayload(kind=tool).mediaUrl =", toolResult.mediaUrl ?? "(none)");
85+86+const mediaPath = blockResult.mediaUrl;
87+if (!mediaPath || !fs.existsSync(mediaPath)) {
88+throw new Error("expected block TTS to write a local media file");
89+}
90+const localRoots = [path.dirname(mediaPath)];
91+const trustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads(
92+[{ mediaUrl: mediaPath, trustedLocalMedia: true }],
93+{ localRoots },
94+);
95+const untrustedBlocks = await buildWebchatAudioContentBlocksFromReplyPayloads(
96+[{ mediaUrl: mediaPath }],
97+{ localRoots },
98+);
99+console.log(
100+"buildWebchatAudioContentBlocksFromReplyPayloads(trustedLocalMedia=true).length =",
101+trustedBlocks.length,
102+);
103+console.log(
104+"buildWebchatAudioContentBlocksFromReplyPayloads(trustedLocalMedia missing).length =",
105+untrustedBlocks.length,
106+);
107+108+if (blockResult.mediaUrl) {
109+fs.rmSync(path.dirname(blockResult.mediaUrl), { recursive: true, force: true });
110+}
111+try {
112+fs.unlinkSync(prefsPath);
113+} catch {
114+// optional prefs file
115+}
116+}
117+118+await main();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。