openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
258 lines (257 loc) • 15.8 kB
JavaScript
import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs";
import { t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs";
import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, t as compileSqliteQueryBindings } from "./kysely-sync-K7ftY-g4.mjs";
import { o as resolveCompatibilityHostVersion } from "./version-BJGPJZu6.mjs";
import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-D1jWDzK3.mjs";
import { n as readOpenClawStateLease } from "./openclaw-state-lease-store-gySh31JU.mjs";
import { o as isGatewayPluginMetadataSnapshotActive } from "./current-plugin-metadata-state-CfLPTM0e.mjs";
import { t as clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle-BSDrXrxO.mjs";
import { t as INSTALLED_PLUGIN_INDEX_STATE_KEY } from "./installed-plugin-index-row-CeyMwv4w.mjs";
import { c as hasMissingConfigPathActivationMetadata, g as withBundledPluginEnablementCompat, h as isBundledProviderCompatPlugin, l as INSTALLED_PLUGIN_INDEX_WARNING, n as hasInstalledPluginIndexWorkspaceScopeMismatch, o as refreshInstalledPluginIndex, p as extractPluginInstallRecordsFromInstalledPluginIndex, v as resolveCompatRegistryVersion, y as resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-BayjZgqB.mjs";
import { t as isPluginEnabledByDefaultForPlatform } from "./default-enablement-CEIbpabL.mjs";
import { f as resolveEffectiveEnableState, u as normalizePluginsConfig } from "./config-state-DY_WJYue.mjs";
import { n as hashStableJson } from "./installed-plugin-index-hash-N3AVaUCF.mjs";
import { r as resolveInstalledPluginIndexInstallOwner, t as isInstalledPluginIndexInstallOwnerAmbiguous } from "./installed-plugin-index-install-owner-CQJv99I6.mjs";
import { i as createPluginInstallRecordMap, l as serializePluginInstallRecordMap, o as inspectPluginInstallRecordMap, s as parsePluginInstallRecord, u as setPluginInstallRecordMapEntry } from "./installed-plugin-record-match-CVFkDlud.mjs";
import { n as resolveInstalledPluginIndexStateDatabaseOptions, r as resolveInstalledPluginIndexStorePath, t as legacyInstalledPluginIndexUnsupportedMessage } from "./installed-plugin-index-store-path-vGDuKHrq.mjs";
import { o as clearLoadInstalledPluginIndexInstallRecordsCache, t as findForeignManagedNpmInstallRecordPluginIds } from "./installed-plugin-index-record-reader-BP4r74yH.mjs";
import { t as preservePluginSourceAdmissions } from "./installed-plugin-index-source-admissions-BEyyngFY.mjs";
import { r as readPersistedInstalledPluginIndexSync, t as parseInstalledPluginIndex } from "./installed-plugin-index-store-CC8tZSgR.mjs";
import { n as hasMissingInstalledPluginOwnerMetadata } from "./installed-plugin-package-ownership-EjDH4q5b.mjs";
import { a as PLUGIN_LIFECYCLE_LEASE_IDENTITY, i as withPluginLifecycleLease } from "./plugin-lifecycle-lease-DHQMWweY.mjs";
import { existsSync } from "node:fs";
import { isDeepStrictEqual } from "node:util";
//#region src/plugins/installed-plugin-index-store-write.ts
/** Writes, restores, and refreshes the installed plugin index in the state database. */
function readInstalledPluginIndexRow(database) {
return executeSqliteQueryTakeFirstSync(database, getNodeSqliteKysely(database).selectFrom("config_machine_state").select([
"state_key",
"value_json",
"updated_at_ms"
]).where("state_key", "=", INSTALLED_PLUGIN_INDEX_STATE_KEY));
}
function parseInstalledPluginIndexRow(row) {
if (!row) return;
const value = safeParseJson(row.value_json);
if (!value || typeof value !== "object" || typeof value.revision !== "number") return;
return value;
}
function assertWritableInstalledPluginIndexStoreOptions(options) {
if (options.filePath?.endsWith(".json")) throw new Error(legacyInstalledPluginIndexUnsupportedMessage(options.filePath));
}
function preparePersistedInstalledPluginIndex(index) {
const installRecords = createPluginInstallRecordMap();
for (const [pluginId, rawRecord] of Object.entries(index.installRecords)) {
const record = parsePluginInstallRecord(rawRecord);
if (!record) throw new Error("Invalid plugin install record");
setPluginInstallRecordMapEntry(installRecords, pluginId, record);
}
return {
...index,
warning: INSTALLED_PLUGIN_INDEX_WARNING,
installRecords
};
}
function resolveNextInstalledPluginIndexRevision(current) {
return Math.max(Date.now(), (current ?? 0) + 1);
}
function writePersistedInstalledPluginIndexRow(database, index, revision) {
const persistedIndex = {
version: index.version,
warning: index.warning ?? "DO NOT EDIT. This file is generated by OpenClaw from plugin manifests, install records, and config policy. Use `openclaw plugins registry --refresh`, `openclaw plugins install/update/uninstall`, or `openclaw plugins enable/disable` instead.",
hostContractVersion: index.hostContractVersion,
compatRegistryVersion: index.compatRegistryVersion,
migrationVersion: index.migrationVersion,
policyHash: index.policyHash,
generatedAtMs: index.generatedAtMs,
...index.workspaceDir !== void 0 ? { workspaceDir: index.workspaceDir } : {},
...index.refreshReason ? { refreshReason: index.refreshReason } : {},
installRecords: JSON.parse(serializePluginInstallRecordMap(index.installRecords)),
plugins: index.plugins.map((plugin) => {
const installOwner = resolveInstalledPluginIndexInstallOwner(plugin);
return {
...plugin,
...installOwner ? { installOwner } : {},
...isInstalledPluginIndexInstallOwnerAmbiguous(plugin) ? { installOwnerAmbiguous: true } : {}
};
}),
diagnostics: index.diagnostics.map((diagnostic) => diagnostic.level === "info" && diagnostic.code === "explicit-config-plugin-selection" ? {
...diagnostic,
level: "warn"
} : diagnostic)
};
const valueJson = JSON.stringify({
revision,
index: persistedIndex
});
const { compiled, bind } = compileSqliteQueryBindings(() => getNodeSqliteKysely(database).insertInto("config_machine_state").values({
state_key: INSTALLED_PLUGIN_INDEX_STATE_KEY,
value_json: valueJson,
updated_at_ms: revision
}).onConflict((conflict) => conflict.column("state_key").doUpdateSet((eb) => ({
value_json: eb.ref("excluded.value_json"),
updated_at_ms: eb.ref("excluded.updated_at_ms")
}))));
database.prepare(compiled.sql).run(...bind());
}
function writePersistedInstalledPluginIndexToSqlite(index, options, lease, preserveAdmissions = false) {
assertWritableInstalledPluginIndexStoreOptions(options);
const persisted = preparePersistedInstalledPluginIndex(index);
return runOpenClawStateWriteTransaction(({ db, path: databasePath }) => {
const before = readInstalledPluginIndexRow(db);
const previousRow = parseInstalledPluginIndexRow(before);
if (previousRow) {
const previousInstallRecords = previousRow.index?.installRecords;
if (previousInstallRecords === void 0 || inspectPluginInstallRecordMap(previousInstallRecords).status === "invalid") throw new Error("Persisted plugin install records are invalid. Repair the state before writing plugin installation metadata.");
}
lease.assertOwnedInTransaction(db);
if (preserveAdmissions) preservePluginSourceAdmissions(previousRow ? parseInstalledPluginIndex(previousRow.index) : null, persisted);
const revision = resolveNextInstalledPluginIndexRevision(previousRow ? previousRow.revision : null);
writePersistedInstalledPluginIndexRow(db, persisted, revision);
const after = readInstalledPluginIndexRow(db);
if (!after) throw new Error("Installed plugin index write did not persist its row");
return {
previous: previousRow ? parseInstalledPluginIndex(previousRow.index) : null,
revision,
mutation: {
databasePath,
before: before ?? null,
after
}
};
}, resolveInstalledPluginIndexStateDatabaseOptions(options));
}
/** Merge prepared facts against the worker's current row, never the caller's old inventory. */
function publishPluginSourceAdmissionInDatabase(database, publication) {
if (readOpenClawStateLease(database, PLUGIN_LIFECYCLE_LEASE_IDENTITY)) return false;
const stored = parseInstalledPluginIndexRow(readInstalledPluginIndexRow(database));
const index = stored ? parseInstalledPluginIndex(stored.index) : null;
const plugin = index?.plugins.find((record) => record.pluginId === publication.pluginId);
if (!stored || !index || !plugin || plugin.rootDir !== publication.rootDir || plugin.installRecordHash !== publication.installRecordHash) return false;
if (isDeepStrictEqual(plugin.sourceAdmissions?.[publication.key], publication.receipt)) return true;
plugin.sourceAdmissions = {
...plugin.sourceAdmissions,
[publication.key]: publication.receipt
};
writePersistedInstalledPluginIndexRow(database, index, resolveNextInstalledPluginIndexRevision(stored.revision));
return true;
}
function clearPersistedInstalledPluginIndexCaches() {
if (!isGatewayPluginMetadataSnapshotActive()) clearPluginMetadataLifecycleCaches();
clearLoadInstalledPluginIndexInstallRecordsCache();
}
async function writePersistedInstalledPluginIndex(index, options = {}) {
assertWritableInstalledPluginIndexStoreOptions(options);
return await withPluginLifecycleLease(resolveInstalledPluginIndexStateDatabaseOptions(options), async (lease) => writePersistedInstalledPluginIndexWithLeaseSync(index, {
...options,
lease
}));
}
/** Restore a snapshot only while the caller's tentative write is still current. */
async function restorePersistedInstalledPluginIndexIfCurrent(index, expectedRevision, options) {
const { lease, ...storeOptions } = options;
assertWritableInstalledPluginIndexStoreOptions(storeOptions);
if (!existsSync(resolveInstalledPluginIndexStorePath(storeOptions))) return false;
const restored = runOpenClawStateWriteTransaction(({ db }) => {
lease.assertOwnedInTransaction(db);
const currentRow = parseInstalledPluginIndexRow(readInstalledPluginIndexRow(db) ?? null ?? void 0);
const currentRevision = currentRow ? currentRow.revision : null;
if (currentRevision !== expectedRevision) return false;
if (index) writePersistedInstalledPluginIndexRow(db, preparePersistedInstalledPluginIndex(index), resolveNextInstalledPluginIndexRevision(currentRevision));
else {
const { compiled, bind } = compileSqliteQueryBindings(() => getNodeSqliteKysely(db).deleteFrom("config_machine_state").where("state_key", "=", INSTALLED_PLUGIN_INDEX_STATE_KEY));
db.prepare(compiled.sql).run(...bind());
}
return true;
}, resolveInstalledPluginIndexStateDatabaseOptions(storeOptions));
clearPersistedInstalledPluginIndexCaches();
return restored;
}
function writePersistedInstalledPluginIndexWithLeaseSync(index, options) {
const { lease, ...storeOptions } = options;
const filePath = resolveInstalledPluginIndexStorePath(storeOptions);
writePersistedInstalledPluginIndexToSqlite(index, storeOptions, lease);
clearPersistedInstalledPluginIndexCaches();
return filePath;
}
function hasCompletePolicyRefreshProjection(persisted, policyPluginIds, env) {
const pluginIds = new Set(persisted.plugins.map((plugin) => plugin.pluginId));
if (policyPluginIds?.some((pluginId) => !pluginIds.has(pluginId))) return false;
const installOwners = new Set(persisted.plugins.map(resolveInstalledPluginIndexInstallOwner));
return Object.entries(persisted.installRecords).every(([installOwner, record]) => {
if (installOwners.has(installOwner)) return true;
const installedPath = record.installPath?.trim() || record.sourcePath?.trim();
return !installedPath || !existsSync(resolveUserPath(installedPath, env));
});
}
function canRefreshPersistedPolicyState(persisted, params) {
if (!persisted || params.reason !== "policy-changed") return false;
if ((params.diagnostics?.length ?? 0) > 0 || persisted.diagnostics.some((diagnostic) => diagnostic.code === "workspace-scope-omitted") || hasInstalledPluginIndexWorkspaceScopeMismatch(persisted, params.workspaceDir)) return false;
const env = params.env ?? process.env;
if (persisted.version !== 1 || persisted.hostContractVersion !== resolveCompatibilityHostVersion(env) || persisted.compatRegistryVersion !== resolveCompatRegistryVersion() || persisted.migrationVersion !== 1 || hasMissingConfigPathActivationMetadata(persisted) || hasMissingInstalledPluginOwnerMetadata(persisted, env)) return false;
if (params.installRecords && hashStableJson(params.installRecords) !== hashStableJson(persisted.installRecords ?? {})) return false;
return hasCompletePolicyRefreshProjection(persisted, params.policyPluginIds, env);
}
function refreshPersistedPolicyState(persisted, params) {
const activationConfig = withBundledPluginEnablementCompat({
config: params.config,
env: params.env,
pluginIds: persisted.plugins.filter((plugin) => isBundledProviderCompatPlugin({
origin: plugin.origin,
providers: plugin.contributions?.providers,
contracts: plugin.contributions?.contracts
})).map((plugin) => plugin.pluginId),
activation: "defaults"
});
const normalizedConfig = normalizePluginsConfig(activationConfig?.plugins);
return {
...persisted,
policyHash: resolveInstalledPluginIndexPolicyHash(params.config, params.env),
generatedAtMs: (params.now?.() ?? /* @__PURE__ */ new Date()).getTime(),
refreshReason: params.reason,
plugins: persisted.plugins.map((plugin) => ({
...plugin,
enabled: resolveEffectiveEnableState({
id: plugin.pluginId,
origin: plugin.origin,
channelIds: plugin.contributions?.channels,
config: normalizedConfig,
rootConfig: activationConfig,
enabledByDefault: isPluginEnabledByDefaultForPlatform(plugin)
}).enabled
}))
};
}
function resolveRefreshedPersistedInstalledPluginIndex(params) {
const persisted = params.reason === "policy-changed" || !params.installRecords ? readPersistedInstalledPluginIndexSync(params) : null;
if (canRefreshPersistedPolicyState(persisted, params)) return refreshPersistedPolicyState(persisted, params);
if (params.reason === "manual" && !params.installRecords) {
const foreignPluginIds = findForeignManagedNpmInstallRecordPluginIds(extractPluginInstallRecordsFromInstalledPluginIndex(persisted), params);
if (foreignPluginIds.length > 0) throw new Error(`Plugin registry refresh cannot verify npm install ownership outside the selected state directory: ${foreignPluginIds.join(", ")}. Reinstall copied plugins in this state directory, then run \`openclaw plugins registry --refresh\` again.`);
}
return refreshInstalledPluginIndex({
...params,
installRecords: params.installRecords ?? extractPluginInstallRecordsFromInstalledPluginIndex(persisted)
});
}
async function refreshPersistedInstalledPluginIndex(params) {
const { lease: callerLease, ...storeParams } = params;
assertWritableInstalledPluginIndexStoreOptions(storeParams);
return await withPluginLifecycleLease(resolveInstalledPluginIndexStateDatabaseOptions(storeParams), async (lease) => {
const index = resolveRefreshedPersistedInstalledPluginIndex(storeParams);
writePersistedInstalledPluginIndexToSqlite(index, storeParams, { assertOwnedInTransaction(database) {
lease.assertOwnedInTransaction(database);
callerLease?.assertOwnedInTransaction(database);
} }, true);
clearPersistedInstalledPluginIndexCaches();
return index;
});
}
function refreshPersistedInstalledPluginIndexWithLeaseSync(params) {
const { lease, ...storeParams } = params;
const receipt = writePersistedInstalledPluginIndexToSqlite(resolveRefreshedPersistedInstalledPluginIndex(storeParams), storeParams, lease, true);
clearPersistedInstalledPluginIndexCaches();
return receipt;
}
//#endregion
export { writePersistedInstalledPluginIndex as a, restorePersistedInstalledPluginIndexIfCurrent as i, refreshPersistedInstalledPluginIndex as n, writePersistedInstalledPluginIndexWithLeaseSync as o, refreshPersistedInstalledPluginIndexWithLeaseSync as r, publishPluginSourceAdmissionInDatabase as t };