From f095bbd7b0c6430e91bb5b7be2b4ab3cc5326547 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Mar 2026 17:57:57 +0000 Subject: [PATCH] refactor: simplify plugin runtime singletons --- src/plugins/conversation-binding.ts | 12 ++++++++---- src/plugins/hook-runner-global.ts | 23 ++++++++--------------- src/plugins/interactive.ts | 4 ++-- src/plugins/runtime.ts | 23 ++++++++--------------- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/plugins/conversation-binding.ts b/src/plugins/conversation-binding.ts index 10ceeeb9fd5..f0c353f272b 100644 --- a/src/plugins/conversation-binding.ts +++ b/src/plugins/conversation-binding.ts @@ -118,13 +118,17 @@ type PluginBindingGlobalState = { }; const pluginBindingGlobalStateKey = Symbol.for("openclaw.plugins.binding.global-state"); - -function getPluginBindingGlobalState(): PluginBindingGlobalState { - return resolveGlobalSingleton(pluginBindingGlobalStateKey, () => ({ +const pluginBindingGlobalState = resolveGlobalSingleton( + pluginBindingGlobalStateKey, + () => ({ fallbackNoticeBindingIds: new Set(), approvalsCache: null, approvalsLoaded: false, - })); + }), +); + +function getPluginBindingGlobalState(): PluginBindingGlobalState { + return pluginBindingGlobalState; } function resolveApprovalsPath(): string { diff --git a/src/plugins/hook-runner-global.ts b/src/plugins/hook-runner-global.ts index b2613f3467f..43d212025e7 100644 --- a/src/plugins/hook-runner-global.ts +++ b/src/plugins/hook-runner-global.ts @@ -6,6 +6,7 @@ */ import { createSubsystemLogger } from "../logging/subsystem.js"; +import { resolveGlobalSingleton } from "../shared/global-singleton.js"; import { createHookRunner, type HookRunner } from "./hooks.js"; import type { PluginRegistry } from "./registry.js"; import type { PluginHookGatewayContext, PluginHookGatewayStopEvent } from "./types.js"; @@ -18,23 +19,16 @@ type HookRunnerGlobalState = { }; const hookRunnerGlobalStateKey = Symbol.for("openclaw.plugins.hook-runner-global-state"); - -function getHookRunnerGlobalState(): HookRunnerGlobalState { - const globalStore = globalThis as typeof globalThis & { - [hookRunnerGlobalStateKey]?: HookRunnerGlobalState; - }; - return (globalStore[hookRunnerGlobalStateKey] ??= { - hookRunner: null, - registry: null, - }); -} +const state = resolveGlobalSingleton(hookRunnerGlobalStateKey, () => ({ + hookRunner: null, + registry: null, +})); /** * Initialize the global hook runner with a plugin registry. * Called once when plugins are loaded during gateway startup. */ export function initializeGlobalHookRunner(registry: PluginRegistry): void { - const state = getHookRunnerGlobalState(); state.registry = registry; state.hookRunner = createHookRunner(registry, { logger: { @@ -56,7 +50,7 @@ export function initializeGlobalHookRunner(registry: PluginRegistry): void { * Returns null if plugins haven't been loaded yet. */ export function getGlobalHookRunner(): HookRunner | null { - return getHookRunnerGlobalState().hookRunner; + return state.hookRunner; } /** @@ -64,14 +58,14 @@ export function getGlobalHookRunner(): HookRunner | null { * Returns null if plugins haven't been loaded yet. */ export function getGlobalPluginRegistry(): PluginRegistry | null { - return getHookRunnerGlobalState().registry; + return state.registry; } /** * Check if any hooks are registered for a given hook name. */ export function hasGlobalHooks(hookName: Parameters[0]): boolean { - return getHookRunnerGlobalState().hookRunner?.hasHooks(hookName) ?? false; + return state.hookRunner?.hasHooks(hookName) ?? false; } export async function runGlobalGatewayStopSafely(params: { @@ -98,7 +92,6 @@ export async function runGlobalGatewayStopSafely(params: { * Reset the global hook runner (for testing). */ export function resetGlobalHookRunner(): void { - const state = getHookRunnerGlobalState(); state.hookRunner = null; state.registry = null; } diff --git a/src/plugins/interactive.ts b/src/plugins/interactive.ts index b090da43d68..d711751dc53 100644 --- a/src/plugins/interactive.ts +++ b/src/plugins/interactive.ts @@ -1,4 +1,4 @@ -import { createDedupeCache } from "../infra/dedupe.js"; +import { createDedupeCache, resolveGlobalDedupeCache } from "../infra/dedupe.js"; import { resolveGlobalSingleton } from "../shared/global-singleton.js"; import { dispatchDiscordInteractiveHandler, @@ -43,7 +43,7 @@ const PLUGIN_INTERACTIVE_STATE_KEY = Symbol.for("openclaw.pluginInteractiveState const state = resolveGlobalSingleton(PLUGIN_INTERACTIVE_STATE_KEY, () => ({ interactiveHandlers: new Map(), - callbackDedupe: createDedupeCache({ + callbackDedupe: resolveGlobalDedupeCache(Symbol.for("openclaw.pluginInteractiveCallbackDedupe"), { ttlMs: 5 * 60_000, maxSize: 4096, }), diff --git a/src/plugins/runtime.ts b/src/plugins/runtime.ts index c1c8974adc2..03c43ed4d1b 100644 --- a/src/plugins/runtime.ts +++ b/src/plugins/runtime.ts @@ -1,3 +1,4 @@ +import { resolveGlobalSingleton } from "../shared/global-singleton.js"; import { createEmptyPluginRegistry } from "./registry-empty.js"; import type { PluginRegistry } from "./registry.js"; @@ -11,21 +12,13 @@ type RegistryState = { version: number; }; -const state: RegistryState = (() => { - const globalState = globalThis as typeof globalThis & { - [REGISTRY_STATE]?: RegistryState; - }; - if (!globalState[REGISTRY_STATE]) { - globalState[REGISTRY_STATE] = { - registry: createEmptyPluginRegistry(), - httpRouteRegistry: null, - httpRouteRegistryPinned: false, - key: null, - version: 0, - }; - } - return globalState[REGISTRY_STATE]; -})(); +const state = resolveGlobalSingleton(REGISTRY_STATE, () => ({ + registry: createEmptyPluginRegistry(), + httpRouteRegistry: null, + httpRouteRegistryPinned: false, + key: null, + version: 0, +})); export function setActivePluginRegistry(registry: PluginRegistry, cacheKey?: string) { state.registry = registry;