mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-22 16:38:12 +00:00
fix: harden legacy session SQLite migration
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
// Assertions for upgrade-survivor E2E scenarios.
|
||||
import fs from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { readPluginInstallIndex } from "../plugin-index-sqlite.mjs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
const command = process.argv[2];
|
||||
const SCENARIOS = new Set([
|
||||
"base",
|
||||
@@ -23,6 +26,10 @@ const PERSONA_FILES = new Map([
|
||||
["MEMORY.md", "# Existing Memory\n\nUpgrade reports came from real users.\n"],
|
||||
]);
|
||||
|
||||
const LEGACY_SESSION_MAIN_ID = "upgrade-main-session";
|
||||
const LEGACY_SESSION_DIRECT_ID = "upgrade-direct-session";
|
||||
const LEGACY_SESSION_GROUP_ID = "upgrade-group-session";
|
||||
|
||||
function requireEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (!value) {
|
||||
@@ -83,6 +90,71 @@ function assert(condition, message) {
|
||||
}
|
||||
}
|
||||
|
||||
function readSessionRowsFromAgentSqlite(stateDir, agentId = "main") {
|
||||
const dbPath = path.join(stateDir, "agents", agentId, "agent", "openclaw-agent.sqlite");
|
||||
assert(fs.existsSync(dbPath), `agent SQLite session database missing: ${dbPath}`);
|
||||
const { DatabaseSync } = require("node:sqlite");
|
||||
const db = new DatabaseSync(dbPath, { readOnly: true });
|
||||
try {
|
||||
const rows = db
|
||||
.prepare("SELECT key, value_json FROM cache_entries WHERE scope = ? ORDER BY key ASC")
|
||||
.all("session_entries");
|
||||
return Object.fromEntries(
|
||||
rows.map((row) => {
|
||||
assert(typeof row.key === "string", "session SQLite row key was not a string");
|
||||
assert(
|
||||
typeof row.value_json === "string",
|
||||
`session SQLite row ${String(row.key)} had no JSON payload`,
|
||||
);
|
||||
return [row.key, JSON.parse(row.value_json)];
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
function seedLegacySessionMetadata(stateDir) {
|
||||
const legacySessionsDir = path.join(stateDir, "sessions");
|
||||
writeJson(path.join(legacySessionsDir, "sessions.json"), {
|
||||
main: {
|
||||
sessionId: LEGACY_SESSION_MAIN_ID,
|
||||
sessionFile: path.join(legacySessionsDir, `${LEGACY_SESSION_MAIN_ID}.jsonl`),
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
updatedAt: 1710000000000,
|
||||
skillsSnapshot: {
|
||||
prompt: "legacy prompt survives as metadata",
|
||||
resolvedSkills: [
|
||||
{
|
||||
name: "legacy-heavy-skill-cache",
|
||||
filePath: "/tmp/openclaw-old-package/skills/legacy-heavy-skill-cache/SKILL.md",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"+15551234567": {
|
||||
sessionId: LEGACY_SESSION_DIRECT_ID,
|
||||
sessionFile: path.join(legacySessionsDir, `${LEGACY_SESSION_DIRECT_ID}.jsonl`),
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
updatedAt: 1710000000100,
|
||||
},
|
||||
"slack:channel:CUPGRADE": {
|
||||
sessionId: LEGACY_SESSION_GROUP_ID,
|
||||
sessionFile: path.join(legacySessionsDir, `${LEGACY_SESSION_GROUP_ID}.jsonl`),
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
updatedAt: 1710000000200,
|
||||
lastChannel: "slack",
|
||||
lastTo: "CUPGRADE",
|
||||
},
|
||||
});
|
||||
write(path.join(legacySessionsDir, `${LEGACY_SESSION_MAIN_ID}.jsonl`), '{"type":"main"}\n');
|
||||
write(path.join(legacySessionsDir, `${LEGACY_SESSION_DIRECT_ID}.jsonl`), '{"type":"direct"}\n');
|
||||
write(path.join(legacySessionsDir, `${LEGACY_SESSION_GROUP_ID}.jsonl`), '{"type":"group"}\n');
|
||||
}
|
||||
|
||||
function getScenario() {
|
||||
const scenario = process.env.OPENCLAW_UPGRADE_SURVIVOR_SCENARIO || "base";
|
||||
assert(SCENARIOS.has(scenario), `unknown upgrade survivor scenario: ${scenario}`);
|
||||
@@ -139,6 +211,7 @@ function seedState() {
|
||||
agentId: "main",
|
||||
title: "Existing user session",
|
||||
});
|
||||
seedLegacySessionMetadata(stateDir);
|
||||
|
||||
const runtimeRoot = path.join(stateDir, "plugin-runtime-deps");
|
||||
for (const plugin of ["discord", "telegram", "whatsapp"]) {
|
||||
@@ -357,12 +430,15 @@ function assertStateSurvived() {
|
||||
const stateDir = requireEnv("OPENCLAW_STATE_DIR");
|
||||
const workspace = requireEnv("OPENCLAW_TEST_WORKSPACE_DIR");
|
||||
const scenario = getScenario();
|
||||
const stage = process.env.OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE || "survival";
|
||||
assert(fs.existsSync(path.join(workspace, "IDENTITY.md")), "workspace identity file missing");
|
||||
assert(
|
||||
fs.existsSync(path.join(stateDir, "agents", "main", "sessions", "legacy-session.json")),
|
||||
"legacy session file missing",
|
||||
);
|
||||
const stage = process.env.OPENCLAW_UPGRADE_SURVIVOR_ASSERT_STAGE || "survival";
|
||||
if (stage !== "baseline") {
|
||||
assertSessionMetadataMigratedToSqlite(stateDir);
|
||||
}
|
||||
const legacyRuntimeRoot = path.join(stateDir, "plugin-runtime-deps");
|
||||
if (stage === "baseline") {
|
||||
if (fs.existsSync(legacyRuntimeRoot)) {
|
||||
@@ -406,6 +482,59 @@ function assertStateSurvived() {
|
||||
}
|
||||
}
|
||||
|
||||
function assertSessionMetadataMigratedToSqlite(stateDir) {
|
||||
const legacyStorePath = path.join(stateDir, "sessions", "sessions.json");
|
||||
const agentSessionsDir = path.join(stateDir, "agents", "main", "sessions");
|
||||
assert(
|
||||
!fs.existsSync(legacyStorePath),
|
||||
`legacy sessions.json survived migration: ${legacyStorePath}`,
|
||||
);
|
||||
for (const sessionId of [
|
||||
LEGACY_SESSION_MAIN_ID,
|
||||
LEGACY_SESSION_DIRECT_ID,
|
||||
LEGACY_SESSION_GROUP_ID,
|
||||
]) {
|
||||
assert(
|
||||
fs.existsSync(path.join(agentSessionsDir, `${sessionId}.jsonl`)),
|
||||
`legacy session transcript was not moved for ${sessionId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const store = readSessionRowsFromAgentSqlite(stateDir);
|
||||
const main = store["agent:main:main"];
|
||||
const direct = store["agent:main:+15551234567"];
|
||||
const group = store["agent:main:slack:channel:cupgrade"];
|
||||
assert(main?.sessionId === LEGACY_SESSION_MAIN_ID, "main legacy session row missing from SQLite");
|
||||
assert(
|
||||
direct?.sessionId === LEGACY_SESSION_DIRECT_ID,
|
||||
"direct legacy session row missing from SQLite",
|
||||
);
|
||||
assert(
|
||||
group?.sessionId === LEGACY_SESSION_GROUP_ID,
|
||||
"channel legacy session row missing from SQLite",
|
||||
);
|
||||
assert(
|
||||
main?.sessionFile === path.join(agentSessionsDir, `${LEGACY_SESSION_MAIN_ID}.jsonl`),
|
||||
"main legacy session row still points at the old sessions directory",
|
||||
);
|
||||
assert(
|
||||
direct?.sessionFile === path.join(agentSessionsDir, `${LEGACY_SESSION_DIRECT_ID}.jsonl`),
|
||||
"direct legacy session row still points at the old sessions directory",
|
||||
);
|
||||
assert(
|
||||
group?.sessionFile === path.join(agentSessionsDir, `${LEGACY_SESSION_GROUP_ID}.jsonl`),
|
||||
"channel legacy session row still points at the old sessions directory",
|
||||
);
|
||||
assert(
|
||||
main.skillsSnapshot?.prompt === "legacy prompt survives as metadata",
|
||||
"legacy session metadata prompt was not preserved",
|
||||
);
|
||||
assert(
|
||||
main.skillsSnapshot?.resolvedSkills === undefined,
|
||||
"heavy resolvedSkills cache was persisted into SQLite session metadata",
|
||||
);
|
||||
}
|
||||
|
||||
function readInstalledPluginIndex() {
|
||||
const stateDir = requireEnv("OPENCLAW_STATE_DIR");
|
||||
const index = readPluginInstallIndex({ stateDir });
|
||||
|
||||
Reference in New Issue
Block a user