mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 17:12:10 +00:00
Improve Telegram groups config shape diagnostics (#83260)
* Improve Telegram groups config diagnostics Add targeted guidance when channels.telegram.groups uses a non-object shape so startup/config validation and doctor explain the required group-id object map and topic nesting. * fix(config): keep channel validation hints generic
This commit is contained in:
@@ -5,12 +5,14 @@ import {
|
||||
collectTelegramApiRootWarnings,
|
||||
collectTelegramEmptyAllowlistExtraWarnings,
|
||||
collectTelegramGroupPolicyWarnings,
|
||||
collectTelegramMalformedGroupsWarnings,
|
||||
collectTelegramMissingEnvTokenWarnings,
|
||||
collectTelegramSelectedQuoteToolProgressWarnings,
|
||||
maybeRepairTelegramApiRoots,
|
||||
maybeRepairTelegramAllowFromUsernames,
|
||||
scanTelegramBotEndpointApiRoots,
|
||||
scanTelegramInvalidAllowFromEntries,
|
||||
scanTelegramMalformedGroupsConfig,
|
||||
scanTelegramSelectedQuoteToolProgressWarnings,
|
||||
telegramDoctor,
|
||||
} from "./doctor.js";
|
||||
@@ -206,6 +208,42 @@ describe("telegram doctor", () => {
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("warns when Telegram groups use a non-object shape", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: ["-1001234567890"],
|
||||
accounts: {
|
||||
work: {
|
||||
groups: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
const hits = scanTelegramMalformedGroupsConfig(cfg);
|
||||
expect(hits).toEqual([
|
||||
{ path: "channels.telegram.groups", actualType: "array" },
|
||||
{ path: "channels.telegram.accounts.work.groups", actualType: "null" },
|
||||
]);
|
||||
|
||||
const warnings = collectTelegramMalformedGroupsWarnings({
|
||||
hits,
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
});
|
||||
expect(warnings[0]).toContain("object map keyed by Telegram group/chat id");
|
||||
expect(warnings[1]).toContain('channels.telegram.groups."-1001234567890".topics."99"');
|
||||
expect(warnings[1]).toContain("openclaw doctor --fix");
|
||||
|
||||
expect(
|
||||
await telegramDoctor.collectPreviewWarnings?.({
|
||||
cfg,
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
}),
|
||||
).toEqual(expect.arrayContaining(warnings));
|
||||
});
|
||||
|
||||
it("repairs @username entries to numeric ids", async () => {
|
||||
lookupTelegramChatIdMock.mockResolvedValue("111");
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { resolveTelegramPreviewStreamMode } from "./preview-streaming.js";
|
||||
|
||||
type TelegramAllowFromInvalidHit = { path: string; entry: string };
|
||||
type TelegramMalformedGroupsHit = { path: string; actualType: string };
|
||||
type TelegramSelectedQuoteToolProgressHit = { path: string; replyToMode: string };
|
||||
type TelegramApiRootBotEndpointHit = {
|
||||
path: string;
|
||||
@@ -131,6 +132,53 @@ function collectTelegramAllowFromLists(
|
||||
return refs;
|
||||
}
|
||||
|
||||
function describeConfigValueType(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return "array";
|
||||
}
|
||||
if (value === null) {
|
||||
return "null";
|
||||
}
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
export function scanTelegramMalformedGroupsConfig(
|
||||
cfg: OpenClawConfig,
|
||||
): TelegramMalformedGroupsHit[] {
|
||||
const hits: TelegramMalformedGroupsHit[] = [];
|
||||
for (const scope of collectTelegramAccountScopes(cfg)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(scope.account, "groups")) {
|
||||
continue;
|
||||
}
|
||||
const groups = scope.account.groups;
|
||||
if (asObjectRecord(groups)) {
|
||||
continue;
|
||||
}
|
||||
hits.push({
|
||||
path: `${scope.prefix}.groups`,
|
||||
actualType: describeConfigValueType(groups),
|
||||
});
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
export function collectTelegramMalformedGroupsWarnings(params: {
|
||||
hits: TelegramMalformedGroupsHit[];
|
||||
doctorFixCommand: string;
|
||||
}): string[] {
|
||||
if (params.hits.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const sample = params.hits[0] ?? {
|
||||
path: "channels.telegram.groups",
|
||||
actualType: "unknown",
|
||||
};
|
||||
return [
|
||||
`- ${sanitizeForLog(sample.path)} has invalid Telegram groups shape (${sanitizeForLog(sample.actualType)}); expected an object map keyed by Telegram group/chat id, not an array, string, or null.`,
|
||||
`- Example shape: channels.telegram.groups."-1001234567890".topics."99" = { agentId: "support" }. Use topics for forum-topic routing, then rerun ${params.doctorFixCommand} for any remaining Telegram config cleanup.`,
|
||||
];
|
||||
}
|
||||
|
||||
export function scanTelegramInvalidAllowFromEntries(
|
||||
cfg: OpenClawConfig,
|
||||
): TelegramAllowFromInvalidHit[] {
|
||||
@@ -557,6 +605,10 @@ export const telegramDoctor: ChannelDoctorAdapter = {
|
||||
normalizeCompatibilityConfig: normalizeTelegramCompatibilityConfig,
|
||||
collectPreviewWarnings: ({ cfg, doctorFixCommand, env }) => [
|
||||
...collectTelegramMissingEnvTokenWarnings({ cfg, env }),
|
||||
...collectTelegramMalformedGroupsWarnings({
|
||||
hits: scanTelegramMalformedGroupsConfig(cfg),
|
||||
doctorFixCommand,
|
||||
}),
|
||||
...collectTelegramInvalidAllowFromWarnings({
|
||||
hits: scanTelegramInvalidAllowFromEntries(cfg),
|
||||
doctorFixCommand,
|
||||
|
||||
@@ -246,6 +246,28 @@ describe("validateConfigObjectRawWithPlugins channel metadata", () => {
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps raw channel validation diagnostics plugin-agnostic", () => {
|
||||
const result = validateConfigObjectRawWithPlugins({
|
||||
channels: {
|
||||
telegram: {
|
||||
groups: ["-1001234567890"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: "channels.telegram.groups",
|
||||
message: expect.stringContaining("invalid config:"),
|
||||
}),
|
||||
);
|
||||
expect(result.issues[0]?.message).not.toContain("Telegram groups");
|
||||
expect(result.issues[0]?.message).not.toContain("openclaw doctor --fix");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateConfigObjectRawWithPlugins plugin config defaults", () => {
|
||||
|
||||
@@ -231,6 +231,9 @@ function collectAllowedValuesFromBundledChannelSchemaPath(
|
||||
return collectAllowedValuesFromJsonSchemaNode(targetNode);
|
||||
}
|
||||
|
||||
function formatRawChannelConfigIssueMessage(message: string): string {
|
||||
return `invalid config: ${message}`;
|
||||
}
|
||||
function collectRawBundledChannelConfigIssues(config: OpenClawConfig): ConfigValidationIssue[] {
|
||||
if (!config.channels || !isRecord(config.channels)) {
|
||||
return [];
|
||||
@@ -253,10 +256,11 @@ function collectRawBundledChannelConfigIssues(config: OpenClawConfig): ConfigVal
|
||||
const message = error.additionalProperty
|
||||
? `${error.message}: "${error.additionalProperty}"`
|
||||
: error.message;
|
||||
const path =
|
||||
error.path === "<root>" ? `channels.${channelId}` : `channels.${channelId}.${error.path}`;
|
||||
issues.push({
|
||||
path:
|
||||
error.path === "<root>" ? `channels.${channelId}` : `channels.${channelId}.${error.path}`,
|
||||
message: `invalid config: ${message}`,
|
||||
path,
|
||||
message: formatRawChannelConfigIssueMessage(message),
|
||||
allowedValues: error.allowedValues,
|
||||
allowedValuesHiddenCount: error.allowedValuesHiddenCount,
|
||||
});
|
||||
@@ -1401,10 +1405,11 @@ function validateConfigObjectWithPluginsBase(
|
||||
});
|
||||
if (!result.ok) {
|
||||
for (const error of result.errors) {
|
||||
const path =
|
||||
error.path === "<root>" ? `channels.${trimmed}` : `channels.${trimmed}.${error.path}`;
|
||||
issues.push({
|
||||
path:
|
||||
error.path === "<root>" ? `channels.${trimmed}` : `channels.${trimmed}.${error.path}`,
|
||||
message: `invalid config: ${error.message}`,
|
||||
path,
|
||||
message: formatRawChannelConfigIssueMessage(error.message),
|
||||
allowedValues: error.allowedValues,
|
||||
allowedValuesHiddenCount: error.allowedValuesHiddenCount,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user