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);
}
|