























@@ -226,6 +226,126 @@ describe("gateway startup config recovery", () => {
226226expect(log.info).not.toHaveBeenCalled();
227227});
228228229+it("preserves empty model allowlist entries through startup auto-enable writes", async () => {
230+const sourceConfig = {
231+agents: {
232+defaults: {
233+model: { primary: "dos-ai/dos-ai" },
234+models: {
235+"dos-ai/dos-ai": {},
236+"dos-ai/dos-auto": {},
237+},
238+},
239+},
240+gateway: { mode: "local" },
241+models: {
242+mode: "replace",
243+providers: {
244+"dos-ai": {
245+baseUrl: "https://dos.example.test/v1",
246+apiKey: "test-key",
247+api: "openai-completions",
248+models: [
249+{ id: "dos-ai", name: "DOS AI" },
250+{ id: "dos-auto", name: "DOS Auto" },
251+],
252+},
253+},
254+},
255+} as OpenClawConfig;
256+const autoEnabledConfig = {
257+ ...sourceConfig,
258+channels: {
259+telegram: { enabled: true },
260+},
261+} as OpenClawConfig;
262+const initialSnapshot = {
263+ ...buildTestConfigSnapshot({
264+path: configPath,
265+exists: true,
266+raw: `${JSON.stringify(sourceConfig)}\n`,
267+parsed: sourceConfig,
268+valid: true,
269+config: sourceConfig,
270+issues: [],
271+legacyIssues: [],
272+}),
273+ sourceConfig,
274+resolved: sourceConfig,
275+runtimeConfig: sourceConfig,
276+config: sourceConfig,
277+} satisfies ConfigFileSnapshot;
278+const postWriteSnapshot = {
279+ ...buildTestConfigSnapshot({
280+path: configPath,
281+exists: true,
282+raw: `${JSON.stringify(autoEnabledConfig)}\n`,
283+parsed: autoEnabledConfig,
284+valid: true,
285+config: autoEnabledConfig,
286+issues: [],
287+legacyIssues: [],
288+}),
289+sourceConfig: autoEnabledConfig,
290+resolved: autoEnabledConfig,
291+runtimeConfig: autoEnabledConfig,
292+config: autoEnabledConfig,
293+} satisfies ConfigFileSnapshot;
294+vi.mocked(configIo.readConfigFileSnapshotWithPluginMetadata)
295+.mockResolvedValueOnce({
296+snapshot: initialSnapshot,
297+ pluginMetadataSnapshot,
298+})
299+.mockResolvedValueOnce({
300+snapshot: postWriteSnapshot,
301+ pluginMetadataSnapshot,
302+});
303+applyPluginAutoEnable.mockReturnValueOnce({
304+config: autoEnabledConfig,
305+changes: ["Telegram configured, enabled automatically."],
306+autoEnabledReasons: {},
307+});
308+const log = { info: vi.fn(), warn: vi.fn() };
309+310+await expect(
311+loadGatewayStartupConfigSnapshot({
312+minimalTestGateway: false,
313+ log,
314+}),
315+).resolves.toEqual({
316+snapshot: postWriteSnapshot,
317+wroteConfig: true,
318+ pluginMetadataSnapshot,
319+});
320+321+expect(applyPluginAutoEnable).toHaveBeenCalledWith({
322+config: sourceConfig,
323+env: process.env,
324+manifestRegistry: pluginManifestRegistry,
325+});
326+expect(configMutate.replaceConfigFile).toHaveBeenCalledWith({
327+nextConfig: expect.objectContaining({
328+agents: expect.objectContaining({
329+defaults: expect.objectContaining({
330+models: {
331+"dos-ai/dos-ai": {},
332+"dos-ai/dos-auto": {},
333+},
334+}),
335+}),
336+}),
337+afterWrite: { mode: "auto" },
338+});
339+expect(postWriteSnapshot.sourceConfig.agents?.defaults?.models).toEqual({
340+"dos-ai/dos-ai": {},
341+"dos-ai/dos-auto": {},
342+});
343+expect(postWriteSnapshot.config.agents?.defaults?.models).toEqual({
344+"dos-ai/dos-ai": {},
345+"dos-ai/dos-auto": {},
346+});
347+});
348+229349it("restores last-known-good config before startup validation", async () => {
230350const invalidSnapshot = buildSnapshot({ valid: false, raw: "{ invalid json" });
231351const recoveredSnapshot = buildSnapshot({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。