File size: 4,966 Bytes
fcd8223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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<OpenClawPluginApi, keyof PluginApiFacadeFields>;
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<T extends object>(
  api: T & PluginApiFacadeSource & Partial<PluginApiFacadeFields>,
): 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<OpenClawPluginCliRegistrar>[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;
}