mirror of https://github.com/openclaw/openclaw.git
fix(config): migrate removed telegram groupMentionsOnly key (#55336)
Merged via squash.
Prepared head SHA: 23731e27bf
Co-authored-by: jameslcowan <112015792+jameslcowan@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
parent
8dfbcaa200
commit
3bed73dc36
|
|
@ -130,6 +130,7 @@ Docs: https://docs.openclaw.ai
|
|||
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
|
||||
- Telegram/forum topics: restore reply routing to the active topic and keep ACP `sessions_spawn(..., thread=true, mode="session")` bound to that same topic instead of falling back to root chat or losing follow-up routing. (#56060) Thanks @one27001.
|
||||
- Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.
|
||||
- Config/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups["*"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
|
||||
|
||||
## 2026.3.28
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,25 @@ describe("legacy config detection", () => {
|
|||
expect(res.changes).toEqual([]);
|
||||
expect(res.config).toBeNull();
|
||||
});
|
||||
|
||||
it("flags channels.telegram.groupMentionsOnly as legacy in snapshot", async () => {
|
||||
await withSnapshotForConfig(
|
||||
{ channels: { telegram: { groupMentionsOnly: true } } },
|
||||
async (ctx) => {
|
||||
expect(ctx.snapshot.valid).toBe(true);
|
||||
expect(
|
||||
ctx.snapshot.legacyIssues.some(
|
||||
(issue) => issue.path === "channels.telegram.groupMentionsOnly",
|
||||
),
|
||||
).toBe(true);
|
||||
const parsed = ctx.parsed as {
|
||||
channels?: { telegram?: { groupMentionsOnly?: boolean } };
|
||||
};
|
||||
expect(parsed.channels?.telegram?.groupMentionsOnly).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("does not rewrite removed messages.tts.enabled migrations", async () => {
|
||||
const res = migrateLegacyConfig({
|
||||
messages: { tts: { enabled: true } },
|
||||
|
|
|
|||
|
|
@ -242,6 +242,17 @@ describe("legacy config detection", () => {
|
|||
expect(res.issues[0]?.message).toContain('"telegram"');
|
||||
}
|
||||
});
|
||||
it("rejects channels.telegram.groupMentionsOnly", async () => {
|
||||
const res = validateConfigObject({
|
||||
channels: { telegram: { groupMentionsOnly: true } },
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.issues.some((issue) => issue.path === "channels.telegram.groupMentionsOnly")).toBe(
|
||||
true,
|
||||
);
|
||||
}
|
||||
});
|
||||
it("rejects gateway.token", async () => {
|
||||
const res = validateConfigObject({
|
||||
gateway: { token: "legacy-token" },
|
||||
|
|
|
|||
|
|
@ -77,6 +77,89 @@ describe("legacy migrate mention routing", () => {
|
|||
expect(res.changes).toEqual([]);
|
||||
expect(res.config).toBeNull();
|
||||
});
|
||||
|
||||
it("moves channels.telegram.groupMentionsOnly into groups.*.requireMention", () => {
|
||||
const res = migrateLegacyConfig({
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.changes).toContain(
|
||||
'Moved channels.telegram.groupMentionsOnly → channels.telegram.groups."*".requireMention.',
|
||||
);
|
||||
expect(res.config?.channels?.telegram?.groups?.["*"]?.requireMention).toBe(true);
|
||||
expect(
|
||||
(res.config?.channels?.telegram as { groupMentionsOnly?: unknown } | undefined)
|
||||
?.groupMentionsOnly,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps explicit channels.telegram.groups."*".requireMention when migrating groupMentionsOnly', () => {
|
||||
const res = migrateLegacyConfig({
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
groups: {
|
||||
"*": {
|
||||
requireMention: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.changes).toContain(
|
||||
'Removed channels.telegram.groupMentionsOnly (channels.telegram.groups."*" already set).',
|
||||
);
|
||||
expect(res.config?.channels?.telegram?.groups?.["*"]?.requireMention).toBe(false);
|
||||
expect(
|
||||
(res.config?.channels?.telegram as { groupMentionsOnly?: unknown } | undefined)
|
||||
?.groupMentionsOnly,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not overwrite invalid channels.telegram.groups when migrating groupMentionsOnly", () => {
|
||||
const res = migrateLegacyConfig({
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
groups: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config).toBeNull();
|
||||
expect(res.changes).toContain(
|
||||
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Migration applied, but config still invalid; fix remaining issues manually.",
|
||||
);
|
||||
});
|
||||
|
||||
it('does not overwrite invalid channels.telegram.groups."*" when migrating groupMentionsOnly', () => {
|
||||
const res = migrateLegacyConfig({
|
||||
channels: {
|
||||
telegram: {
|
||||
groupMentionsOnly: true,
|
||||
groups: {
|
||||
"*": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config).toBeNull();
|
||||
expect(res.changes).toContain(
|
||||
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
|
||||
);
|
||||
expect(res.changes).toContain(
|
||||
"Migration applied, but config still invalid; fix remaining issues manually.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy migrate tts provider shape", () => {
|
||||
|
|
|
|||
|
|
@ -215,12 +215,36 @@ function migrateLegacyTtsConfig(
|
|||
}
|
||||
}
|
||||
|
||||
function resolveCompatibleDefaultGroupEntry(section: Record<string, unknown>): {
|
||||
groups: Record<string, unknown>;
|
||||
entry: Record<string, unknown>;
|
||||
} | null {
|
||||
const existingGroups = section.groups;
|
||||
if (existingGroups !== undefined && !getRecord(existingGroups)) {
|
||||
return null;
|
||||
}
|
||||
const groups = getRecord(existingGroups) ?? {};
|
||||
const defaultKey = "*";
|
||||
const existingEntry = groups[defaultKey];
|
||||
if (existingEntry !== undefined && !getRecord(existingEntry)) {
|
||||
return null;
|
||||
}
|
||||
const entry = getRecord(existingEntry) ?? {};
|
||||
return { groups, entry };
|
||||
}
|
||||
|
||||
const MEMORY_SEARCH_RULE: LegacyConfigRule = {
|
||||
path: ["memorySearch"],
|
||||
message:
|
||||
"top-level memorySearch was moved; use agents.defaults.memorySearch instead (auto-migrated on load).",
|
||||
};
|
||||
|
||||
const GROUP_MENTIONS_ONLY_RULE: LegacyConfigRule = {
|
||||
path: ["channels", "telegram", "groupMentionsOnly"],
|
||||
message:
|
||||
'channels.telegram.groupMentionsOnly was removed; use channels.telegram.groups."*".requireMention instead (auto-migrated on load).',
|
||||
};
|
||||
|
||||
const GATEWAY_BIND_RULE: LegacyConfigRule = {
|
||||
path: ["gateway", "bind"],
|
||||
message:
|
||||
|
|
@ -307,6 +331,53 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
|
|||
);
|
||||
},
|
||||
}),
|
||||
defineLegacyConfigMigration({
|
||||
// v2026.2.23 replaced channels.telegram.groupMentionsOnly with
|
||||
// channels.telegram.groups."*".requireMention. Existing configs crash on
|
||||
// startup because gateway auto-migration only runs for registered legacy
|
||||
// keys, and this removed key previously fell through as an unknown field.
|
||||
id: "channels.telegram.groupMentionsOnly->channels.telegram.groups.*.requireMention",
|
||||
describe:
|
||||
"Move channels.telegram.groupMentionsOnly to channels.telegram.groups.*.requireMention",
|
||||
legacyRules: [GROUP_MENTIONS_ONLY_RULE],
|
||||
apply: (raw, changes) => {
|
||||
const channels = ensureRecord(raw, "channels");
|
||||
const telegram = getRecord(channels.telegram);
|
||||
if (!telegram || telegram.groupMentionsOnly === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupMentionsOnly = telegram.groupMentionsOnly;
|
||||
const defaultGroupEntry = resolveCompatibleDefaultGroupEntry(telegram);
|
||||
const defaultKey = "*";
|
||||
|
||||
if (!defaultGroupEntry) {
|
||||
changes.push(
|
||||
"Skipped channels.telegram.groupMentionsOnly migration because channels.telegram.groups already has an incompatible shape; fix remaining issues manually.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { groups, entry } = defaultGroupEntry;
|
||||
|
||||
if (entry.requireMention === undefined) {
|
||||
entry.requireMention = groupMentionsOnly;
|
||||
groups[defaultKey] = entry;
|
||||
telegram.groups = groups;
|
||||
changes.push(
|
||||
'Moved channels.telegram.groupMentionsOnly → channels.telegram.groups."*".requireMention.',
|
||||
);
|
||||
} else {
|
||||
changes.push(
|
||||
'Removed channels.telegram.groupMentionsOnly (channels.telegram.groups."*" already set).',
|
||||
);
|
||||
}
|
||||
|
||||
delete telegram.groupMentionsOnly;
|
||||
channels.telegram = telegram;
|
||||
raw.channels = channels;
|
||||
},
|
||||
}),
|
||||
defineLegacyConfigMigration({
|
||||
id: "memorySearch->agents.defaults.memorySearch",
|
||||
describe: "Move top-level memorySearch to agents.defaults.memorySearch",
|
||||
|
|
|
|||
Loading…
Reference in New Issue