






















@@ -47,6 +47,64 @@ vi.mock("./doctor/shared/channel-legacy-config-migrate.js", () => ({
4747}),
4848}));
494950+vi.mock("../secrets/target-registry.js", () => {
51+const entry = {
52+id: "channels.discord.token",
53+targetType: "channels.discord.token",
54+configFile: "openclaw.json",
55+pathPattern: "channels.discord.token",
56+secretShape: "secret_input",
57+expectedResolvedValue: "string",
58+includeInPlan: true,
59+includeInConfigure: true,
60+includeInAudit: true,
61+};
62+63+const readRecord = (value: unknown): Record<string, unknown> | null =>
64+value && typeof value === "object" && !Array.isArray(value)
65+ ? (value as Record<string, unknown>)
66+ : null;
67+68+return {
69+discoverConfigSecretTargets: (cfg: OpenClawConfig) => {
70+const targets: Array<{
71+entry: typeof entry;
72+path: string;
73+pathSegments: string[];
74+value: unknown;
75+accountId?: string;
76+}> = [];
77+const channels = readRecord(cfg.channels);
78+const discord = readRecord(channels?.discord);
79+if (!discord) {
80+return targets;
81+}
82+targets.push({
83+ entry,
84+path: "channels.discord.token",
85+pathSegments: ["channels", "discord", "token"],
86+value: discord.token,
87+});
88+89+const accounts = readRecord(discord.accounts);
90+for (const [accountId, accountConfig] of Object.entries(accounts ?? {})) {
91+const account = readRecord(accountConfig);
92+if (!account) {
93+continue;
94+}
95+targets.push({
96+ entry,
97+path: `channels.discord.accounts.${accountId}.token`,
98+pathSegments: ["channels", "discord", "accounts", accountId, "token"],
99+value: account.token,
100+ accountId,
101+});
102+}
103+return targets;
104+},
105+};
106+});
107+50108describe("normalizeCompatibilityConfigValues", () => {
51109let previousOauthDir: string | undefined;
52110let tempOauthDir = "";
@@ -115,6 +173,57 @@ describe("normalizeCompatibilityConfigValues", () => {
115173});
116174});
117175176+it("migrates legacy secretref-env markers on SecretRef credential paths", () => {
177+const res = normalizeCompatibilityConfigValues({
178+secrets: {
179+defaults: {
180+env: "gateway-env",
181+},
182+},
183+channels: {
184+discord: {
185+token: "secretref-env:DISCORD_BOT_TOKEN",
186+accounts: {
187+work: {
188+token: "secretref-env:DISCORD_WORK_TOKEN",
189+},
190+},
191+},
192+},
193+} as unknown as OpenClawConfig);
194+195+expect(res.config.channels?.discord?.token).toBeUndefined();
196+expect(res.config.channels?.discord?.accounts?.default?.token).toEqual({
197+source: "env",
198+provider: "gateway-env",
199+id: "DISCORD_BOT_TOKEN",
200+});
201+expect(res.config.channels?.discord?.accounts?.work?.token).toEqual({
202+source: "env",
203+provider: "gateway-env",
204+id: "DISCORD_WORK_TOKEN",
205+});
206+expect(res.changes).toContain(
207+"Moved channels.discord.accounts.default.token secretref-env:DISCORD_BOT_TOKEN marker → structured env SecretRef.",
208+);
209+expect(res.changes).toContain(
210+"Moved channels.discord.accounts.work.token secretref-env:DISCORD_WORK_TOKEN marker → structured env SecretRef.",
211+);
212+});
213+214+it("leaves invalid legacy secretref-env markers for validation to reject", () => {
215+const res = normalizeCompatibilityConfigValues({
216+channels: {
217+discord: {
218+token: "secretref-env:not-valid",
219+},
220+},
221+} as unknown as OpenClawConfig);
222+223+expect(res.config.channels?.discord?.token).toBe("secretref-env:not-valid");
224+expect(res.changes).toEqual([]);
225+});
226+118227it("moves WhatsApp access defaults into accounts.default for named accounts", () => {
119228const res = normalizeCompatibilityConfigValues({
120229channels: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。