mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-11 04:48:05 +00:00
refactor: keep legacy state paths in doctor
This commit is contained in:
@@ -131,7 +131,8 @@ The branch already has a real shared SQLite base:
|
||||
The old TUI JSON file is doctor migration input only.
|
||||
- Default TTS prefs now live in shared plugin-state SQLite rows keyed under the
|
||||
`speech-core` plugin. The old `settings/tts.json` file is doctor migration
|
||||
input only; runtime no longer reads or writes TTS prefs JSON files.
|
||||
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.
|
||||
@@ -154,6 +155,10 @@ The branch already has a real shared SQLite base:
|
||||
same split: runtime modules expose SQLite-backed operations and narrow
|
||||
migration writers, 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`,
|
||||
`bootstrap.json`, and `cron/jobs.json` source paths for import tests and
|
||||
migration only.
|
||||
- `src/commands/doctor-sqlite-state.ts` already imports several legacy JSON
|
||||
state files, including node host config, into SQLite from doctor.
|
||||
- `src/commands/doctor/state-migrations.ts` imports legacy `sessions.json` and
|
||||
|
||||
@@ -72,7 +72,7 @@ type LedgerStore = {
|
||||
sessions: Record<string, LedgerSession>;
|
||||
};
|
||||
|
||||
export type AcpEventLedgerMigrationStore = LedgerStore;
|
||||
export type AcpEventLedgerSnapshot = LedgerStore;
|
||||
|
||||
type LedgerOptions = {
|
||||
maxSessions?: number;
|
||||
@@ -622,14 +622,12 @@ function writeStoreToSqlite(
|
||||
}, options);
|
||||
}
|
||||
|
||||
export function normalizeAcpEventLedgerStoreForMigration(
|
||||
raw: unknown,
|
||||
): AcpEventLedgerMigrationStore {
|
||||
export function normalizeAcpEventLedgerSnapshot(raw: unknown): AcpEventLedgerSnapshot {
|
||||
return normalizeStore(raw);
|
||||
}
|
||||
|
||||
export function writeAcpEventLedgerStoreToSqliteForMigration(
|
||||
store: AcpEventLedgerMigrationStore,
|
||||
export function writeAcpEventLedgerSnapshotToSqlite(
|
||||
store: AcpEventLedgerSnapshot,
|
||||
options: OpenClawStateDatabaseOptions & { now?: () => number } = {},
|
||||
): void {
|
||||
writeStoreToSqlite(store, {
|
||||
|
||||
@@ -4,12 +4,7 @@ import { promisify } from "node:util";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveCronRunLogPruneOptions } from "../cron/run-log.js";
|
||||
import {
|
||||
resolveCronStoreKey,
|
||||
resolveLegacyCronStorePath,
|
||||
loadCronStore,
|
||||
saveCronStore,
|
||||
} from "../cron/store.js";
|
||||
import { resolveCronStoreKey, loadCronStore, saveCronStore } from "../cron/store.js";
|
||||
import type { CronJob } from "../cron/types.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
@@ -32,6 +27,7 @@ import {
|
||||
legacyCronStoreFileExists,
|
||||
legacyCronStateFileExists,
|
||||
loadLegacyCronStoreForMigration,
|
||||
resolveLegacyCronStorePath,
|
||||
} from "./doctor/legacy/cron-store.js";
|
||||
|
||||
type CronDoctorOutcome = {
|
||||
|
||||
@@ -3,8 +3,8 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
ACP_EVENT_LEDGER_VERSION,
|
||||
normalizeAcpEventLedgerStoreForMigration,
|
||||
writeAcpEventLedgerStoreToSqliteForMigration,
|
||||
normalizeAcpEventLedgerSnapshot,
|
||||
writeAcpEventLedgerSnapshotToSqlite,
|
||||
} from "../../../acp/event-ledger.js";
|
||||
import { resolveStateDir } from "../../../config/paths.js";
|
||||
|
||||
@@ -54,8 +54,8 @@ export async function importLegacyAcpEventLedgerFileToSqlite(
|
||||
if (!isLegacyAcpEventLedgerShape(parsed)) {
|
||||
return { imported: false, sessions: 0, events: 0 };
|
||||
}
|
||||
const store = normalizeAcpEventLedgerStoreForMigration(parsed);
|
||||
writeAcpEventLedgerStoreToSqliteForMigration(store, { env });
|
||||
const store = normalizeAcpEventLedgerSnapshot(parsed);
|
||||
writeAcpEventLedgerSnapshotToSqlite(store, { env });
|
||||
await fs.rm(filePath, { force: true }).catch(() => undefined);
|
||||
return {
|
||||
imported: true,
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import {
|
||||
extractCronStateFileForMigration,
|
||||
type CronStateFile,
|
||||
type CronStateFileEntry,
|
||||
writeCronJobRuntimeStateForMigration,
|
||||
writeCronJobsForMigration,
|
||||
extractCronRuntimeStateSnapshot,
|
||||
saveCronStore,
|
||||
type CronRuntimeStateEntry,
|
||||
type CronRuntimeStateSnapshot,
|
||||
writeCronRuntimeStateSnapshot,
|
||||
} from "../../../cron/store.js";
|
||||
import type { CronStoreFile } from "../../../cron/types.js";
|
||||
import { expandHomePrefix } from "../../../infra/home-dir.js";
|
||||
import { resolveConfigDir } from "../../../utils.js";
|
||||
import { parseJsonWithJson5Fallback } from "../../../utils/parse-json-compat.js";
|
||||
|
||||
function resolveDefaultCronDir(): string {
|
||||
return path.join(resolveConfigDir(), "cron");
|
||||
}
|
||||
|
||||
function resolveDefaultLegacyCronStorePath(): string {
|
||||
return path.join(resolveDefaultCronDir(), "jobs.json");
|
||||
}
|
||||
|
||||
export function resolveLegacyCronStorePath(configuredLegacyStorePath?: string): string {
|
||||
if (configuredLegacyStorePath?.trim()) {
|
||||
const raw = configuredLegacyStorePath.trim();
|
||||
if (raw.startsWith("~")) {
|
||||
return path.resolve(expandHomePrefix(raw));
|
||||
}
|
||||
return path.resolve(raw);
|
||||
}
|
||||
return resolveDefaultLegacyCronStorePath();
|
||||
}
|
||||
|
||||
function resolveStatePath(storePath: string): string {
|
||||
if (storePath.endsWith(".json")) {
|
||||
return storePath.replace(/\.json$/, "-state.json");
|
||||
@@ -20,16 +42,16 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeCronStateFile(value: unknown): CronStateFile | null {
|
||||
function normalizeCronStateFile(value: unknown): CronRuntimeStateSnapshot | null {
|
||||
if (!isRecord(value) || value.version !== 1 || !isRecord(value.jobs)) {
|
||||
return null;
|
||||
}
|
||||
const jobs: Record<string, CronStateFileEntry> = {};
|
||||
const jobs: Record<string, CronRuntimeStateEntry> = {};
|
||||
for (const [jobId, entry] of Object.entries(value.jobs)) {
|
||||
if (!isRecord(entry)) {
|
||||
continue;
|
||||
}
|
||||
const normalized: CronStateFileEntry = {};
|
||||
const normalized: CronRuntimeStateEntry = {};
|
||||
if (typeof entry.updatedAtMs === "number" && Number.isFinite(entry.updatedAtMs)) {
|
||||
normalized.updatedAtMs = entry.updatedAtMs;
|
||||
}
|
||||
@@ -60,7 +82,7 @@ export function legacyCronStateFileExists(storePath: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStateFile(statePath: string): Promise<CronStateFile | null> {
|
||||
async function loadStateFile(statePath: string): Promise<CronRuntimeStateSnapshot | null> {
|
||||
let raw: string;
|
||||
try {
|
||||
raw = await fs.promises.readFile(statePath, "utf-8");
|
||||
@@ -125,7 +147,7 @@ export async function importLegacyCronStateFileToSqlite(params: {
|
||||
if (!stateFile) {
|
||||
return { imported: false, importedJobs: 0 };
|
||||
}
|
||||
const importedJobs = writeCronJobRuntimeStateForMigration(params.storeKey, stateFile);
|
||||
const importedJobs = writeCronRuntimeStateSnapshot(params.storeKey, stateFile);
|
||||
try {
|
||||
await fs.promises.rm(statePath, { force: true });
|
||||
} catch {
|
||||
@@ -150,11 +172,11 @@ export async function importLegacyCronStoreToSqlite(params: {
|
||||
if (!store) {
|
||||
return { imported: false, importedJobs: 0 };
|
||||
}
|
||||
const stateFile =
|
||||
const stateSnapshot =
|
||||
(await loadStateFile(resolveStatePath(params.legacyStorePath))) ??
|
||||
extractCronStateFileForMigration(store);
|
||||
writeCronJobsForMigration(params.storeKey, store);
|
||||
writeCronJobRuntimeStateForMigration(params.storeKey, stateFile);
|
||||
extractCronRuntimeStateSnapshot(store);
|
||||
await saveCronStore(params.storeKey, store);
|
||||
writeCronRuntimeStateSnapshot(params.storeKey, stateSnapshot);
|
||||
try {
|
||||
await fs.promises.rm(params.legacyStorePath, { force: true });
|
||||
} catch {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { type DeviceBootstrapState } from "../../../infra/device-bootstrap.js";
|
||||
import { resolvePairingPaths, writePairingStateRecord } from "../../../infra/pairing-files.js";
|
||||
import { writePairingStateRecord } from "../../../infra/pairing-files.js";
|
||||
import { resolveLegacyPairingPaths } from "./pairing-files.js";
|
||||
|
||||
function resolveBootstrapPath(baseDir?: string): string {
|
||||
return path.join(resolvePairingPaths(baseDir, "devices").dir, "bootstrap.json");
|
||||
return path.join(resolveLegacyPairingPaths(baseDir, "devices").dir, "bootstrap.json");
|
||||
}
|
||||
|
||||
export async function legacyDeviceBootstrapFileExists(baseDir?: string): Promise<boolean> {
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { resolveStateDir } from "../../../config/paths.js";
|
||||
import { readJsonIfExists } from "../../../infra/json-files.js";
|
||||
import {
|
||||
coercePairingStateRecord,
|
||||
resolvePairingPaths,
|
||||
writePairingStateRecord,
|
||||
} from "../../../infra/pairing-files.js";
|
||||
import { coercePairingStateRecord, writePairingStateRecord } from "../../../infra/pairing-files.js";
|
||||
|
||||
export function resolveLegacyPairingPaths(baseDir: string | undefined, subdir: string) {
|
||||
const root = baseDir ?? resolveStateDir();
|
||||
const dir = path.join(root, subdir);
|
||||
return {
|
||||
dir,
|
||||
pendingPath: path.join(dir, "pending.json"),
|
||||
pairedPath: path.join(dir, "paired.json"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function legacyPairingStateFilesExist(params: {
|
||||
baseDir?: string;
|
||||
subdir: string;
|
||||
}): Promise<boolean> {
|
||||
const { pendingPath, pairedPath } = resolvePairingPaths(params.baseDir, params.subdir);
|
||||
const { pendingPath, pairedPath } = resolveLegacyPairingPaths(params.baseDir, params.subdir);
|
||||
const [pendingExists, pairedExists] = await Promise.all([
|
||||
fs
|
||||
.access(pendingPath)
|
||||
@@ -32,7 +40,7 @@ export async function importLegacyPairingStateFilesToSqlite(params: {
|
||||
paired: number;
|
||||
files: number;
|
||||
}> {
|
||||
const { pendingPath, pairedPath } = resolvePairingPaths(params.baseDir, params.subdir);
|
||||
const { pendingPath, pairedPath } = resolveLegacyPairingPaths(params.baseDir, params.subdir);
|
||||
const [pending, paired] = await Promise.all([
|
||||
readJsonIfExists<unknown>(pendingPath),
|
||||
readJsonIfExists<unknown>(pairedPath),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import fs from "node:fs/promises";
|
||||
import {
|
||||
resolveLegacyDefaultTtsPrefsPath,
|
||||
writeTtsUserPrefsForMigration,
|
||||
type TtsUserPrefs,
|
||||
} from "../../../tts/tts-prefs-store.js";
|
||||
import path from "node:path";
|
||||
import { type TtsUserPrefs, writeTtsUserPrefsSnapshot } from "../../../tts/tts-prefs-store.js";
|
||||
import { resolveConfigDir } from "../../../utils.js";
|
||||
|
||||
function resolveLegacyDefaultTtsPrefsPath(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return path.join(resolveConfigDir(env), "settings", "tts.json");
|
||||
}
|
||||
|
||||
function coerceTtsPrefs(value: unknown): TtsUserPrefs {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as TtsUserPrefs) : {};
|
||||
@@ -36,7 +38,7 @@ export async function importLegacyTtsPrefsFileToSqlite(
|
||||
}
|
||||
prefs = {};
|
||||
}
|
||||
writeTtsUserPrefsForMigration(prefs, env);
|
||||
writeTtsUserPrefsSnapshot(prefs, env);
|
||||
await fs.rm(filePath, { force: true }).catch(() => undefined);
|
||||
return { imported: true };
|
||||
}
|
||||
|
||||
@@ -6,15 +6,10 @@ import {
|
||||
importLegacyCronStateFileToSqlite,
|
||||
importLegacyCronStoreToSqlite,
|
||||
loadLegacyCronStoreForMigration,
|
||||
resolveLegacyCronStorePath,
|
||||
} from "../commands/doctor/legacy/cron-store.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../state/openclaw-state-db.js";
|
||||
import {
|
||||
loadCronStore,
|
||||
loadCronStoreSync,
|
||||
resolveLegacyCronStorePath,
|
||||
saveCronStore,
|
||||
updateCronStoreJobs,
|
||||
} from "./store.js";
|
||||
import { loadCronStore, loadCronStoreSync, saveCronStore, updateCronStoreJobs } from "./store.js";
|
||||
import type { CronStoreFile } from "./types.js";
|
||||
|
||||
let fixtureRoot = "";
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import path from "node:path";
|
||||
import type { Insertable } from "kysely";
|
||||
import { expandHomePrefix } from "../infra/home-dir.js";
|
||||
import { executeSqliteQuerySync, getNodeSqliteKysely } from "../infra/kysely-sync.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "../state/openclaw-state-db.generated.js";
|
||||
import {
|
||||
openOpenClawStateDatabase,
|
||||
runOpenClawStateWriteTransaction,
|
||||
} from "../state/openclaw-state-db.js";
|
||||
import { resolveConfigDir } from "../utils.js";
|
||||
import { tryCronScheduleIdentity } from "./schedule-identity.js";
|
||||
import type { CronJob, CronStoreFile } from "./types.js";
|
||||
|
||||
@@ -23,27 +20,19 @@ type CronJobRow = {
|
||||
state_json: string;
|
||||
};
|
||||
|
||||
const DEFAULT_CRON_STORE_KEY = "default";
|
||||
|
||||
function resolveDefaultCronDir(): string {
|
||||
return path.join(resolveConfigDir(), "cron");
|
||||
}
|
||||
|
||||
function resolveDefaultLegacyCronStorePath(): string {
|
||||
return path.join(resolveDefaultCronDir(), "jobs.json");
|
||||
}
|
||||
|
||||
export type CronStateFileEntry = {
|
||||
export type CronRuntimeStateEntry = {
|
||||
updatedAtMs?: number;
|
||||
scheduleIdentity?: string;
|
||||
state?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type CronStateFile = {
|
||||
export type CronRuntimeStateSnapshot = {
|
||||
version: 1;
|
||||
jobs: Record<string, CronStateFileEntry>;
|
||||
jobs: Record<string, CronRuntimeStateEntry>;
|
||||
};
|
||||
|
||||
const DEFAULT_CRON_STORE_KEY = "default";
|
||||
|
||||
function cronStoreKey(storeKey: string): string {
|
||||
const normalized = storeKey.trim();
|
||||
return normalized || DEFAULT_CRON_STORE_KEY;
|
||||
@@ -62,8 +51,8 @@ function stripRuntimeOnlyCronJobFields(job: CronJob): Record<string, unknown> {
|
||||
return { ...rest, state: {} };
|
||||
}
|
||||
|
||||
function extractStateFile(store: CronStoreFile): CronStateFile {
|
||||
const jobs: Record<string, CronStateFileEntry> = {};
|
||||
export function extractCronRuntimeStateSnapshot(store: CronStoreFile): CronRuntimeStateSnapshot {
|
||||
const jobs: Record<string, CronRuntimeStateEntry> = {};
|
||||
for (const job of store.jobs) {
|
||||
jobs[job.id] = {
|
||||
updatedAtMs: job.updatedAtMs,
|
||||
@@ -74,23 +63,10 @@ function extractStateFile(store: CronStoreFile): CronStateFile {
|
||||
return { version: 1, jobs };
|
||||
}
|
||||
|
||||
export const extractCronStateFileForMigration = extractStateFile;
|
||||
|
||||
export function resolveCronStoreKey(): string {
|
||||
return DEFAULT_CRON_STORE_KEY;
|
||||
}
|
||||
|
||||
export function resolveLegacyCronStorePath(configuredLegacyStorePath?: string): string {
|
||||
if (configuredLegacyStorePath?.trim()) {
|
||||
const raw = configuredLegacyStorePath.trim();
|
||||
if (raw.startsWith("~")) {
|
||||
return path.resolve(expandHomePrefix(raw));
|
||||
}
|
||||
return path.resolve(raw);
|
||||
}
|
||||
return resolveDefaultLegacyCronStorePath();
|
||||
}
|
||||
|
||||
function ensureJobStateObject(job: CronStoreFile["jobs"][number]): void {
|
||||
if (!job.state || typeof job.state !== "object") {
|
||||
job.state = {} as never;
|
||||
@@ -116,7 +92,10 @@ function resolveUpdatedAtMs(job: CronStoreFile["jobs"][number], updatedAtMs: unk
|
||||
: Date.now();
|
||||
}
|
||||
|
||||
function mergeStateFileEntry(job: CronStoreFile["jobs"][number], entry: CronStateFileEntry): void {
|
||||
function mergeRuntimeStateSnapshotEntry(
|
||||
job: CronStoreFile["jobs"][number],
|
||||
entry: CronRuntimeStateEntry,
|
||||
): void {
|
||||
job.updatedAtMs = resolveUpdatedAtMs(job, entry.updatedAtMs);
|
||||
job.state = (entry.state ?? {}) as never;
|
||||
if (
|
||||
@@ -138,7 +117,7 @@ function parseCronStateJson(value: string): Record<string, unknown> {
|
||||
}
|
||||
|
||||
function mergeCronJobRowRuntimeState(job: CronStoreFile["jobs"][number], row: CronJobRow): void {
|
||||
mergeStateFileEntry(job, {
|
||||
mergeRuntimeStateSnapshotEntry(job, {
|
||||
updatedAtMs: row.runtime_updated_at_ms ?? undefined,
|
||||
scheduleIdentity: row.schedule_identity ?? undefined,
|
||||
state: parseCronStateJson(row.state_json),
|
||||
@@ -257,17 +236,16 @@ function writeCronJobsToSqlite(storeKey: string, store: CronStoreFile): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function writeCronJobsForMigration(storeKey: string, store: CronStoreFile): void {
|
||||
writeCronJobsToSqlite(storeKey, store);
|
||||
}
|
||||
|
||||
function writeCronJobRuntimeStateToSqlite(storeKey: string, stateFile: CronStateFile): number {
|
||||
export function writeCronRuntimeStateSnapshot(
|
||||
storeKey: string,
|
||||
stateSnapshot: CronRuntimeStateSnapshot,
|
||||
): number {
|
||||
const normalizedStoreKey = cronStoreKey(storeKey);
|
||||
const updatedAt = Date.now();
|
||||
let importedJobs = 0;
|
||||
runOpenClawStateWriteTransaction((database) => {
|
||||
const db = getCronJobsKysely(database.db);
|
||||
for (const [jobId, entry] of Object.entries(stateFile.jobs)) {
|
||||
for (const [jobId, entry] of Object.entries(stateSnapshot.jobs)) {
|
||||
const result = executeSqliteQuerySync(
|
||||
database.db,
|
||||
db
|
||||
@@ -293,13 +271,6 @@ function writeCronJobRuntimeStateToSqlite(storeKey: string, stateFile: CronState
|
||||
return importedJobs;
|
||||
}
|
||||
|
||||
export function writeCronJobRuntimeStateForMigration(
|
||||
storeKey: string,
|
||||
stateFile: CronStateFile,
|
||||
): number {
|
||||
return writeCronJobRuntimeStateToSqlite(storeKey, stateFile);
|
||||
}
|
||||
|
||||
export async function saveCronStore(
|
||||
storeKey: string,
|
||||
store: CronStoreFile,
|
||||
@@ -307,7 +278,7 @@ export async function saveCronStore(
|
||||
) {
|
||||
void opts?.skipBackup;
|
||||
if (opts?.stateOnly === true) {
|
||||
writeCronJobRuntimeStateToSqlite(storeKey, extractStateFile(store));
|
||||
writeCronRuntimeStateSnapshot(storeKey, extractCronRuntimeStateSnapshot(store));
|
||||
return;
|
||||
}
|
||||
writeCronJobsToSqlite(storeKey, store);
|
||||
|
||||
@@ -22,11 +22,16 @@ import {
|
||||
type PairedDevice,
|
||||
type RotateDeviceTokenResult,
|
||||
} from "./device-pairing.js";
|
||||
import {
|
||||
readPairingStateRecord,
|
||||
resolvePairingPaths,
|
||||
writePairingStateRecord,
|
||||
} from "./pairing-files.js";
|
||||
import { readPairingStateRecord, writePairingStateRecord } from "./pairing-files.js";
|
||||
|
||||
function resolveLegacyPairingFixturePaths(baseDir: string, subdir: string) {
|
||||
const dir = path.join(baseDir, subdir);
|
||||
return {
|
||||
dir,
|
||||
pendingPath: path.join(dir, "pending.json"),
|
||||
pairedPath: path.join(dir, "paired.json"),
|
||||
};
|
||||
}
|
||||
|
||||
async function setupPairedOperatorDevice(baseDir: string, scopes: string[]) {
|
||||
const request = await requestDevicePairing(
|
||||
@@ -175,7 +180,7 @@ describe("device pairing tokens", () => {
|
||||
|
||||
test("ignores legacy pairing state files at runtime", async () => {
|
||||
const baseDir = await makeDevicePairingDir();
|
||||
const paths = resolvePairingPaths(baseDir, "devices");
|
||||
const paths = resolveLegacyPairingFixturePaths(baseDir, "devices");
|
||||
await mkdir(paths.dir, { recursive: true });
|
||||
await writeFile(paths.pendingPath, "[]", "utf8");
|
||||
await writeFile(paths.pairedPath, "[]", "utf8");
|
||||
@@ -1425,7 +1430,7 @@ describe("device pairing tokens", () => {
|
||||
},
|
||||
baseDir,
|
||||
);
|
||||
const { pairedPath } = resolvePairingPaths(baseDir, "devices");
|
||||
const { pairedPath } = resolveLegacyPairingFixturePaths(baseDir, "devices");
|
||||
await mkdir(path.dirname(pairedPath), { recursive: true });
|
||||
await writeFile(pairedPath, "{not-json}", "utf8");
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
||||
import { createSuiteTempRootTracker } from "../test-helpers/temp-dir.js";
|
||||
import {
|
||||
@@ -10,7 +11,16 @@ import {
|
||||
updatePairedNodeMetadata,
|
||||
verifyNodeToken,
|
||||
} from "./node-pairing.js";
|
||||
import { readPairingStateRecord, resolvePairingPaths } from "./pairing-files.js";
|
||||
import { readPairingStateRecord } from "./pairing-files.js";
|
||||
|
||||
function resolveLegacyPairingFixturePaths(baseDir: string, subdir: string) {
|
||||
const dir = path.join(baseDir, subdir);
|
||||
return {
|
||||
dir,
|
||||
pendingPath: path.join(dir, "pending.json"),
|
||||
pairedPath: path.join(dir, "paired.json"),
|
||||
};
|
||||
}
|
||||
|
||||
async function setupPairedNode(baseDir: string): Promise<string> {
|
||||
const request = await requestNodePairing(
|
||||
@@ -131,7 +141,7 @@ describe("node pairing tokens", () => {
|
||||
|
||||
test("ignores legacy pairing state files at runtime", async () => {
|
||||
await withNodePairingDir(async (baseDir) => {
|
||||
const paths = resolvePairingPaths(baseDir, "nodes");
|
||||
const paths = resolveLegacyPairingFixturePaths(baseDir, "nodes");
|
||||
await fs.mkdir(paths.dir, { recursive: true });
|
||||
await fs.writeFile(paths.pendingPath, "[]", "utf8");
|
||||
await fs.writeFile(paths.pairedPath, "[]", "utf8");
|
||||
@@ -268,7 +278,7 @@ describe("node pairing tokens", () => {
|
||||
|
||||
test("ignores corrupt legacy paired node state when requesting pairing", async () => {
|
||||
await withNodePairingDir(async (baseDir) => {
|
||||
const { dir, pairedPath } = resolvePairingPaths(baseDir, "nodes");
|
||||
const { dir, pairedPath } = resolveLegacyPairingFixturePaths(baseDir, "nodes");
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await fs.writeFile(pairedPath, "{not-json}", "utf8");
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import path from "node:path";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { OpenClawStateDatabaseOptions } from "../state/openclaw-state-db.js";
|
||||
import {
|
||||
readOpenClawStateKvJson,
|
||||
@@ -11,16 +9,6 @@ export { createAsyncLock } from "./json-files.js";
|
||||
|
||||
const PAIRING_STATE_SCOPE_PREFIX = "pairing";
|
||||
|
||||
export function resolvePairingPaths(baseDir: string | undefined, subdir: string) {
|
||||
const root = baseDir ?? resolveStateDir();
|
||||
const dir = path.join(root, subdir);
|
||||
return {
|
||||
dir,
|
||||
pendingPath: path.join(dir, "pending.json"),
|
||||
pairedPath: path.join(dir, "paired.json"),
|
||||
};
|
||||
}
|
||||
|
||||
function sqliteOptionsForBaseDir(baseDir: string | undefined): OpenClawStateDatabaseOptions {
|
||||
return baseDir ? { env: { ...process.env, OPENCLAW_STATE_DIR: baseDir } } : {};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resetPluginStateStoreForTests } from "../plugin-state/plugin-state-store.js";
|
||||
import { resolveStatusTtsSnapshot } from "./status-config.js";
|
||||
import { writeTtsUserPrefsForMigration } from "./tts-prefs-store.js";
|
||||
import { writeTtsUserPrefsSnapshot } from "./tts-prefs-store.js";
|
||||
|
||||
let fixtureRoot = "";
|
||||
let fixtureId = 0;
|
||||
@@ -54,7 +54,7 @@ function restoreEnv(key: string, value: string | undefined): void {
|
||||
describe("resolveStatusTtsSnapshot", () => {
|
||||
it("uses prefs overrides without loading speech providers", async () => {
|
||||
await withStatusTempHome(async () => {
|
||||
writeTtsUserPrefsForMigration({
|
||||
writeTtsUserPrefsSnapshot({
|
||||
tts: {
|
||||
auto: "always",
|
||||
provider: "edge",
|
||||
@@ -283,7 +283,7 @@ describe("resolveStatusTtsSnapshot", () => {
|
||||
|
||||
it("uses provider metadata for local provider prefs overrides", async () => {
|
||||
await withStatusTempHome(async () => {
|
||||
writeTtsUserPrefsForMigration({
|
||||
writeTtsUserPrefsSnapshot({
|
||||
tts: {
|
||||
auto: "always",
|
||||
provider: "edge",
|
||||
@@ -326,7 +326,7 @@ describe("resolveStatusTtsSnapshot", () => {
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
vi.stubEnv("OPENCLAW_CONFIG_PATH", path.join(stateDir, "openclaw.json"));
|
||||
try {
|
||||
writeTtsUserPrefsForMigration({
|
||||
writeTtsUserPrefsSnapshot({
|
||||
tts: {
|
||||
auto: "always",
|
||||
provider: "openai",
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
resolveEffectiveTtsConfig,
|
||||
shouldAttemptTtsPayload,
|
||||
} from "./tts-config.js";
|
||||
import { writeTtsUserPrefsForMigration } from "./tts-prefs-store.js";
|
||||
import { writeTtsUserPrefsSnapshot } from "./tts-prefs-store.js";
|
||||
|
||||
describe("shouldAttemptTtsPayload", () => {
|
||||
let originalStateDir: string | undefined;
|
||||
@@ -59,7 +59,7 @@ describe("shouldAttemptTtsPayload", () => {
|
||||
});
|
||||
|
||||
it("honors session auto state before prefs and config", () => {
|
||||
writeTtsUserPrefsForMigration({ tts: { auto: "off" } });
|
||||
writeTtsUserPrefsSnapshot({ tts: { auto: "off" } });
|
||||
const cfg = { messages: { tts: { auto: "off" } } } as OpenClawConfig;
|
||||
|
||||
expect(shouldAttemptTtsPayload({ cfg, ttsAuto: "always" })).toBe(true);
|
||||
@@ -69,10 +69,10 @@ describe("shouldAttemptTtsPayload", () => {
|
||||
it("uses local prefs before config auto mode", () => {
|
||||
const cfg = { messages: { tts: { auto: "off" } } } as OpenClawConfig;
|
||||
|
||||
writeTtsUserPrefsForMigration({ tts: { enabled: true } });
|
||||
writeTtsUserPrefsSnapshot({ tts: { enabled: true } });
|
||||
expect(shouldAttemptTtsPayload({ cfg })).toBe(true);
|
||||
|
||||
writeTtsUserPrefsForMigration({ tts: { auto: "off" } });
|
||||
writeTtsUserPrefsSnapshot({ tts: { auto: "off" } });
|
||||
expect(
|
||||
shouldAttemptTtsPayload({ cfg: { messages: { tts: { enabled: true } } } as OpenClawConfig }),
|
||||
).toBe(false);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import path from "node:path";
|
||||
import type { TtsAutoMode, TtsProvider } from "../config/types.tts.js";
|
||||
import { createPluginStateSyncKeyedStore } from "../plugin-state/plugin-state-store.js";
|
||||
import { resolveConfigDir } from "../utils.js";
|
||||
|
||||
const TTS_PREFS_PLUGIN_ID = "speech-core";
|
||||
const TTS_PREFS_NAMESPACE = "tts-prefs";
|
||||
@@ -40,10 +38,6 @@ export function isSqliteTtsPrefsRef(value: string): boolean {
|
||||
return value === SQLITE_TTS_PREFS_REF;
|
||||
}
|
||||
|
||||
export function resolveLegacyDefaultTtsPrefsPath(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return path.join(resolveConfigDir(env), "settings", "tts.json");
|
||||
}
|
||||
|
||||
export function resolveTtsPrefsRef(
|
||||
_prefsPath?: string,
|
||||
_env: NodeJS.ProcessEnv = process.env,
|
||||
@@ -58,7 +52,7 @@ export function readTtsUserPrefs(
|
||||
return coercePrefs(openTtsPrefsStore(env).lookup(TTS_PREFS_KEY));
|
||||
}
|
||||
|
||||
export function writeTtsUserPrefsForMigration(
|
||||
export function writeTtsUserPrefsSnapshot(
|
||||
prefs: TtsUserPrefs,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): void {
|
||||
|
||||
Reference in New Issue
Block a user