diff --git a/src/extension-host/cutover-inventory.md b/src/extension-host/cutover-inventory.md index e8454e5fe23..d53384db790 100644 --- a/src/extension-host/cutover-inventory.md +++ b/src/extension-host/cutover-inventory.md @@ -42,8 +42,9 @@ This is an implementation checklist, not a future-design spec. | Loader module-export, config-validation, and memory-slot decisions | `src/plugins/loader.ts` | `src/extension-host/loader-runtime.ts` | `partial` | Module export resolution, export-metadata application, config validation, and early or final memory-slot decisions now delegate through host-owned loader-runtime helpers. | | 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 top-level load orchestration | `src/plugins/loader.ts` | `src/extension-host/loader-orchestrator.ts` | `partial` | High-level load entry and compatibility facade behavior now route through a host-owned loader orchestrator while `src/plugins/loader.ts` remains the external compatibility surface. | | Loader preflight and cache-hit setup | mixed inside `src/plugins/loader.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-preflight.ts` | `partial` | Test-default application, config normalization, cache-key construction, cache-hit activation, and command-clear preflight now delegate through a host-owned loader-preflight helper. | +| Loader post-preflight pipeline composition | mixed inside `src/plugins/loader.ts` and `src/extension-host/loader-orchestrator.ts` | `src/extension-host/loader-pipeline.ts` | `partial` | Post-preflight execution setup and session-run composition now delegate through a host-owned loader-pipeline helper. | | 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. | diff --git a/src/extension-host/loader-orchestrator.ts b/src/extension-host/loader-orchestrator.ts index ea5c3380251..09c47cb23e7 100644 --- a/src/extension-host/loader-orchestrator.ts +++ b/src/extension-host/loader-orchestrator.ts @@ -1,25 +1,22 @@ import type { OpenClawConfig } from "../config/config.js"; -import { activateExtensionHostRegistry } from "../extension-host/activation.js"; -import { - clearExtensionHostRegistryCache, - setCachedExtensionHostRegistry, -} from "../extension-host/loader-cache.js"; -import type { GatewayRequestHandler } from "../gateway/server-methods/types.js"; +import { clearExtensionHostRegistryCache } from "../extension-host/loader-cache.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { clearPluginCommands } from "../plugins/commands.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 { prepareExtensionHostLoaderExecution } from "./loader-execution.js"; +import { executeExtensionHostLoaderPipeline } from "./loader-pipeline.js"; import { prepareExtensionHostLoaderPreflight } from "./loader-preflight.js"; -import { runExtensionHostLoaderSession } from "./loader-run.js"; export type ExtensionHostPluginLoadOptions = { config?: OpenClawConfig; workspaceDir?: string; env?: NodeJS.ProcessEnv; logger?: PluginLogger; - coreGatewayHandlers?: Record; + coreGatewayHandlers?: Record< + string, + import("../gateway/server-methods/types.js").GatewayRequestHandler + >; runtimeOptions?: CreatePluginRuntimeOptions; cache?: boolean; mode?: "full" | "validate"; @@ -46,30 +43,13 @@ export function loadExtensionHostPluginRegistry( return preflight.registry; } - const execution = prepareExtensionHostLoaderExecution({ - config: preflight.config, + return executeExtensionHostLoaderPipeline({ + preflight, workspaceDir: options.workspaceDir, - env: preflight.env, cache: options.cache, - cacheKey: preflight.cacheKey, - normalizedConfig: preflight.normalizedConfig, - logger: preflight.logger, - coreGatewayHandlers: options.coreGatewayHandlers as Record, + coreGatewayHandlers: options.coreGatewayHandlers, runtimeOptions: options.runtimeOptions, warningCache: openAllowlistWarningCache, - setCachedRegistry: setCachedExtensionHostRegistry, - activateRegistry: activateExtensionHostRegistry, createRuntime: createPluginRuntime, }); - - return runExtensionHostLoaderSession({ - session: execution.session, - orderedCandidates: execution.orderedCandidates, - manifestByRoot: execution.manifestByRoot, - normalizedConfig: preflight.normalizedConfig, - rootConfig: preflight.config, - validateOnly: preflight.validateOnly, - createApi: execution.createApi, - loadModule: execution.loadModule, - }); } diff --git a/src/extension-host/loader-pipeline.test.ts b/src/extension-host/loader-pipeline.test.ts new file mode 100644 index 00000000000..3c80eddf0b4 --- /dev/null +++ b/src/extension-host/loader-pipeline.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, vi } from "vitest"; +import { executeExtensionHostLoaderPipeline } from "./loader-pipeline.js"; + +describe("extension host loader pipeline", () => { + it("threads preflight data through execution setup and session run", () => { + const session = {} as never; + const createApi = vi.fn() as never; + const loadModule = vi.fn() as never; + const registry = { plugins: [] } as never; + const resultRegistry = { plugins: [{ id: "demo" }] } as never; + + const result = executeExtensionHostLoaderPipeline({ + preflight: { + cacheHit: false, + env: { TEST: "1" }, + config: { plugins: { enabled: true } }, + logger: { info() {}, warn() {}, error() {} }, + validateOnly: true, + normalizedConfig: { + enabled: true, + allow: [], + loadPaths: [], + entries: {}, + slots: {}, + }, + cacheKey: "cache-key", + }, + workspaceDir: "/workspace", + cache: false, + coreGatewayHandlers: { ping: vi.fn() as never }, + warningCache: new Set(), + createRuntime: vi.fn(() => ({}) as never) as never, + prepareExecution: vi.fn(() => ({ + registry, + createApi, + loadModule, + session, + orderedCandidates: [{ rootDir: "/plugins/a" }], + manifestByRoot: new Map([["/plugins/a", { rootDir: "/plugins/a" }]]), + })) as never, + runSession: vi.fn(() => resultRegistry) as never, + }); + + expect(result).toBe(resultRegistry); + }); +}); diff --git a/src/extension-host/loader-pipeline.ts b/src/extension-host/loader-pipeline.ts new file mode 100644 index 00000000000..602d2684912 --- /dev/null +++ b/src/extension-host/loader-pipeline.ts @@ -0,0 +1,51 @@ +import type { GatewayRequestHandler } from "../gateway/server-methods/types.js"; +import type { PluginRegistry } from "../plugins/registry.js"; +import type { CreatePluginRuntimeOptions } from "../plugins/runtime/index.js"; +import type { PluginRuntime } from "../plugins/runtime/types.js"; +import { activateExtensionHostRegistry } from "./activation.js"; +import { setCachedExtensionHostRegistry } from "./loader-cache.js"; +import { prepareExtensionHostLoaderExecution } from "./loader-execution.js"; +import type { ExtensionHostLoaderPreflightReady } from "./loader-preflight.js"; +import { runExtensionHostLoaderSession } from "./loader-run.js"; + +export function executeExtensionHostLoaderPipeline(params: { + preflight: ExtensionHostLoaderPreflightReady; + workspaceDir?: string; + cache?: boolean; + coreGatewayHandlers?: Record; + runtimeOptions?: CreatePluginRuntimeOptions; + warningCache: Set; + createRuntime: (runtimeOptions?: CreatePluginRuntimeOptions) => PluginRuntime; + prepareExecution?: typeof prepareExtensionHostLoaderExecution; + runSession?: typeof runExtensionHostLoaderSession; +}): PluginRegistry { + const prepareExecution = params.prepareExecution ?? prepareExtensionHostLoaderExecution; + const runSession = params.runSession ?? runExtensionHostLoaderSession; + + const execution = prepareExecution({ + config: params.preflight.config, + workspaceDir: params.workspaceDir, + env: params.preflight.env, + cache: params.cache, + cacheKey: params.preflight.cacheKey, + normalizedConfig: params.preflight.normalizedConfig, + logger: params.preflight.logger, + coreGatewayHandlers: params.coreGatewayHandlers as Record, + runtimeOptions: params.runtimeOptions, + warningCache: params.warningCache, + setCachedRegistry: setCachedExtensionHostRegistry, + activateRegistry: activateExtensionHostRegistry, + createRuntime: params.createRuntime, + }); + + return runSession({ + session: execution.session, + orderedCandidates: execution.orderedCandidates, + manifestByRoot: execution.manifestByRoot, + normalizedConfig: params.preflight.normalizedConfig, + rootConfig: params.preflight.config, + validateOnly: params.preflight.validateOnly, + createApi: execution.createApi, + loadModule: execution.loadModule, + }); +} diff --git a/src/extension-host/loader-preflight.ts b/src/extension-host/loader-preflight.ts index dbd16d87212..3bb7049599f 100644 --- a/src/extension-host/loader-preflight.ts +++ b/src/extension-host/loader-preflight.ts @@ -18,6 +18,27 @@ export type ExtensionHostLoaderPreflightOptions = { mode?: ExtensionHostPluginLoadMode; }; +export type ExtensionHostLoaderPreflightCacheHit = { + cacheHit: true; + registry: ReturnType extends infer T + ? Exclude + : never; +}; + +export type ExtensionHostLoaderPreflightReady = { + cacheHit: false; + env: NodeJS.ProcessEnv; + config: OpenClawConfig; + logger: PluginLogger; + validateOnly: boolean; + normalizedConfig: ReturnType; + cacheKey: string; +}; + +export type ExtensionHostLoaderPreflightResult = + | ExtensionHostLoaderPreflightCacheHit + | ExtensionHostLoaderPreflightReady; + export function prepareExtensionHostLoaderPreflight(params: { options: ExtensionHostLoaderPreflightOptions; createDefaultLogger: () => PluginLogger; @@ -27,7 +48,7 @@ export function prepareExtensionHostLoaderPreflight(params: { buildCacheKey?: typeof buildExtensionHostRegistryCacheKey; getCachedRegistry?: typeof getCachedExtensionHostRegistry; activateRegistry?: typeof activateExtensionHostRegistry; -}) { +}): ExtensionHostLoaderPreflightResult { const applyTestDefaults = params.applyTestDefaults ?? applyTestPluginDefaults; const normalizeConfig = params.normalizeConfig ?? normalizePluginsConfig; const buildCacheKey = params.buildCacheKey ?? buildExtensionHostRegistryCacheKey;