




















@@ -185,6 +185,51 @@ describe("config io write", () => {
185185});
186186});
187187188+it("keeps shipped plugin install config records when index migration fails", async () => {
189+await withSuiteHome(async (home) => {
190+const configPath = path.join(home, ".openclaw", "openclaw.json");
191+const unwritableStatePath = path.join(home, ".openclaw");
192+const pluginDir = path.join(unwritableStatePath, "plugins", "demo");
193+const original = {
194+plugins: {
195+entries: { demo: { enabled: true } },
196+installs: {
197+demo: {
198+source: "npm",
199+spec: "demo@1.0.0",
200+installPath: pluginDir,
201+},
202+},
203+},
204+};
205+await fs.mkdir(path.dirname(configPath), { recursive: true });
206+await fs.writeFile(configPath, `${JSON.stringify(original, null, 2)}\n`, "utf-8");
207+const warn = vi.fn();
208+const io = createConfigIO({
209+env: { OPENCLAW_TEST_FAST: "1" } as NodeJS.ProcessEnv,
210+homedir: () => home,
211+logger: { warn, error: vi.fn() },
212+});
213+await fs.writeFile(path.join(unwritableStatePath, "plugins"), "not a directory", "utf-8");
214+215+expect(() => io.loadConfig()).toThrow('Unrecognized key: "installs"');
216+expect(warn).toHaveBeenCalledWith(
217+expect.stringContaining("could not migrate shipped plugins.installs records"),
218+);
219+220+await expect(io.writeConfigFile({ gateway: { mode: "local" } })).rejects.toThrow(
221+"Config write blocked: shipped plugins.installs records",
222+);
223+224+const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as typeof original;
225+expect(persisted.plugins.installs.demo).toMatchObject({
226+source: "npm",
227+spec: "demo@1.0.0",
228+installPath: pluginDir,
229+});
230+});
231+});
232+188233const writeGatewayPortAndReadConfig = async (home: string, configPath: string) => {
189234const io = createFastConfigIO(home);
190235此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。