import { bindPluginCliProgram } from "./cli-callback-binding.js"; import { pluginInstanceState, type PluginInstanceHandle } from "./plugin-instance-scope.js"; import type { OpenClawPluginCliRegistrar } from "./plugin-registration.types.js"; import type { OpenClawPluginApi } from "./types.js"; type PluginApiFacadeFields = Pick< OpenClawPluginApi, "agent" | "lifecycle" | "runContext" | "session" >; /** Plugin API shape without nested facade namespaces attached. */ export type OpenClawPluginApiWithoutFacades = Omit; type PluginApiFacadeSource = Pick< OpenClawPluginApi, | "clearRunContext" | "emitAgentEvent" | "enqueueNextTurnInjection" | "getRunContext" | "registerAgentEventSubscription" | "registerControlUiDescriptor" | "registerRuntimeLifecycle" | "registerSessionAction" | "registerSessionExtension" | "registerSessionSchedulerJob" | "scheduleSessionTurn" | "sendSessionAttachment" | "setRunContext" | "unscheduleSessionTurnsByTag" >; const identitySensitiveRegistrations = new Set([ "registerCompactionProvider", "registerHttpRoute", "registerImageGenerationProvider", "registerMediaUnderstandingProvider", "registerMigrationProvider", "registerMusicGenerationProvider", "registerRealtimeTranscriptionProvider", "registerRealtimeVoiceProvider", "registerSpeechProvider", "registerTranscriptSourceProvider", "registerVideoGenerationProvider", "registerWebFetchProvider", "registerWebSearchProvider", ]); /** Attaches nested facade namespaces to the flat plugin API implementation. */ export function attachPluginApiFacades( api: T & PluginApiFacadeSource & Partial, ): T & PluginApiFacadeFields { api.session = { state: { registerSessionExtension: (...args) => api.registerSessionExtension(...args), }, workflow: { enqueueNextTurnInjection: (...args) => api.enqueueNextTurnInjection(...args), registerSessionSchedulerJob: (...args) => api.registerSessionSchedulerJob(...args), sendSessionAttachment: (...args) => api.sendSessionAttachment(...args), scheduleSessionTurn: (...args) => api.scheduleSessionTurn(...args), unscheduleSessionTurnsByTag: (...args) => api.unscheduleSessionTurnsByTag(...args), }, controls: { registerSessionAction: (...args) => api.registerSessionAction(...args), registerControlUiDescriptor: (...args) => api.registerControlUiDescriptor(...args), }, }; api.agent = { events: { registerAgentEventSubscription: (...args) => api.registerAgentEventSubscription(...args), emitAgentEvent: (...args) => api.emitAgentEvent(...args), }, }; api.runContext = { setRunContext: (...args) => api.setRunContext(...args), getRunContext: (...args) => api.getRunContext(...args), clearRunContext: (...args) => api.clearRunContext(...args), }; api.lifecycle = { ...api.lifecycle, registerRuntimeLifecycle: (...args) => api.registerRuntimeLifecycle(...args), }; return api as T & PluginApiFacadeFields; } /** Registration callbacks and their API retain the exact admitted instance. */ export function instrumentPluginInstanceApi( api: OpenClawPluginApi, instance?: PluginInstanceHandle, ): OpenClawPluginApi { if (!instance) { return api; } api.lifecycle = { ...api.lifecycle, ...instance.lifecycle }; const instrumented = attachPluginApiFacades( new Proxy(api, { get: (target, key, receiver) => { const value = Reflect.get(target, key, receiver); if ( typeof value !== "function" || typeof key !== "string" || (!key.startsWith("register") && key !== "on" && key !== "onConversationBindingResolved") ) { return value; } if (key === "registerCli" || key === "registerNodeCliFeature") { return (registrar: OpenClawPluginCliRegistrar, ...options: unknown[]) => instance.run(() => Reflect.apply(value, target, [ instance.wrap((context: Parameters[0]) => { // Commander retains callbacks beyond this registrar's invocation. // Bind at the typed host boundary, without proxying its native objects. bindPluginCliProgram(context.program); return registrar(context); }), ...options.map((option) => instance.wrap(option)), ]), ); } return (...args: unknown[]) => instance.run(() => Reflect.apply( value, target, args.map((arg) => identitySensitiveRegistrations.has(key) ? instance.adopt(arg) : instance.wrap(arg), ), ), ); }, }), ); pluginInstanceState.values.set(instrumented, instance); return instrumented; }