UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

119 lines (118 loc) • 5.78 kB
import { c as normalizeOptionalLowercaseString } from "./string-coerce-1VuiLaaR.mjs"; import { n as resolveGlobalDedupeCache } from "./dedupe-qhwfyIea.mjs"; import { m as wrapCurrentPluginInstance } from "./plugin-instance-scope-DEzoxqBj.mjs"; import { _ as requireActivePluginChannelRegistry, b as resolveDirectPluginRegistrationOwner, p as getPluginRegistrationContext } from "./runtime-NsVZTERq.mjs"; //#region src/plugins/interactive-shared.ts function toPluginInteractiveRegistryKey(channel, namespace) { return `${normalizeOptionalLowercaseString(channel) ?? ""}:${namespace.trim()}`; } function validatePluginInteractiveNamespace(namespace) { if (!namespace.trim()) return "Interactive handler namespace cannot be empty"; if (!/^[A-Za-z0-9._-]+$/.test(namespace.trim())) return "Interactive handler namespace must contain only letters, numbers, dots, underscores, and hyphens"; return null; } function resolvePluginInteractiveMatch(params) { const trimmedData = params.data.trim(); if (!trimmedData) return null; const separatorIndex = trimmedData.indexOf(":"); const namespace = separatorIndex >= 0 ? trimmedData.slice(0, separatorIndex) : trimmedData; const registration = params.interactiveHandlers.get(toPluginInteractiveRegistryKey(params.channel, namespace)); if (!registration) return null; return { registration, namespace, payload: separatorIndex >= 0 ? trimmedData.slice(separatorIndex + 1) : "" }; } //#endregion //#region src/plugins/interactive-state.ts const PLUGIN_INTERACTIVE_STATE_KEY = Symbol.for("openclaw.pluginInteractiveState"); const PLUGIN_INTERACTIVE_CALLBACK_DEDUPE_KEY = Symbol.for("openclaw.pluginInteractiveCallbackDedupe"); function hydrateInteractiveState(value) { const state = typeof value === "object" && value !== null ? value : void 0; return { callbackDedupe: resolveGlobalDedupeCache(PLUGIN_INTERACTIVE_CALLBACK_DEDUPE_KEY, { ttlMs: 3e5, maxSize: 4096 }), inflightCallbackDedupe: state?.inflightCallbackDedupe instanceof Set ? state.inflightCallbackDedupe : /* @__PURE__ */ new Set() }; } function getState() { const globalStore = globalThis; const hydrated = hydrateInteractiveState(globalStore[PLUGIN_INTERACTIVE_STATE_KEY]); globalStore[PLUGIN_INTERACTIVE_STATE_KEY] = hydrated; return hydrated; } /** Claims an interactive callback dedupe key while the callback is in flight. */ function claimPluginInteractiveCallbackDedupe(dedupeKey, now = Date.now()) { if (!dedupeKey) return true; const state = getState(); if (state.inflightCallbackDedupe.has(dedupeKey) || state.callbackDedupe.peek(dedupeKey, now)) return false; state.inflightCallbackDedupe.add(dedupeKey); return true; } /** Commits an interactive callback dedupe key after successful handling. */ function commitPluginInteractiveCallbackDedupe(dedupeKey, now = Date.now()) { if (!dedupeKey) return; const state = getState(); state.inflightCallbackDedupe.delete(dedupeKey); state.callbackDedupe.check(dedupeKey, now); } /** Releases an in-flight interactive callback dedupe claim without committing it. */ function releasePluginInteractiveCallbackDedupe(dedupeKey) { if (!dedupeKey) return; getState().inflightCallbackDedupe.delete(dedupeKey); } /** Clears plugin interactive handlers and callback dedupe state. */ function clearPluginInteractiveHandlersState() { const state = getState(); state.callbackDedupe.clear(); state.inflightCallbackDedupe.clear(); } //#endregion //#region src/plugins/interactive-registry.ts /** Resolves a handler from registry-owned registrations without changing global state. */ function resolvePluginInteractiveRegistrationsMatch(registrations, channel, data) { return resolvePluginInteractiveMatch({ interactiveHandlers: { get: (key) => registrations.find((entry) => toPluginInteractiveRegistryKey(entry.channel, entry.namespace) === key) }, channel, data }); } /** Registers one handler whose lifetime follows its owning plugin registry. */ function registerPluginInteractiveHandlerInRegistry(registry, pluginId, registration, opts) { const registrations = registry.interactiveHandlers; const namespace = registration.namespace.trim(); const validationError = validatePluginInteractiveNamespace(namespace); if (validationError) return { ok: false, error: validationError }; const key = toPluginInteractiveRegistryKey(registration.channel, namespace); const existing = registrations.find((entry) => toPluginInteractiveRegistryKey(entry.channel, entry.namespace) === key); if (existing) return { ok: false, error: `Interactive handler namespace "${namespace}" already registered by plugin "${existing.pluginId}"` }; registrations.push({ ...wrapCurrentPluginInstance(registration), namespace, channel: normalizeOptionalLowercaseString(registration.channel) ?? "", pluginId, pluginName: opts?.pluginName, pluginRoot: opts?.pluginRoot }); return { ok: true }; } /** Registers one process-global interactive handler. */ function registerPluginInteractiveHandler(pluginId, registration, opts) { return registerPluginInteractiveHandlerInRegistry(getPluginRegistrationContext()?.registry ?? requireActivePluginChannelRegistry(), resolveDirectPluginRegistrationOwner(pluginId) ?? pluginId, registration, opts); } /** Clears all active plugin interactive handlers. */ function clearPluginInteractiveHandlers() { requireActivePluginChannelRegistry().interactiveHandlers.length = 0; clearPluginInteractiveHandlersState(); } //#endregion export { claimPluginInteractiveCallbackDedupe as a, resolvePluginInteractiveRegistrationsMatch as i, registerPluginInteractiveHandler as n, commitPluginInteractiveCallbackDedupe as o, registerPluginInteractiveHandlerInRegistry as r, releasePluginInteractiveCallbackDedupe as s, clearPluginInteractiveHandlers as t };