openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
178 lines (177 loc) • 6.93 kB
JavaScript
import { c as isRecord, l as isStringRecord } from "./record-coerce-DItp3I4t.js";
import { n as resolveEffectiveHomeDir } from "./home-dir-BPhrG-aM.js";
import { b as resolveLegacyStateDirs, f as resolveConfigPath, w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { m as tryReadJsonSync } from "./json-files-Bq1lIlQB.js";
import { t as discoverConfiguredPluginLoadPaths } from "./discovery-D_VDsZuY.js";
import { n as loadPluginManifestRegistryCore } from "./manifest-registry-DCCgYk7q.js";
import { i as inspectPluginStartupMetadata, r as inspectBundledPluginStartupMetadata, t as configMayRequireStartupPluginConvergence } from "./startup-plugin-convergence-plan--U1korSw.js";
import fs from "node:fs";
import path from "node:path";
//#region src/commands/doctor/shared/pristine-startup-state.ts
const STATEFUL_CONFIG_KEYS = /* @__PURE__ */ new Set([
"accessGroups",
"acp",
"approvals",
"audio",
"bindings",
"broadcast",
"channels",
"cloudWorkers",
"commitments",
"cron",
"discovery",
"env",
"marketplaces",
"mcp",
"media",
"memory",
"messages",
"nodeHost",
"proxy",
"secrets",
"session",
"surfaces",
"talk",
"tools",
"transcripts",
"web"
]);
function hasOnlyMigrationSafeInternalHooks(config) {
const hooks = config.hooks;
if (hooks === void 0) return true;
if (!isRecord(hooks) || Object.keys(hooks).some((key) => key !== "internal")) return false;
const internal = hooks.internal;
if (internal === void 0) return true;
if (!isRecord(internal) || Object.keys(internal).some((key) => !["enabled", "entries"].includes(key)) || internal.enabled !== void 0 && typeof internal.enabled !== "boolean") return false;
if (internal.entries === void 0) return true;
if (!isRecord(internal.entries)) return false;
return Object.values(internal.entries).every((entry) => {
if (!isRecord(entry)) return false;
if (entry.enabled !== void 0 && typeof entry.enabled !== "boolean") return false;
if (entry.env === void 0) return true;
return isStringRecord(entry.env);
});
}
function containsObjectKey(value, targetKey) {
if (Array.isArray(value)) return value.some((entry) => containsObjectKey(entry, targetKey));
if (!isRecord(value)) return false;
return Object.hasOwn(value, targetKey) || Object.values(value).some((entry) => containsObjectKey(entry, targetKey));
}
function hasOnlyMigrationSafePluginEntries(config, env) {
const plugins = config.plugins;
if (!isRecord(plugins)) return plugins === void 0;
if (Object.keys(plugins).some((key) => ![
"enabled",
"entries",
"allow",
"deny",
"load"
].includes(key))) return false;
if (plugins.load !== void 0) {
if (!isRecord(plugins.load) || Object.keys(plugins.load).some((key) => key !== "paths") || !Array.isArray(plugins.load.paths) || !plugins.load.paths.every((entry) => typeof entry === "string" && entry.trim().length > 0)) return false;
if (plugins.load.paths.length > 0) {
const discovery = discoverConfiguredPluginLoadPaths({
loadPaths: plugins.load.paths,
env
});
if (discovery.candidates.length === 0) return false;
const registry = loadPluginManifestRegistryCore({
config,
discovery,
env,
installRecords: {}
});
if (registry.diagnostics.length > 0 || registry.plugins.length !== discovery.candidates.length) return false;
for (const plugin of registry.plugins) {
const metadata = inspectPluginStartupMetadata({
pluginId: plugin.id,
rootDir: plugin.rootDir
});
if (!metadata || metadata.hasDoctorContract) return false;
}
}
}
if (!isRecord(plugins.entries)) return plugins.entries === void 0;
return Object.entries(plugins.entries).every(([pluginId, entry]) => {
if (!isRecord(entry)) return false;
if (entry.enabled === false) return true;
if (entry.config !== void 0) return false;
const metadata = inspectBundledPluginStartupMetadata({
pluginId,
env
});
return Boolean(metadata && !metadata.hasDoctorContract);
});
}
function configIsPristineCoreStateSafe(config) {
if ([...STATEFUL_CONFIG_KEYS].some((key) => Object.hasOwn(config, key))) return false;
if (!hasOnlyMigrationSafeInternalHooks(config)) return false;
if (containsObjectKey(config.agents, "memorySearch")) return false;
return true;
}
/** Revalidates the authored config after startup recovery without rereading physical state. */
function planPristineStartupConfigMigrations(config, env = process.env) {
if (!isRecord(config) || containsObjectKey(config, "$include")) return {
skipAllStateMigrations: false,
skipCoreStateMigrations: false
};
const skipCoreStateMigrations = configIsPristineCoreStateSafe(config);
return {
skipAllStateMigrations: skipCoreStateMigrations && configIsPristineStateSafe(config, env),
skipCoreStateMigrations
};
}
function configIsPristineStateSafe(config, env) {
if (!configIsPristineCoreStateSafe(config)) return false;
if (!hasOnlyMigrationSafePluginEntries(config, env)) return false;
return !configMayRequireStartupPluginConvergence({
config,
env
});
}
function stateDirHasOnlyConfig(stateDir, configPath) {
let entries;
try {
entries = fs.readdirSync(stateDir, { withFileTypes: true });
} catch (error) {
return error.code === "ENOENT";
}
const resolvedConfigPath = path.resolve(configPath);
return entries.every((entry) => path.resolve(stateDir, entry.name) === resolvedConfigPath);
}
/**
* A missing/empty state root plus migration-free bundled config has no legacy data to migrate.
* Keep ambiguity on the full migration path; this shortcut only accepts a proven new install.
*/
function canSkipPristineStartupStateMigrations(env = process.env) {
return planPristineStartupStateMigrations(env).skipAllStateMigrations;
}
/** Separates provably absent core state from plugin-owned migration work. */
function planPristineStartupStateMigrations(env = process.env) {
const stateDir = resolveStateDir(env);
const configPath = resolveConfigPath(env, stateDir);
if (!stateDirHasOnlyConfig(stateDir, configPath)) return {
skipAllStateMigrations: false,
skipCoreStateMigrations: false
};
const homeDir = resolveEffectiveHomeDir(env);
if (!homeDir) return {
skipAllStateMigrations: false,
skipCoreStateMigrations: false
};
const explicitStateDir = env.OPENCLAW_STATE_DIR?.trim();
if (!(Boolean(explicitStateDir) || resolveLegacyStateDirs(() => homeDir).every((legacyDir) => {
if (path.resolve(legacyDir) === path.resolve(stateDir)) return false;
return !fs.existsSync(legacyDir);
}))) return {
skipAllStateMigrations: false,
skipCoreStateMigrations: false
};
const configPlan = planPristineStartupConfigMigrations(fs.existsSync(configPath) ? tryReadJsonSync(configPath) : {}, env);
return {
skipAllStateMigrations: configPlan.skipAllStateMigrations,
skipCoreStateMigrations: configPlan.skipCoreStateMigrations
};
}
//#endregion
export { canSkipPristineStartupStateMigrations, planPristineStartupConfigMigrations, planPristineStartupStateMigrations };