Channels: move single-account config into accounts.default (#27334)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 50b5771808
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana
2026-02-26 04:06:03 -05:00
committed by GitHub
parent da6a96ed33
commit dfa0b5b4fc
15 changed files with 639 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { ZodIssue } from "zod";
import { normalizeChatChannelId } from "../channels/registry.js";
import {
isNumericTelegramUserId,
normalizeTelegramAllowFromEntry,
@@ -27,6 +28,7 @@ import {
isTrustedSafeBinPath,
normalizeTrustedSafeBinDirs,
} from "../infra/exec-safe-bin-trust.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
import {
isDiscordMutableAllowEntry,
isGoogleChatMutableAllowEntry,
@@ -207,6 +209,103 @@ function asObjectRecord(value: unknown): Record<string, unknown> | null {
return value as Record<string, unknown>;
}
function normalizeBindingChannelKey(raw?: string | null): string {
const normalized = normalizeChatChannelId(raw);
if (normalized) {
return normalized;
}
return (raw ?? "").trim().toLowerCase();
}
export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig): string[] {
const channels = asObjectRecord(cfg.channels);
if (!channels) {
return [];
}
const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : [];
const warnings: string[] = [];
for (const [channelKey, rawChannel] of Object.entries(channels)) {
const channel = asObjectRecord(rawChannel);
if (!channel) {
continue;
}
const accounts = asObjectRecord(channel.accounts);
if (!accounts) {
continue;
}
const normalizedAccountIds = Array.from(
new Set(
Object.keys(accounts)
.map((accountId) => normalizeAccountId(accountId))
.filter(Boolean),
),
);
if (normalizedAccountIds.length === 0 || normalizedAccountIds.includes(DEFAULT_ACCOUNT_ID)) {
continue;
}
const accountIdSet = new Set(normalizedAccountIds);
const channelPattern = normalizeBindingChannelKey(channelKey);
let hasWildcardBinding = false;
const coveredAccountIds = new Set<string>();
for (const binding of bindings) {
const bindingRecord = asObjectRecord(binding);
if (!bindingRecord) {
continue;
}
const match = asObjectRecord(bindingRecord.match);
if (!match) {
continue;
}
const matchChannel =
typeof match.channel === "string" ? normalizeBindingChannelKey(match.channel) : "";
if (!matchChannel || matchChannel !== channelPattern) {
continue;
}
const rawAccountId = typeof match.accountId === "string" ? match.accountId.trim() : "";
if (!rawAccountId) {
continue;
}
if (rawAccountId === "*") {
hasWildcardBinding = true;
continue;
}
const normalizedBindingAccountId = normalizeAccountId(rawAccountId);
if (accountIdSet.has(normalizedBindingAccountId)) {
coveredAccountIds.add(normalizedBindingAccountId);
}
}
if (hasWildcardBinding) {
continue;
}
const uncoveredAccountIds = normalizedAccountIds.filter(
(accountId) => !coveredAccountIds.has(accountId),
);
if (uncoveredAccountIds.length === 0) {
continue;
}
if (coveredAccountIds.size > 0) {
warnings.push(
`- channels.${channelKey}: accounts.default is missing and account bindings only cover a subset of configured accounts. Uncovered accounts: ${uncoveredAccountIds.join(", ")}. Add bindings[].match.accountId for uncovered accounts (or "*"), or add channels.${channelKey}.accounts.default.`,
);
continue;
}
warnings.push(
`- channels.${channelKey}: accounts.default is missing and no valid account-scoped binding exists for configured accounts (${normalizedAccountIds.join(", ")}). Channel-only bindings (no accountId) match only default. Add bindings[].match.accountId for one of these accounts (or "*"), or add channels.${channelKey}.accounts.default.`,
);
}
return warnings;
}
function collectTelegramAccountScopes(
cfg: OpenClawConfig,
): Array<{ prefix: string; account: Record<string, unknown> }> {
@@ -1421,6 +1520,12 @@ export async function loadAndMaybeMigrateDoctorConfig(params: {
}
}
const missingDefaultAccountBindingWarnings =
collectMissingDefaultAccountBindingWarnings(candidate);
if (missingDefaultAccountBindingWarnings.length > 0) {
note(missingDefaultAccountBindingWarnings.join("\n"), "Doctor warnings");
}
if (shouldRepair) {
const repair = await maybeRepairTelegramAllowFromUsernames(candidate);
if (repair.changes.length > 0) {