fix(plugins): include inherited tts persona providers at startup

This commit is contained in:
Peter Steinberger
2026-05-03 13:17:12 +01:00
parent 4bc82fc787
commit 2b82c05a7f
3 changed files with 80 additions and 6 deletions

View File

@@ -54,6 +54,7 @@ Docs: https://docs.openclaw.ai
- Feishu: suppress duplicate text when replies send native voice media while preserving captions for ordinary audio files and falling back to text plus attachment links when voice uploads fail.
- Feishu: send the skipped reply text when `audioAsVoice` falls back to a generic file attachment after transcode failure, so voice-intent replies do not lose their caption.
- TTS/plugins: activate the configured speech provider plugin during Gateway startup, so Microsoft and Local CLI voice replies work immediately after selecting them instead of staying invisible in the startup plugin set. Fixes #76481. Thanks @amknight.
- TTS/plugins: include speech providers selected through inherited agent, channel, and account TTS personas during Gateway startup, matching the runtime TTS config merge. Carries forward #76481. Thanks @amknight.
- Feishu: keep packaged Feishu startup from bundling the Lark SDK's ESM `__dirname` path by loading the SDK as a plugin-local runtime dependency. Fixes #76291 and #76494. (#76392) Thanks @zqchris.
- Plugins/npm: build package-local runtime dist files for publishable plugins and stop listing root-package-excluded plugin sidecars in the core package metadata, so npm plugin installs such as `@openclaw/diffs` and `@openclaw/discord` no longer publish source-only runtime payloads. Fixes #76426. Thanks @PrinceOfEgypt.
- Channels/secrets: resolve SecretRef-backed channel credentials through external plugin secret contracts after the plugin split, covering runtime startup, target discovery, webhook auth, disabled-account enumeration, and late-bound web_search config. Fixes #76371. (#76449) Thanks @joshavant and @neeravmakwana.

View File

@@ -635,6 +635,68 @@ describe("resolveGatewayStartupPluginIds", () => {
} as OpenClawConfig,
["browser", "microsoft", "memory-core"],
],
[
"includes agent-inherited active persona speech providers at startup",
{
channels: {},
messages: {
tts: {
personas: {
narrator: {
label: "Narrator",
provider: "microsoft",
},
},
},
},
agents: {
list: [{ id: "reader", tts: { persona: "narrator" } }],
},
} as OpenClawConfig,
["browser", "microsoft", "memory-core"],
],
[
"includes channel-inherited active persona speech providers at startup",
{
channels: {
"demo-channel": { tts: { persona: "narrator" } },
},
messages: {
tts: {
personas: {
narrator: {
label: "Narrator",
provider: "microsoft",
},
},
},
},
} as OpenClawConfig,
["demo-channel", "browser", "microsoft", "memory-core"],
],
[
"includes account-inherited active persona speech providers at startup",
{
channels: {
"demo-channel": {
accounts: {
primary: { tts: { persona: "narrator" } },
},
},
},
messages: {
tts: {
personas: {
narrator: {
label: "Narrator",
provider: "microsoft",
},
},
},
},
} as OpenClawConfig,
["demo-channel", "browser", "microsoft", "memory-core"],
],
[
"honors disabled speech provider config blocks at startup",
{

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { resolveEffectiveTtsConfig } from "../tts/tts-config.js";
const TTS_PROVIDER_CONFIG_RESERVED_KEYS = new Set([
"auto",
@@ -141,33 +142,43 @@ function addConfiguredTtsProviderIds(target: Set<string>, value: unknown): void
export function collectConfiguredSpeechProviderIds(config: OpenClawConfig): ReadonlySet<string> {
const configured = new Set<string>();
addConfiguredTtsProviderIds(configured, config.messages?.tts);
addConfiguredTtsProviderIds(configured, resolveEffectiveTtsConfig(config));
const agents = config.agents;
if (isRecord(agents) && Array.isArray(agents.list)) {
for (const agent of agents.list) {
if (isRecord(agent)) {
addConfiguredTtsProviderIds(configured, agent.tts);
if (typeof agent.id === "string") {
addConfiguredTtsProviderIds(
configured,
resolveEffectiveTtsConfig(config, { agentId: agent.id }),
);
} else {
addConfiguredTtsProviderIds(configured, agent.tts);
}
}
}
}
const channels = config.channels;
if (isRecord(channels)) {
for (const channelConfig of Object.values(channels)) {
for (const [channelId, channelConfig] of Object.entries(channels)) {
if (!isRecord(channelConfig)) {
continue;
}
addConfiguredTtsProviderIds(configured, channelConfig.tts);
addConfiguredTtsProviderIds(configured, resolveEffectiveTtsConfig(config, { channelId }));
if (isRecord(channelConfig.voice)) {
addConfiguredTtsProviderIds(configured, channelConfig.voice.tts);
}
if (isRecord(channelConfig.accounts)) {
for (const accountConfig of Object.values(channelConfig.accounts)) {
for (const [accountId, accountConfig] of Object.entries(channelConfig.accounts)) {
if (!isRecord(accountConfig)) {
continue;
}
addConfiguredTtsProviderIds(configured, accountConfig.tts);
addConfiguredTtsProviderIds(
configured,
resolveEffectiveTtsConfig(config, { channelId, accountId }),
);
if (isRecord(accountConfig.voice)) {
addConfiguredTtsProviderIds(configured, accountConfig.voice.tts);
}