UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

348 lines (347 loc) • 18.9 kB
import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import { t as safeParseJson } from "./json-coercion-pfKbZnCJ.mjs"; import { a as executeSqliteQueryTakeFirstSync, o as getNodeSqliteKysely, t as compileSqliteQueryBindings } from "./kysely-sync-DUWzQvCU.mjs"; import { o as resolveCompatibilityHostVersion } from "./version-D0DaWqMn.mjs"; import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-BzUE_dOh.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-DJZ7EKoI.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { n as readOpenClawStateLease } from "./openclaw-state-lease-store-BRb-PB1S.mjs"; import { F as resolvePluginCandidateInstallOwner, M as isPluginCandidateInstallOwnerAmbiguous } from "./discovery-D42cUZXk.mjs"; import { o as isGatewayPluginMetadataSnapshotActive } from "./current-plugin-metadata-state-LHE4BTlf.mjs"; import { r as resolveInstalledPluginIndexInstallOwner, t as isInstalledPluginIndexInstallOwnerAmbiguous } from "./installed-plugin-index-install-owner-CQJv99I6.mjs"; import { r as resolveActivePluginInstallRoots } from "./install-root-context-BJfgtQE2.mjs"; import { t as clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle-COmFOHdR.mjs"; import { t as INSTALLED_PLUGIN_INDEX_STATE_KEY } from "./installed-plugin-index-row-CvqFVPqV.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-bw8ayeUf.mjs"; import { t as isPluginEnabledByDefaultForPlatform } from "./default-enablement-CEIbpabL.mjs"; import { f as resolveEffectiveEnableState, u as normalizePluginsConfig } from "./config-state-Ba1MVBAI.mjs"; import { n as hashStableJson } from "./installed-plugin-index-hash-N3AVaUCF.mjs"; import { a as parsePluginInstallRecord, c as setPluginInstallRecordMapEntry, i as inspectPluginInstallRecordMap, n as createPluginInstallRecordMap, s as serializePluginInstallRecordMap } from "./plugin-install-record-map-BzxU2Isf.mjs"; import { n as resolveInstalledPluginIndexStateDatabaseOptions, r as resolveInstalledPluginIndexStorePath, t as legacyInstalledPluginIndexUnsupportedMessage } from "./installed-plugin-index-store-path-CUbUiRdU.mjs"; import { o as clearLoadInstalledPluginIndexInstallRecordsCache, t as findForeignManagedNpmInstallRecordPluginIds } from "./installed-plugin-index-record-reader-D5Zh_tO0.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-DvrINVXo.mjs"; import { n as hasMissingInstalledPluginOwnerMetadata } from "./installed-plugin-package-ownership-yp92PpU9.mjs"; import { a as withPluginLifecycleLease, o as PLUGIN_LIFECYCLE_LEASE_IDENTITY } from "./plugin-lifecycle-lease-CJFG12c4.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"); lease.assertOwnedInTransaction(db, "commit"); return { previous: previousRow ? parseInstalledPluginIndex(previousRow.index) : null, revision, mutation: { databasePath, before: before ?? null, after } }; }, { ...resolveInstalledPluginIndexStateDatabaseOptions(options), database: options.database }); } /** 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); const prepared = structuredClone(index); const captured = { ...options, env: cloneEnvWithPlatformSemantics(options.env ?? process.env), filePath: resolveInstalledPluginIndexStorePath(options) }; return await withPluginLifecycleLease(resolveInstalledPluginIndexStateDatabaseOptions(captured), async (lease) => { await runInstalledPluginIndexWrite(captured, lease, (scope, identity) => scope.execute({ type: "plugins.metadata.index.write", input: { identity, index: prepared } })); return captured.filePath; }); } /** 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 prepared = index && structuredClone(index); return runInstalledPluginIndexWrite(storeOptions, lease, (scope, identity) => scope.execute({ type: "plugins.metadata.index.restore", input: { identity, index: prepared, expectedRevision } })); } function restorePersistedInstalledPluginIndexInDatabase(index, expectedRevision, database, lease) { return runOpenClawStateWriteTransaction(({ db }) => { lease.assertOwnedInTransaction(db); const currentRow = parseInstalledPluginIndexRow(readInstalledPluginIndexRow(db) ?? null ?? void 0); const currentRevision = currentRow ? currentRow.revision : null; if (currentRevision !== expectedRevision) { lease.assertOwnedInTransaction(db, "commit"); 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()); } lease.assertOwnedInTransaction(db, "commit"); return true; }, { database }); } async function runInstalledPluginIndexWrite(options, lease, operation, assertCurrent, onAcknowledged) { const context = captureOpenClawStateWorkerContext(resolveInstalledPluginIndexStateDatabaseOptions(options)); const assertAuthority = () => { lease.assertCurrent(); assertCurrent?.(); }; assertAuthority(); const { runWithOpenClawStateLeaseWorker } = await import("./openclaw-state-lease-worker-operation-IoOMMWFv.mjs"); const result = await runWithOpenClawStateLeaseWorker(lease.stateLease, context, operation, { assertCurrent: () => lease.assertCurrent(), beforeTransaction: assertAuthority, beforeCommit: assertAuthority }); try { onAcknowledged?.(result); } finally { clearPersistedInstalledPluginIndexCaches(); } assertAuthority(); return result; } 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) { return (await refreshPersistedInstalledPluginIndexWithReceipt(params)).index; } async function refreshPersistedInstalledPluginIndexWithReceipt(params) { const { lease: callerLease, now, assertCurrent, onAcknowledged } = params; assertWritableInstalledPluginIndexStoreOptions(params); const databaseOptions = resolveInstalledPluginIndexStateDatabaseOptions(params); const env = cloneEnvWithPlatformSemantics(databaseOptions.env ?? params.env ?? process.env); const options = { ...databaseOptions, env }; const storeOptions = { env, filePath: resolveInstalledPluginIndexStorePath(params) }; const prepareCandidates = (candidates) => candidates.map((candidate) => ({ candidate, installOwner: resolvePluginCandidateInstallOwner(candidate), ambiguous: isPluginCandidateInstallOwnerAmbiguous(candidate) })); const prepared = structuredClone({ config: params.config, workspaceDir: params.workspaceDir, stateDir: params.stateDir, filePath: params.filePath, pluginIndexFilePath: params.pluginIndexFilePath, installRecords: params.installRecords, candidates: params.candidates && prepareCandidates(params.candidates), diagnostics: params.diagnostics, discovery: params.discovery && { candidates: prepareCandidates(params.discovery.candidates), diagnostics: params.discovery.diagnostics }, reason: params.reason, policyPluginIds: params.policyPluginIds, artifactPreservingReadOnly: params.artifactPreservingReadOnly, env: { ...env }, installRoots: resolveActivePluginInstallRoots(env), ...now ? { nowMs: now().getTime() } : {} }); const refresh = async (lease) => { return await runInstalledPluginIndexWrite(storeOptions, lease, (scope, identity) => scope.execute({ type: "plugins.metadata.index.refresh", input: { identity, prepared } }), assertCurrent, onAcknowledged); }; return callerLease ? refresh(callerLease) : withPluginLifecycleLease(options, refresh); } function refreshPersistedInstalledPluginIndexWithLeaseSync(params) { const { lease, ...storeParams } = params; const index = resolveRefreshedPersistedInstalledPluginIndex(storeParams); const receipt = writePersistedInstalledPluginIndexToSqlite(index, storeParams, lease, true); clearPersistedInstalledPluginIndexCaches(); return { ...receipt, index }; } //#endregion export { restorePersistedInstalledPluginIndexIfCurrent as a, writePersistedInstalledPluginIndexToSqlite as c, refreshPersistedInstalledPluginIndexWithReceipt as i, refreshPersistedInstalledPluginIndex as n, restorePersistedInstalledPluginIndexInDatabase as o, refreshPersistedInstalledPluginIndexWithLeaseSync as r, writePersistedInstalledPluginIndex as s, publishPluginSourceAdmissionInDatabase as t };