





















@@ -7,6 +7,7 @@ import { asObjectRecord, normalizeLegacyChannelAliases } from "openclaw/plugin-s
77import { resolveDiscordPreviewStreamMode } from "./preview-streaming.js";
8899const LEGACY_TTS_PROVIDER_KEYS = ["openai", "elevenlabs", "microsoft", "edge"] as const;
10+const DISCORD_REALTIME_WAKE_NAME_MAX_WORDS = 2;
1011type AgentBindingConfig = NonNullable<OpenClawConfig["bindings"]>[number];
11121213function hasLegacyTtsProviderKeys(value: unknown): boolean {
@@ -77,6 +78,51 @@ function hasLegacyDiscordAccountGuildChannelAgentId(value: unknown): boolean {
7778return Object.values(accounts).some((account) => hasLegacyDiscordGuildChannelAgentId(account));
7879}
798081+function realtimeWakeNameWordCount(value: string): number {
82+return Array.from(value.matchAll(/[a-z0-9]+/gi)).length;
83+}
84+85+function normalizeRealtimeWakeName(value: string): string | undefined {
86+const words = Array.from(value.matchAll(/[a-z0-9]+/gi), (match) => match[0]);
87+if (words.length === 0) {
88+return undefined;
89+}
90+return words.slice(0, DISCORD_REALTIME_WAKE_NAME_MAX_WORDS).join(" ");
91+}
92+93+function isSupportedRealtimeWakeName(value: string): boolean {
94+const wordCount = realtimeWakeNameWordCount(value);
95+return wordCount >= 1 && wordCount <= DISCORD_REALTIME_WAKE_NAME_MAX_WORDS;
96+}
97+98+function hasUnsupportedRealtimeWakeNamesInVoice(value: unknown): boolean {
99+const voice = asObjectRecord(value);
100+const realtime = asObjectRecord(voice?.realtime);
101+const wakeNames = realtime?.wakeNames;
102+return Array.isArray(wakeNames)
103+ ? wakeNames.length === 0 ||
104+wakeNames.some(
105+(wakeName) => typeof wakeName === "string" && !isSupportedRealtimeWakeName(wakeName),
106+)
107+ : false;
108+}
109+110+function hasUnsupportedDiscordRealtimeWakeNames(value: unknown): boolean {
111+const entry = asObjectRecord(value);
112+if (!entry) {
113+return false;
114+}
115+return hasUnsupportedRealtimeWakeNamesInVoice(entry.voice);
116+}
117+118+function hasUnsupportedDiscordAccountRealtimeWakeNames(value: unknown): boolean {
119+const accounts = asObjectRecord(value);
120+if (!accounts) {
121+return false;
122+}
123+return Object.values(accounts).some((account) => hasUnsupportedDiscordRealtimeWakeNames(account));
124+}
125+80126function mergeMissing(target: Record<string, unknown>, source: Record<string, unknown>) {
81127for (const [key, value] of Object.entries(source)) {
82128if (value === undefined) {
@@ -152,6 +198,83 @@ function migrateLegacyTtsConfig(
152198return changed;
153199}
154200201+function normalizeUnsupportedRealtimeWakeNames(
202+entry: Record<string, unknown>,
203+pathPrefix: string,
204+changes: string[],
205+): { entry: Record<string, unknown>; changed: boolean } {
206+const voice = asObjectRecord(entry.voice);
207+const realtime = asObjectRecord(voice?.realtime);
208+const wakeNames = realtime?.wakeNames;
209+if (!voice || !realtime || !Array.isArray(wakeNames)) {
210+return { entry, changed: false };
211+}
212+213+if (wakeNames.length === 0) {
214+const nextRealtime = { ...realtime };
215+delete nextRealtime.wakeNames;
216+changes.push(
217+`Removed empty ${pathPrefix}.voice.realtime.wakeNames; unset wake names use the default agent/OpenClaw fallback.`,
218+);
219+return {
220+entry: {
221+ ...entry,
222+voice: {
223+ ...voice,
224+realtime: nextRealtime,
225+},
226+},
227+changed: true,
228+};
229+}
230+231+let normalized = 0;
232+let removed = 0;
233+const nextWakeNames = wakeNames.flatMap((wakeName) => {
234+if (typeof wakeName !== "string" || isSupportedRealtimeWakeName(wakeName)) {
235+return [wakeName];
236+}
237+const nextWakeName = normalizeRealtimeWakeName(wakeName);
238+if (!nextWakeName) {
239+removed += 1;
240+return [];
241+}
242+normalized += 1;
243+return [nextWakeName];
244+});
245+if (normalized === 0 && removed === 0) {
246+return { entry, changed: false };
247+}
248+const dedupedWakeNames = Array.from(new Set(nextWakeNames));
249+250+const nextRealtime = { ...realtime };
251+if (dedupedWakeNames.length > 0) {
252+nextRealtime.wakeNames = dedupedWakeNames;
253+} else {
254+delete nextRealtime.wakeNames;
255+}
256+if (normalized > 0) {
257+changes.push(
258+`Shortened ${normalized} unsupported ${pathPrefix}.voice.realtime.wakeNames entries to one or two words.`,
259+);
260+}
261+if (removed > 0) {
262+changes.push(
263+`Removed ${removed} unsupported ${pathPrefix}.voice.realtime.wakeNames entries with no usable words.`,
264+);
265+}
266+return {
267+entry: {
268+ ...entry,
269+voice: {
270+ ...voice,
271+realtime: nextRealtime,
272+},
273+},
274+changed: true,
275+};
276+}
277+155278function normalizeDiscordGuildChannelAllowAliases(params: {
156279entry: Record<string, unknown>;
157280pathPrefix: string;
@@ -343,6 +466,18 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
343466'channels.discord.accounts.<id>.guilds.<id>.channels.<id>.agentId is legacy; use top-level bindings[] with match.accountId for per-channel Discord agent routing. Run "openclaw doctor --fix".',
344467match: hasLegacyDiscordAccountGuildChannelAgentId,
345468},
469+{
470+path: ["channels", "discord"],
471+message:
472+'channels.discord.voice.realtime.wakeNames entries longer than two words are unsupported; use one- or two-word activation names. Run "openclaw doctor --fix".',
473+match: hasUnsupportedDiscordRealtimeWakeNames,
474+},
475+{
476+path: ["channels", "discord", "accounts"],
477+message:
478+'channels.discord.accounts.<id>.voice.realtime.wakeNames entries longer than two words are unsupported; use one- or two-word activation names. Run "openclaw doctor --fix".',
479+match: hasUnsupportedDiscordAccountRealtimeWakeNames,
480+},
346481];
347482348483export function normalizeCompatibilityConfig({
@@ -438,6 +573,13 @@ export function normalizeCompatibilityConfig({
438573});
439574nextAccount = normalizedAgentIds.entry;
440575accountChanged = accountChanged || normalizedAgentIds.changed;
576+const normalizedWakeNames = normalizeUnsupportedRealtimeWakeNames(
577+nextAccount,
578+`channels.discord.accounts.${accountId}`,
579+changes,
580+);
581+nextAccount = normalizedWakeNames.entry;
582+accountChanged = accountChanged || normalizedWakeNames.changed;
441583if (!accountChanged) {
442584continue;
443585}
@@ -458,6 +600,13 @@ export function normalizeCompatibilityConfig({
458600updated = { ...updated, voice };
459601changed = true;
460602}
603+const normalizedWakeNames = normalizeUnsupportedRealtimeWakeNames(
604+updated,
605+"channels.discord",
606+changes,
607+);
608+updated = normalizedWakeNames.entry;
609+changed = changed || normalizedWakeNames.changed;
461610462611if (!changed) {
463612return { config: cfg, changes: [] };
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。