UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

273 lines (272 loc) • 12.8 kB
import { N as appendPluginInstanceCleanupFailures } from "./plugin-cache-BABafcXD.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { x as isConfiguredSessionStoreAgentId } from "./openclaw-state-db-D1jWDzK3.mjs"; import { d as runPluginCleanup, n as getPluginInstance } from "./plugin-instance-scope-DEzoxqBj.mjs"; import { a as normalizeOptionalAgentRuntimeId } from "./agent-runtime-id-BZLMe-UU.mjs"; import { o as getRuntimeConfigSnapshot } from "./runtime-snapshot-DYptB0Cf.mjs"; import { l as getPluginRecordRegistry } from "./registry-lifecycle-CZIcWb1R.mjs"; import { $ as withPluginHostCleanupTimeout, H as preparePluginRunContextCleanup, K as withPluginRunContextCleanup, L as cleanupPluginSessionSchedulerJobs, V as makePluginSessionSchedulerJobKey, l as getActivePluginRegistry } from "./runtime-NsVZTERq.mjs"; import { r as getRuntimeConfig } from "./io.runtime-BsUXSsD4.mjs"; import "./config-DLycWFvB.mjs"; import { g as readAgentDatabaseAdmissionRefusal } from "./agent-database-admission-dWNS6Rcs.mjs"; import { t as normalizeSessionEntrySlotKey } from "./session-entry-slot-keys-sCRI4mq1.mjs"; import { a as resolveAllAgentSessionStoreTargetsSync } from "./targets-BbcRkdUC.mjs"; import { a as listRetainedDeletedAgentIdsForCleanup } from "./agent-deletion-discovery-BRLBtW0y.mjs"; import "./session-accessor-BDmTPdtx.mjs"; import { h as cleanupPluginHostSessionStore } from "./session-accessor.reset-CwTHm9f4.mjs"; //#region src/plugins/host-hook-cleanup.ts const log = createSubsystemLogger("plugins/cleanup"); function shouldCleanPlugin(pluginId, filterPluginId) { return !filterPluginId || pluginId === filterPluginId; } async function clearPluginSessionStores(params) { if (!params.pluginId && !params.sessionKey || params.mode === "promoted-slots" && params.sessionEntrySlotKeys?.size === 0) return 0; const storeTargets = params.storeTargets ?? params.resolveStoreTargets?.() ?? resolveAllAgentSessionStoreTargetsSync(params.cfg); let retainedAgentIds = /* @__PURE__ */ new Set(); if (storeTargets.some((target) => !isConfiguredSessionStoreAgentId(params.cfg, target.agentId))) try { retainedAgentIds = await listRetainedDeletedAgentIdsForCleanup(process.env); } catch {} let cleared = 0; for (const target of storeTargets) { if (params.shouldCleanup && !params.shouldCleanup()) break; try { if (readAgentDatabaseAdmissionRefusal(target.agentId)) continue; if (!isConfiguredSessionStoreAgentId(params.cfg, target.agentId) && retainedAgentIds.has(target.agentId)) continue; cleared += await cleanupPluginHostSessionStore({ agentId: target.agentId, storePath: target.storePath, mode: params.mode, pluginId: params.pluginId, sessionKey: params.sessionKey, sessionEntrySlotKeys: params.sessionEntrySlotKeys, preserveLockedHarnessIds: params.preserveLockedHarnessIds, shouldCleanup: params.shouldCleanup }); } catch (error) { params.failures.push({ pluginId: params.pluginId ?? "plugin-host", hookId: "session-store", error }); } } return cleared; } function collectSessionEntrySlotKeys(registry, pluginId) { const slotKeys = /* @__PURE__ */ new Set(); for (const registration of registry?.sessionExtensions ?? []) { if (!shouldCleanPlugin(registration.pluginId, pluginId)) continue; const slotKey = registration.extension.sessionEntrySlotKey; if (slotKey === void 0) continue; const normalized = normalizeSessionEntrySlotKey(slotKey); if (normalized.ok) slotKeys.add(normalized.key); } return slotKeys; } function collectAgentHarnessIds(registry, pluginId) { const harnessIds = /* @__PURE__ */ new Set(); for (const registration of registry?.agentHarnesses ?? []) { if (!shouldCleanPlugin(registration.pluginId, pluginId)) continue; const harnessId = normalizeOptionalAgentRuntimeId(registration.harness.id); if (harnessId) harnessIds.add(harnessId); } return harnessIds; } /** Runs persistent and in-memory cleanup for a plugin, session, or host lifecycle event. */ async function runPluginHostCleanup(params) { const failures = []; const shouldCleanup = params.shouldCleanup ?? (() => true); if (!shouldCleanup()) return { cleanupCount: 0, failures }; return withPluginRunContextCleanup(params, async (clearRunContext) => { const registry = params.registry; const cleanupRegistry = registry ?? getActivePluginRegistry(); const sessionEntrySlotKeys = collectSessionEntrySlotKeys(cleanupRegistry, params.pluginId); const preserveLockedHarnessIds = params.reason === "disable" ? collectAgentHarnessIds(cleanupRegistry, params.pluginId) : void 0; const restartPromotedSessionEntrySlotKeys = params.restartPromotedSessionEntrySlotKeys ?? sessionEntrySlotKeys; let cleanupCount = 0; if (!params.skipPersistentSessionState && shouldCleanup()) try { cleanupCount = await clearPluginSessionStores({ cfg: params.cfg ?? getRuntimeConfig(), mode: params.reason === "restart" ? "promoted-slots" : "plugin-owned-state", pluginId: params.pluginId, sessionKey: params.sessionKey, sessionEntrySlotKeys: params.reason === "restart" ? restartPromotedSessionEntrySlotKeys : sessionEntrySlotKeys, preserveLockedHarnessIds, storeTargets: params.sessionStoreTargets, resolveStoreTargets: params.resolveSessionStoreTargets, shouldCleanup, failures }); } catch (error) { failures.push({ pluginId: params.pluginId ?? "plugin-host", hookId: "session-store", error }); } if (registry) { const context = { reason: params.reason, sessionKey: params.sessionKey }; const cleanups = [...registry.sessionExtensions.map(({ pluginId, extension }) => ({ pluginId, hookId: `session:${extension.namespace}`, cleanup: extension.cleanup, context })), ...registry.runtimeLifecycles.map(({ pluginId, lifecycle }) => ({ pluginId, hookId: `runtime:${lifecycle.id}`, cleanup: lifecycle.cleanup, context: { ...context, runId: params.runId } }))]; for (const { pluginId, hookId, cleanup, context: cleanupContext } of cleanups) { if (!shouldCleanup()) return { cleanupCount, failures }; if (!cleanup || !shouldCleanPlugin(pluginId, params.pluginId)) continue; try { await withPluginHostCleanupTimeout(hookId, () => runPluginCleanup(cleanup, () => cleanup(cleanupContext))); cleanupCount += 1; } catch (error) { failures.push({ pluginId, hookId, error }); } } const schedulerFailures = await cleanupPluginSessionSchedulerJobs({ pluginId: params.pluginId, reason: params.reason, sessionKey: params.sessionKey, records: registry.sessionSchedulerJobs, preserveJobIds: params.preserveSchedulerJobIds, cleanupOwnerRegistry: registry, preserveOwnerRegistry: params.preserveSchedulerOwnerRegistry, shouldCleanup }); failures.push(...schedulerFailures); } if (params.reason !== "restart" && shouldCleanup()) { const registrySchedulerJobKeys = new Set((registry?.sessionSchedulerJobs ?? []).filter((record) => !params.pluginId || record.pluginId === params.pluginId).map((record) => ({ pluginId: record.pluginId, jobId: typeof record.job.id === "string" ? record.job.id.trim() : "" })).filter(({ jobId }) => jobId.length > 0).map(({ pluginId, jobId }) => makePluginSessionSchedulerJobKey(pluginId, jobId))); const runtimeSchedulerFailures = await cleanupPluginSessionSchedulerJobs({ pluginId: params.pluginId, reason: params.reason, sessionKey: params.sessionKey, preserveJobIds: params.preserveSchedulerJobIds, excludeJobKeys: registrySchedulerJobKeys, cleanupOwnerRegistry: registry ?? void 0, shouldCleanup }); failures.push(...runtimeSchedulerFailures); } if (shouldCleanup() && (params.pluginId || params.runId) && (params.reason !== "restart" || params.runId)) clearRunContext(); return { cleanupCount, failures }; }); } function collectHostHookPluginIds(registry) { return [ ...registry.sessionExtensions, ...registry.runtimeLifecycles, ...registry.agentEventSubscriptions, ...registry.sessionSchedulerJobs ].map((registration) => registration.pluginId); } function collectLoadedPluginIds(registry) { return new Set(registry.plugins.filter((plugin) => plugin.status === "loaded").map((plugin) => plugin.id)); } function collectSchedulerJobIds(registry, pluginId) { return new Set((registry?.sessionSchedulerJobs ?? []).filter((registration) => registration.pluginId === pluginId).map((registration) => typeof registration.job.id === "string" ? registration.job.id.trim() : "").filter(Boolean)); } function collectRestartPromotedSessionEntrySlotKeys(previousRegistry, nextRegistry, pluginId) { const staleSlotKeys = collectSessionEntrySlotKeys(previousRegistry, pluginId); const preservedSlotKeys = collectSessionEntrySlotKeys(nextRegistry, pluginId); for (const slotKey of preservedSlotKeys) staleSlotKeys.delete(slotKey); return staleSlotKeys; } function createPluginHostRetirementObserver(waits) { return async (options) => { const results = await Promise.all(waits.map((wait) => wait(options))); const deferredPluginIds = results.flatMap((result) => result.deferredPluginIds ?? []); return { cleanupCount: results.reduce((count, result) => count + result.cleanupCount, 0), failures: results.flatMap((result) => result.failures), ...deferredPluginIds.length ? { deferredPluginIds } : {} }; }; } /** Prepares one retirement; each waiter keeps its caller's exact instance admission. */ function createPluginHostRegistryRetirement(params) { const previousRegistry = params.previousRegistry; const shouldCleanup = params.shouldCleanup ?? (() => true); if (!previousRegistry || previousRegistry === params.nextRegistry || !shouldCleanup()) return async () => ({ cleanupCount: 0, failures: [] }); const cfg = params.cfg ?? getRuntimeConfigSnapshot() ?? void 0; const runContextCleanup = preparePluginRunContextCleanup(); const nextPluginIds = params.nextRegistry ? collectLoadedPluginIds(params.nextRegistry) : /* @__PURE__ */ new Set(); const hostPluginIds = /* @__PURE__ */ new Set([...collectLoadedPluginIds(previousRegistry), ...collectHostHookPluginIds(previousRegistry)]); const previousPluginIds = /* @__PURE__ */ new Set([...previousRegistry.plugins.map((record) => record.id), ...hostPluginIds]); let sessionStoreTargets; const resolveSessionStoreTargets = () => sessionStoreTargets ??= resolveAllAgentSessionStoreTargetsSync(cfg ?? getRuntimeConfig()); const waits = []; for (const pluginId of previousPluginIds) { const record = previousRegistry.plugins.find((entry) => entry.id === pluginId); if (record && (getPluginRecordRegistry(previousRegistry, record) !== previousRegistry || params.nextRegistry?.plugins.includes(record))) continue; const instance = record ? getPluginInstance(record) : void 0; const restarted = params.skipPersistentSessionState || nextPluginIds.has(pluginId); let result = { cleanupCount: 0, failures: [] }; const cleanup = hostPluginIds.has(pluginId) ? () => runContextCleanup(async () => { result = await runPluginHostCleanup({ cfg, registry: previousRegistry, pluginId, reason: restarted ? "restart" : "disable", preserveSchedulerJobIds: restarted ? collectSchedulerJobIds(params.nextRegistry, pluginId) : void 0, shouldCleanup, restartPromotedSessionEntrySlotKeys: restarted ? collectRestartPromotedSessionEntrySlotKeys(previousRegistry, params.nextRegistry, pluginId) : void 0, preserveSchedulerOwnerRegistry: restarted ? params.nextRegistry : void 0, resolveSessionStoreTargets, skipPersistentSessionState: params.skipPersistentSessionState }); for (const failure of result.failures) log.warn(`Plugin ${failure.pluginId} cleanup failed (${failure.hookId}): ${formatErrorMessage(failure.error)}`); }) : void 0; const completion = instance ? instance.dispose(instance.disposing ? void 0 : cleanup) : Promise.resolve().then(cleanup).then(() => ({ errors: [] })); completion.catch(() => {}); waits.push(async (options) => { if (options?.deferConsumers && instance?.hasRetainedConsumers) return { ...result, deferredPluginIds: [pluginId] }; const disposed = await (instance ? instance.dispose() : completion); const failures = [...result.failures]; appendPluginInstanceCleanupFailures(failures, pluginId, disposed); return { cleanupCount: result.cleanupCount, failures }; }); } return createPluginHostRetirementObserver(waits); } //#endregion export { runPluginHostCleanup as n, createPluginHostRegistryRetirement as t };