





















11// Migrate Hermes tests cover files and skills plugin behavior.
22import fs from "node:fs/promises";
33import path from "node:path";
4+import { DatabaseSync } from "node:sqlite";
45import { loadAuthProfileStoreWithoutExternalProfiles } from "openclaw/plugin-sdk/agent-runtime";
56import { MIGRATION_REASON_TARGET_EXISTS } from "openclaw/plugin-sdk/migration";
67import { afterEach, describe, expect, it } from "vitest";
@@ -203,6 +204,75 @@ describe("Hermes migration file and skill items", () => {
203204await expectPathMissing(path.join(workspaceDir, "logs", "session.log"));
204205});
205206207+it("archives committed Hermes SQLite WAL state", async () => {
208+const root = await makeTempRoot();
209+const source = path.join(root, "hermes");
210+const workspaceDir = path.join(root, "workspace");
211+const stateDir = path.join(root, "state");
212+const reportDir = path.join(root, "report");
213+const stateDbPath = path.join(source, "state.db");
214+await fs.mkdir(source, { recursive: true });
215+216+const sourceDb = new DatabaseSync(stateDbPath);
217+try {
218+sourceDb.exec(`
219+ PRAGMA journal_mode = WAL;
220+ CREATE TABLE marker(value TEXT NOT NULL);
221+ PRAGMA wal_checkpoint(TRUNCATE);
222+ `);
223+sourceDb.prepare("INSERT INTO marker(value) VALUES (?)").run("committed-only-in-wal");
224+expect((await fs.stat(`${stateDbPath}-wal`)).size).toBeGreaterThan(0);
225+226+const provider = buildHermesMigrationProvider();
227+const result = await provider.apply(
228+makeContext({ source, stateDir, workspaceDir, reportDir }),
229+);
230+231+const archivedState = itemById(result.items, "archive:state.db");
232+const archivedStatePath = path.join(reportDir, "archive", "state.db");
233+expect(archivedState?.status).toBe("migrated");
234+expect(archivedState?.source).toBe(stateDbPath);
235+expect(archivedState?.target).toBe(archivedStatePath);
236+237+const archivedDb = new DatabaseSync(archivedStatePath, { readOnly: true });
238+try {
239+expect(archivedDb.prepare("SELECT value FROM marker").all()).toEqual([
240+{ value: "committed-only-in-wal" },
241+]);
242+expect(archivedDb.prepare("PRAGMA integrity_check").get()).toEqual({
243+integrity_check: "ok",
244+});
245+} finally {
246+archivedDb.close();
247+}
248+} finally {
249+sourceDb.close();
250+}
251+});
252+253+it("preserves raw Hermes state when SQLite snapshotting fails", async () => {
254+const root = await makeTempRoot();
255+const source = path.join(root, "hermes");
256+const workspaceDir = path.join(root, "workspace");
257+const stateDir = path.join(root, "state");
258+const reportDir = path.join(root, "report");
259+const stateDbPath = path.join(source, "state.db");
260+const archivedStatePath = path.join(reportDir, "archive", "state.db");
261+await writeFile(stateDbPath, "legacy non-SQLite Hermes state\n");
262+263+const provider = buildHermesMigrationProvider();
264+const result = await provider.apply(makeContext({ source, stateDir, workspaceDir, reportDir }));
265+266+const archivedState = itemById(result.items, "archive:state.db");
267+expect(archivedState?.status).toBe("error");
268+expect(archivedState?.target).toBe(archivedStatePath);
269+expect(archivedState?.reason).toContain(
270+"SQLite snapshot failed; raw state.db preserved for manual review",
271+);
272+expect(await fs.readFile(archivedStatePath, "utf8")).toBe("legacy non-SQLite Hermes state\n");
273+expect(result.summary.errors).toBe(1);
274+});
275+206276it("reports legacy Hermes OpenAI auth.json OAuth state as manual reauth work", async () => {
207277const root = await makeTempRoot();
208278const source = path.join(root, "hermes");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。