fix(ci): align scheduled e2e checks with sqlite sessions

This commit is contained in:
Vincent Koc
2026-07-12 15:22:37 +08:00
parent ba9a04580b
commit efdce6754d
8 changed files with 295 additions and 112 deletions

View File

@@ -1197,7 +1197,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-${DOCKER_E2E_CHUNK}-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
OPENCLAW_SKIP_DOCKER_BUILD=0 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
node .release-harness/scripts/test-docker-all.mjs
@@ -1525,7 +1525,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/targeted-${ARTIFACT_SUFFIX}-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
OPENCLAW_SKIP_DOCKER_BUILD=0 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
export OPENCLAW_DOCKER_ALL_BUILD=0
@@ -1717,7 +1717,7 @@ jobs:
export OPENCLAW_DOCKER_ALL_TIMINGS_FILE=".artifacts/docker-tests/release-openwebui-timings.json"
export OPENCLAW_DOCKER_ALL_PNPM_COMMAND="$(command -v pnpm)"
if [[ "${{ steps.plan.outputs.needs_live_image }}" == "1" ]]; then
OPENCLAW_DOCKER_BUILD_ON_MISSING=1 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
OPENCLAW_SKIP_DOCKER_BUILD=0 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" bash .release-harness/scripts/test-live-build-docker.sh
fi
node .release-harness/scripts/test-docker-all.mjs

View File

