UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

375 lines (374 loc) 11.3 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { r as resolveProfileStateDir } from "./profile-utils-Bkq2PPxE.js"; import { a as isWithinDir } from "./path-safety-Bi0ppMWC.js"; import { S as resolveNewStateDir, b as resolveLegacyStateDirs, w as resolveStateDir } from "./paths-D2sRr1a_.js"; import { n as logStateMigrationResult } from "./state-migrations.messages-oWDQJF4s.js"; import { f as migrateLegacyTaskStateSidecars, r as preflightLegacyInstalledPluginIndexMigration, t as migrateLegacyInstalledPluginIndex } from "./state-migrations.plugin-state-V0Kd8Mka.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; //#region src/infra/state-migrations.state-dir.ts let autoMigrateStateDirChecked = false; let autoMigrateTaskStateSidecarsChecked = false; function resetAutoMigrateLegacyStateDirForTest() { autoMigrateStateDirChecked = false; } function resetAutoMigrateLegacyTaskStateSidecarsForTest() { autoMigrateTaskStateSidecarsChecked = false; } function lstatIfPresent(filePath) { try { return fs.lstatSync(filePath); } catch (error) { if (error.code === "ENOENT") return null; throw error; } } function migrateLegacyProfileWorkspace(params) { const env = params.env ?? process.env; const homedir = params.homedir ?? os.homedir; const profile = env.OPENCLAW_PROFILE?.trim(); if (!profile || normalizeLowercaseStringOrEmpty(profile) === "default") return { changes: [], warnings: [] }; try { const legacyDir = path.join(resolveProfileStateDir("default", env, homedir), `workspace-${profile}`); const targetDir = path.join(resolveProfileStateDir(profile, env, homedir), "workspace"); const legacyStat = lstatIfPresent(legacyDir); if (!legacyStat) return { changes: [], warnings: [] }; if (!legacyStat.isDirectory() && !legacyStat.isSymbolicLink()) return { changes: [], warnings: [`Profile workspace migration skipped: legacy path is not a directory (${legacyDir}).`] }; if (lstatIfPresent(targetDir)) return { changes: [], warnings: [`Profile workspace migration skipped: target already exists (${targetDir}). Kept legacy workspace at ${legacyDir}; merge manually.`] }; fs.mkdirSync(path.dirname(targetDir), { recursive: true }); fs.renameSync(legacyDir, targetDir); return { changes: [`Profile workspace: ${legacyDir}${targetDir}`], warnings: [] }; } catch (error) { return { changes: [], warnings: [`Profile workspace migration failed: ${String(error)}`] }; } } function resolveSymlinkTarget(linkPath) { try { const target = fs.readlinkSync(linkPath); return path.resolve(path.dirname(linkPath), target); } catch { return null; } } function formatStateDirMigration(legacyDir, targetDir) { return `State dir: ${legacyDir}${targetDir} (legacy path now symlinked)`; } function isDirPath(filePath) { try { return fs.statSync(filePath).isDirectory(); } catch { return false; } } function isEmptyDirPath(filePath) { try { return fs.readdirSync(filePath).length === 0; } catch { return false; } } function isLegacyTreeSymlinkMirror(currentDir, realTargetDir) { let entries; try { entries = fs.readdirSync(currentDir, { withFileTypes: true }); } catch { return false; } if (entries.length === 0) return false; for (const entry of entries) { const entryPath = path.join(currentDir, entry.name); let stat; try { stat = fs.lstatSync(entryPath); } catch { return false; } if (stat.isSymbolicLink()) { const resolvedTarget = resolveSymlinkTarget(entryPath); if (!resolvedTarget) return false; let resolvedRealTarget; try { resolvedRealTarget = fs.realpathSync(resolvedTarget); } catch { return false; } if (!isWithinDir(realTargetDir, resolvedRealTarget)) return false; continue; } if (stat.isDirectory()) { if (!isLegacyTreeSymlinkMirror(entryPath, realTargetDir)) return false; continue; } return false; } return true; } function isLegacyDirSymlinkMirror(legacyDir, targetDir) { let realTargetDir; try { realTargetDir = fs.realpathSync(targetDir); } catch { return false; } return isLegacyTreeSymlinkMirror(legacyDir, realTargetDir); } async function autoMigrateLegacyStateDir(params) { if (autoMigrateStateDirChecked) return { migrated: false, skipped: true, changes: [], warnings: [] }; autoMigrateStateDirChecked = true; const homedir = params.homedir ?? os.homedir; const env = params.env ?? process.env; const warnings = []; const changes = []; const notices = []; const hasCustomStateDir = Boolean(env.OPENCLAW_STATE_DIR?.trim()); const targetDir = hasCustomStateDir ? resolveStateDir(env, homedir) : resolveNewStateDir(homedir); const migratePluginInstallIndex = async () => { const result = await migrateLegacyInstalledPluginIndex({ stateDir: targetDir }); changes.push(...result.changes); warnings.push(...result.warnings); notices.push(...result.notices ?? []); }; if (hasCustomStateDir) { await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: changes.length === 0 && warnings.length === 0 && notices.length === 0, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } const legacyDirs = resolveLegacyStateDirs(homedir); let legacyDir = legacyDirs.find((dir) => { try { return fs.existsSync(dir); } catch { return false; } }); let legacyStat; try { legacyStat = legacyDir ? fs.lstatSync(legacyDir) : null; } catch { legacyStat = null; } if (!legacyStat) { await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: false, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } if (!legacyStat.isDirectory() && !legacyStat.isSymbolicLink()) { warnings.push(`Legacy state path is not a directory: ${legacyDir}`); return { migrated: false, skipped: false, changes, warnings }; } let symlinkDepth = 0; while (legacyStat.isSymbolicLink()) { const legacyTarget = legacyDir ? resolveSymlinkTarget(legacyDir) : null; if (!legacyTarget) { warnings.push(`Legacy state dir is a symlink (${legacyDir ?? "unknown"}); could not resolve target.`); return { migrated: false, skipped: false, changes, warnings }; } if (path.resolve(legacyTarget) === path.resolve(targetDir)) { await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: false, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } if (legacyDirs.some((dir) => path.resolve(dir) === path.resolve(legacyTarget))) { legacyDir = legacyTarget; try { legacyStat = fs.lstatSync(legacyDir); } catch { legacyStat = null; } if (!legacyStat) { warnings.push(`Legacy state dir missing after symlink resolution: ${legacyDir}`); return { migrated: false, skipped: false, changes, warnings }; } if (!legacyStat.isDirectory() && !legacyStat.isSymbolicLink()) { warnings.push(`Legacy state path is not a directory: ${legacyDir}`); return { migrated: false, skipped: false, changes, warnings }; } symlinkDepth += 1; if (symlinkDepth > 2) { warnings.push(`Legacy state dir symlink chain too deep: ${legacyDir}`); return { migrated: false, skipped: false, changes, warnings }; } continue; } warnings.push(`Legacy state dir is a symlink (${legacyDir ?? "unknown"}${legacyTarget}); skipping auto-migration.`); return { migrated: false, skipped: false, changes, warnings }; } if (isDirPath(targetDir)) { if (legacyDir && isLegacyDirSymlinkMirror(legacyDir, targetDir)) { await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: false, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } if (legacyDir && isEmptyDirPath(legacyDir)) try { fs.rmdirSync(legacyDir); fs.symlinkSync(targetDir, legacyDir, process.platform === "win32" ? "junction" : "dir"); changes.push(formatStateDirMigration(legacyDir, targetDir)); } catch (err) { warnings.push(`Failed to retire empty legacy state dir (${legacyDir}): ${String(err)}`); } else warnings.push(`State dir migration skipped: target already exists (${targetDir}). Remove or merge manually.`); await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: false, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } if (legacyDir) { const pluginInstallWarning = preflightLegacyInstalledPluginIndexMigration({ stateDir: legacyDir }); if (pluginInstallWarning) { warnings.push(pluginInstallWarning); return { migrated: false, skipped: false, changes, warnings }; } } try { if (!legacyDir) throw new Error("Legacy state dir not found"); fs.renameSync(legacyDir, targetDir); } catch (err) { warnings.push(`Failed to move legacy state dir (${legacyDir ?? "unknown"}${targetDir}): ${String(err)}`); return { migrated: false, skipped: false, changes, warnings }; } try { if (!legacyDir) throw new Error("Legacy state dir not found"); fs.symlinkSync(targetDir, legacyDir, "dir"); changes.push(formatStateDirMigration(legacyDir, targetDir)); } catch (err) { try { if (process.platform === "win32") { if (!legacyDir) throw new Error("Legacy state dir not found", { cause: err }); fs.symlinkSync(targetDir, legacyDir, "junction"); changes.push(formatStateDirMigration(legacyDir, targetDir)); } else throw err; } catch (fallbackErr) { try { if (!legacyDir) throw new Error("Legacy state dir not found", { cause: fallbackErr }); fs.renameSync(targetDir, legacyDir); warnings.push(`State dir migration rolled back (failed to link legacy path): ${String(fallbackErr)}`); return { migrated: false, skipped: false, changes: [], warnings }; } catch (rollbackErr) { warnings.push(`State dir moved but failed to link legacy path (${legacyDir ?? "unknown"}${targetDir}): ${String(fallbackErr)}`); warnings.push(`Rollback failed; set OPENCLAW_STATE_DIR=${targetDir} to avoid split state: ${String(rollbackErr)}`); changes.push(`State dir: ${legacyDir ?? "unknown"}${targetDir}`); } } } await migratePluginInstallIndex(); return { migrated: changes.length > 0, skipped: false, changes, warnings, ...notices.length > 0 ? { notices } : {} }; } async function autoMigrateLegacyTaskStateSidecars(params) { if (autoMigrateTaskStateSidecarsChecked) return { migrated: false, skipped: true, changes: [], warnings: [] }; autoMigrateTaskStateSidecarsChecked = true; const stateDir = resolveStateDir(params.env ?? process.env, params.homedir); const result = await migrateLegacyTaskStateSidecars({ stateDir }); logStateMigrationResult(result, params.log); return { migrated: result.changes.length > 0, skipped: false, changes: result.changes, warnings: result.warnings }; } //#endregion export { resetAutoMigrateLegacyTaskStateSidecarsForTest as a, resetAutoMigrateLegacyStateDirForTest as i, autoMigrateLegacyTaskStateSidecars as n, migrateLegacyProfileWorkspace as r, autoMigrateLegacyStateDir as t };