mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 08:50:21 +00:00
Plugins: extract loader preflight
This commit is contained in:
@@ -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 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 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. |
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { activateExtensionHostRegistry } from "../extension-host/activation.js";
|
||||
import {
|
||||
buildExtensionHostRegistryCacheKey,
|
||||
clearExtensionHostRegistryCache,
|
||||
getCachedExtensionHostRegistry,
|
||||
setCachedExtensionHostRegistry,
|
||||
} from "../extension-host/loader-cache.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 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 { prepareExtensionHostLoaderPreflight } from "./loader-preflight.js";
|
||||
import { runExtensionHostLoaderSession } from "./loader-run.js";
|
||||
|
||||
export type ExtensionHostPluginLoadOptions = {
|
||||
@@ -39,39 +37,23 @@ export function clearExtensionHostLoaderState(): void {
|
||||
export function loadExtensionHostPluginRegistry(
|
||||
options: ExtensionHostPluginLoadOptions = {},
|
||||
): PluginRegistry {
|
||||
const env = options.env ?? process.env;
|
||||
// Test env: default-disable plugins unless explicitly configured.
|
||||
// This keeps unit/gateway suites fast and avoids loading heavyweight plugin deps by accident.
|
||||
const cfg = applyTestPluginDefaults(options.config ?? {}, env);
|
||||
const logger = options.logger ?? defaultLogger();
|
||||
const validateOnly = options.mode === "validate";
|
||||
const normalized = normalizePluginsConfig(cfg.plugins);
|
||||
const cacheKey = buildExtensionHostRegistryCacheKey({
|
||||
workspaceDir: options.workspaceDir,
|
||||
plugins: normalized,
|
||||
installs: cfg.plugins?.installs,
|
||||
env,
|
||||
const preflight = prepareExtensionHostLoaderPreflight({
|
||||
options,
|
||||
createDefaultLogger: defaultLogger,
|
||||
clearPluginCommands,
|
||||
});
|
||||
const cacheEnabled = options.cache !== false;
|
||||
if (cacheEnabled) {
|
||||
const cached = getCachedExtensionHostRegistry(cacheKey);
|
||||
if (cached) {
|
||||
activateExtensionHostRegistry(cached, cacheKey);
|
||||
return cached;
|
||||
}
|
||||
if (preflight.cacheHit) {
|
||||
return preflight.registry;
|
||||
}
|
||||
|
||||
// Clear previously registered plugin commands before reloading.
|
||||
clearPluginCommands();
|
||||
|
||||
const execution = prepareExtensionHostLoaderExecution({
|
||||
config: cfg,
|
||||
config: preflight.config,
|
||||
workspaceDir: options.workspaceDir,
|
||||
env,
|
||||
env: preflight.env,
|
||||
cache: options.cache,
|
||||
cacheKey,
|
||||
normalizedConfig: normalized,
|
||||
logger,
|
||||
cacheKey: preflight.cacheKey,
|
||||
normalizedConfig: preflight.normalizedConfig,
|
||||
logger: preflight.logger,
|
||||
coreGatewayHandlers: options.coreGatewayHandlers as Record<string, GatewayRequestHandler>,
|
||||
runtimeOptions: options.runtimeOptions,
|
||||
warningCache: openAllowlistWarningCache,
|
||||
@@ -84,9 +66,9 @@ export function loadExtensionHostPluginRegistry(
|
||||
session: execution.session,
|
||||
orderedCandidates: execution.orderedCandidates,
|
||||
manifestByRoot: execution.manifestByRoot,
|
||||
normalizedConfig: normalized,
|
||||
rootConfig: cfg,
|
||||
validateOnly,
|
||||
normalizedConfig: preflight.normalizedConfig,
|
||||
rootConfig: preflight.config,
|
||||
validateOnly: preflight.validateOnly,
|
||||
createApi: execution.createApi,
|
||||
loadModule: execution.loadModule,
|
||||
});
|
||||
|
||||
72
src/extension-host/loader-preflight.test.ts
Normal file
72
src/extension-host/loader-preflight.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { prepareExtensionHostLoaderPreflight } from "./loader-preflight.js";
|
||||
|
||||
describe("extension host loader preflight", () => {
|
||||
it("returns a cache hit without clearing commands", () => {
|
||||
const registry = { plugins: [] } as never;
|
||||
const clearPluginCommands = vi.fn();
|
||||
const activateRegistry = vi.fn();
|
||||
|
||||
const result = prepareExtensionHostLoaderPreflight({
|
||||
options: {
|
||||
env: { TEST: "1" },
|
||||
},
|
||||
createDefaultLogger: vi.fn(() => ({ info() {}, warn() {}, error() {} })) as never,
|
||||
clearPluginCommands,
|
||||
applyTestDefaults: vi.fn((config) => config) as never,
|
||||
normalizeConfig: vi.fn(() => ({ installs: [], entries: {}, slots: {} })) as never,
|
||||
buildCacheKey: vi.fn(() => "cache-key") as never,
|
||||
getCachedRegistry: vi.fn(() => registry) as never,
|
||||
activateRegistry: activateRegistry as never,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
cacheHit: true,
|
||||
registry,
|
||||
});
|
||||
expect(activateRegistry).toHaveBeenCalledWith(registry, "cache-key");
|
||||
expect(clearPluginCommands).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("normalizes inputs and clears commands on a cache miss", () => {
|
||||
const clearPluginCommands = vi.fn();
|
||||
const logger = { info() {}, warn() {}, error() {} };
|
||||
|
||||
const result = prepareExtensionHostLoaderPreflight({
|
||||
options: {
|
||||
config: { plugins: { enabled: true } },
|
||||
workspaceDir: "/workspace",
|
||||
env: { TEST: "1" },
|
||||
mode: "validate",
|
||||
},
|
||||
createDefaultLogger: vi.fn(() => logger) as never,
|
||||
clearPluginCommands,
|
||||
applyTestDefaults: vi.fn((config) => ({
|
||||
...config,
|
||||
plugins: { ...config.plugins, allow: ["demo"] },
|
||||
})) as never,
|
||||
normalizeConfig: vi.fn(() => ({
|
||||
enabled: true,
|
||||
allow: ["demo"],
|
||||
loadPaths: [],
|
||||
entries: {},
|
||||
slots: {},
|
||||
})) as never,
|
||||
buildCacheKey: vi.fn(() => "cache-key") as never,
|
||||
getCachedRegistry: vi.fn(() => null) as never,
|
||||
activateRegistry: vi.fn() as never,
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
cacheHit: false,
|
||||
env: { TEST: "1" },
|
||||
logger,
|
||||
validateOnly: true,
|
||||
cacheKey: "cache-key",
|
||||
normalizedConfig: {
|
||||
allow: ["demo"],
|
||||
},
|
||||
});
|
||||
expect(clearPluginCommands).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
75
src/extension-host/loader-preflight.ts
Normal file
75
src/extension-host/loader-preflight.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { applyTestPluginDefaults, normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import type { PluginLogger } from "../plugins/types.js";
|
||||
import { activateExtensionHostRegistry } from "./activation.js";
|
||||
import {
|
||||
buildExtensionHostRegistryCacheKey,
|
||||
getCachedExtensionHostRegistry,
|
||||
} from "./loader-cache.js";
|
||||
|
||||
export type ExtensionHostPluginLoadMode = "full" | "validate";
|
||||
|
||||
export type ExtensionHostLoaderPreflightOptions = {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
logger?: PluginLogger;
|
||||
cache?: boolean;
|
||||
mode?: ExtensionHostPluginLoadMode;
|
||||
};
|
||||
|
||||
export function prepareExtensionHostLoaderPreflight(params: {
|
||||
options: ExtensionHostLoaderPreflightOptions;
|
||||
createDefaultLogger: () => PluginLogger;
|
||||
clearPluginCommands: () => void;
|
||||
applyTestDefaults?: typeof applyTestPluginDefaults;
|
||||
normalizeConfig?: typeof normalizePluginsConfig;
|
||||
buildCacheKey?: typeof buildExtensionHostRegistryCacheKey;
|
||||
getCachedRegistry?: typeof getCachedExtensionHostRegistry;
|
||||
activateRegistry?: typeof activateExtensionHostRegistry;
|
||||
}) {
|
||||
const applyTestDefaults = params.applyTestDefaults ?? applyTestPluginDefaults;
|
||||
const normalizeConfig = params.normalizeConfig ?? normalizePluginsConfig;
|
||||
const buildCacheKey = params.buildCacheKey ?? buildExtensionHostRegistryCacheKey;
|
||||
const getCachedRegistry = params.getCachedRegistry ?? getCachedExtensionHostRegistry;
|
||||
const activateRegistry = params.activateRegistry ?? activateExtensionHostRegistry;
|
||||
|
||||
const env = params.options.env ?? process.env;
|
||||
// Test env: default-disable plugins unless explicitly configured.
|
||||
// This keeps unit/gateway suites fast and avoids loading heavyweight plugin deps by accident.
|
||||
const config = applyTestDefaults(params.options.config ?? {}, env);
|
||||
const logger = params.options.logger ?? params.createDefaultLogger();
|
||||
const validateOnly = params.options.mode === "validate";
|
||||
const normalizedConfig = normalizeConfig(config.plugins);
|
||||
const cacheKey = buildCacheKey({
|
||||
workspaceDir: params.options.workspaceDir,
|
||||
plugins: normalizedConfig,
|
||||
installs: config.plugins?.installs,
|
||||
env,
|
||||
});
|
||||
const cacheEnabled = params.options.cache !== false;
|
||||
|
||||
if (cacheEnabled) {
|
||||
const cachedRegistry = getCachedRegistry(cacheKey);
|
||||
if (cachedRegistry) {
|
||||
activateRegistry(cachedRegistry, cacheKey);
|
||||
return {
|
||||
cacheHit: true as const,
|
||||
registry: cachedRegistry,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Clear previously registered plugin commands before reloading.
|
||||
params.clearPluginCommands();
|
||||
|
||||
return {
|
||||
cacheHit: false as const,
|
||||
env,
|
||||
config,
|
||||
logger,
|
||||
validateOnly,
|
||||
normalizedConfig,
|
||||
cacheKey,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user