Plugins: extract loader pipeline

This commit is contained in:
Gustavo Madeira Santana 2026-03-15 16:46:13 +00:00
parent d6d28037db
commit a8b3b1c008
No known key found for this signature in database
5 changed files with 130 additions and 31 deletions

View File

@ -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. |

View File

@ -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<string, GatewayRequestHandler>;
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<string, GatewayRequestHandler>,
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,
});
}

View File

@ -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<string>(),
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);
});
});

View File

@ -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<string, GatewayRequestHandler>;
runtimeOptions?: CreatePluginRuntimeOptions;
warningCache: Set<string>;
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<string, GatewayRequestHandler>,
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,
});
}

View File

@ -18,6 +18,27 @@ export type ExtensionHostLoaderPreflightOptions = {
mode?: ExtensionHostPluginLoadMode;
};
export type ExtensionHostLoaderPreflightCacheHit = {
cacheHit: true;
registry: ReturnType<typeof getCachedExtensionHostRegistry> extends infer T
? Exclude<T, undefined>
: never;
};
export type ExtensionHostLoaderPreflightReady = {
cacheHit: false;
env: NodeJS.ProcessEnv;
config: OpenClawConfig;
logger: PluginLogger;
validateOnly: boolean;
normalizedConfig: ReturnType<typeof normalizePluginsConfig>;
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;