mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-21 13:44:03 +00:00
refactor: move legacy config migration behind doctor
This commit is contained in:
@@ -16,9 +16,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "bluebubbles"],
|
||||
message:
|
||||
"channels.bluebubbles.allowPrivateNetwork is legacy; use channels.bluebubbles.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.bluebubbles.allowPrivateNetwork is legacy; use channels.bluebubbles.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "bluebubbles", "accounts"],
|
||||
message:
|
||||
"channels.bluebubbles.accounts.<id>.allowPrivateNetwork is legacy; use channels.bluebubbles.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.bluebubbles.accounts.<id>.allowPrivateNetwork is legacy; use channels.bluebubbles.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,9 +17,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,13 +88,13 @@ const BLUEBUBBLES_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "bluebubbles"],
|
||||
message:
|
||||
"channels.bluebubbles.allowPrivateNetwork is legacy; use channels.bluebubbles.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.bluebubbles.allowPrivateNetwork is legacy; use channels.bluebubbles.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "bluebubbles", "accounts"],
|
||||
message:
|
||||
"channels.bluebubbles.accounts.<id>.allowPrivateNetwork is legacy; use channels.bluebubbles.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.bluebubbles.accounts.<id>.allowPrivateNetwork is legacy; use channels.bluebubbles.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -286,13 +286,13 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "discord", "voice", "tts"],
|
||||
message:
|
||||
"channels.discord.voice.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use channels.discord.voice.tts.providers.<provider> (auto-migrated on load).",
|
||||
'channels.discord.voice.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use channels.discord.voice.tts.providers.<provider>. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyTtsProviderKeys,
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord", "accounts"],
|
||||
message:
|
||||
"channels.discord.accounts.<id>.voice.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use channels.discord.accounts.<id>.voice.tts.providers.<provider> (auto-migrated on load).",
|
||||
'channels.discord.accounts.<id>.voice.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use channels.discord.accounts.<id>.voice.tts.providers.<provider>. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyDiscordAccountTtsProviderKeys,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -78,31 +78,31 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "matrix"],
|
||||
message:
|
||||
"channels.matrix.allowPrivateNetwork is legacy; use channels.matrix.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.matrix.allowPrivateNetwork is legacy; use channels.matrix.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "accounts"],
|
||||
message:
|
||||
"channels.matrix.accounts.<id>.allowPrivateNetwork is legacy; use channels.matrix.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.matrix.accounts.<id>.allowPrivateNetwork is legacy; use channels.matrix.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixAccountPrivateNetworkAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "groups"],
|
||||
message:
|
||||
"channels.matrix.groups.<room>.allow is legacy; use channels.matrix.groups.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.groups.<room>.allow is legacy; use channels.matrix.groups.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixRoomMapAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "rooms"],
|
||||
message:
|
||||
"channels.matrix.rooms.<room>.allow is legacy; use channels.matrix.rooms.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.rooms.<room>.allow is legacy; use channels.matrix.rooms.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixRoomMapAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "accounts"],
|
||||
message:
|
||||
"channels.matrix.accounts.<id>.{groups,rooms}.<room>.allow is legacy; use channels.matrix.accounts.<id>.{groups,rooms}.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.accounts.<id>.{groups,rooms}.<room>.allow is legacy; use channels.matrix.accounts.<id>.{groups,rooms}.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixAccountRoomAllowAliases,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,31 +195,31 @@ const MATRIX_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "matrix"],
|
||||
message:
|
||||
"channels.matrix.allowPrivateNetwork is legacy; use channels.matrix.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.matrix.allowPrivateNetwork is legacy; use channels.matrix.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "accounts"],
|
||||
message:
|
||||
"channels.matrix.accounts.<id>.allowPrivateNetwork is legacy; use channels.matrix.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.matrix.accounts.<id>.allowPrivateNetwork is legacy; use channels.matrix.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixAccountPrivateNetworkAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "groups"],
|
||||
message:
|
||||
"channels.matrix.groups.<room>.allow is legacy; use channels.matrix.groups.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.groups.<room>.allow is legacy; use channels.matrix.groups.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixRoomMapAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "rooms"],
|
||||
message:
|
||||
"channels.matrix.rooms.<room>.allow is legacy; use channels.matrix.rooms.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.rooms.<room>.allow is legacy; use channels.matrix.rooms.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixRoomMapAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "matrix", "accounts"],
|
||||
message:
|
||||
"channels.matrix.accounts.<id>.{groups,rooms}.<room>.allow is legacy; use channels.matrix.accounts.<id>.{groups,rooms}.<room>.enabled instead (auto-migrated on load).",
|
||||
'channels.matrix.accounts.<id>.{groups,rooms}.<room>.allow is legacy; use channels.matrix.accounts.<id>.{groups,rooms}.<room>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMatrixAccountRoomAllowAliases,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -16,9 +16,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "mattermost"],
|
||||
message:
|
||||
"channels.mattermost.allowPrivateNetwork is legacy; use channels.mattermost.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.mattermost.allowPrivateNetwork is legacy; use channels.mattermost.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "mattermost", "accounts"],
|
||||
message:
|
||||
"channels.mattermost.accounts.<id>.allowPrivateNetwork is legacy; use channels.mattermost.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.mattermost.accounts.<id>.allowPrivateNetwork is legacy; use channels.mattermost.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -53,9 +53,9 @@ function hasLegacyMattermostAllowPrivateNetworkInAccounts(value: unknown): boole
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,18 +63,20 @@ export const MATTERMOST_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "mattermost"],
|
||||
message:
|
||||
"channels.mattermost.allowPrivateNetwork is legacy; use channels.mattermost.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.mattermost.allowPrivateNetwork is legacy; use channels.mattermost.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "mattermost", "accounts"],
|
||||
message:
|
||||
"channels.mattermost.accounts.<id>.allowPrivateNetwork is legacy; use channels.mattermost.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.mattermost.accounts.<id>.allowPrivateNetwork is legacy; use channels.mattermost.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyMattermostAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
export function normalizeMattermostCompatibilityConfig(cfg: OpenClawConfig): ChannelDoctorConfigMutation {
|
||||
export function normalizeMattermostCompatibilityConfig(
|
||||
cfg: OpenClawConfig,
|
||||
): ChannelDoctorConfigMutation {
|
||||
const channels = isRecord(cfg.channels) ? cfg.channels : null;
|
||||
const mattermost = isRecord(channels?.mattermost) ? channels.mattermost : null;
|
||||
if (!mattermost) {
|
||||
|
||||
@@ -16,9 +16,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "nextcloud-talk"],
|
||||
message:
|
||||
"channels.nextcloud-talk.allowPrivateNetwork is legacy; use channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.nextcloud-talk.allowPrivateNetwork is legacy; use channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "nextcloud-talk", "accounts"],
|
||||
message:
|
||||
"channels.nextcloud-talk.accounts.<id>.allowPrivateNetwork is legacy; use channels.nextcloud-talk.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.nextcloud-talk.accounts.<id>.allowPrivateNetwork is legacy; use channels.nextcloud-talk.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
@@ -95,8 +95,9 @@ export function normalizeCompatibilityConfig({
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
"nextcloud-talk":
|
||||
updatedNextcloudTalk as NonNullable<OpenClawConfig["channels"]>["nextcloud-talk"],
|
||||
"nextcloud-talk": updatedNextcloudTalk as NonNullable<
|
||||
OpenClawConfig["channels"]
|
||||
>["nextcloud-talk"],
|
||||
},
|
||||
},
|
||||
changes,
|
||||
|
||||
@@ -17,13 +17,15 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeNextcloudTalkCompatibilityConfig(cfg: OpenClawConfig): ChannelDoctorConfigMutation {
|
||||
function normalizeNextcloudTalkCompatibilityConfig(
|
||||
cfg: OpenClawConfig,
|
||||
): ChannelDoctorConfigMutation {
|
||||
const channels = isRecord(cfg.channels) ? cfg.channels : null;
|
||||
const nextcloudTalk = isRecord(channels?.["nextcloud-talk"]) ? channels["nextcloud-talk"] : null;
|
||||
if (!nextcloudTalk) {
|
||||
@@ -77,8 +79,9 @@ function normalizeNextcloudTalkCompatibilityConfig(cfg: OpenClawConfig): Channel
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
"nextcloud-talk":
|
||||
updatedNextcloudTalk as NonNullable<OpenClawConfig["channels"]>["nextcloud-talk"],
|
||||
"nextcloud-talk": updatedNextcloudTalk as NonNullable<
|
||||
OpenClawConfig["channels"]
|
||||
>["nextcloud-talk"],
|
||||
},
|
||||
},
|
||||
changes,
|
||||
@@ -89,13 +92,13 @@ const NEXTCLOUD_TALK_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "nextcloud-talk"],
|
||||
message:
|
||||
"channels.nextcloud-talk.allowPrivateNetwork is legacy; use channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.nextcloud-talk.allowPrivateNetwork is legacy; use channels.nextcloud-talk.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "nextcloud-talk", "accounts"],
|
||||
message:
|
||||
"channels.nextcloud-talk.accounts.<id>.allowPrivateNetwork is legacy; use channels.nextcloud-talk.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.nextcloud-talk.accounts.<id>.allowPrivateNetwork is legacy; use channels.nextcloud-talk.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -94,7 +94,7 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "telegram", "groupMentionsOnly"],
|
||||
message:
|
||||
'channels.telegram.groupMentionsOnly was removed; use channels.telegram.groups."*".requireMention instead (auto-migrated on load).',
|
||||
'channels.telegram.groupMentionsOnly was removed; use channels.telegram.groups."*".requireMention instead. Run "openclaw doctor --fix".',
|
||||
},
|
||||
{
|
||||
path: ["channels", "telegram"],
|
||||
|
||||
@@ -16,9 +16,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "tlon"],
|
||||
message:
|
||||
"channels.tlon.allowPrivateNetwork is legacy; use channels.tlon.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.tlon.allowPrivateNetwork is legacy; use channels.tlon.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "tlon", "accounts"],
|
||||
message:
|
||||
"channels.tlon.accounts.<id>.allowPrivateNetwork is legacy; use channels.tlon.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.tlon.accounts.<id>.allowPrivateNetwork is legacy; use channels.tlon.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -17,9 +17,9 @@ function hasLegacyAllowPrivateNetworkInAccounts(value: unknown): boolean {
|
||||
const accounts = isRecord(value) ? value : null;
|
||||
return Boolean(
|
||||
accounts &&
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
Object.values(accounts).some((account) =>
|
||||
hasLegacyFlatAllowPrivateNetworkAlias(isRecord(account) ? account : {}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,13 +88,13 @@ const TLON_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "tlon"],
|
||||
message:
|
||||
"channels.tlon.allowPrivateNetwork is legacy; use channels.tlon.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.tlon.allowPrivateNetwork is legacy; use channels.tlon.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyFlatAllowPrivateNetworkAlias(isRecord(value) ? value : {}),
|
||||
},
|
||||
{
|
||||
path: ["channels", "tlon", "accounts"],
|
||||
message:
|
||||
"channels.tlon.accounts.<id>.allowPrivateNetwork is legacy; use channels.tlon.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead (auto-migrated on load).",
|
||||
'channels.tlon.accounts.<id>.allowPrivateNetwork is legacy; use channels.tlon.accounts.<id>.network.dangerouslyAllowPrivateNetwork instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyAllowPrivateNetworkInAccounts,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -145,13 +145,13 @@ const ZALOUSER_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "zalouser", "groups"],
|
||||
message:
|
||||
"channels.zalouser.groups.<id>.allow is legacy; use channels.zalouser.groups.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.zalouser.groups.<id>.allow is legacy; use channels.zalouser.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserGroupAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "zalouser", "accounts"],
|
||||
message:
|
||||
"channels.zalouser.accounts.<id>.groups.<id>.allow is legacy; use channels.zalouser.accounts.<id>.groups.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.zalouser.accounts.<id>.groups.<id>.allow is legacy; use channels.zalouser.accounts.<id>.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserAccountGroupAllowAliases,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Type } from "@sinclair/typebox";
|
||||
import { isRestartEnabled } from "../../config/commands.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { parseConfigJson5, resolveConfigSnapshotHash } from "../../config/io.js";
|
||||
import { applyLegacyMigrations } from "../../config/legacy.js";
|
||||
import { applyMergePatch } from "../../config/merge-patch.js";
|
||||
import { extractDeliveryInfo } from "../../config/sessions.js";
|
||||
import {
|
||||
@@ -97,10 +96,8 @@ function assertGatewayConfigMutationAllowed(params: {
|
||||
: (applyMergePatch(params.currentConfig, parsed, {
|
||||
mergeObjectArraysById: true,
|
||||
}) as Record<string, unknown>);
|
||||
const migratedNextConfig = applyLegacyMigrations(nextConfig).next ?? nextConfig;
|
||||
const changedProtectedPaths = PROTECTED_GATEWAY_CONFIG_PATHS.filter(
|
||||
(path) =>
|
||||
getValueAtPath(params.currentConfig, path) !== getValueAtPath(migratedNextConfig, path),
|
||||
(path) => getValueAtPath(params.currentConfig, path) !== getValueAtPath(nextConfig, path),
|
||||
);
|
||||
if (changedProtectedPaths.length === 0) {
|
||||
return;
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
import type { LegacyConfigRule } from "../../config/legacy.shared.js";
|
||||
import type { OpenClawConfig } from "../../config/types.js";
|
||||
import { listBootstrapChannelPlugins } from "./bootstrap-registry.js";
|
||||
|
||||
export function collectChannelLegacyConfigRules(): LegacyConfigRule[] {
|
||||
return listBootstrapChannelPlugins().flatMap((plugin) => plugin.doctor?.legacyConfigRules ?? []);
|
||||
}
|
||||
|
||||
export function applyChannelDoctorCompatibilityMigrations(cfg: Record<string, unknown>): {
|
||||
next: Record<string, unknown>;
|
||||
changes: string[];
|
||||
} {
|
||||
let nextCfg = cfg as OpenClawConfig & Record<string, unknown>;
|
||||
const changes: string[] = [];
|
||||
for (const plugin of listBootstrapChannelPlugins()) {
|
||||
const mutation = plugin.doctor?.normalizeCompatibilityConfig?.({ cfg: nextCfg });
|
||||
if (!mutation || mutation.changes.length === 0) {
|
||||
continue;
|
||||
}
|
||||
nextCfg = mutation.config as OpenClawConfig & Record<string, unknown>;
|
||||
changes.push(...mutation.changes);
|
||||
}
|
||||
return { next: nextCfg, changes };
|
||||
}
|
||||
|
||||
19
src/commands/doctor/shared/channel-legacy-config-migrate.ts
Normal file
19
src/commands/doctor/shared/channel-legacy-config-migrate.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { listBootstrapChannelPlugins } from "../../../channels/plugins/bootstrap-registry.js";
|
||||
import type { OpenClawConfig } from "../../../config/types.js";
|
||||
|
||||
export function applyChannelDoctorCompatibilityMigrations(cfg: Record<string, unknown>): {
|
||||
next: Record<string, unknown>;
|
||||
changes: string[];
|
||||
} {
|
||||
let nextCfg = cfg as OpenClawConfig & Record<string, unknown>;
|
||||
const changes: string[] = [];
|
||||
for (const plugin of listBootstrapChannelPlugins()) {
|
||||
const mutation = plugin.doctor?.normalizeCompatibilityConfig?.({ cfg: nextCfg });
|
||||
if (!mutation || mutation.changes.length === 0) {
|
||||
continue;
|
||||
}
|
||||
nextCfg = mutation.config as OpenClawConfig & Record<string, unknown>;
|
||||
changes.push(...mutation.changes);
|
||||
}
|
||||
return { next: nextCfg, changes };
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { migrateLegacyConfig } from "../../../config/config.js";
|
||||
import { formatConfigIssueLines } from "../../../config/issue-format.js";
|
||||
import { stripUnknownConfigKeys } from "../../doctor-config-analysis.js";
|
||||
import type { DoctorConfigPreflightResult } from "../../doctor-config-preflight.js";
|
||||
import type { DoctorConfigMutationState } from "./config-mutation-state.js";
|
||||
import { migrateLegacyConfig } from "./legacy-config-migrate.js";
|
||||
|
||||
export function applyLegacyCompatibilityStep(params: {
|
||||
snapshot: DoctorConfigPreflightResult["snapshot"];
|
||||
|
||||
40
src/commands/doctor/shared/legacy-config-migrate.ts
Normal file
40
src/commands/doctor/shared/legacy-config-migrate.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { LEGACY_CONFIG_MIGRATIONS } from "../../../config/legacy.migrations.js";
|
||||
import type { OpenClawConfig } from "../../../config/types.js";
|
||||
import { validateConfigObjectWithPlugins } from "../../../config/validation.js";
|
||||
import { applyChannelDoctorCompatibilityMigrations } from "./channel-legacy-config-migrate.js";
|
||||
|
||||
export function applyLegacyDoctorMigrations(raw: unknown): {
|
||||
next: Record<string, unknown> | null;
|
||||
changes: string[];
|
||||
} {
|
||||
if (!raw || typeof raw !== "object") {
|
||||
return { next: null, changes: [] };
|
||||
}
|
||||
const next = structuredClone(raw) as Record<string, unknown>;
|
||||
const changes: string[] = [];
|
||||
for (const migration of LEGACY_CONFIG_MIGRATIONS) {
|
||||
migration.apply(next, changes);
|
||||
}
|
||||
const compat = applyChannelDoctorCompatibilityMigrations(next);
|
||||
changes.push(...compat.changes);
|
||||
if (changes.length === 0) {
|
||||
return { next: null, changes: [] };
|
||||
}
|
||||
return { next: compat.next, changes };
|
||||
}
|
||||
|
||||
export function migrateLegacyConfig(raw: unknown): {
|
||||
config: OpenClawConfig | null;
|
||||
changes: string[];
|
||||
} {
|
||||
const { next, changes } = applyLegacyDoctorMigrations(raw);
|
||||
if (!next) {
|
||||
return { config: null, changes: [] };
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(next);
|
||||
if (!validated.ok) {
|
||||
changes.push("Migration applied, but config still invalid; fix remaining issues manually.");
|
||||
return { config: null, changes };
|
||||
}
|
||||
return { config: validated.config, changes };
|
||||
}
|
||||
@@ -22,7 +22,6 @@ export {
|
||||
writeConfigFile,
|
||||
} from "./io.js";
|
||||
export type { ConfigWriteNotification } from "./io.js";
|
||||
export { migrateLegacyConfig } from "./legacy-migrate.js";
|
||||
export { ConfigMutationConflictError, mutateConfigFile, replaceConfigFile } from "./mutate.js";
|
||||
export * from "./paths.js";
|
||||
export * from "./runtime-overrides.js";
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
readConfigIncludeFileWithGuards,
|
||||
resolveConfigIncludes,
|
||||
} from "./includes.js";
|
||||
import { migrateLegacyConfig } from "./legacy-migrate.js";
|
||||
import { findLegacyConfigIssues } from "./legacy.js";
|
||||
import {
|
||||
asResolvedSourceConfig,
|
||||
@@ -1646,14 +1645,7 @@ function resolveLegacyConfigForRead(
|
||||
sourceRaw,
|
||||
listPluginDoctorLegacyConfigRules(),
|
||||
);
|
||||
if (sourceLegacyIssues.length === 0) {
|
||||
return { effectiveConfigRaw: resolvedConfigRaw, sourceLegacyIssues };
|
||||
}
|
||||
const migrated = migrateLegacyConfig(resolvedConfigRaw);
|
||||
return {
|
||||
effectiveConfigRaw: migrated.config ?? resolvedConfigRaw,
|
||||
sourceLegacyIssues,
|
||||
};
|
||||
return { effectiveConfigRaw: resolvedConfigRaw, sourceLegacyIssues };
|
||||
}
|
||||
|
||||
type ReadConfigFileSnapshotInternalResult = {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { applyLegacyMigrations } from "./legacy.js";
|
||||
import type { OpenClawConfig } from "./types.js";
|
||||
import { validateConfigObjectWithPlugins } from "./validation.js";
|
||||
|
||||
export function migrateLegacyConfig(raw: unknown): {
|
||||
config: OpenClawConfig | null;
|
||||
changes: string[];
|
||||
} {
|
||||
const { next, changes } = applyLegacyMigrations(raw);
|
||||
if (!next) {
|
||||
return { config: null, changes: [] };
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(next);
|
||||
if (!validated.ok) {
|
||||
changes.push("Migration applied, but config still invalid; fix remaining issues manually.");
|
||||
return { config: null, changes };
|
||||
}
|
||||
return { config: validated.config, changes };
|
||||
}
|
||||
@@ -328,13 +328,13 @@ const THREAD_BINDING_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["session", "threadBindings"],
|
||||
message:
|
||||
"session.threadBindings.ttlHours was renamed to session.threadBindings.idleHours (auto-migrated on load).",
|
||||
'session.threadBindings.ttlHours was renamed to session.threadBindings.idleHours. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyThreadBindingTtl(value),
|
||||
},
|
||||
{
|
||||
path: ["channels"],
|
||||
message:
|
||||
"channels.<id>.threadBindings.ttlHours was renamed to channels.<id>.threadBindings.idleHours (auto-migrated on load).",
|
||||
'channels.<id>.threadBindings.ttlHours was renamed to channels.<id>.threadBindings.idleHours. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyThreadBindingTtlInAnyChannel(value),
|
||||
},
|
||||
];
|
||||
@@ -343,37 +343,37 @@ const CHANNEL_STREAMING_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "telegram"],
|
||||
message:
|
||||
"channels.telegram.streamMode is legacy; use channels.telegram.streaming instead (auto-migrated on load).",
|
||||
'channels.telegram.streamMode is legacy; use channels.telegram.streaming instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyTelegramStreamingKeys(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "telegram", "accounts"],
|
||||
message:
|
||||
"channels.telegram.accounts.<id>.streamMode is legacy; use channels.telegram.accounts.<id>.streaming instead (auto-migrated on load).",
|
||||
'channels.telegram.accounts.<id>.streamMode is legacy; use channels.telegram.accounts.<id>.streaming instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacyTelegramStreamingKeys),
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord"],
|
||||
message:
|
||||
"channels.discord.streamMode and boolean channels.discord.streaming are legacy; use channels.discord.streaming with enum values instead (auto-migrated on load).",
|
||||
'channels.discord.streamMode and boolean channels.discord.streaming are legacy; use channels.discord.streaming with enum values instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyDiscordStreamingKeys(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord", "accounts"],
|
||||
message:
|
||||
"channels.discord.accounts.<id>.streamMode and boolean channels.discord.accounts.<id>.streaming are legacy; use channels.discord.accounts.<id>.streaming with enum values instead (auto-migrated on load).",
|
||||
'channels.discord.accounts.<id>.streamMode and boolean channels.discord.accounts.<id>.streaming are legacy; use channels.discord.accounts.<id>.streaming with enum values instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacyDiscordStreamingKeys),
|
||||
},
|
||||
{
|
||||
path: ["channels", "slack"],
|
||||
message:
|
||||
"channels.slack.streamMode and boolean channels.slack.streaming are legacy; use channels.slack.streaming with enum values instead (auto-migrated on load).",
|
||||
'channels.slack.streamMode and boolean channels.slack.streaming are legacy; use channels.slack.streaming with enum values instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacySlackStreamingKeys(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "slack", "accounts"],
|
||||
message:
|
||||
"channels.slack.accounts.<id>.streamMode and boolean channels.slack.accounts.<id>.streaming are legacy; use channels.slack.accounts.<id>.streaming with enum values instead (auto-migrated on load).",
|
||||
'channels.slack.accounts.<id>.streamMode and boolean channels.slack.accounts.<id>.streaming are legacy; use channels.slack.accounts.<id>.streaming with enum values instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacySlackStreamingKeys),
|
||||
},
|
||||
];
|
||||
@@ -382,37 +382,37 @@ const CHANNEL_ENABLED_ALIAS_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "slack"],
|
||||
message:
|
||||
"channels.slack.channels.<id>.allow is legacy; use channels.slack.channels.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.slack.channels.<id>.allow is legacy; use channels.slack.channels.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacySlackChannelAllowAlias(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "slack", "accounts"],
|
||||
message:
|
||||
"channels.slack.accounts.<id>.channels.<id>.allow is legacy; use channels.slack.accounts.<id>.channels.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.slack.accounts.<id>.channels.<id>.allow is legacy; use channels.slack.accounts.<id>.channels.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacySlackChannelAllowAlias),
|
||||
},
|
||||
{
|
||||
path: ["channels", "googlechat"],
|
||||
message:
|
||||
"channels.googlechat.groups.<id>.allow is legacy; use channels.googlechat.groups.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.googlechat.groups.<id>.allow is legacy; use channels.googlechat.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyGoogleChatGroupAllowAlias(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "googlechat", "accounts"],
|
||||
message:
|
||||
"channels.googlechat.accounts.<id>.groups.<id>.allow is legacy; use channels.googlechat.accounts.<id>.groups.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.googlechat.accounts.<id>.groups.<id>.allow is legacy; use channels.googlechat.accounts.<id>.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacyGoogleChatGroupAllowAlias),
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord"],
|
||||
message:
|
||||
"channels.discord.guilds.<id>.channels.<id>.allow is legacy; use channels.discord.guilds.<id>.channels.<id>.enabled instead (auto-migrated on load).",
|
||||
'channels.discord.guilds.<id>.channels.<id>.allow is legacy; use channels.discord.guilds.<id>.channels.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyDiscordGuildChannelAllowAlias(value),
|
||||
},
|
||||
{
|
||||
path: ["channels", "discord", "accounts"],
|
||||
message:
|
||||
"channels.discord.accounts.<id>.guilds.<id>.channels.<id>.allow is legacy; use channels.discord.accounts.<id>.guilds.<id>.channels.<id>.enabled instead (auto-migrated on load).",
|
||||
'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".',
|
||||
match: (value) => hasLegacyKeysInAccounts(value, hasLegacyDiscordGuildChannelAllowAlias),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -219,13 +219,13 @@ function migrateLegacyTtsConfig(
|
||||
const MEMORY_SEARCH_RULE: LegacyConfigRule = {
|
||||
path: ["memorySearch"],
|
||||
message:
|
||||
"top-level memorySearch was moved; use agents.defaults.memorySearch instead (auto-migrated on load).",
|
||||
'top-level memorySearch was moved; use agents.defaults.memorySearch instead. Run "openclaw doctor --fix".',
|
||||
};
|
||||
|
||||
const GATEWAY_BIND_RULE: LegacyConfigRule = {
|
||||
path: ["gateway", "bind"],
|
||||
message:
|
||||
"gateway.bind host aliases (for example 0.0.0.0/localhost) are legacy; use bind modes (lan/loopback/custom/tailnet/auto) instead (auto-migrated on load).",
|
||||
'gateway.bind host aliases (for example 0.0.0.0/localhost) are legacy; use bind modes (lan/loopback/custom/tailnet/auto) instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => isLegacyGatewayBindHostAlias(value),
|
||||
requireSourceLiteral: true,
|
||||
};
|
||||
@@ -239,20 +239,20 @@ const HEARTBEAT_RULE: LegacyConfigRule = {
|
||||
const X_SEARCH_RULE: LegacyConfigRule = {
|
||||
path: ["tools", "web", "x_search", "apiKey"],
|
||||
message:
|
||||
"tools.web.x_search.apiKey moved to the xAI plugin; use plugins.entries.xai.config.webSearch.apiKey instead (auto-migrated on load).",
|
||||
'tools.web.x_search.apiKey moved to the xAI plugin; use plugins.entries.xai.config.webSearch.apiKey instead. Run "openclaw doctor --fix".',
|
||||
};
|
||||
|
||||
const LEGACY_TTS_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["messages", "tts"],
|
||||
message:
|
||||
"messages.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use messages.tts.providers.<provider> (auto-migrated on load).",
|
||||
'messages.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use messages.tts.providers.<provider>. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyTtsProviderKeys(value),
|
||||
},
|
||||
{
|
||||
path: ["plugins", "entries"],
|
||||
message:
|
||||
"plugins.entries.voice-call.config.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use plugins.entries.voice-call.config.tts.providers.<provider> (auto-migrated on load).",
|
||||
'plugins.entries.voice-call.config.tts.<provider> keys (openai/elevenlabs/microsoft/edge) are legacy; use plugins.entries.voice-call.config.tts.providers.<provider>. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyPluginEntryTtsProviderKeys(value),
|
||||
},
|
||||
];
|
||||
@@ -261,13 +261,13 @@ const LEGACY_SANDBOX_SCOPE_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["agents", "defaults", "sandbox"],
|
||||
message:
|
||||
"agents.defaults.sandbox.perSession is legacy; use agents.defaults.sandbox.scope instead (auto-migrated on load).",
|
||||
'agents.defaults.sandbox.perSession is legacy; use agents.defaults.sandbox.scope instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacySandboxPerSession(value),
|
||||
},
|
||||
{
|
||||
path: ["agents", "list"],
|
||||
message:
|
||||
"agents.list[].sandbox.perSession is legacy; use agents.list[].sandbox.scope instead (auto-migrated on load).",
|
||||
'agents.list[].sandbox.perSession is legacy; use agents.list[].sandbox.scope instead. Run "openclaw doctor --fix".',
|
||||
match: (value) => hasLegacyAgentListSandboxPerSession(value),
|
||||
},
|
||||
];
|
||||
@@ -346,8 +346,7 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
|
||||
// to seed this for new installs, but existing bind=lan/bind=custom installs that upgrade
|
||||
// crash-loop immediately on next startup with no recovery path (issue #29385).
|
||||
//
|
||||
// This migration runs on every gateway start via migrateLegacyConfig → applyLegacyMigrations
|
||||
// and writes the seeded origins to disk before the startup guard fires, preventing the loop.
|
||||
// Doctor-only migration path. Runtime now stops and points users to doctor before startup.
|
||||
id: "gateway.controlUi.allowedOrigins-seed-for-non-loopback",
|
||||
describe: "Seed gateway.controlUi.allowedOrigins for existing non-loopback gateway installs",
|
||||
apply: (raw, changes) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ const LEGACY_WEB_SEARCH_RULES: LegacyConfigRule[] = [
|
||||
{
|
||||
path: ["tools", "web", "search"],
|
||||
message:
|
||||
"tools.web.search provider-owned config moved to plugins.entries.<plugin>.config.webSearch (auto-migrated on load).",
|
||||
'tools.web.search provider-owned config moved to plugins.entries.<plugin>.config.webSearch. Run "openclaw doctor --fix".',
|
||||
match: (_value, root) => listLegacyWebSearchConfigPaths(root).length > 0,
|
||||
requireSourceLiteral: true,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
applyChannelDoctorCompatibilityMigrations,
|
||||
collectChannelLegacyConfigRules,
|
||||
} from "../channels/plugins/legacy-config.js";
|
||||
import { LEGACY_CONFIG_MIGRATIONS } from "./legacy.migrations.js";
|
||||
import { collectChannelLegacyConfigRules } from "../channels/plugins/legacy-config.js";
|
||||
import { LEGACY_CONFIG_RULES } from "./legacy.rules.js";
|
||||
import type { LegacyConfigRule } from "./legacy.shared.js";
|
||||
import type { LegacyConfigIssue } from "./types.js";
|
||||
@@ -51,23 +47,3 @@ export function findLegacyConfigIssues(
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
export function applyLegacyMigrations(raw: unknown): {
|
||||
next: Record<string, unknown> | null;
|
||||
changes: string[];
|
||||
} {
|
||||
if (!raw || typeof raw !== "object") {
|
||||
return { next: null, changes: [] };
|
||||
}
|
||||
const next = structuredClone(raw) as Record<string, unknown>;
|
||||
const changes: string[] = [];
|
||||
for (const migration of LEGACY_CONFIG_MIGRATIONS) {
|
||||
migration.apply(next, changes);
|
||||
}
|
||||
const compat = applyChannelDoctorCompatibilityMigrations(next);
|
||||
changes.push(...compat.changes);
|
||||
if (changes.length === 0) {
|
||||
return { next: null, changes: [] };
|
||||
}
|
||||
return { next: compat.next, changes };
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-di
|
||||
import { appendAllowedValuesHint, summarizeAllowedValues } from "./allowed-values.js";
|
||||
import { GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA } from "./bundled-channel-config-metadata.generated.js";
|
||||
import { collectChannelSchemaMetadata } from "./channel-config-metadata.js";
|
||||
import { applyLegacyMigrations, findLegacyConfigIssues } from "./legacy.js";
|
||||
import { findLegacyConfigIssues } from "./legacy.js";
|
||||
import { materializeRuntimeConfig } from "./materialize.js";
|
||||
import type { OpenClawConfig, ConfigValidationIssue } from "./types.js";
|
||||
import { coerceSecretRef } from "./types.secrets.js";
|
||||
@@ -546,13 +546,7 @@ function validateConfigObjectWithPluginsBase(
|
||||
raw: unknown,
|
||||
opts: { applyDefaults: boolean; env?: NodeJS.ProcessEnv },
|
||||
): ValidateConfigWithPluginsResult {
|
||||
// Config edit flows often start from raw parsed files that may still contain legacy keys.
|
||||
// Accept known legacy inputs here by normalizing them before schema/plugin validation.
|
||||
const migrated = applyLegacyMigrations(raw);
|
||||
const normalizedRaw = migrated.next ?? raw;
|
||||
const base = opts.applyDefaults
|
||||
? validateConfigObject(normalizedRaw)
|
||||
: validateConfigObjectRaw(normalizedRaw);
|
||||
const base = opts.applyDefaults ? validateConfigObject(raw) : validateConfigObjectRaw(raw);
|
||||
if (!base.ok) {
|
||||
return { ok: false, issues: base.issues, warnings: [] };
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
writeConfigFile,
|
||||
} from "../../config/config.js";
|
||||
import { formatConfigIssueLines } from "../../config/issue-format.js";
|
||||
import { applyLegacyMigrations } from "../../config/legacy.js";
|
||||
import { applyMergePatch } from "../../config/merge-patch.js";
|
||||
import {
|
||||
redactConfigObject,
|
||||
@@ -489,9 +488,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const migrated = applyLegacyMigrations(restoredMerge.result);
|
||||
const resolved = migrated.next ?? restoredMerge.result;
|
||||
const validated = validateConfigObjectWithPlugins(resolved);
|
||||
const validated = validateConfigObjectWithPlugins(restoredMerge.result);
|
||||
if (!validated.ok) {
|
||||
respond(
|
||||
false,
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
getRuntimeConfig,
|
||||
isNixMode,
|
||||
loadConfig,
|
||||
migrateLegacyConfig,
|
||||
registerConfigWriteListener,
|
||||
readConfigFileSnapshot,
|
||||
writeConfigFile,
|
||||
@@ -263,7 +262,7 @@ function assertValidGatewayStartupConfigSnapshot(
|
||||
? formatConfigIssueLines(snapshot.issues, "", { normalizeRoot: true }).join("\n")
|
||||
: "Unknown validation issue.";
|
||||
const doctorHint = options.includeDoctorHint
|
||||
? `\nRun "${formatCliCommand("openclaw doctor")}" to repair, then retry.`
|
||||
? `\nRun "${formatCliCommand("openclaw doctor --fix")}" to repair, then retry.`
|
||||
: "";
|
||||
throw new Error(`Invalid config at ${snapshot.path}.\n${issues}${doctorHint}`);
|
||||
}
|
||||
@@ -418,24 +417,7 @@ export async function startGatewayServer(
|
||||
"Legacy config entries detected while running in Nix mode. Update your Nix config to the latest schema and restart.",
|
||||
);
|
||||
}
|
||||
const { config: migrated, changes } = migrateLegacyConfig(configSnapshot.parsed);
|
||||
if (!migrated) {
|
||||
log.warn(
|
||||
"gateway: legacy config entries detected but no auto-migration changes were produced; continuing with validation.",
|
||||
);
|
||||
} else {
|
||||
await writeConfigFile(migrated);
|
||||
if (changes.length > 0) {
|
||||
log.info(
|
||||
`gateway: migrated legacy config entries:\n${changes
|
||||
.map((entry) => `- ${entry}`)
|
||||
.join("\n")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configSnapshot = await readConfigFileSnapshot();
|
||||
if (configSnapshot.exists) {
|
||||
assertValidGatewayStartupConfigSnapshot(configSnapshot, { includeDoctorHint: true });
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import {
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { migrateLegacyConfig } from "../config/legacy-migrate.js";
|
||||
import { migrateLegacyXSearchConfig } from "../config/legacy-x-search.js";
|
||||
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import type { PluginOrigin } from "../plugins/types.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
@@ -175,10 +173,8 @@ export async function prepareSecretsRuntimeSnapshot(params: {
|
||||
loadablePluginOrigins?: ReadonlyMap<string, PluginOrigin>;
|
||||
}): Promise<PreparedSecretsRuntimeSnapshot> {
|
||||
const runtimeEnv = mergeSecretsRuntimeEnv(params.env);
|
||||
const migrated = migrateLegacyConfig(params.config);
|
||||
const migratedConfig = migrated.config ?? migrateLegacyXSearchConfig(params.config).config;
|
||||
const sourceConfig = structuredClone(migratedConfig);
|
||||
const resolvedConfig = structuredClone(migratedConfig);
|
||||
const sourceConfig = structuredClone(params.config);
|
||||
const resolvedConfig = structuredClone(params.config);
|
||||
const loadablePluginOrigins =
|
||||
params.loadablePluginOrigins ??
|
||||
resolveLoadablePluginOrigins({ config: sourceConfig, env: runtimeEnv });
|
||||
|
||||
Reference in New Issue
Block a user