




























@@ -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+type AgentBindingConfig = NonNullable<OpenClawConfig["bindings"]>[number];
10111112function hasLegacyTtsProviderKeys(value: unknown): boolean {
1213const tts = asObjectRecord(value);
@@ -44,6 +45,22 @@ function hasLegacyDiscordGuildChannelAllowAlias(value: unknown): boolean {
4445});
4546}
464748+function hasLegacyDiscordGuildChannelAgentId(value: unknown): boolean {
49+const guilds = asObjectRecord(asObjectRecord(value)?.guilds);
50+if (!guilds) {
51+return false;
52+}
53+return Object.values(guilds).some((guildValue) => {
54+const channels = asObjectRecord(asObjectRecord(guildValue)?.channels);
55+if (!channels) {
56+return false;
57+}
58+return Object.values(channels).some((channel) =>
59+Object.prototype.hasOwnProperty.call(asObjectRecord(channel) ?? {}, "agentId"),
60+);
61+});
62+}
63+4764function hasLegacyDiscordAccountGuildChannelAllowAlias(value: unknown): boolean {
4865const accounts = asObjectRecord(value);
4966if (!accounts) {
@@ -52,6 +69,14 @@ function hasLegacyDiscordAccountGuildChannelAllowAlias(value: unknown): boolean
5269return Object.values(accounts).some((account) => hasLegacyDiscordGuildChannelAllowAlias(account));
5370}
547172+function hasLegacyDiscordAccountGuildChannelAgentId(value: unknown): boolean {
73+const accounts = asObjectRecord(value);
74+if (!accounts) {
75+return false;
76+}
77+return Object.values(accounts).some((account) => hasLegacyDiscordGuildChannelAgentId(account));
78+}
79+5580function mergeMissing(target: Record<string, unknown>, source: Record<string, unknown>) {
5681for (const [key, value] of Object.entries(source)) {
5782if (value === undefined) {
@@ -179,6 +204,108 @@ function normalizeDiscordGuildChannelAllowAliases(params: {
179204 : { entry: params.entry, changed: false };
180205}
181206207+function isDiscordChannelAgentBinding(
208+value: unknown,
209+match: { accountId?: string; guildId: string; channelId: string },
210+): value is Record<string, unknown> {
211+const binding = asObjectRecord(value);
212+const bindingMatch = asObjectRecord(binding?.match);
213+const peer = asObjectRecord(bindingMatch?.peer);
214+if (!binding || !bindingMatch || !peer) {
215+return false;
216+}
217+return (
218+bindingMatch.channel === "discord" &&
219+bindingMatch.guildId === match.guildId &&
220+(match.accountId === undefined || bindingMatch.accountId === match.accountId) &&
221+peer.kind === "channel" &&
222+peer.id === match.channelId
223+);
224+}
225+226+function normalizeDiscordGuildChannelAgentIds(params: {
227+cfg: OpenClawConfig;
228+entry: Record<string, unknown>;
229+pathPrefix: string;
230+accountId?: string;
231+changes: string[];
232+bindingsToAdd: AgentBindingConfig[];
233+}): { entry: Record<string, unknown>; changed: boolean } {
234+const guilds = asObjectRecord(params.entry.guilds);
235+if (!guilds) {
236+return { entry: params.entry, changed: false };
237+}
238+239+const existingBindings = Array.isArray(params.cfg.bindings) ? params.cfg.bindings : [];
240+let changed = false;
241+const nextGuilds = { ...guilds };
242+for (const [guildId, guildValue] of Object.entries(guilds)) {
243+const guild = asObjectRecord(guildValue);
244+const channels = asObjectRecord(guild?.channels);
245+if (!guild || !channels) {
246+continue;
247+}
248+let channelsChanged = false;
249+const nextChannels = { ...channels };
250+for (const [channelId, channelValue] of Object.entries(channels)) {
251+const channel = asObjectRecord(channelValue);
252+if (!channel || !Object.prototype.hasOwnProperty.call(channel, "agentId")) {
253+continue;
254+}
255+const nextChannel = { ...channel };
256+const rawAgentId = nextChannel.agentId;
257+delete nextChannel.agentId;
258+nextChannels[channelId] = nextChannel;
259+channelsChanged = true;
260+261+const path = `${params.pathPrefix}.guilds.${guildId}.channels.${channelId}.agentId`;
262+const agentId = typeof rawAgentId === "string" ? rawAgentId.trim() : "";
263+if (!agentId) {
264+params.changes.push(
265+`Removed ${path}; configure top-level bindings[] for per-channel Discord agent routing.`,
266+);
267+continue;
268+}
269+270+const match = { accountId: params.accountId, guildId, channelId };
271+const existingBinding = existingBindings.find((binding) =>
272+isDiscordChannelAgentBinding(binding, match),
273+);
274+if (existingBinding) {
275+params.changes.push(
276+`Removed ${path}; a matching top-level bindings[] route already exists for Discord channel ${channelId}.`,
277+);
278+continue;
279+}
280+281+const bindingMatch: AgentBindingConfig["match"] = {
282+channel: "discord",
283+ guildId,
284+peer: { kind: "channel", id: channelId },
285+};
286+if (params.accountId) {
287+bindingMatch.accountId = params.accountId;
288+}
289+params.bindingsToAdd.push({
290+ agentId,
291+match: bindingMatch,
292+});
293+params.changes.push(
294+`Moved ${path} → top-level bindings[] route for Discord channel ${channelId}.`,
295+);
296+}
297+if (!channelsChanged) {
298+continue;
299+}
300+nextGuilds[guildId] = { ...guild, channels: nextChannels };
301+changed = true;
302+}
303+304+return changed
305+ ? { entry: { ...params.entry, guilds: nextGuilds }, changed: true }
306+ : { entry: params.entry, changed: false };
307+}
308+182309export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
183310{
184311path: ["channels", "discord", "voice", "tts"],
@@ -204,6 +331,18 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
204331'channels.discord.accounts.<id>.guilds.<id>.channels.<id>.allow is legacy; use channels.discord.accounts.<id>.guilds.<id>.channels.<id>.enabled instead. Run "openclaw doctor --fix".',
205332match: hasLegacyDiscordAccountGuildChannelAllowAlias,
206333},
334+{
335+path: ["channels", "discord"],
336+message:
337+'channels.discord.guilds.<id>.channels.<id>.agentId is legacy; use top-level bindings[] for per-channel Discord agent routing. Run "openclaw doctor --fix".',
338+match: hasLegacyDiscordGuildChannelAgentId,
339+},
340+{
341+path: ["channels", "discord", "accounts"],
342+message:
343+'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".',
344+match: hasLegacyDiscordAccountGuildChannelAgentId,
345+},
207346];
208347209348export function normalizeCompatibilityConfig({
@@ -219,6 +358,7 @@ export function normalizeCompatibilityConfig({
219358const changes: string[] = [];
220359let updated = rawEntry;
221360let changed = false;
361+const bindingsToAdd: AgentBindingConfig[] = [];
222362223363const aliases = normalizeLegacyChannelAliases({
224364entry: rawEntry,
@@ -262,6 +402,16 @@ export function normalizeCompatibilityConfig({
262402updated = guildAliases.entry;
263403changed = changed || guildAliases.changed;
264404405+const channelAgentIds = normalizeDiscordGuildChannelAgentIds({
406+ cfg,
407+entry: updated,
408+pathPrefix: "channels.discord",
409+ changes,
410+ bindingsToAdd,
411+});
412+updated = channelAgentIds.entry;
413+changed = changed || channelAgentIds.changed;
414+265415const accounts = asObjectRecord(updated.accounts);
266416if (accounts) {
267417let accountsChanged = false;
@@ -276,10 +426,22 @@ export function normalizeCompatibilityConfig({
276426pathPrefix: `channels.discord.accounts.${accountId}`,
277427 changes,
278428});
279-if (!normalized.changed) {
429+let nextAccount = normalized.entry;
430+let accountChanged = normalized.changed;
431+const normalizedAgentIds = normalizeDiscordGuildChannelAgentIds({
432+ cfg,
433+entry: nextAccount,
434+pathPrefix: `channels.discord.accounts.${accountId}`,
435+ accountId,
436+ changes,
437+ bindingsToAdd,
438+});
439+nextAccount = normalizedAgentIds.entry;
440+accountChanged = accountChanged || normalizedAgentIds.changed;
441+if (!accountChanged) {
280442continue;
281443}
282-nextAccounts[accountId] = normalized.entry;
444+nextAccounts[accountId] = nextAccount;
283445accountsChanged = true;
284446}
285447if (accountsChanged) {
@@ -307,6 +469,8 @@ export function normalizeCompatibilityConfig({
307469 ...cfg.channels,
308470discord: updated,
309471} as OpenClawConfig["channels"],
472+bindings:
473+bindingsToAdd.length > 0 ? [...(cfg.bindings ?? []), ...bindingsToAdd] : cfg.bindings,
310474},
311475 changes,
312476};
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。