File size: 5,767 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { attachPluginApiFacades, type OpenClawPluginApiWithoutFacades } from "./api-facades.js";
import type { PluginRuntime } from "./runtime/types.js";
import type { OpenClawPluginApi, PluginLogger } from "./types.js";

type BuildPluginApiParams = {
  id: string;
  name: string;
  version?: string;
  description?: string;
  source: string;
  runtimeSource?: string;
  rootDir?: string;
  registrationMode: OpenClawPluginApi["registrationMode"];
  config: OpenClawConfig;
  pluginConfig?: Record<string, unknown>;
  runtime: PluginRuntime;
  logger: PluginLogger;
  resolvePath: (input: string) => string;
  handlers?: Partial<Pick<OpenClawPluginApi, keyof typeof noops>>;
};

const noops = {
  registerCli: () => {},
  registerTool: () => {},
  registerHook: () => {},
  registerHttpRoute: () => {},
  registerHostedMediaResolver: () => {},
  registerWidgetPresenter: () => {},
  registerMcpServerConnectionResolver: () => {},
  registerChannel: () => {},
  registerGatewayMethod: () => {},
  registerSessionCatalog: () => {},
  registerReload: () => {},
  registerNodeHostCommand: () => {},
  registerNodeInvokePolicy: () => {},
  registerSecurityAuditCollector: () => {},
  registerService: () => {},
  registerGatewayDiscoveryService: () => {},
  registerCliBackend: () => {},
  registerTextTransforms: () => {},
  registerConfigMigration: () => {},
  registerMigrationProvider: () => {},
  registerAutoEnableProbe: () => {},
  registerProvider: () => {},
  registerWorkerProvider: () => {},
  registerModelCatalogProvider: () => {},
  registerEmbeddingProvider: () => {},
  registerSpeechProvider: () => {},
  registerRealtimeTranscriptionProvider: () => {},
  registerRealtimeVoiceProvider: () => {},
  registerMediaUnderstandingProvider: () => {},
  registerTranscriptSourceProvider: () => {},
  registerImageGenerationProvider: () => {},
  registerVideoGenerationProvider: () => {},
  registerMusicGenerationProvider: () => {},
  registerWebFetchProvider: () => {},
  registerWebSearchProvider: () => {},
  registerInteractiveHandler: () => {},
  onConversationBindingResolved: () => {},
  registerCommand: () => {},
  registerContextEngine: () => {},
  registerCompactionProvider: () => {},
  registerAgentHarness: () => {},
  registerCodexAppServerExtensionFactory: () => {},
  registerAgentToolResultMiddleware: () => {},
  registerSessionExtension: () => {},
  enqueueNextTurnInjection: async (injection) => ({
    enqueued: false,
    id: "",
    sessionKey: injection.sessionKey,
  }),
  registerTrustedToolPolicy: () => {},
  registerToolMetadata: () => {},
  registerControlUiDescriptor: () => {},
  registerBoardWidgetContentKind: () => {},
  registerRuntimeLifecycle: () => {},
  registerAgentEventSubscription: () => {},
  emitAgentEvent: () => ({
    emitted: false,
    reason: "not wired",
  }),
  setRunContext: () => false,
  getRunContext: () => undefined,
  clearRunContext: () => {},
  registerSessionSchedulerJob: () => undefined,
  registerSessionAction: () => {},
  sendSessionAttachment: async () => ({
    ok: false,
    error: "not wired",
  }),
  scheduleSessionTurn: async () => undefined,
  unscheduleSessionTurnsByTag: async () => ({ removed: 0, failed: 0 }),
  registerDetachedTaskRuntime: () => {},
  registerMemoryCapability: () => {},
  registerMemoryPromptSupplement: () => {},
  registerMemoryPromptPreparation: () => {},
  registerMemoryCorpusSupplement: () => {},
  on: () => {},
} satisfies Partial<OpenClawPluginApi>;
const noopEntries = Object.entries(noops);

export function createUnavailableRuntime(
  registrationMode: "cli-metadata" | "setup-only",
  pluginId?: string,
): PluginRuntime {
  const owner = pluginId ? `Plugin "${pluginId}"` : "Plugin";
  const guidance =
    registrationMode === "cli-metadata"
      ? "Declare root commands in the manifest's cliCommands or defer runtime access out of register()."
      : "Defer runtime access out of register().";
  // SAFETY: String capabilities fail closed; symbols stay inert so reflection cannot trigger runtime errors.
  return new Proxy(Object.create(null) as PluginRuntime, {
    get(_target, property) {
      if (typeof property === "symbol") {
        return undefined;
      }
      throw new Error(
        `${owner} runtime is intentionally unavailable during "${registrationMode}" registration. ${guidance}`,
      );
    },
  });
}

export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi {
  const handlers = params.handlers ?? {};
  // Iterate the declared surface so inherited handlers and nullish defaults keep
  // the same behavior without maintaining a second list of every API method.
  const registrations = Object.fromEntries(
    noopEntries.map(([key, fallback]) => [
      key,
      // SAFETY: Object.entries reads only the fixed noops declaration, which defines these handler keys.
      handlers[key as keyof typeof noops] ?? fallback,
    ]),
    // SAFETY: Every declared method receives its typed handler or matching default.
  ) as Pick<OpenClawPluginApi, keyof typeof noops>;
  const api: OpenClawPluginApiWithoutFacades = {
    id: params.id,
    name: params.name,
    version: params.version,
    description: params.description,
    source: params.source,
    runtimeSource: params.runtimeSource,
    rootDir: params.rootDir,
    registrationMode: params.registrationMode,
    config: params.config,
    pluginConfig: params.pluginConfig,
    runtime: params.runtime,
    logger: params.logger,
    ...registrations,
    registerNodeCliFeature: (registrar, opts) =>
      registrations.registerCli(registrar, { ...opts, parentPath: ["nodes"] }),
    resolvePath: params.resolvePath,
  };
  return attachPluginApiFacades(api);
}