diff --git a/docs/refactor/database-first.md b/docs/refactor/database-first.md index 45cc4ff0435..10360497afb 100644 --- a/docs/refactor/database-first.md +++ b/docs/refactor/database-first.md @@ -134,8 +134,8 @@ The branch already has a real shared SQLite base: input only; runtime no longer reads or writes TTS prefs JSON files, and the legacy path resolver lives in the doctor migration module. - Subagent run recovery and OpenRouter model capability cache runtime modules - now keep SQLite readers/writers separate from doctor-only legacy JSON import - helpers. + now keep SQLite snapshot readers/writers separate from doctor-only legacy JSON + import helpers. - `src/agents/filesystem/virtual-agent-fs.sqlite.ts` implements a SQLite VFS over the agent database `vfs_entries` table. - `src/agents/runtime-worker.entry.ts` creates per-run SQLite VFS, tool artifact, @@ -147,13 +147,14 @@ The branch already has a real shared SQLite base: Doctor imports legacy `~/.openclaw/exec-approvals.json`; runtime writes no longer create or rewrite that file. - Device identity, device auth, and bootstrap runtime modules now keep their - SQLite readers/writers separate from doctor-only legacy JSON import helpers. -- Web push, APNs, Voice Wake, and Voice Wake routing runtime modules now keep - their SQLite readers/writers separate from doctor-only legacy JSON import + SQLite snapshot readers/writers separate from doctor-only legacy JSON import helpers. +- Web push, APNs, Voice Wake, and Voice Wake routing runtime modules now keep + their SQLite snapshot readers/writers separate from doctor-only legacy JSON + import helpers. - Pairing state, plugin binding approvals, and cron job state now follow the - same split: runtime modules expose SQLite-backed operations and narrow - migration writers, while doctor imports/removes the old JSON files through + same split: runtime modules expose SQLite-backed operations and neutral + snapshot helpers, while doctor imports/removes the old JSON files through `src/commands/doctor/legacy/*` modules. - Core pairing and cron runtime modules no longer export legacy JSON path builders. Doctor-owned legacy modules construct `pending.json`, `paired.json`, diff --git a/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts b/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts index 7a6b83b89ef..664ef47609a 100644 --- a/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts +++ b/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts @@ -164,7 +164,7 @@ function readPersistentCache(): Map | undef return readSqliteCache(); } -export function writeOpenRouterModelCapabilitiesCacheForMigration( +export function writeOpenRouterModelCapabilitiesCacheSnapshot( map: Map, env?: NodeJS.ProcessEnv, ): void { diff --git a/src/agents/subagent-registry.store.ts b/src/agents/subagent-registry.store.ts index ee013d83b82..3b3079f042b 100644 --- a/src/agents/subagent-registry.store.ts +++ b/src/agents/subagent-registry.store.ts @@ -110,7 +110,7 @@ function normalizePersistedRunRecords(params: { return out; } -export function normalizeSubagentRunRecordsForMigration(params: { +export function normalizeSubagentRunRecordsSnapshot(params: { runsRaw: Record; isLegacy: boolean; }): Map { @@ -372,7 +372,7 @@ function writeSubagentRegistryRunsToSqlite( }, subagentRegistryDbOptions(env)); } -export function writeSubagentRegistryRunsForMigration( +export function writeSubagentRegistryRunsSnapshot( runs: Map, env: NodeJS.ProcessEnv = process.env, ): void { diff --git a/src/commands/doctor/legacy/device-auth-store.ts b/src/commands/doctor/legacy/device-auth-store.ts index ea7f7cd61d8..88fde253188 100644 --- a/src/commands/doctor/legacy/device-auth-store.ts +++ b/src/commands/doctor/legacy/device-auth-store.ts @@ -2,8 +2,8 @@ import fs from "node:fs"; import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { - parseDeviceAuthStoreForMigration, - writeDeviceAuthStoreForMigration, + parseDeviceAuthStoreSnapshot, + writeDeviceAuthStoreSnapshot, } from "../../../infra/device-auth-store.js"; function resolveDeviceAuthPath(env: NodeJS.ProcessEnv = process.env): string { @@ -32,11 +32,11 @@ export function importLegacyDeviceAuthFileToSqlite(env: NodeJS.ProcessEnv = proc } throw error; } - const store = parseDeviceAuthStoreForMigration(parsed); + const store = parseDeviceAuthStoreSnapshot(parsed); if (!store) { return { imported: false, tokens: 0 }; } - writeDeviceAuthStoreForMigration(env, store); + writeDeviceAuthStoreSnapshot(env, store); try { fs.rmSync(filePath, { force: true }); } catch { diff --git a/src/commands/doctor/legacy/device-identity.ts b/src/commands/doctor/legacy/device-identity.ts index e97274701c1..dd419c919df 100644 --- a/src/commands/doctor/legacy/device-identity.ts +++ b/src/commands/doctor/legacy/device-identity.ts @@ -2,8 +2,8 @@ import fs from "node:fs"; import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { - parseStoredDeviceIdentityForMigration, - writeStoredDeviceIdentityForMigration, + parseStoredDeviceIdentitySnapshot, + writeStoredDeviceIdentitySnapshot, } from "../../../infra/device-identity.js"; function resolveIdentityPathForEnv(env: NodeJS.ProcessEnv = process.env): string { @@ -31,11 +31,11 @@ export function importLegacyDeviceIdentityFileToSqlite(env: NodeJS.ProcessEnv = } throw error; } - const stored = parseStoredDeviceIdentityForMigration(parsed); + const stored = parseStoredDeviceIdentitySnapshot(parsed); if (!stored) { return { imported: false }; } - writeStoredDeviceIdentityForMigration(filePath, stored); + writeStoredDeviceIdentitySnapshot(filePath, stored); try { fs.rmSync(filePath, { force: true }); } catch { diff --git a/src/commands/doctor/legacy/openrouter-model-capabilities.ts b/src/commands/doctor/legacy/openrouter-model-capabilities.ts index 773dc1a58db..cafbd44b9fe 100644 --- a/src/commands/doctor/legacy/openrouter-model-capabilities.ts +++ b/src/commands/doctor/legacy/openrouter-model-capabilities.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { parseOpenRouterModelCapabilitiesCachePayload, - writeOpenRouterModelCapabilitiesCacheForMigration, + writeOpenRouterModelCapabilitiesCacheSnapshot, type OpenRouterModelCapabilities, } from "../../../agents/pi-embedded-runner/openrouter-model-capabilities.js"; import { resolveStateDir } from "../../../config/paths.js"; @@ -47,7 +47,7 @@ export function importLegacyOpenRouterModelCapabilitiesCacheToSqlite( } const legacyJsonCache = readLegacyJsonCache(env); if (legacyJsonCache) { - writeOpenRouterModelCapabilitiesCacheForMigration(legacyJsonCache, env); + writeOpenRouterModelCapabilitiesCacheSnapshot(legacyJsonCache, env); } try { unlinkSync(resolveLegacyJsonCachePath(env)); diff --git a/src/commands/doctor/legacy/plugin-conversation-binding.ts b/src/commands/doctor/legacy/plugin-conversation-binding.ts index f1797455366..9e7eecbd9ab 100644 --- a/src/commands/doctor/legacy/plugin-conversation-binding.ts +++ b/src/commands/doctor/legacy/plugin-conversation-binding.ts @@ -3,8 +3,8 @@ import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { expandHomePrefix } from "../../../infra/home-dir.js"; import { - normalizePluginBindingApprovalsForMigration, - writePluginBindingApprovalsForMigration, + normalizePluginBindingApprovalsSnapshot, + writePluginBindingApprovalsSnapshot, } from "../../../plugins/conversation-binding.js"; const LEGACY_APPROVALS_PATH = "~/.openclaw/plugin-binding-approvals.json"; @@ -35,10 +35,10 @@ export function importLegacyPluginBindingApprovalFileToSqlite(): { if (!legacyPluginBindingApprovalFileExists()) { return { imported: false, approvals: 0 }; } - const file = normalizePluginBindingApprovalsForMigration( + const file = normalizePluginBindingApprovalsSnapshot( JSON.parse(fs.readFileSync(filePath, "utf8")) as unknown, ); - writePluginBindingApprovalsForMigration(file); + writePluginBindingApprovalsSnapshot(file); try { fs.unlinkSync(filePath); } catch { diff --git a/src/commands/doctor/legacy/push-apns.ts b/src/commands/doctor/legacy/push-apns.ts index 2b067bd28b9..b2dab65d9ad 100644 --- a/src/commands/doctor/legacy/push-apns.ts +++ b/src/commands/doctor/legacy/push-apns.ts @@ -2,8 +2,8 @@ import fs from "node:fs/promises"; import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { - normalizeApnsRegistrationStateForMigration, - writeApnsRegistrationStateForMigration, + normalizeApnsRegistrationStateSnapshot, + writeApnsRegistrationStateSnapshot, } from "../../../infra/push-apns.js"; const LEGACY_APNS_STATE_FILENAME = "push/apns-registrations.json"; @@ -33,11 +33,11 @@ export async function importLegacyApnsRegistrationFileToSqlite(baseDir?: string) } throw error; } - const normalized = normalizeApnsRegistrationStateForMigration(parsed); + const normalized = normalizeApnsRegistrationStateSnapshot(parsed); if (!normalized) { return { imported: false, registrations: 0 }; } - await writeApnsRegistrationStateForMigration(normalized, baseDir); + await writeApnsRegistrationStateSnapshot(normalized, baseDir); await fs.rm(filePath, { force: true }).catch(() => undefined); return { imported: true, registrations: Object.keys(normalized.registrationsByNodeId).length }; } diff --git a/src/commands/doctor/legacy/push-web.ts b/src/commands/doctor/legacy/push-web.ts index eddddbd9b07..b3d1790fa35 100644 --- a/src/commands/doctor/legacy/push-web.ts +++ b/src/commands/doctor/legacy/push-web.ts @@ -2,8 +2,8 @@ import fs from "node:fs/promises"; import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { - writeWebPushRegistrationStateForMigration, - writeWebPushVapidKeysForMigration, + writeWebPushRegistrationStateSnapshot, + writeWebPushVapidKeysSnapshot, type VapidKeyPair, type WebPushRegistrationState, } from "../../../infra/push-web.js"; @@ -45,7 +45,7 @@ export async function importLegacyWebPushFilesToSqlite(baseDir?: string): Promis try { const state = JSON.parse(await fs.readFile(statePath, "utf8")) as WebPushRegistrationState; if (state && typeof state === "object") { - await writeWebPushRegistrationStateForMigration(state, baseDir); + await writeWebPushRegistrationStateSnapshot(state, baseDir); subscriptions = Object.keys(state.subscriptionsByEndpointHash ?? {}).length; await fs.rm(statePath, { force: true }).catch(() => undefined); files += 1; @@ -60,7 +60,7 @@ export async function importLegacyWebPushFilesToSqlite(baseDir?: string): Promis try { const keys = JSON.parse(await fs.readFile(vapidPath, "utf8")) as VapidKeyPair; if (keys?.publicKey && keys.privateKey) { - writeWebPushVapidKeysForMigration(keys, baseDir); + writeWebPushVapidKeysSnapshot(keys, baseDir); await fs.rm(vapidPath, { force: true }).catch(() => undefined); importedVapidKeys = true; files += 1; diff --git a/src/commands/doctor/legacy/subagent-registry.ts b/src/commands/doctor/legacy/subagent-registry.ts index f5fc2fe5888..bae6f347f7f 100644 --- a/src/commands/doctor/legacy/subagent-registry.ts +++ b/src/commands/doctor/legacy/subagent-registry.ts @@ -1,9 +1,9 @@ import fs from "node:fs"; import path from "node:path"; import { - normalizeSubagentRunRecordsForMigration, + normalizeSubagentRunRecordsSnapshot, resolveSubagentStateDir, - writeSubagentRegistryRunsForMigration, + writeSubagentRegistryRunsSnapshot, } from "../../../agents/subagent-registry.store.js"; import type { SubagentRunRecord } from "../../../agents/subagent-registry.types.js"; import { loadJsonFile } from "../../../infra/json-file.js"; @@ -44,7 +44,7 @@ function loadLegacySubagentRegistryFile(pathname: string): Map, isLegacy: record.version === 1, }); @@ -70,7 +70,7 @@ export function importLegacySubagentRegistryFileToSqlite(env: NodeJS.ProcessEnv return { imported: false, runs: 0 }; } const runs = loadLegacySubagentRegistryFile(pathname); - writeSubagentRegistryRunsForMigration(runs, env); + writeSubagentRegistryRunsSnapshot(runs, env); try { fs.unlinkSync(pathname); } catch { diff --git a/src/commands/doctor/legacy/voicewake-routing.ts b/src/commands/doctor/legacy/voicewake-routing.ts index 6dc07ffdb5d..fb21e275f6a 100644 --- a/src/commands/doctor/legacy/voicewake-routing.ts +++ b/src/commands/doctor/legacy/voicewake-routing.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { normalizeVoiceWakeRoutingConfig, - writeVoiceWakeRoutingConfigForMigration, + writeVoiceWakeRoutingConfigSnapshot, } from "../../../infra/voicewake-routing.js"; function resolveLegacyPath(baseDir?: string) { @@ -38,7 +38,7 @@ export async function importLegacyVoiceWakeRoutingConfigFileToSqlite(baseDir?: s throw error; } const normalized = normalizeVoiceWakeRoutingConfig(raw); - writeVoiceWakeRoutingConfigForMigration(normalized, baseDir); + writeVoiceWakeRoutingConfigSnapshot(normalized, baseDir); await fs.rm(filePath, { force: true }).catch(() => undefined); return { imported: true, routes: normalized.routes.length }; } diff --git a/src/commands/doctor/legacy/voicewake.ts b/src/commands/doctor/legacy/voicewake.ts index 164df86dedc..88a7261c028 100644 --- a/src/commands/doctor/legacy/voicewake.ts +++ b/src/commands/doctor/legacy/voicewake.ts @@ -2,8 +2,8 @@ import fs from "node:fs/promises"; import path from "node:path"; import { resolveStateDir } from "../../../config/paths.js"; import { - normalizeVoiceWakeConfigForMigration, - writeVoiceWakeConfigForMigration, + normalizeVoiceWakeConfigSnapshot, + writeVoiceWakeConfigSnapshot, } from "../../../infra/voicewake.js"; function resolveLegacyPath(baseDir?: string) { @@ -37,8 +37,8 @@ export async function importLegacyVoiceWakeConfigFileToSqlite(baseDir?: string): } throw error; } - const normalized = normalizeVoiceWakeConfigForMigration(raw); - writeVoiceWakeConfigForMigration(normalized, baseDir); + const normalized = normalizeVoiceWakeConfigSnapshot(raw); + writeVoiceWakeConfigSnapshot(normalized, baseDir); await fs.rm(filePath, { force: true }).catch(() => undefined); return { imported: true, triggers: normalized.triggers.length }; } diff --git a/src/infra/device-auth-store.ts b/src/infra/device-auth-store.ts index 10742a1e168..c548329f1f4 100644 --- a/src/infra/device-auth-store.ts +++ b/src/infra/device-auth-store.ts @@ -58,12 +58,12 @@ export function storeDeviceAuthStore(params: { return params.store; } -export function parseDeviceAuthStoreForMigration(raw: unknown): DeviceAuthStore | null { +export function parseDeviceAuthStoreSnapshot(raw: unknown): DeviceAuthStore | null { const store = DeviceAuthStoreSchema.safeParse(raw); return store.success ? store.data : null; } -export function writeDeviceAuthStoreForMigration( +export function writeDeviceAuthStoreSnapshot( env: NodeJS.ProcessEnv | undefined, store: DeviceAuthStore, ): void { diff --git a/src/infra/device-identity.ts b/src/infra/device-identity.ts index 8019d504e95..089ff0b70f1 100644 --- a/src/infra/device-identity.ts +++ b/src/infra/device-identity.ts @@ -226,11 +226,11 @@ export function loadDeviceIdentityIfPresentForEnv( } } -export function parseStoredDeviceIdentityForMigration(value: unknown): StoredDeviceIdentity | null { +export function parseStoredDeviceIdentitySnapshot(value: unknown): StoredDeviceIdentity | null { return parseStoredIdentity(value); } -export function writeStoredDeviceIdentityForMigration( +export function writeStoredDeviceIdentitySnapshot( filePath: string, stored: StoredDeviceIdentity, ): void { diff --git a/src/infra/push-apns.ts b/src/infra/push-apns.ts index 0690d740a9e..c8e8958c1e1 100644 --- a/src/infra/push-apns.ts +++ b/src/infra/push-apns.ts @@ -396,7 +396,7 @@ async function persistRegistrationsState( ); } -export function normalizeApnsRegistrationStateForMigration( +export function normalizeApnsRegistrationStateSnapshot( parsed: unknown, ): ApnsRegistrationState | null { if (!parsed || typeof parsed !== "object") { @@ -419,7 +419,7 @@ export function normalizeApnsRegistrationStateForMigration( return { registrationsByNodeId: normalized }; } -export async function writeApnsRegistrationStateForMigration( +export async function writeApnsRegistrationStateSnapshot( state: ApnsRegistrationState, baseDir?: string, ): Promise { diff --git a/src/infra/push-web.ts b/src/infra/push-web.ts index afc8296bf07..12ab6c786d2 100644 --- a/src/infra/push-web.ts +++ b/src/infra/push-web.ts @@ -103,14 +103,14 @@ async function persistState(state: WebPushRegistrationState, baseDir?: string): ); } -export async function writeWebPushRegistrationStateForMigration( +export async function writeWebPushRegistrationStateSnapshot( state: WebPushRegistrationState, baseDir?: string, ): Promise { await persistState(state, baseDir); } -export function writeWebPushVapidKeysForMigration(keys: VapidKeyPair, baseDir?: string): void { +export function writeWebPushVapidKeysSnapshot(keys: VapidKeyPair, baseDir?: string): void { writeOpenClawStateKvJson( WEB_PUSH_SCOPE, WEB_PUSH_VAPID_KEY, diff --git a/src/infra/voicewake-routing.ts b/src/infra/voicewake-routing.ts index 489fdaf18c3..c4b927fab02 100644 --- a/src/infra/voicewake-routing.ts +++ b/src/infra/voicewake-routing.ts @@ -295,7 +295,7 @@ export async function setVoiceWakeRoutingConfig( return next; } -export function writeVoiceWakeRoutingConfigForMigration( +export function writeVoiceWakeRoutingConfigSnapshot( config: VoiceWakeRoutingConfig, baseDir?: string, ): void { diff --git a/src/infra/voicewake.ts b/src/infra/voicewake.ts index 58fd65a9c36..01ca89c4320 100644 --- a/src/infra/voicewake.ts +++ b/src/infra/voicewake.ts @@ -66,7 +66,7 @@ export async function setVoiceWakeTriggers( return next; } -export function normalizeVoiceWakeConfigForMigration(raw: unknown): VoiceWakeConfig { +export function normalizeVoiceWakeConfigSnapshot(raw: unknown): VoiceWakeConfig { const updatedAtMs = (raw as Partial | undefined)?.updatedAtMs; return { triggers: sanitizeTriggers((raw as Partial | undefined)?.triggers), @@ -74,7 +74,7 @@ export function normalizeVoiceWakeConfigForMigration(raw: unknown): VoiceWakeCon }; } -export function writeVoiceWakeConfigForMigration(config: VoiceWakeConfig, baseDir?: string): void { +export function writeVoiceWakeConfigSnapshot(config: VoiceWakeConfig, baseDir?: string): void { writeOpenClawStateKvJson( VOICEWAKE_SCOPE, VOICEWAKE_CONFIG_KEY, diff --git a/src/plugins/conversation-binding.ts b/src/plugins/conversation-binding.ts index 40ffaab70d1..22f758ef542 100644 --- a/src/plugins/conversation-binding.ts +++ b/src/plugins/conversation-binding.ts @@ -342,7 +342,7 @@ function createApprovalRequestId(): string { return crypto.randomBytes(9).toString("base64url"); } -export function normalizePluginBindingApprovalsForMigration( +export function normalizePluginBindingApprovalsSnapshot( value: unknown, ): PluginBindingApprovalsFile { const parsed = value as Partial | undefined; @@ -372,7 +372,7 @@ export function normalizePluginBindingApprovalsForMigration( function loadApprovalsFromSqlite(): PluginBindingApprovalsFile { try { - return normalizePluginBindingApprovalsForMigration( + return normalizePluginBindingApprovalsSnapshot( readOpenClawStateKvJson( APPROVALS_KV_SCOPE, APPROVALS_KV_KEY, @@ -385,7 +385,7 @@ function loadApprovalsFromSqlite(): PluginBindingApprovalsFile { } } -export function writePluginBindingApprovalsForMigration(file: PluginBindingApprovalsFile): void { +export function writePluginBindingApprovalsSnapshot(file: PluginBindingApprovalsFile): void { writeOpenClawStateKvJson( APPROVALS_KV_SCOPE, APPROVALS_KV_KEY, @@ -398,7 +398,7 @@ export function writePluginBindingApprovalsForMigration(file: PluginBindingAppro } async function saveApprovals(file: PluginBindingApprovalsFile): Promise { - writePluginBindingApprovalsForMigration(file); + writePluginBindingApprovalsSnapshot(file); } function getApprovals(): PluginBindingApprovalsFile {