@@ -1,6 +1,7 @@
// Assertions for live plugin tool E2E scenarios.
import fs from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { extractAgentReplyTexts } from "../agent-turn-output.mjs";
import { readPluginInstallRecords } from "../plugin-index-sqlite.mjs";
import { readTextFileTail, tailText } from "../text-file-utils.mjs";
@@ -33,6 +34,7 @@ const AGENT_OUTPUT_MAX_BYTES = readPositiveIntEnv(
1024 * 1024,
);
const SESSION_FILE_LIST_LIMIT = 20;
const LIVE_PLUGIN_TOOL_SESSION_ID = "live-plugin-tool";
const SESSION_SCAN_MAX_ENTRIES = readPositiveIntEnv(
"OPENCLAW_LIVE_PLUGIN_TOOL_SESSION_SCAN_MAX_ENTRIES",
50_000,
@@ -373,6 +375,41 @@ function scanSessionTranscripts(sessionsDir, toolName, expected) {
return { checkedFiles, filesChecked, found: false, missingDir: false };
}
function scanSqliteSessionTranscript(databasePath, sessionId, toolName, expected) {
if (!fs.existsSync(databasePath)) {
return { eventsChecked: 0, found: false };
}
const database = new DatabaseSync(databasePath, { readOnly: true });
try {
const table = database
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'transcript_events'")
.get();
if (!table) {
return { eventsChecked: 0, found: false };
}
const rows = database
.prepare("SELECT event_json FROM transcript_events WHERE session_id = ? ORDER BY seq LIMIT ?")
.all(sessionId, SESSION_SCAN_MAX_ENTRIES + 1);
if (rows.length > SESSION_SCAN_MAX_ENTRIES) {
throw new Error(`session transcript scan exceeded ${SESSION_SCAN_MAX_ENTRIES} SQLite events`);
}
const tracker = createToolEvidenceTracker(toolName, expected);
for (const row of rows) {
if (typeof row.event_json !== "string") {
continue;
}
const message = transcriptMessageFromLine(row.event_json);
if (message && tracker.recordMessage(message)) {
return { eventsChecked: rows.length, found: true };
}
}
return { eventsChecked: rows.length, found: false };
} finally {
database.close();
}
}
function realPathMaybe(filePath) {
try {
return fs.realpathSync(filePath);
@@ -610,13 +647,22 @@ function assertAgentTurn() {
`live agent reply did not contain tool slug ${expected}:\nstdout tail=${tailText(stdout, ERROR_DETAIL_TAIL_BYTES)}\nstderr tail=${stderrTail}`,
);
}
const sessionsDir = path.join(stateDir(), "agents", "main", "sessions");
const scan = scanSessionTranscripts(sessionsDir, toolName, expected);
if (!scan.found) {
const checkedFiles = scan.checkedFiles.length > 0 ? scan.checkedFiles.join(", ") : "<none>";
const missingDir = scan.missingDir ? " sessions directory was missing." : "";
const agentStateDir = path.join(stateDir(), "agents", "main");
const sqliteScan = scanSqliteSessionTranscript(
path.join(agentStateDir, "agent", "openclaw-agent.sqlite"),
LIVE_PLUGIN_TOOL_SESSION_ID,
toolName,
expected,
);
const fileScan = sqliteScan.found
? { checkedFiles: [], filesChecked: 0, found: false, missingDir: false }
: scanSessionTranscripts(path.join(agentStateDir, "sessions"), toolName, expected);
if (!sqliteScan.found && !fileScan.found) {
const checkedFiles =
fileScan.checkedFiles.length > 0 ? fileScan.checkedFiles.join(", ") : "<none>";
const missingDir = fileScan.missingDir ? " sessions directory was missing." : "";
throw new Error(
`session transcript did not show ${toolName} returning ${expected}; missing causal tool-result evidence after checking ${scan.filesChecked} jsonl file(s): ${checkedFiles}.${missingDir}`,
`session transcript did not show ${toolName} returning ${expected}; missing causal tool-result evidence after checking ${sqliteScan.eventsChecked} SQLite event(s) and ${fileScan.filesChecked} jsonl file(s): ${checkedFiles}.${missingDir}`,
);
}
}

View File

@@ -478,20 +478,11 @@ function assertSessionMetadataMigrated(stateDir) {
const legacyStorePath = path.join(stateDir, "sessions", "sessions.json");
const agentSessionsDir = path.join(stateDir, "agents", "main", "sessions");
const targetStorePath = path.join(agentSessionsDir, "sessions.json");
const dbPath = path.join(stateDir, "agents", "main", "agent", "openclaw-agent.sqlite");
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 = readMigratedSessionStore(stateDir, targetStorePath);
const main = store["agent:main:main"];
@@ -500,18 +491,44 @@ function assertSessionMetadataMigrated(stateDir) {
assert(main?.sessionId === LEGACY_SESSION_MAIN_ID, "main legacy session row missing");
assert(direct?.sessionId === LEGACY_SESSION_DIRECT_ID, "direct legacy session row missing");
assert(group?.sessionId === LEGACY_SESSION_GROUP_ID, "channel legacy session row missing");
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",
);
const migratedSessionIds = [
LEGACY_SESSION_MAIN_ID,
LEGACY_SESSION_DIRECT_ID,
LEGACY_SESSION_GROUP_ID,
];
if (fs.existsSync(dbPath)) {
const db = new DatabaseSync(dbPath, { readOnly: true });
try {
const count = db.prepare(
"SELECT COUNT(*) AS count FROM transcript_events WHERE session_id = ?",
);
for (const sessionId of migratedSessionIds) {
const row = count.get(sessionId);
assert(
Number(row?.count ?? 0) > 0,
`legacy session transcript was not imported for ${sessionId}`,
);
}
} finally {
db.close();
}
} else {
for (const [sessionId, entry] of [
[LEGACY_SESSION_MAIN_ID, main],
[LEGACY_SESSION_DIRECT_ID, direct],
[LEGACY_SESSION_GROUP_ID, group],
]) {
const expectedPath = path.join(agentSessionsDir, `${sessionId}.jsonl`);
assert(
fs.existsSync(expectedPath),
`legacy session transcript was not moved for ${sessionId}`,
);
assert(
entry?.sessionFile === expectedPath,
`legacy session row still points at the old sessions directory for ${sessionId}`,
);
}
}
assert(
main.skillsSnapshot?.prompt === "legacy prompt survives as metadata",
"legacy session metadata prompt was not preserved",
@@ -533,15 +550,28 @@ function readMigratedSessionStore(stateDir, targetStorePath) {
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const rows = db
.prepare("SELECT key, value_json FROM cache_entries WHERE scope = ?")
.all("session_entries");
const hasSessionEntries = db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'session_entries'")
.get();
const rows = hasSessionEntries
? db
.prepare(
`SELECT se.session_key AS key, sr.session_id, se.entry_json AS value_json
FROM session_entries AS se
INNER JOIN session_routes AS sr ON sr.session_key = se.session_key`,
)
.all()
: db
.prepare("SELECT key, value_json FROM cache_entries WHERE scope = ?")
.all("session_entries");
const store = {};
for (const row of rows) {
if (typeof row?.key !== "string" || typeof row?.value_json !== "string") {
continue;
}
store[row.key] = JSON.parse(row.value_json);
const entry = JSON.parse(row.value_json);
store[row.key] =
typeof row.session_id === "string" ? { ...entry, sessionId: row.session_id } : entry;
}
return store;
} finally {

View File

@@ -150,7 +150,7 @@ openclaw_e2e_enable_openclaw_cli_timeout
fixture_dir="$(mktemp -d /tmp/openclaw-live-plugin-tool.XXXXXX)"
plugin_dir="$fixture_dir/package"
mkdir -p "$plugin_dir"
node scripts/e2e/lib/live-plugin-tool/assertions.mjs write-fixture "$plugin_dir"
node --disable-warning=ExperimentalWarning scripts/e2e/lib/live-plugin-tool/assertions.mjs write-fixture "$plugin_dir"
(cd "$plugin_dir" && npm pack --pack-destination "$fixture_dir" --silent) \
>/tmp/openclaw-live-plugin-tool-pack.log 2>&1
plugin_tgzs=()
@@ -166,11 +166,11 @@ plugin_tgz="${plugin_tgzs[0]}"
echo "Installing fixture plugin from npm-pack: $plugin_tgz"
openclaw plugins install "npm-pack:$plugin_tgz" --force >/tmp/openclaw-plugin-install.log 2>&1
node scripts/e2e/lib/live-plugin-tool/assertions.mjs configure
node --disable-warning=ExperimentalWarning scripts/e2e/lib/live-plugin-tool/assertions.mjs configure
openclaw plugins enable "$PLUGIN_ID" >/tmp/openclaw-plugin-enable.log 2>&1
openclaw plugins list --json >/tmp/openclaw-plugins-list.json
openclaw plugins inspect "$PLUGIN_ID" --runtime --json >/tmp/openclaw-plugin-inspect.json
node scripts/e2e/lib/live-plugin-tool/assertions.mjs assert-installed
node --disable-warning=ExperimentalWarning scripts/e2e/lib/live-plugin-tool/assertions.mjs assert-installed
echo "Running live OpenAI agent turn that must call $TOOL_NAME..."
openclaw agent --local \
@@ -182,7 +182,7 @@ openclaw agent --local \
--timeout "${OPENCLAW_LIVE_PLUGIN_TOOL_TIMEOUT_SECONDS:-300}" \
--json >/tmp/openclaw-agent.json 2>/tmp/openclaw-agent.err
node scripts/e2e/lib/live-plugin-tool/assertions.mjs assert-agent-turn
node --disable-warning=ExperimentalWarning scripts/e2e/lib/live-plugin-tool/assertions.mjs assert-agent-turn
echo "Live plugin tool Docker E2E passed"
EOF

View File

@@ -5,7 +5,9 @@ import { spawnSync } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
import { readSessionTranscriptEvents } from "openclaw/plugin-sdk/session-transcript-runtime";
import {
buildRuntimeContextCustomMessage,
resolveRuntimeContextPromptParts,
@@ -32,14 +34,6 @@ function setEnvValue(key: string, value: string): void {
Reflect.set(process.env, key, value);
}
async function readJsonl(filePath: string): Promise<TranscriptEntry[]> {
const raw = await fs.readFile(filePath, "utf-8");
return raw
.split(/\r?\n/)
.filter(Boolean)
.map((line) => JSON.parse(line) as TranscriptEntry);
}
function messageText(content: unknown): string {
if (typeof content === "string") {
return content;
@@ -91,7 +85,7 @@ async function verifyRuntimeContextTranscriptShape(root: string) {
timestamp: Date.now() + 1,
});
const entries = await readJsonl(sessionFile);
const entries = sessionManager.getEntries() as TranscriptEntry[];
const customEntry = entries.find((entry) => entry.type === "custom_message");
assert(!customEntry, "runtime custom message should not be persisted without its user turn");
assert(
@@ -218,7 +212,25 @@ async function verifyDoctorRepair(root: string) {
result.status === 0,
`doctor --fix failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
);
const entries = await readJsonl(sessionFile);
const databasePath = path.join(stateDir, "agents", "main", "agent", "openclaw-agent.sqlite");
const database = new DatabaseSync(databasePath, { readOnly: true });
let migratedSessionId: string | undefined;
try {
const row = database
.prepare("SELECT session_id FROM session_routes WHERE session_key = ?")
.get("agent:main:qa:docker-runtime-context");
if (typeof row?.session_id === "string") {
migratedSessionId = row.session_id;
}
} finally {
database.close();
}
assert(migratedSessionId, "doctor did not migrate session");
const entries = (await readSessionTranscriptEvents({
agentId: "main",
sessionId: migratedSessionId,
sessionKey: "agent:main:qa:docker-runtime-context",
})) as TranscriptEntry[];
const ids = entries.map((entryValue) => (entryValue as { id?: string }).id).filter(Boolean);
assert(
JSON.stringify(ids) ===

View File

@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, it } from "vitest";
const ASSERTIONS_SCRIPT = "scripts/e2e/lib/live-plugin-tool/assertions.mjs";
@@ -68,7 +69,7 @@ describe("live plugin tool assertions", () => {
OPENCLAW_LIVE_PLUGIN_TOOL_TIMEOUT_SECONDS: "240",
});
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
const config = JSON.parse(readFileSync(path.join(root, "state", "openclaw.json"), "utf8"));
expect(config.models.providers.openai.timeoutSeconds).toBe(240);
expect(config.agents.defaults.timeoutSeconds).toBe(240);
@@ -115,7 +116,75 @@ describe("live plugin tool assertions", () => {
const result = runAssertion(root);
expect(result.status).toBe(0);
expect(result.status, result.stderr).toBe(0);
expect(result.stderr).toBe("");
} finally {
rmSync(root, { force: true, recursive: true });
}
});
it("reads causal tool evidence from the canonical SQLite transcript", () => {
const root = mkdtempSync(path.join(tmpdir(), "openclaw-live-plugin-tool-"));
const databasePath = path.join(
root,
"state",
"agents",
"main",
"agent",
"openclaw-agent.sqlite",
);
try {
writeJson(path.join(root, "agent.json"), {
payloads: [{ text: "live-plugin-slug" }],
});
mkdirSync(path.dirname(databasePath), { recursive: true });
const database = new DatabaseSync(databasePath);
try {
database.exec(`
CREATE TABLE transcript_events (
session_id TEXT NOT NULL,
seq INTEGER NOT NULL,
event_json TEXT NOT NULL
)
`);
const insert = database.prepare(
"INSERT INTO transcript_events (session_id, seq, event_json) VALUES (?, ?, ?)",
);
insert.run(
"live-plugin-tool",
1,
JSON.stringify({
message: {
role: "assistant",
content: [
{
type: "tool_use",
id: "call-live-plugin-tool",
name: "e2e_slug_probe",
},
],
},
}),
);
insert.run(
"live-plugin-tool",
2,
JSON.stringify({
message: {
role: "tool",
tool_call_id: "call-live-plugin-tool",
content: "live-plugin-slug",
},
}),
);
} finally {
database.close();
}
const result = runAssertion(root);
expect(result.status, result.stderr).toBe(0);
expect(result.stderr).toBe("");
} finally {
rmSync(root, { force: true, recursive: true });
@@ -162,7 +231,7 @@ describe("live plugin tool assertions", () => {
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("session transcript did not show");
expect(result.stderr).toContain("after checking 2 jsonl file(s)");
expect(result.stderr).toContain("0 SQLite event(s) and 2 jsonl file(s)");
} finally {
rmSync(root, { force: true, recursive: true });
}
@@ -322,7 +391,7 @@ describe("live plugin tool assertions", () => {
expect(result.status).not.toBe(0);
expect(result.stderr).toContain("session transcript did not show");
expect(result.stderr).toContain("after checking 1 jsonl file(s)");
expect(result.stderr).toContain("0 SQLite event(s) and 1 jsonl file(s)");
expect(result.stderr).toContain("session.jsonl");
expect(result.stderr).not.toContain("DO_NOT_DUMP_SESSION_CONTENT");
} finally {

View File

@@ -188,6 +188,23 @@ function expectReadOnlyPackagePermission(workflowJob: WorkflowJob): void {
}
describe("release validation no-push transport", () => {
it("builds planned live images locally without entering pull fallback", () => {
const workflow = readWorkflow(LIVE_E2E);
for (const jobName of [
"validate_docker_e2e",
"validate_docker_lanes",
"validate_docker_openwebui",
]) {
const job = workflow.jobs?.[jobName];
const runStep = job?.steps?.find((candidate) =>
candidate.run?.includes("test-live-build-docker.sh"),
);
expect(runStep?.run, jobName).toContain("OPENCLAW_SKIP_DOCKER_BUILD=0");
expect(runStep?.run, jobName).not.toContain("OPENCLAW_DOCKER_BUILD_ON_MISSING=1");
}
});
it("keeps every local reusable-workflow permission request within its caller ceiling", () => {
const readOnlyCalls = [
[PLUGIN_PRERELEASE, "plugin-prerelease-docker-suite"],

View File

@@ -15,79 +15,88 @@ function writeJson(path: string, value: unknown): void {
function writeMigratedSessionState(stateDir: string): void {
const agentSessionsDir = join(stateDir, "agents", "main", "sessions");
const agentDbDir = join(stateDir, "agents", "main", "agent");
const mainSessionFile = join(agentSessionsDir, "upgrade-main-session.jsonl");
const directSessionFile = join(agentSessionsDir, "upgrade-direct-session.jsonl");
const groupSessionFile = join(agentSessionsDir, "upgrade-group-session.jsonl");
mkdirSync(agentSessionsDir, { recursive: true });
mkdirSync(agentDbDir, { recursive: true });
writeFileSync(mainSessionFile, '{"type":"main"}\n');
writeFileSync(directSessionFile, '{"type":"direct"}\n');
writeFileSync(groupSessionFile, '{"type":"group"}\n');
writeJson(join(agentSessionsDir, "sessions.json"), {
"agent:main:main": {
sessionFile: mainSessionFile,
sessionId: "upgrade-main-session",
skillsSnapshot: {
prompt: "legacy prompt survives as metadata",
},
},
"agent:main:+15551234567": {
sessionFile: directSessionFile,
sessionId: "upgrade-direct-session",
},
"agent:main:slack:channel:cupgrade": {
sessionFile: groupSessionFile,
sessionId: "upgrade-group-session",
},
});
const db = new DatabaseSync(join(agentDbDir, "openclaw-agent.sqlite"));
try {
db.exec(`
CREATE TABLE IF NOT EXISTS cache_entries (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value_json TEXT,
blob BLOB,
expires_at INTEGER,
CREATE TABLE sessions (
session_id TEXT PRIMARY KEY,
session_key TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE session_routes (
session_key TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, key)
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
);
CREATE TABLE session_entries (
session_key TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
entry_json TEXT NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
);
CREATE TABLE transcript_events (
session_id TEXT NOT NULL,
seq INTEGER NOT NULL,
event_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, seq),
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
);
`);
const insert = db.prepare(`
INSERT INTO cache_entries (scope, key, value_json, updated_at)
const insertSession = db.prepare(`
INSERT INTO sessions (session_id, session_key, created_at, updated_at)
VALUES (?, ?, ?, ?)
`);
insert.run(
"session_entries",
"agent:main:main",
JSON.stringify({
sessionFile: mainSessionFile,
sessionId: "upgrade-main-session",
skillsSnapshot: {
prompt: "legacy prompt survives as metadata",
const insertRoute = db.prepare(`
INSERT INTO session_routes (session_key, session_id, updated_at)
VALUES (?, ?, ?)
`);
const insertEntry = db.prepare(`
INSERT INTO session_entries (session_key, session_id, entry_json, updated_at)
VALUES (?, ?, ?, ?)
`);
const insertTranscript = db.prepare(`
INSERT INTO transcript_events (session_id, seq, event_json, created_at)
VALUES (?, ?, ?, ?)
`);
const migratedSessions = [
{
entry: {
skillsSnapshot: {
prompt: "legacy prompt survives as metadata",
},
},
}),
1710000000000,
);
insert.run(
"session_entries",
"agent:main:+15551234567",
JSON.stringify({
sessionFile: directSessionFile,
sessionId: "upgrade-main-session",
sessionKey: "agent:main:main",
},
{
entry: {},
sessionId: "upgrade-direct-session",
}),
1710000000100,
);
insert.run(
"session_entries",
"agent:main:slack:channel:cupgrade",
JSON.stringify({
sessionFile: groupSessionFile,
sessionKey: "agent:main:+15551234567",
},
{
entry: {},
sessionId: "upgrade-group-session",
}),
1710000000200,
);
sessionKey: "agent:main:slack:channel:cupgrade",
},
];
for (const { entry, sessionId, sessionKey } of migratedSessions) {
insertSession.run(sessionId, sessionKey, 1710000000000, 1710000000000);
insertRoute.run(sessionKey, sessionId, 1710000000000);
insertEntry.run(sessionKey, sessionId, JSON.stringify(entry), 1710000000000);
insertTranscript.run(
sessionId,
1,
JSON.stringify({ type: "session", id: sessionId }),
1710000000000,
);
}
} finally {
db.close();
}