diff --git a/scripts/lib/vitest-worker-build-entries.mts b/scripts/lib/vitest-worker-build-entries.mts index 610f685a7520..e94c73c9396d 100644 --- a/scripts/lib/vitest-worker-build-entries.mts +++ b/scripts/lib/vitest-worker-build-entries.mts @@ -20,7 +20,10 @@ import { triageMaintenanceRuntimeEntrypoints, } from "../../src/infra/triage-runtime.test-support.ts"; import { nodeHostConfigRuntimeEntrypoint } from "../../src/node-host/config-runtime.test-support.ts"; -import { publishedSdkBridgeEntrypoints } from "../../src/plugins/loader-sdk-bridge-artifacts.test-support.ts"; +import { + mcpProviderCatalogEntrypoint, + publishedSdkBridgeEntrypoints, +} from "../../src/plugins/loader-sdk-bridge-artifacts.test-support.ts"; import { persistenceRuntimeEntrypoint } from "../../src/skills/library/persistence-runtime.test-support.ts"; import { agentDatabaseHeldRuntimeEntrypoint, @@ -41,6 +44,7 @@ export const vitestWorkerBuildEntries = { codeModeDescriptionRetentionEntrypoint, ...cliCompactionBackendEntrypoints, ...publishedSdkBridgeEntrypoints, + mcpProviderCatalogEntrypoint, ...Object.values(cliRecoveryEntrypoints), ...Object.values(gatewayDirectStopEntrypoints), stateDirGatewayFixtureEntrypoint, diff --git a/src/mcp/plugin-tools-registration.test.ts b/src/mcp/plugin-tools-registration.test.ts index 53e50fb66d71..8f0b24a18eed 100644 --- a/src/mcp/plugin-tools-registration.test.ts +++ b/src/mcp/plugin-tools-registration.test.ts @@ -3,14 +3,18 @@ import path from "node:path"; import type { DatabaseSync } from "node:sqlite"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; -import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { createTempDirTracker } from "../../test/helpers/temp-dir.js"; +import { createCompiledSdkHost } from "../plugins/compiled-sdk-host.test-support.js"; import { LegacyPluginSdkResourceHost } from "../plugins/legacy-sdk-resource-host.js"; +import { mcpProviderCatalogEntrypoint } from "../plugins/loader-sdk-bridge-artifacts.test-support.js"; import { cleanupPluginLoaderFixturesForTest, resetPluginLoaderTestStateForTest, useNoBundledPlugins, writePlugin, } from "../plugins/loader.test-fixtures.js"; +import { getPluginModuleLoaderStats } from "../plugins/plugin-module-loader-cache.js"; import { markPluginRegistryActive } from "../plugins/registry-lifecycle.js"; import { withPluginRuntimeRegistryScope } from "../plugins/runtime/gateway-request-scope.js"; import { acquireStandalonePluginToolRegistry } from "../plugins/tools.js"; @@ -19,6 +23,20 @@ import { createCodexSupervisionToolsMcpServer } from "./codex-supervision-tools- import { createToolsMcpServer, serveRegisteredToolsMcpServer } from "./tools-stdio-server.js"; let sequence = 0; +const sdkHostDirs = createTempDirTracker(); +let sdkHost: string | undefined; +let beforeLoad: ReturnType; +beforeAll(() => { + sdkHost = createCompiledSdkHost(mcpProviderCatalogEntrypoint, (prefix) => + sdkHostDirs.make(prefix), + ); +}); +beforeEach(() => { + if (sdkHost) { + vi.stubEnv("OPENCLAW_DEV_SOURCE_ROOT", sdkHost); + } + beforeLoad = getPluginModuleLoaderStats(); +}); function nativePlugin(options: { failDisposal?: boolean; abortSdk?: boolean } = {}) { useNoBundledPlugins(); const key = `__mcp_registration_native_${sequence++}`; @@ -117,10 +135,21 @@ module.exports = { id: "mcp-native", register(api) { } afterEach(() => { - vi.restoreAllMocks(); - resetPluginLoaderTestStateForTest(); + try { + if (sdkHost) { + const afterLoad = getPluginModuleLoaderStats(); + expect(afterLoad.nativeHits).toBeGreaterThan(beforeLoad.nativeHits); + expect(afterLoad.sourceTransformForced).toBe(beforeLoad.sourceTransformForced); + expect(afterLoad.sourceTransformFallbacks).toBe(beforeLoad.sourceTransformFallbacks); + } + } finally { + vi.restoreAllMocks(); + resetPluginLoaderTestStateForTest(); + vi.unstubAllEnvs(); + } }); afterAll(cleanupPluginLoaderFixturesForTest); +afterAll(sdkHostDirs.cleanup); function causes(error: unknown): unknown[] { if (error instanceof AggregateError) { diff --git a/src/plugins/compiled-sdk-host.test-support.ts b/src/plugins/compiled-sdk-host.test-support.ts new file mode 100644 index 000000000000..70d4cd148716 --- /dev/null +++ b/src/plugins/compiled-sdk-host.test-support.ts @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; + +/** Keep native SDK consumers on the invocation's verified, current-source graph. */ +export function createCompiledSdkHost( + entrypoint: Parameters[0], + makeTempDir: (prefix: string) => string, +): string | undefined { + const artifact = fileURLToPath(resolveRuntimeWorkerUrl(entrypoint)); + // Standalone and watch-mode Vitest deliberately retain source declarations. + if (path.extname(artifact) !== ".js") { + return undefined; + } + const hostRoot = makeTempDir("openclaw-sdk-host-"); + fs.cpSync(path.dirname(path.dirname(artifact)), path.join(hostRoot, "dist"), { + recursive: true, + }); + fs.copyFileSync( + path.resolve(import.meta.dirname, "../../package.json"), + path.join(hostRoot, "package.json"), + ); + fs.mkdirSync(path.join(hostRoot, "src")); + fs.mkdirSync(path.join(hostRoot, "extensions")); + fs.symlinkSync(path.resolve("node_modules"), path.join(hostRoot, "node_modules"), "junction"); + return hostRoot; +} diff --git a/src/plugins/loader-sdk-bridge-artifacts.test-support.ts b/src/plugins/loader-sdk-bridge-artifacts.test-support.ts index 276589d845cf..c7d70bf26f0b 100644 --- a/src/plugins/loader-sdk-bridge-artifacts.test-support.ts +++ b/src/plugins/loader-sdk-bridge-artifacts.test-support.ts @@ -1,5 +1,10 @@ // Published plugin compatibility uses the same compiled SDK graph as native execution. const currentModuleUrl = import.meta.url; +export const mcpProviderCatalogEntrypoint = { + currentModuleUrl, + sourceWorkerName: "../plugin-sdk/provider-catalog-runtime", + distWorkerPath: "plugin-sdk/provider-catalog-runtime.js", +} as const; export const publishedSdkBridgeEntrypoints = [ { currentModuleUrl, diff --git a/src/plugins/loader.native-module-loader.test.ts b/src/plugins/loader.native-module-loader.test.ts index f574f7b01dac..cb4d67fbe304 100644 --- a/src/plugins/loader.native-module-loader.test.ts +++ b/src/plugins/loader.native-module-loader.test.ts @@ -1,10 +1,9 @@ /** Verifies plugin loader behavior for native module loading and resolver hooks. */ import fs from "node:fs"; import path from "node:path"; -import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { createCompiledSdkHost } from "./compiled-sdk-host.test-support.js"; import { publishedSdkBridgeEntrypoints } from "./loader-sdk-bridge-artifacts.test-support.js"; import { loadOpenClawPlugins } from "./loader.js"; import { resetPluginCache } from "./plugin-cache.js"; @@ -199,20 +198,9 @@ describe("createPluginModuleLoader", () => { it("loads published pre-split SDK bridge imports (doctor repair, WhatsApp ack, Slack render)", () => { const pluginRoot = writePreSplitSdkBridgeConsumerFixture(); const [entrypoint] = publishedSdkBridgeEntrypoints; - const artifact = fileURLToPath(resolveRuntimeWorkerUrl(entrypoint)); - const hasCompiledSdk = path.extname(artifact) === ".js"; + const hostRoot = createCompiledSdkHost(entrypoint, (prefix) => tempDirs.make(prefix)); + const hasCompiledSdk = hostRoot !== undefined; if (hasCompiledSdk) { - const hostRoot = tempDirs.make("openclaw-sdk-bridge-host-"); - fs.cpSync(path.dirname(path.dirname(artifact)), path.join(hostRoot, "dist"), { - recursive: true, - }); - fs.copyFileSync( - path.resolve(import.meta.dirname, "../../package.json"), - path.join(hostRoot, "package.json"), - ); - fs.mkdirSync(path.join(hostRoot, "src")); - fs.mkdirSync(path.join(hostRoot, "extensions")); - fs.symlinkSync(path.resolve("node_modules"), path.join(hostRoot, "node_modules"), "junction"); vi.stubEnv("OPENCLAW_DEV_SOURCE_ROOT", hostRoot); vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", path.join(hostRoot, "extensions")); } else {