From f2f34e5f357261ce6ea22e655edfe5e894dcf4e2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 28 Apr 2026 19:42:50 +0100 Subject: [PATCH] fix: restore ci gates on main --- extensions/browser/src/browser/trash.test.ts | 47 +++++++++++++------ .../codex-app-server.extensions.test.ts | 29 ++++++++---- .../temp-plugin-extension-fixtures.ts | 16 ++++++- src/commands/doctor-plugin-manifests.test.ts | 20 ++++++-- src/commands/doctor-plugin-manifests.ts | 4 ++ src/gateway/gateway.test.ts | 4 ++ .../server.models-voicewake-misc.test.ts | 17 +++++-- src/gateway/test-helpers.server.ts | 2 + src/plugins/bundled-runtime-deps.test.ts | 19 +++++++- src/plugins/bundled-runtime-deps.ts | 8 ++++ src/plugins/effective-plugin-ids.ts | 5 +- 11 files changed, 136 insertions(+), 35 deletions(-) diff --git a/extensions/browser/src/browser/trash.test.ts b/extensions/browser/src/browser/trash.test.ts index 6335794dd9f..43884767b65 100644 --- a/extensions/browser/src/browser/trash.test.ts +++ b/extensions/browser/src/browser/trash.test.ts @@ -3,11 +3,16 @@ import os from "node:os"; import { beforeEach, describe, expect, it, vi } from "vitest"; const runExec = vi.hoisted(() => vi.fn()); +const resolvePreferredOpenClawTmpDirMock = vi.hoisted(() => vi.fn(() => "/tmp/openclaw")); vi.mock("../process/exec.js", () => ({ runExec, })); +vi.mock("openclaw/plugin-sdk/temp-path", () => ({ + resolvePreferredOpenClawTmpDir: resolvePreferredOpenClawTmpDirMock, +})); + function mockTrashContainer(...suffixes: string[]) { let call = 0; return vi.spyOn(fs, "mkdtempSync").mockImplementation((prefix) => { @@ -21,6 +26,8 @@ describe("browser trash", () => { beforeEach(() => { vi.restoreAllMocks(); runExec.mockReset(); + resolvePreferredOpenClawTmpDirMock.mockReset(); + resolvePreferredOpenClawTmpDirMock.mockReturnValue("/tmp/openclaw"); vi.spyOn(Date, "now").mockReturnValue(123); vi.spyOn(os, "homedir").mockReturnValue("/home/test"); vi.spyOn(os, "tmpdir").mockReturnValue("/tmp"); @@ -39,7 +46,7 @@ describe("browser trash", () => { const cpSync = vi.spyOn(fs, "cpSync"); const rmSync = vi.spyOn(fs, "rmSync"); - await expect(movePathToTrash("/tmp/demo")).resolves.toBe( + await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe( "/home/test/.Trash/demo-123-secure/demo", ); expect(runExec).not.toHaveBeenCalled(); @@ -48,7 +55,10 @@ describe("browser trash", () => { mode: 0o700, }); expect(mkdtempSync).toHaveBeenCalledWith("/home/test/.Trash/demo-123-"); - expect(renameSync).toHaveBeenCalledWith("/tmp/demo", "/home/test/.Trash/demo-123-secure/demo"); + expect(renameSync).toHaveBeenCalledWith( + "/tmp/openclaw/demo", + "/home/test/.Trash/demo-123-secure/demo", + ); expect(cpSync).not.toHaveBeenCalled(); expect(rmSync).not.toHaveBeenCalled(); }); @@ -69,12 +79,12 @@ describe("browser trash", () => { const mkdtempSync = mockTrashContainer("secure"); const renameSync = vi.spyOn(fs, "renameSync").mockImplementation(() => undefined); - await expect(movePathToTrash("/tmp/demo")).resolves.toBe( + await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe( "/real/home/test/.Trash/demo-123-secure/demo", ); expect(mkdtempSync).toHaveBeenCalledWith("/real/home/test/.Trash/demo-123-"); expect(renameSync).toHaveBeenCalledWith( - "/tmp/demo", + "/tmp/openclaw/demo", "/real/home/test/.Trash/demo-123-secure/demo", ); }); @@ -101,7 +111,7 @@ describe("browser trash", () => { isSymbolicLink: () => true, } as fs.Stats); - await expect(movePathToTrash("/tmp/demo")).rejects.toThrow( + await expect(movePathToTrash("/tmp/openclaw/demo")).rejects.toThrow( "Refusing to use non-directory/symlink trash directory", ); }); @@ -117,15 +127,22 @@ describe("browser trash", () => { const cpSync = vi.spyOn(fs, "cpSync").mockImplementation(() => undefined); const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined); - await expect(movePathToTrash("/tmp/demo")).resolves.toBe( + await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe( "/home/test/.Trash/demo-123-secure/demo", ); - expect(cpSync).toHaveBeenCalledWith("/tmp/demo", "/home/test/.Trash/demo-123-secure/demo", { + expect(cpSync).toHaveBeenCalledWith( + "/tmp/openclaw/demo", + "/home/test/.Trash/demo-123-secure/demo", + { + recursive: true, + force: false, + errorOnExist: true, + }, + ); + expect(rmSync).toHaveBeenCalledWith("/tmp/openclaw/demo", { recursive: true, force: false, - errorOnExist: true, }); - expect(rmSync).toHaveBeenCalledWith("/tmp/demo", { recursive: true, force: false }); }); it("retries copy fallback when the copy destination is created concurrently", async () => { @@ -147,12 +164,12 @@ describe("browser trash", () => { .mockImplementation(() => undefined); const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined); - await expect(movePathToTrash("/tmp/demo")).resolves.toBe( + await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe( "/home/test/.Trash/demo-123-second/demo", ); expect(cpSync).toHaveBeenNthCalledWith( 1, - "/tmp/demo", + "/tmp/openclaw/demo", "/home/test/.Trash/demo-123-first/demo", { recursive: true, @@ -162,7 +179,7 @@ describe("browser trash", () => { ); expect(cpSync).toHaveBeenNthCalledWith( 2, - "/tmp/demo", + "/tmp/openclaw/demo", "/home/test/.Trash/demo-123-second/demo", { recursive: true, @@ -186,17 +203,17 @@ describe("browser trash", () => { }) .mockImplementation(() => undefined); - await expect(movePathToTrash("/tmp/demo")).resolves.toBe( + await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe( "/home/test/.Trash/demo-123-second/demo", ); expect(renameSync).toHaveBeenNthCalledWith( 1, - "/tmp/demo", + "/tmp/openclaw/demo", "/home/test/.Trash/demo-123-first/demo", ); expect(renameSync).toHaveBeenNthCalledWith( 2, - "/tmp/demo", + "/tmp/openclaw/demo", "/home/test/.Trash/demo-123-second/demo", ); expect(Date.now).toHaveBeenCalledTimes(1); diff --git a/src/agents/codex-app-server.extensions.test.ts b/src/agents/codex-app-server.extensions.test.ts index b2348565c5e..bdb44b49d5d 100644 --- a/src/agents/codex-app-server.extensions.test.ts +++ b/src/agents/codex-app-server.extensions.test.ts @@ -1,3 +1,4 @@ +import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js"; import { @@ -15,20 +16,32 @@ import { } from "./test-helpers/temp-plugin-extension-fixtures.js"; const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; +const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; const tempDirs: string[] = []; function createTempDir(): string { return createTempPluginDir(tempDirs, "openclaw-codex-ext-"); } +function createBundledTempDir(): string { + delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; + return createTempPluginDir(tempDirs, "openclaw-codex-ext-", { + parentDir: path.join(process.cwd(), "dist-runtime", "extensions"), + }); +} + afterEach(() => { clearRuntimeConfigSnapshot(); - cleanupTempPluginTestEnvironment(tempDirs, originalBundledPluginsDir); + cleanupTempPluginTestEnvironment( + tempDirs, + originalBundledPluginsDir, + originalDisableBundledPlugins, + ); }); describe("agent tool result middleware", () => { it("includes plugin-registered middleware and restores it from cache", async () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -81,7 +94,7 @@ describe("agent tool result middleware", () => { }); it("rejects middleware when the manifest omits the runtime contract", () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -158,7 +171,7 @@ describe("agent tool result middleware", () => { }); it("merges runtimes when a plugin registers the same middleware function twice", () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -194,7 +207,7 @@ export default { id: "tool-result-middleware", register(api) { }); it("lazily loads bundled middleware owners from manifest contracts", async () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -246,7 +259,7 @@ export default { id: "tool-result-middleware", register(api) { describe("Codex app-server extension factories", () => { it("includes plugin-registered Codex app-server extension factories and restores them from cache", async () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -337,7 +350,7 @@ describe("Codex app-server extension factories", () => { }); it("rejects bundled plugins that omit the Codex app-server extension contract", () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ @@ -373,7 +386,7 @@ describe("Codex app-server extension factories", () => { }); it("rejects non-function Codex app-server extension factories from bundled plugins", () => { - const tmp = createTempDir(); + const tmp = createBundledTempDir(); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp; writeTempPlugin({ diff --git a/src/agents/test-helpers/temp-plugin-extension-fixtures.ts b/src/agents/test-helpers/temp-plugin-extension-fixtures.ts index 326a053634c..3a4027bee0c 100644 --- a/src/agents/test-helpers/temp-plugin-extension-fixtures.ts +++ b/src/agents/test-helpers/temp-plugin-extension-fixtures.ts @@ -7,8 +7,14 @@ import { setActivePluginRegistry } from "../../plugins/runtime.js"; const EMPTY_PLUGIN_SCHEMA = { type: "object", additionalProperties: false, properties: {} }; -export function createTempPluginDir(tempDirs: string[], prefix: string): string { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +export function createTempPluginDir( + tempDirs: string[], + prefix: string, + options?: { parentDir?: string }, +): string { + const parentDir = options?.parentDir ?? os.tmpdir(); + fs.mkdirSync(parentDir, { recursive: true }); + const dir = fs.mkdtempSync(path.join(parentDir, prefix)); tempDirs.push(dir); return dir; } @@ -43,6 +49,7 @@ export function writeTempPlugin(params: { export function cleanupTempPluginTestEnvironment( tempDirs: string[], originalBundledPluginsDir: string | undefined, + originalDisableBundledPlugins?: string, ) { for (const dir of tempDirs.splice(0)) { fs.rmSync(dir, { recursive: true, force: true }); @@ -54,6 +61,11 @@ export function cleanupTempPluginTestEnvironment( } else { process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir; } + if (originalDisableBundledPlugins === undefined) { + delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; + } else { + process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = originalDisableBundledPlugins; + } } export function resetActivePluginRegistryForTest() { diff --git a/src/commands/doctor-plugin-manifests.test.ts b/src/commands/doctor-plugin-manifests.test.ts index fe579b998bc..2459a9ad0a6 100644 --- a/src/commands/doctor-plugin-manifests.test.ts +++ b/src/commands/doctor-plugin-manifests.test.ts @@ -15,6 +15,20 @@ function makeTempDir() { return makeTrackedTempDir("openclaw-doctor-plugin-manifests", tempDirs); } +function makePluginWorkspace() { + const workspaceDir = makeTempDir(); + return { + workspaceDir, + pluginsRoot: path.join(workspaceDir, ".openclaw", "extensions"), + env: { + ...process.env, + OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1", + OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", + OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1", + }, + }; +} + function writeManifest(dir: string, manifest: Record) { fs.writeFileSync( path.join(dir, "openclaw.plugin.json"), @@ -76,7 +90,7 @@ describe("doctor plugin manifest legacy contract repair", () => { }); it("collects legacy top-level capability keys for migration", () => { - const pluginsRoot = makeTempDir(); + const { pluginsRoot } = makePluginWorkspace(); const root = path.join(pluginsRoot, "openai"); fs.mkdirSync(root, { recursive: true }); writePackageJson(root); @@ -101,7 +115,7 @@ describe("doctor plugin manifest legacy contract repair", () => { }); it("rewrites legacy top-level capability keys into contracts", async () => { - const pluginsRoot = makeTempDir(); + const { pluginsRoot } = makePluginWorkspace(); const root = path.join(pluginsRoot, "openai"); fs.mkdirSync(root, { recursive: true }); writePackageJson(root); @@ -141,7 +155,7 @@ describe("doctor plugin manifest legacy contract repair", () => { }); it("ignores non-object contracts payloads when collecting migrations", () => { - const pluginsRoot = makeTempDir(); + const { pluginsRoot } = makePluginWorkspace(); const root = path.join(pluginsRoot, "openai"); fs.mkdirSync(root, { recursive: true }); writePackageJson(root); diff --git a/src/commands/doctor-plugin-manifests.ts b/src/commands/doctor-plugin-manifests.ts index 7eb088eeb31..fd55025337e 100644 --- a/src/commands/doctor-plugin-manifests.ts +++ b/src/commands/doctor-plugin-manifests.ts @@ -83,6 +83,7 @@ function buildLegacyManifestContractMigration(params: { export function collectLegacyPluginManifestContractMigrations(params?: { env?: NodeJS.ProcessEnv; manifestRoots?: string[]; + workspaceDir?: string; }): LegacyManifestContractMigration[] { const seen = new Set(); const migrations: LegacyManifestContractMigration[] = []; @@ -114,6 +115,7 @@ export function collectLegacyPluginManifestContractMigrations(params?: { for (const plugin of loadPluginManifestRegistry({ cache: false, ...(params?.env ? { env: params.env } : {}), + ...(params?.workspaceDir ? { workspaceDir: params.workspaceDir } : {}), }).plugins) { if (seen.has(plugin.manifestPath)) { continue; @@ -138,6 +140,7 @@ export function collectLegacyPluginManifestContractMigrations(params?: { export async function maybeRepairLegacyPluginManifestContracts(params: { env?: NodeJS.ProcessEnv; manifestRoots?: string[]; + workspaceDir?: string; runtime: RuntimeEnv; prompter: DoctorPrompter; note?: typeof note; @@ -145,6 +148,7 @@ export async function maybeRepairLegacyPluginManifestContracts(params: { const migrations = collectLegacyPluginManifestContractMigrations({ ...(params.env ? { env: params.env } : {}), ...(params.manifestRoots ? { manifestRoots: params.manifestRoots } : {}), + ...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}), }); if (migrations.length === 0) { return; diff --git a/src/gateway/gateway.test.ts b/src/gateway/gateway.test.ts index 2f1af51f05c..6040dd8d3b4 100644 --- a/src/gateway/gateway.test.ts +++ b/src/gateway/gateway.test.ts @@ -35,6 +35,7 @@ const GATEWAY_TEST_ENV_KEYS = [ "OPENCLAW_SKIP_BROWSER_CONTROL_SERVER", "OPENCLAW_SKIP_PROVIDERS", "OPENCLAW_BUNDLED_PLUGINS_DIR", + "OPENCLAW_DISABLE_BUNDLED_PLUGINS", ] as const; function nextGatewayId(prefix: string): string { @@ -110,6 +111,7 @@ async function setupGatewayTempHome(params: { prefix: string; minimalGateway?: b const workspaceDir = path.join(tempHome, "openclaw"); await fs.mkdir(workspaceDir, { recursive: true }); process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = await createEmptyBundledPluginsDir(tempHome); + process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1"; return { envSnapshot, tempHome, workspaceDir }; } @@ -318,6 +320,7 @@ module.exports = { "OPENCLAW_SKIP_BROWSER_CONTROL_SERVER", "OPENCLAW_SKIP_PROVIDERS", "OPENCLAW_BUNDLED_PLUGINS_DIR", + "OPENCLAW_DISABLE_BUNDLED_PLUGINS", "OPENCLAW_TEST_MINIMAL_GATEWAY", ]); @@ -333,6 +336,7 @@ module.exports = { const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-wizard-home-")); process.env.HOME = tempHome; process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = await createEmptyBundledPluginsDir(tempHome); + process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1"; delete process.env.OPENCLAW_STATE_DIR; delete process.env.OPENCLAW_CONFIG_PATH; diff --git a/src/gateway/server.models-voicewake-misc.test.ts b/src/gateway/server.models-voicewake-misc.test.ts index 3ed60eb50c9..c53c87d2062 100644 --- a/src/gateway/server.models-voicewake-misc.test.ts +++ b/src/gateway/server.models-voicewake-misc.test.ts @@ -815,11 +815,18 @@ describe("gateway server misc", () => { "utf-8", ); - await withEnvAsync({ OPENCLAW_TEST_MINIMAL_GATEWAY: undefined }, async () => { - const autoPort = await getFreePort(); - const autoServer = await startGatewayServer(autoPort); - await autoServer.close(); - }); + await withEnvAsync( + { + OPENCLAW_TEST_MINIMAL_GATEWAY: undefined, + OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined, + OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"), + }, + async () => { + const autoPort = await getFreePort(); + const autoServer = await startGatewayServer(autoPort); + await autoServer.close(); + }, + ); const updated = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record; const channels = updated.channels as Record | undefined; diff --git a/src/gateway/test-helpers.server.ts b/src/gateway/test-helpers.server.ts index e3002bbf38d..0f8ac192aa7 100644 --- a/src/gateway/test-helpers.server.ts +++ b/src/gateway/test-helpers.server.ts @@ -70,6 +70,7 @@ const GATEWAY_TEST_ENV_KEYS = [ "OPENCLAW_SKIP_GMAIL_WATCHER", "OPENCLAW_SKIP_CANVAS_HOST", "OPENCLAW_BUNDLED_PLUGINS_DIR", + "OPENCLAW_DISABLE_BUNDLED_PLUGINS", "OPENCLAW_SKIP_CHANNELS", "OPENCLAW_SKIP_PROVIDERS", "OPENCLAW_SKIP_CRON", @@ -235,6 +236,7 @@ function applyGatewaySkipEnv() { process.env.OPENCLAW_SKIP_PROVIDERS = "1"; process.env.OPENCLAW_SKIP_CRON = "1"; process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1"; + process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1"; process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tempHome ? path.join(tempHome, "openclaw-test-no-bundled-extensions") : "openclaw-test-no-bundled-extensions"; diff --git a/src/plugins/bundled-runtime-deps.test.ts b/src/plugins/bundled-runtime-deps.test.ts index 5f2c678d67f..552a8adcb11 100644 --- a/src/plugins/bundled-runtime-deps.test.ts +++ b/src/plugins/bundled-runtime-deps.test.ts @@ -203,6 +203,23 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => { }); }); + it("uses the Node-adjacent POSIX npm shim when npm-cli.js is unavailable", () => { + const execPath = "/opt/node/bin/node"; + const npmPath = "/opt/node/bin/npm"; + const runner = resolveBundledRuntimeDepsNpmRunner({ + env: {}, + execPath, + existsSync: (candidate) => candidate === npmPath, + npmArgs: ["install", "acpx@0.5.3"], + platform: "linux", + }); + + expect(runner).toEqual({ + command: npmPath, + args: ["install", "acpx@0.5.3"], + }); + }); + it("refuses Windows shell fallback when no safe npm executable is available", () => { expect(() => resolveBundledRuntimeDepsNpmRunner({ @@ -222,7 +239,7 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => { PATH: "/repo/evil/bin:/usr/bin:/bin", }, execPath: "/opt/node/bin/node", - existsSync: (candidate) => candidate === "/opt/node/bin/npm", + existsSync: (candidate) => candidate === "/usr/bin/npm", npmArgs: ["install"], platform: "linux", }), diff --git a/src/plugins/bundled-runtime-deps.ts b/src/plugins/bundled-runtime-deps.ts index 94b2f81a744..771e7ffd35b 100644 --- a/src/plugins/bundled-runtime-deps.ts +++ b/src/plugins/bundled-runtime-deps.ts @@ -1325,6 +1325,14 @@ export function resolveBundledRuntimeDepsNpmRunner(params: { throw new Error("Unable to resolve a safe npm executable on Windows"); } + const npmExePath = pathImpl.resolve(nodeDir, "npm"); + if (existsSync(npmExePath)) { + return { + command: npmExePath, + args: params.npmArgs, + }; + } + throw new Error("Unable to resolve a safe npm executable"); } type BundledPluginRuntimeDepsManifest = { diff --git a/src/plugins/effective-plugin-ids.ts b/src/plugins/effective-plugin-ids.ts index f41287467fc..3eb1c359d8b 100644 --- a/src/plugins/effective-plugin-ids.ts +++ b/src/plugins/effective-plugin-ids.ts @@ -45,6 +45,7 @@ function collectBundledChannelOwnerPluginIds(params: { config: OpenClawConfig; channelIds: readonly string[]; env: NodeJS.ProcessEnv; + bundledPluginsDir?: string; }): string[] { const plugins = normalizePluginsConfig(params.config.plugins); const channelIds = new Set( @@ -55,7 +56,7 @@ function collectBundledChannelOwnerPluginIds(params: { if (channelIds.size === 0) { return []; } - const bundledDir = resolveBundledPluginsDir(params.env); + const bundledDir = params.bundledPluginsDir ?? resolveBundledPluginsDir(params.env); if (!bundledDir) { return []; } @@ -126,6 +127,7 @@ export function resolveEffectivePluginIds(params: { config: OpenClawConfig; env: NodeJS.ProcessEnv; workspaceDir?: string; + bundledPluginsDir?: string; }): string[] { const autoEnabled = applyPluginAutoEnable({ config: params.config, @@ -150,6 +152,7 @@ export function resolveEffectivePluginIds(params: { config: effectiveConfig, channelIds: configuredChannelIds, env: params.env, + ...(params.bundledPluginsDir ? { bundledPluginsDir: params.bundledPluginsDir } : {}), })) { ids.add(pluginId); }