From 977610fbde07e52fdd6e1bc6d5f6af86d658e73b Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 15 Mar 2026 16:40:49 +0000 Subject: [PATCH] Plugins: extract loader execution setup --- src/extension-host/cutover-inventory.md | 1 + src/extension-host/loader-execution.test.ts | 49 +++++++++++ src/extension-host/loader-execution.ts | 92 +++++++++++++++++++++ src/extension-host/loader-orchestrator.ts | 59 +++---------- 4 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 src/extension-host/loader-execution.test.ts create mode 100644 src/extension-host/loader-execution.ts diff --git a/src/extension-host/cutover-inventory.md b/src/extension-host/cutover-inventory.md index a584fb84fef..bd124962807 100644 --- a/src/extension-host/cutover-inventory.md +++ b/src/extension-host/cutover-inventory.md @@ -43,6 +43,7 @@ This is an implementation checklist, not a future-design spec. | Loader post-import planning and register execution | `src/plugins/loader.ts` | `src/extension-host/loader-register.ts` | `partial` | Definition application, post-import validation planning, and `register(...)` execution now delegate through host-owned loader-register helpers while preserving current plugin behavior. | | Loader per-candidate orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-flow.ts` | `partial` | The per-candidate load flow now runs through a host-owned orchestrator that composes planning, import, runtime validation, register execution, and record-state helpers. | | Loader top-level load orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-orchestrator.ts` | `partial` | Cache hits, runtime creation, discovery, manifest loading, candidate ordering, candidate processing, and finalization now route through a host-owned loader orchestrator while `src/plugins/loader.ts` remains the compatibility facade. | +| Loader execution setup composition | mixed inside `src/plugins/loader.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-execution.ts` | `partial` | Runtime creation, registry creation, bootstrap setup, module-loader creation, and session creation now delegate through a host-owned loader-execution helper. | | Loader discovery and manifest bootstrap | mixed inside `src/plugins/loader.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-bootstrap.ts` | `partial` | Discovery, manifest loading, manifest diagnostics, discovery-policy logging, provenance building, and candidate ordering now delegate through a host-owned loader-bootstrap helper. | | Loader mutable activation state session | local variables in `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-session.ts` | `partial` | Seen-id tracking, memory-slot selection state, and finalization inputs now live in a host-owned loader session instead of being spread across top-level loader variables. | | Loader session run and finalization composition | mixed inside `src/extension-host/loader-orchestrator.ts` and `src/extension-host/loader-session.ts` | `src/extension-host/loader-run.ts` | `partial` | Candidate iteration, manifest lookup, per-candidate session processing, and finalization handoff now delegate through a host-owned loader-run helper. | diff --git a/src/extension-host/loader-execution.test.ts b/src/extension-host/loader-execution.test.ts new file mode 100644 index 00000000000..7ba59f6170f --- /dev/null +++ b/src/extension-host/loader-execution.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it, vi } from "vitest"; +import { prepareExtensionHostLoaderExecution } from "./loader-execution.js"; + +describe("extension host loader execution", () => { + it("composes runtime, registry, bootstrap, module loader, and session setup", () => { + const runtime = {} as never; + const registry = { plugins: [], diagnostics: [] } as never; + const createApi = vi.fn() as never; + const loadModule = vi.fn() as never; + const session = { registry } as never; + + const result = prepareExtensionHostLoaderExecution({ + config: {}, + env: process.env, + cacheKey: "cache-key", + normalizedConfig: { + enabled: true, + allow: [], + loadPaths: [], + entries: {}, + slots: {}, + }, + logger: { + info: () => {}, + warn: () => {}, + error: () => {}, + }, + warningCache: new Set(), + setCachedRegistry: vi.fn(), + activateRegistry: vi.fn(), + createRuntime: vi.fn(() => runtime) as never, + createRegistry: vi.fn(() => ({ registry, createApi })) as never, + bootstrapLoad: vi.fn(() => ({ + provenance: { loadPathMatcher: { exact: new Set(), dirs: [] }, installRules: new Map() }, + orderedCandidates: [{ rootDir: "/plugins/a" }], + manifestByRoot: new Map([["/plugins/a", { rootDir: "/plugins/a" }]]), + })) as never, + createModuleLoader: vi.fn(() => loadModule) as never, + createSession: vi.fn(() => session) as never, + }); + + expect(result.registry).toBe(registry); + expect(result.createApi).toBe(createApi); + expect(result.loadModule).toBe(loadModule); + expect(result.session).toBe(session); + expect(result.orderedCandidates).toEqual([{ rootDir: "/plugins/a" }]); + expect(result.manifestByRoot.get("/plugins/a")?.rootDir).toBe("/plugins/a"); + }); +}); diff --git a/src/extension-host/loader-execution.ts b/src/extension-host/loader-execution.ts new file mode 100644 index 00000000000..ba6b6cc832b --- /dev/null +++ b/src/extension-host/loader-execution.ts @@ -0,0 +1,92 @@ +import type { OpenClawConfig } from "../config/config.js"; +import type { NormalizedPluginsConfig } from "../plugins/config-state.js"; +import { createPluginRegistry, type PluginRegistry } from "../plugins/registry.js"; +import type { CreatePluginRuntimeOptions } from "../plugins/runtime/index.js"; +import type { PluginRuntime } from "../plugins/runtime/types.js"; +import type { PluginLogger } from "../plugins/types.js"; +import { bootstrapExtensionHostPluginLoad } from "./loader-bootstrap.js"; +import { resolveExtensionHostDiscoveryPolicy } from "./loader-discovery-policy.js"; +import { createExtensionHostModuleLoader } from "./loader-module-loader.js"; +import { + buildExtensionHostProvenanceIndex, + compareExtensionHostDuplicateCandidateOrder, + pushExtensionHostDiagnostics, +} from "./loader-policy.js"; +import { createExtensionHostLazyRuntime } from "./loader-runtime-proxy.js"; +import { + createExtensionHostLoaderSession, + type ExtensionHostLoaderSession, +} from "./loader-session.js"; + +export function prepareExtensionHostLoaderExecution(params: { + config: OpenClawConfig; + workspaceDir?: string; + env: NodeJS.ProcessEnv; + cache?: boolean; + cacheKey: string; + normalizedConfig: NormalizedPluginsConfig; + logger: PluginLogger; + coreGatewayHandlers?: Record; + runtimeOptions?: CreatePluginRuntimeOptions; + warningCache: Set; + setCachedRegistry: (cacheKey: string, registry: PluginRegistry) => void; + activateRegistry: (registry: PluginRegistry, cacheKey: string) => void; + createRuntime: (runtimeOptions?: CreatePluginRuntimeOptions) => PluginRuntime; + createRegistry?: typeof createPluginRegistry; + bootstrapLoad?: typeof bootstrapExtensionHostPluginLoad; + createModuleLoader?: typeof createExtensionHostModuleLoader; + createSession?: typeof createExtensionHostLoaderSession; +}) { + const createRegistry = params.createRegistry ?? createPluginRegistry; + const bootstrapLoad = params.bootstrapLoad ?? bootstrapExtensionHostPluginLoad; + const createModuleLoader = params.createModuleLoader ?? createExtensionHostModuleLoader; + const createSession = params.createSession ?? createExtensionHostLoaderSession; + + const runtime = createExtensionHostLazyRuntime({ + runtimeOptions: params.runtimeOptions, + createRuntime: params.createRuntime, + }); + const { registry, createApi } = createRegistry({ + logger: params.logger, + runtime, + coreGatewayHandlers: params.coreGatewayHandlers as never, + }); + + const bootstrap = bootstrapLoad({ + config: params.config, + workspaceDir: params.workspaceDir, + env: params.env, + warningCacheKey: params.cacheKey, + warningCache: params.warningCache, + cache: params.cache, + normalizedConfig: params.normalizedConfig, + logger: params.logger, + registry, + pushDiagnostics: pushExtensionHostDiagnostics, + resolveDiscoveryPolicy: resolveExtensionHostDiscoveryPolicy, + buildProvenanceIndex: buildExtensionHostProvenanceIndex, + compareDuplicateCandidateOrder: compareExtensionHostDuplicateCandidateOrder, + }); + + const loadModule = createModuleLoader(); + const session: ExtensionHostLoaderSession = createSession({ + registry, + logger: params.logger, + env: params.env, + provenance: bootstrap.provenance, + cacheEnabled: params.cache !== false, + cacheKey: params.cacheKey, + memorySlot: params.normalizedConfig.slots.memory, + setCachedRegistry: params.setCachedRegistry, + activateRegistry: params.activateRegistry, + }); + + return { + registry, + createApi, + loadModule, + session, + orderedCandidates: bootstrap.orderedCandidates, + manifestByRoot: bootstrap.manifestByRoot, + }; +} diff --git a/src/extension-host/loader-orchestrator.ts b/src/extension-host/loader-orchestrator.ts index 550b3f72d9c..9329686e597 100644 --- a/src/extension-host/loader-orchestrator.ts +++ b/src/extension-host/loader-orchestrator.ts @@ -6,24 +6,15 @@ import { getCachedExtensionHostRegistry, setCachedExtensionHostRegistry, } from "../extension-host/loader-cache.js"; -import { - buildExtensionHostProvenanceIndex, - compareExtensionHostDuplicateCandidateOrder, - pushExtensionHostDiagnostics, -} from "../extension-host/loader-policy.js"; import type { GatewayRequestHandler } from "../gateway/server-methods/types.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { clearPluginCommands } from "../plugins/commands.js"; import { applyTestPluginDefaults, normalizePluginsConfig } from "../plugins/config-state.js"; -import { createPluginRegistry, type PluginRegistry } from "../plugins/registry.js"; +import type { PluginRegistry } from "../plugins/registry.js"; import { createPluginRuntime, type CreatePluginRuntimeOptions } from "../plugins/runtime/index.js"; import type { PluginLogger } from "../plugins/types.js"; -import { bootstrapExtensionHostPluginLoad } from "./loader-bootstrap.js"; -import { resolveExtensionHostDiscoveryPolicy } from "./loader-discovery-policy.js"; -import { createExtensionHostModuleLoader } from "./loader-module-loader.js"; +import { prepareExtensionHostLoaderExecution } from "./loader-execution.js"; import { runExtensionHostLoaderSession } from "./loader-run.js"; -import { createExtensionHostLazyRuntime } from "./loader-runtime-proxy.js"; -import { createExtensionHostLoaderSession } from "./loader-session.js"; export type ExtensionHostPluginLoadOptions = { config?: OpenClawConfig; @@ -73,54 +64,30 @@ export function loadExtensionHostPluginRegistry( // Clear previously registered plugin commands before reloading. clearPluginCommands(); - const runtime = createExtensionHostLazyRuntime({ - runtimeOptions: options.runtimeOptions, - createRuntime: createPluginRuntime, - }); - const { registry, createApi } = createPluginRegistry({ - logger, - runtime, - coreGatewayHandlers: options.coreGatewayHandlers as Record, - }); - - const bootstrap = bootstrapExtensionHostPluginLoad({ + const execution = prepareExtensionHostLoaderExecution({ config: cfg, workspaceDir: options.workspaceDir, env, - warningCacheKey: cacheKey, - warningCache: openAllowlistWarningCache, cache: options.cache, + cacheKey, normalizedConfig: normalized, logger, - registry, - pushDiagnostics: pushExtensionHostDiagnostics, - resolveDiscoveryPolicy: resolveExtensionHostDiscoveryPolicy, - buildProvenanceIndex: buildExtensionHostProvenanceIndex, - compareDuplicateCandidateOrder: compareExtensionHostDuplicateCandidateOrder, - }); - - const loadModule = createExtensionHostModuleLoader(); - - const session = createExtensionHostLoaderSession({ - registry, - logger, - env, - provenance: bootstrap.provenance, - cacheEnabled, - cacheKey, - memorySlot: normalized.slots.memory, + coreGatewayHandlers: options.coreGatewayHandlers as Record, + runtimeOptions: options.runtimeOptions, + warningCache: openAllowlistWarningCache, setCachedRegistry: setCachedExtensionHostRegistry, activateRegistry: activateExtensionHostRegistry, + createRuntime: createPluginRuntime, }); return runExtensionHostLoaderSession({ - session, - orderedCandidates: bootstrap.orderedCandidates, - manifestByRoot: bootstrap.manifestByRoot, + session: execution.session, + orderedCandidates: execution.orderedCandidates, + manifestByRoot: execution.manifestByRoot, normalizedConfig: normalized, rootConfig: cfg, validateOnly, - createApi, - loadModule, + createApi: execution.createApi, + loadModule: execution.loadModule, }); }