Skip to content

Commit 2afd657

Browse files
committed
fix: preserve talk provider and speaking state
1 parent 61965e5 commit 2afd657

File tree

3 files changed

+53
-3
lines changed

3 files changed

+53
-3
lines changed

apps/android/app/src/main/java/ai/openclaw/app/voice/TalkModeManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ class TalkModeManager(
748748

749749
private suspend fun playGatewaySpeech(speech: GatewayTalkSpeech, playbackToken: Long) {
750750
ensurePlaybackActive(playbackToken)
751-
stopSpeaking(resetInterrupt = false)
751+
cleanupPlayer()
752752
ensurePlaybackActive(playbackToken)
753753

754754
val audioBytes =

src/gateway/server-methods/talk.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,6 @@ function buildTalkTtsConfig(
171171
...(proxy == null ? {} : { proxy }),
172172
...(timeoutMs == null ? {} : { timeoutMs }),
173173
};
174-
} else {
175-
return { error: `talk.speak unavailable: unsupported talk provider '${resolved.provider}'` };
176174
}
177175

178176
return {

src/gateway/server.talk-config.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
publicKeyRawBase64UrlFromPem,
77
signDevicePayload,
88
} from "../infra/device-identity.js";
9+
import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
10+
import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js";
911
import { withEnvAsync } from "../test-utils/env.js";
1012
import { buildDeviceAuthPayload } from "./device-auth.js";
1113
import { validateTalkConfigResult } from "./protocol/index.js";
@@ -348,4 +350,54 @@ describe("gateway talk.config", () => {
348350
globalThis.fetch = originalFetch;
349351
}
350352
});
353+
354+
it("allows extension speech providers through talk.speak", async () => {
355+
const { writeConfigFile } = await import("../config/config.js");
356+
await writeConfigFile({
357+
talk: {
358+
provider: "acme",
359+
providers: {
360+
acme: {
361+
voiceId: "plugin-voice",
362+
},
363+
},
364+
},
365+
});
366+
367+
const previousRegistry = getActivePluginRegistry() ?? createEmptyPluginRegistry();
368+
setActivePluginRegistry({
369+
...createEmptyPluginRegistry(),
370+
speechProviders: [
371+
{
372+
pluginId: "acme-plugin",
373+
source: "test",
374+
provider: {
375+
id: "acme",
376+
label: "Acme Speech",
377+
isConfigured: () => true,
378+
synthesize: async () => ({
379+
audioBuffer: Buffer.from([7, 8, 9]),
380+
outputFormat: "mp3",
381+
fileExtension: ".mp3",
382+
voiceCompatible: false,
383+
}),
384+
},
385+
},
386+
],
387+
});
388+
389+
try {
390+
await withServer(async (ws) => {
391+
await connectOperator(ws, ["operator.read", "operator.write"]);
392+
const res = await fetchTalkSpeak(ws, {
393+
text: "Hello from plugin talk mode.",
394+
});
395+
expect(res.ok).toBe(true);
396+
expect(res.payload?.provider).toBe("acme");
397+
expect(res.payload?.audioBase64).toBe(Buffer.from([7, 8, 9]).toString("base64"));
398+
});
399+
} finally {
400+
setActivePluginRegistry(previousRegistry);
401+
}
402+
});
351403
});

0 commit comments

Comments
 (0)