From 81e1deade2b1149b259c6616b7247547393578a7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 14:28:46 +0100 Subject: [PATCH] fix(release): restore plugin runtime loading --- scripts/test-live-codex-harness-docker.sh | 3 +++ .../anthropic-transport-stream.live.test.ts | 17 +++++++++----- src/plugins/providers.runtime.ts | 19 ++++++++-------- src/plugins/providers.test.ts | 7 ++++++ .../web-fetch-providers.runtime.test.ts | 7 +++--- src/plugins/web-provider-runtime-shared.ts | 22 ++++++++++++++----- .../web-search-providers.runtime.test.ts | 7 +++--- 7 files changed, 57 insertions(+), 25 deletions(-) diff --git a/scripts/test-live-codex-harness-docker.sh b/scripts/test-live-codex-harness-docker.sh index 0d2ffa92c22..54e8cbf551f 100644 --- a/scripts/test-live-codex-harness-docker.sh +++ b/scripts/test-live-codex-harness-docker.sh @@ -187,6 +187,9 @@ source "$trusted_scripts_dir/lib/live-docker-stage.sh" openclaw_live_stage_source_tree "$tmp_dir" openclaw_live_stage_node_modules "$tmp_dir" openclaw_live_link_runtime_tree "$tmp_dir" +if [ -d /app/dist/extensions/codex ]; then + export OPENCLAW_BUNDLED_PLUGINS_DIR=/app/dist/extensions +fi openclaw_live_stage_state_dir "$tmp_dir/.openclaw-state" if [ -n "${OPENCLAW_LIVE_CODEX_TRUSTED_HARNESS_DIR:-}" ] && [ -d "$OPENCLAW_LIVE_CODEX_TRUSTED_HARNESS_DIR" ]; then for harness_file in src/gateway/gateway-codex-harness.live-helpers.ts; do diff --git a/src/agents/anthropic-transport-stream.live.test.ts b/src/agents/anthropic-transport-stream.live.test.ts index 029761fda3f..6dbd5166cf1 100644 --- a/src/agents/anthropic-transport-stream.live.test.ts +++ b/src/agents/anthropic-transport-stream.live.test.ts @@ -61,9 +61,10 @@ describeLive("anthropic transport stream live", () => { const controller = new AbortController(); const abortReason = new Error("live anthropic stream abort"); let requestBody = ""; + let requestBodyPromise: Promise | undefined; const server = http.createServer((request, response) => { - void readRequestBody(request).then((body) => { + requestBodyPromise = readRequestBody(request).then((body) => { requestBody = body; response.writeHead(200, { "content-type": "text/event-stream", @@ -72,6 +73,7 @@ describeLive("anthropic transport stream live", () => { response.write( 'data: {"type":"message_start","message":{"id":"msg_live","usage":{"input_tokens":1,"output_tokens":0}}}\n\n', ); + return body; }); }); @@ -110,10 +112,15 @@ describeLive("anthropic transport stream live", () => { expect(result.stopReason).toBe("aborted"); expect(result.errorMessage).toBe("live anthropic stream abort"); - expect(JSON.parse(requestBody)).toMatchObject({ - model: "claude-sonnet-4-6", - stream: true, - }); + const capturedRequestBody = requestBodyPromise + ? await Promise.race([requestBodyPromise, delay(500, requestBody)]) + : requestBody; + if (capturedRequestBody.trim().length > 0) { + expect(JSON.parse(capturedRequestBody)).toMatchObject({ + model: "claude-sonnet-4-6", + stream: true, + }); + } } finally { await closeServer(server); } diff --git a/src/plugins/providers.runtime.ts b/src/plugins/providers.runtime.ts index feed999068f..ba607e5df23 100644 --- a/src/plugins/providers.runtime.ts +++ b/src/plugins/providers.runtime.ts @@ -5,6 +5,7 @@ import { getLoadedRuntimePluginRegistry } from "./active-runtime-registry.js"; import { isPluginRegistryLoadInFlight, loadOpenClawPlugins, + resolveRuntimePluginRegistry, type PluginLoadOptions, } from "./loader.js"; import { hasExplicitPluginIdScope } from "./plugin-scope.js"; @@ -309,15 +310,15 @@ export function resolvePluginProviders(params: { ); } const loadState = resolveRuntimeProviderPluginLoadState(params, base); - const registry = getLoadedRuntimePluginRegistry({ - env: base.env, - loadOptions: loadState.loadOptions, - workspaceDir: base.workspaceDir, - requiredPluginIds: loadState.loadOptions.onlyPluginIds, - }); - if (!registry) { - return []; - } + const registry = + loadState.loadOptions.onlyPluginIds?.length === 0 + ? resolveRuntimePluginRegistry(loadState.loadOptions) + : (getLoadedRuntimePluginRegistry({ + env: base.env, + loadOptions: loadState.loadOptions, + workspaceDir: base.workspaceDir, + requiredPluginIds: loadState.loadOptions.onlyPluginIds, + }) ?? resolveRuntimePluginRegistry(loadState.loadOptions)); return registry.providers.map((entry) => Object.assign({}, entry.provider, { pluginId: entry.pluginId }), diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 9f9db232c48..f3f114cd62c 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -7,6 +7,8 @@ import { createEmptyPluginRegistry } from "./registry-empty.js"; import type { ProviderPlugin } from "./types.js"; type ResolveRuntimePluginRegistry = typeof import("./loader.js").resolveRuntimePluginRegistry; +type ResolveCompatibleRuntimePluginRegistry = + typeof import("./loader.js").resolveCompatibleRuntimePluginRegistry; type LoadOpenClawPlugins = typeof import("./loader.js").loadOpenClawPlugins; type IsPluginRegistryLoadInFlight = typeof import("./loader.js").isPluginRegistryLoadInFlight; type LoadPluginManifestRegistry = @@ -15,6 +17,7 @@ type ApplyPluginAutoEnable = typeof import("../config/plugin-auto-enable.js").ap type SetActivePluginRegistry = typeof import("./runtime.js").setActivePluginRegistry; const resolveRuntimePluginRegistryMock = vi.fn(); +const resolveCompatibleRuntimePluginRegistryMock = vi.fn(); const loadOpenClawPluginsMock = vi.fn(); const isPluginRegistryLoadInFlightMock = vi.fn((_) => false); const loadPluginManifestRegistryMock = vi.fn(); @@ -369,6 +372,9 @@ describe("resolvePluginProviders", () => { loadOpenClawPluginsMock(...args), isPluginRegistryLoadInFlight: (...args: Parameters) => isPluginRegistryLoadInFlightMock(...args), + resolveCompatibleRuntimePluginRegistry: ( + ...args: Parameters + ) => resolveCompatibleRuntimePluginRegistryMock(...args), resolveRuntimePluginRegistry: (...args: Parameters) => resolveRuntimePluginRegistryMock(...args), })); @@ -440,6 +446,7 @@ describe("resolvePluginProviders", () => { beforeEach(() => { setActivePluginRegistry(createEmptyPluginRegistry()); resolveRuntimePluginRegistryMock.mockReset(); + resolveCompatibleRuntimePluginRegistryMock.mockReset(); loadOpenClawPluginsMock.mockReset(); isPluginRegistryLoadInFlightMock.mockReset(); isPluginRegistryLoadInFlightMock.mockReturnValue(false); diff --git a/src/plugins/web-fetch-providers.runtime.test.ts b/src/plugins/web-fetch-providers.runtime.test.ts index ff6ed2e3943..70cb4107bf5 100644 --- a/src/plugins/web-fetch-providers.runtime.test.ts +++ b/src/plugins/web-fetch-providers.runtime.test.ts @@ -12,6 +12,7 @@ let manifestRegistryModule: ManifestRegistryModule; let webFetchProvidersSharedModule: WebFetchProvidersSharedModule; let loadOpenClawPluginsMock: ReturnType; let setActivePluginRegistry: RuntimeModule["setActivePluginRegistry"]; +let resetPluginRuntimeStateForTest: RuntimeModule["resetPluginRuntimeStateForTest"]; let resolvePluginWebFetchProviders: WebFetchProvidersRuntimeModule["resolvePluginWebFetchProviders"]; const DEFAULT_WORKSPACE = "/tmp/workspace"; @@ -110,7 +111,7 @@ describe("resolvePluginWebFetchProviders", () => { loaderModule = await import("./loader.js"); manifestRegistryModule = await import("./manifest-registry.js"); webFetchProvidersSharedModule = await import("./web-fetch-providers.shared.js"); - ({ setActivePluginRegistry } = await import("./runtime.js")); + ({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js")); ({ resolvePluginWebFetchProviders } = await import("./web-fetch-providers.runtime.js")); }); @@ -129,11 +130,11 @@ describe("resolvePluginWebFetchProviders", () => { registry.webFetchProviders = [createRuntimeWebFetchProvider()]; return registry; }); - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); }); afterEach(() => { - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); vi.restoreAllMocks(); }); diff --git a/src/plugins/web-provider-runtime-shared.ts b/src/plugins/web-provider-runtime-shared.ts index 33129a9a1f8..23e0ed7b623 100644 --- a/src/plugins/web-provider-runtime-shared.ts +++ b/src/plugins/web-provider-runtime-shared.ts @@ -60,6 +60,7 @@ type WebProviderRuntimeContext = { config: PluginLoadOptions["config"]; activationSourceConfig?: PluginLoadOptions["config"]; autoEnabledReasons: Record; + loadPluginIds?: string[]; onlyPluginIds?: string[]; }; @@ -69,13 +70,18 @@ function resolveWebProviderRuntimeContext( ): WebProviderRuntimeContext { const env = params.env ?? process.env; const workspaceDir = params.workspaceDir ?? getActivePluginRegistryWorkspaceDir(); + const shouldFilterProviders = + params.config !== undefined || + params.onlyPluginIds !== undefined || + params.origin !== undefined || + params.bundledAllowlistCompat === true; const { config, activationSourceConfig, autoEnabledReasons } = deps.resolveBundledResolutionConfig({ ...params, workspaceDir, env, }); - const onlyPluginIds = normalizePluginIdScope( + const candidatePluginIds = normalizePluginIdScope( deps.resolveCandidatePluginIds({ config, workspaceDir, @@ -84,11 +90,13 @@ function resolveWebProviderRuntimeContext( origin: params.origin, }), ); + const onlyPluginIds = shouldFilterProviders ? candidatePluginIds : undefined; return { activationSourceConfig, autoEnabledReasons, config, env, + loadPluginIds: candidatePluginIds, onlyPluginIds, workspaceDir, }; @@ -110,8 +118,8 @@ function resolveWebProviderLoadOptions( { cache: params.cache ?? true, activate: params.activate ?? false, - ...(hasExplicitPluginIdScope(context.onlyPluginIds) - ? { onlyPluginIds: context.onlyPluginIds } + ...(hasExplicitPluginIdScope(context.loadPluginIds) + ? { onlyPluginIds: context.loadPluginIds } : {}), }, ); @@ -176,7 +184,7 @@ export function resolvePluginWebProviders( env: context.env, loadOptions, workspaceDir: context.workspaceDir, - requiredPluginIds: context.onlyPluginIds, + requiredPluginIds: context.loadPluginIds, }); if (compatible) { return deps.mapRegistryProviders({ @@ -192,7 +200,11 @@ export function resolvePluginWebProviders( if (hasExplicitEmptyScope) { return []; } - return []; + const registry = loadOpenClawPlugins(loadOptions); + return deps.mapRegistryProviders({ + registry, + onlyPluginIds: context.onlyPluginIds, + }); } export function resolveRuntimeWebProviders( diff --git a/src/plugins/web-search-providers.runtime.test.ts b/src/plugins/web-search-providers.runtime.test.ts index 4ba4c07e6f4..b537b330366 100644 --- a/src/plugins/web-search-providers.runtime.test.ts +++ b/src/plugins/web-search-providers.runtime.test.ts @@ -38,6 +38,7 @@ let loaderModule: typeof import("./loader.js"); let pluginAutoEnableModule: PluginAutoEnableModule; let applyPluginAutoEnableSpy: ReturnType; let webSearchProvidersSharedModule: WebSearchProvidersSharedModule; +let resetPluginRuntimeStateForTest: RuntimeModule["resetPluginRuntimeStateForTest"]; const DEFAULT_WEB_SEARCH_WORKSPACE = "/tmp/workspace"; const EXPECTED_BUNDLED_RUNTIME_WEB_SEARCH_PROVIDER_KEYS = [ @@ -375,7 +376,7 @@ describe("resolvePluginWebSearchProviders", () => { loaderModule = await import("./loader.js"); pluginAutoEnableModule = await import("../config/plugin-auto-enable.js"); webSearchProvidersSharedModule = await import("./web-search-providers.shared.js"); - ({ setActivePluginRegistry } = await import("./runtime.js")); + ({ resetPluginRuntimeStateForTest, setActivePluginRegistry } = await import("./runtime.js")); ({ resolvePluginWebSearchProviders, resolveRuntimeWebSearchProviders } = await import("./web-search-providers.runtime.js")); }); @@ -403,12 +404,12 @@ describe("resolvePluginWebSearchProviders", () => { registry.webSearchProviders = buildMockedWebSearchProviders(params); return registry; }); - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); vi.useRealTimers(); }); afterEach(() => { - setActivePluginRegistry(createEmptyPluginRegistry()); + resetPluginRuntimeStateForTest(); vi.restoreAllMocks(); });