From 112dedd0939b8a4972c49686f626124c68c36364 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 21:55:27 +0100 Subject: [PATCH] refactor: remove plugin dependency cleanup leftovers --- docs/tools/plugin.md | 2 +- extensions/acpx/src/codex-auth-bridge.ts | 2 +- .../mantle-anthropic.runtime.ts | 2 +- .../src/monitor/reply-delivery.test.ts | 2 +- .../src/memory/provider-adapters.ts | 4 +- extensions/memory-lancedb/index.test.ts | 150 +-------- .../memory-lancedb/lancedb-runtime.test.ts | 87 ------ extensions/memory-lancedb/lancedb-runtime.ts | 295 ++---------------- extensions/msteams/src/sdk.test.ts | 2 +- .../qa-lab/src/bundled-plugin-staging.ts | 17 +- extensions/qa-lab/src/gateway-child.test.ts | 51 --- scripts/check-gateway-watch-regression.mjs | 25 +- scripts/test-projects.test-support.mjs | 8 +- src/commands/channel-setup/plugin-install.ts | 5 +- src/commands/channels.add.test.ts | 4 +- src/commands/channels/add.ts | 2 +- src/flows/channel-setup.test.ts | 3 +- src/flows/channel-setup.ts | 5 +- src/plugins/bundled-dir.test.ts | 2 +- src/plugins/loader-channel-setup.ts | 13 +- .../check-gateway-watch-regression.test.ts | 19 -- 21 files changed, 55 insertions(+), 645 deletions(-) delete mode 100644 extensions/memory-lancedb/lancedb-runtime.test.ts diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 985194c2a2c..a2f31a6cd7d 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -185,7 +185,7 @@ current OpenClaw or a local checkout until a newer npm package is published. - `memory-core` — bundled memory search (default via `plugins.slots.memory`) - - `memory-lancedb` — install-on-demand long-term memory with auto-recall/capture (set `plugins.slots.memory = "memory-lancedb"`) + - `memory-lancedb` — LanceDB-backed long-term memory with auto-recall/capture (set `plugins.slots.memory = "memory-lancedb"`) See [Memory LanceDB](/plugins/memory-lancedb) for OpenAI-compatible embedding setup, Ollama examples, recall limits, and troubleshooting. diff --git a/extensions/acpx/src/codex-auth-bridge.ts b/extensions/acpx/src/codex-auth-bridge.ts index f215b829b3a..05f5f87699d 100644 --- a/extensions/acpx/src/codex-auth-bridge.ts +++ b/extensions/acpx/src/codex-auth-bridge.ts @@ -113,7 +113,7 @@ async function resolveInstalledAcpPackageBinPath( async function resolveInstalledCodexAcpBinPath(): Promise { // Keep OpenClaw's isolated CODEX_HOME wrapper, but launch the plugin-local - // Codex ACP adapter when runtime-deps staging made it available. + // Codex ACP adapter when the package dependency is available. return await resolveInstalledAcpPackageBinPath(CODEX_ACP_PACKAGE, CODEX_ACP_BIN); } diff --git a/extensions/amazon-bedrock-mantle/mantle-anthropic.runtime.ts b/extensions/amazon-bedrock-mantle/mantle-anthropic.runtime.ts index f1e7fed1f89..f381131b251 100644 --- a/extensions/amazon-bedrock-mantle/mantle-anthropic.runtime.ts +++ b/extensions/amazon-bedrock-mantle/mantle-anthropic.runtime.ts @@ -100,7 +100,7 @@ export function createMantleAnthropicStreamFn(deps?: { ), }); const base = buildMantleAnthropicBaseOptions(model, options, apiKey); - // Staged plugin runtime deps can give this plugin a distinct physical SDK copy. + // Plugin package deps can give this plugin a distinct physical SDK copy. // The client API is the same, but the SDK class private field makes types nominal. const streamClient = client as unknown as AnthropicStreamClient; if (!options?.reasoning || requiresDefaultSampling(model.id)) { diff --git a/extensions/discord/src/monitor/reply-delivery.test.ts b/extensions/discord/src/monitor/reply-delivery.test.ts index 7ab1765fcbc..1fb35a1c4cf 100644 --- a/extensions/discord/src/monitor/reply-delivery.test.ts +++ b/extensions/discord/src/monitor/reply-delivery.test.ts @@ -68,7 +68,7 @@ describe("deliverDiscordReply", () => { }); }); - it("bridges regular replies to shared outbound with Discord runtime deps", async () => { + it("bridges regular replies to shared outbound with Discord package deps", async () => { const rest = {} as RequestClient; const replies = [{ text: "shared path" }]; diff --git a/extensions/memory-core/src/memory/provider-adapters.ts b/extensions/memory-core/src/memory/provider-adapters.ts index 14eb71865b2..4dcfa5ce2c5 100644 --- a/extensions/memory-core/src/memory/provider-adapters.ts +++ b/extensions/memory-core/src/memory/provider-adapters.ts @@ -12,8 +12,6 @@ import { formatErrorMessage } from "../dreaming-shared.js"; import { filterUnregisteredMemoryEmbeddingProviderAdapters } from "./provider-adapter-registration.js"; const NODE_LLAMA_CPP_RUNTIME_PACKAGE = "node-llama-cpp"; -const NODE_LLAMA_CPP_RUNTIME_VERSION = "3.18.1"; -const NODE_LLAMA_CPP_INSTALL_SPEC = `${NODE_LLAMA_CPP_RUNTIME_PACKAGE}@${NODE_LLAMA_CPP_RUNTIME_VERSION}`; export type BuiltinMemoryEmbeddingProviderDoctorMetadata = { providerId: string; @@ -59,7 +57,7 @@ function formatLocalSetupError(err: unknown): string { "To enable local embeddings:", "1) Use Node 24 (recommended for installs/updates; Node 22 LTS, currently 22.14+, remains supported)", missing - ? `2) Run openclaw doctor --fix to repair managed plugin runtime deps for ${NODE_LLAMA_CPP_INSTALL_SPEC}` + ? `2) Install ${NODE_LLAMA_CPP_RUNTIME_PACKAGE} next to the OpenClaw package or source checkout` : null, `3) If you use pnpm: pnpm approve-builds (select ${NODE_LLAMA_CPP_RUNTIME_PACKAGE}), then pnpm rebuild ${NODE_LLAMA_CPP_RUNTIME_PACKAGE}`, ...listRemoteEmbeddingSetupHints(), diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index bb360766b4f..ccdaae7de5e 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -18,7 +18,7 @@ import memoryPlugin, { normalizeRecallQuery, shouldCapture, } from "./index.js"; -import { createLanceDbRuntimeLoader, type LanceDbRuntimeLogger } from "./lancedb-runtime.js"; +import { createLanceDbRuntimeLoader } from "./lancedb-runtime.js"; import { installTmpDirHarness } from "./test-helpers.js"; const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "test-key"; @@ -38,22 +38,7 @@ type MemoryPluginTestConfig = { storageOptions?: Record; }; -const TEST_RUNTIME_MANIFEST = { - name: "openclaw-memory-lancedb-runtime", - private: true as const, - type: "module" as const, - dependencies: { - "@lancedb/lancedb": "^0.27.1", - }, -}; - type LanceDbModule = typeof import("@lancedb/lancedb"); -type RuntimeManifest = { - name: string; - private: true; - type: "module"; - dependencies: Record; -}; function createMockModule(): LanceDbModule { return { @@ -67,40 +52,19 @@ function invokeEmbeddingCreate(mock: ReturnType, body: unknown) { function createRuntimeLoader( overrides: { - env?: NodeJS.ProcessEnv; importBundled?: () => Promise; - importResolved?: (resolvedPath: string) => Promise; platform?: NodeJS.Platform; arch?: NodeJS.Architecture; - resolveRuntimeEntry?: (params: { - runtimeDir: string; - manifest: RuntimeManifest; - }) => string | null; - installRuntime?: (params: { - runtimeDir: string; - manifest: RuntimeManifest; - env: NodeJS.ProcessEnv; - logger?: LanceDbRuntimeLogger; - }) => Promise; } = {}, ) { return createLanceDbRuntimeLoader({ - env: overrides.env ?? ({} as NodeJS.ProcessEnv), platform: overrides.platform, arch: overrides.arch, - resolveStateDir: () => "/tmp/openclaw-state", - runtimeManifest: TEST_RUNTIME_MANIFEST, importBundled: overrides.importBundled ?? (async () => { throw new Error("Cannot find package '@lancedb/lancedb'"); }), - importResolved: overrides.importResolved ?? (async () => createMockModule()), - resolveRuntimeEntry: overrides.resolveRuntimeEntry ?? (() => null), - installRuntime: - overrides.installRuntime ?? - (async ({ runtimeDir }: { runtimeDir: string }) => - `${runtimeDir}/node_modules/@lancedb/lancedb/index.js`), }); } @@ -2261,131 +2225,47 @@ describe("lancedb runtime loader", () => { test("uses the bundled module when it is already available", async () => { const bundledModule = createMockModule(); const importBundled = vi.fn(async () => bundledModule); - const importResolved = vi.fn(async () => createMockModule()); - const resolveRuntimeEntry = vi.fn(() => null); - const installRuntime = vi.fn(async () => "/tmp/openclaw-state/plugin-runtimes/lancedb.js"); const loader = createRuntimeLoader({ importBundled, - importResolved, - resolveRuntimeEntry, - installRuntime, }); await expect(loader.load()).resolves.toBe(bundledModule); - expect(resolveRuntimeEntry).not.toHaveBeenCalled(); - expect(installRuntime).not.toHaveBeenCalled(); - expect(importResolved).not.toHaveBeenCalled(); - }); - - test("reuses an existing user runtime install before attempting a reinstall", async () => { - const runtimeModule = createMockModule(); - const importResolved = vi.fn(async () => runtimeModule); - const resolveRuntimeEntry = vi.fn( - () => "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/runtime-entry.js", - ); - const installRuntime = vi.fn( - async () => "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/runtime-entry.js", - ); - const loader = createRuntimeLoader({ - importResolved, - resolveRuntimeEntry, - installRuntime, - }); - - await expect(loader.load()).resolves.toBe(runtimeModule); - - expect(resolveRuntimeEntry).toHaveBeenCalledWith( - expect.objectContaining({ - runtimeDir: "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb", - }), - ); - expect(installRuntime).not.toHaveBeenCalled(); - }); - - test("installs LanceDB into user state when the bundled runtime is unavailable", async () => { - const runtimeModule = createMockModule(); - const logger: LanceDbRuntimeLogger = { - warn: vi.fn(), - info: vi.fn(), - }; - const importResolved = vi.fn(async () => runtimeModule); - const resolveRuntimeEntry = vi.fn(() => null); - const installRuntime = vi.fn( - async ({ runtimeDir }: { runtimeDir: string }) => - `${runtimeDir}/node_modules/@lancedb/lancedb/index.js`, - ); - const loader = createRuntimeLoader({ - importResolved, - resolveRuntimeEntry, - installRuntime, - }); - - await expect(loader.load(logger)).resolves.toBe(runtimeModule); - - expect(installRuntime).toHaveBeenCalledWith( - expect.objectContaining({ - runtimeDir: "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb", - manifest: TEST_RUNTIME_MANIFEST, - }), - ); - expect(logger.warn).toHaveBeenCalledWith( - expect.stringContaining( - "installing runtime deps under /tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb", - ), - ); - }); - - test("fails fast in nix mode instead of attempting auto-install", async () => { - const installRuntime = vi.fn( - async ({ runtimeDir }: { runtimeDir: string }) => - `${runtimeDir}/node_modules/@lancedb/lancedb/index.js`, - ); - const loader = createRuntimeLoader({ - env: { OPENCLAW_NIX_MODE: "1" } as NodeJS.ProcessEnv, - installRuntime, - }); - - await expect(loader.load()).rejects.toThrow( - "memory-lancedb: failed to load LanceDB and Nix mode disables auto-install.", - ); - expect(installRuntime).not.toHaveBeenCalled(); + expect(importBundled).toHaveBeenCalledTimes(1); }); test("fails clearly on Intel macOS instead of attempting an unsupported native install", async () => { - const installRuntime = vi.fn( - async ({ runtimeDir }: { runtimeDir: string }) => - `${runtimeDir}/node_modules/@lancedb/lancedb/index.js`, - ); const loader = createRuntimeLoader({ platform: "darwin", arch: "x64", - installRuntime, }); await expect(loader.load()).rejects.toThrow( "memory-lancedb: LanceDB runtime is unavailable on darwin-x64.", ); - expect(installRuntime).not.toHaveBeenCalled(); }); - test("clears the cached failure so later calls can retry the install", async () => { + test("fails fast when package dependencies are missing", async () => { + const loader = createRuntimeLoader(); + + await expect(loader.load()).rejects.toThrow( + "memory-lancedb: bundled @lancedb/lancedb dependency is unavailable.", + ); + }); + + test("clears the cached failure so later calls can retry the package import", async () => { const runtimeModule = createMockModule(); - const installRuntime = vi + const importBundled = vi .fn() .mockRejectedValueOnce(new Error("network down")) - .mockResolvedValueOnce( - "/tmp/openclaw-state/plugin-runtimes/memory-lancedb/lancedb/node_modules/@lancedb/lancedb/index.js", - ); - const importResolved = vi.fn(async () => runtimeModule); + .mockResolvedValueOnce(runtimeModule); const loader = createRuntimeLoader({ - installRuntime, - importResolved, + importBundled, }); await expect(loader.load()).rejects.toThrow("network down"); await expect(loader.load()).resolves.toBe(runtimeModule); - expect(installRuntime).toHaveBeenCalledTimes(2); + expect(importBundled).toHaveBeenCalledTimes(2); }); }); diff --git a/extensions/memory-lancedb/lancedb-runtime.test.ts b/extensions/memory-lancedb/lancedb-runtime.test.ts deleted file mode 100644 index a4b819b7c52..00000000000 --- a/extensions/memory-lancedb/lancedb-runtime.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import path from "node:path"; -import { describe, expect, it } from "vitest"; -import { resolveLanceDbDependencySpec } from "./lancedb-runtime.js"; - -function mapReader( - entries: ReadonlyArray<[string, { dependencies?: Record } | null]>, -): (manifestPath: string) => { dependencies?: Record } | null { - const byPath = new Map( - entries.map(([manifestPath, value]) => [path.normalize(manifestPath), value]), - ); - return (manifestPath: string) => byPath.get(path.normalize(manifestPath)) ?? null; -} - -describe("resolveLanceDbDependencySpec", () => { - it("reads dependency from source-layout sibling manifest", () => { - const modulePath = path.join("/repo/extensions/memory-lancedb", "lancedb-runtime.js"); - const packagePath = path.join("/repo/extensions/memory-lancedb", "package.json"); - const readPackageJson = mapReader([ - [ - packagePath, - { - dependencies: { "@lancedb/lancedb": "^0.27.1" }, - }, - ], - ]); - - expect(resolveLanceDbDependencySpec(modulePath, readPackageJson)).toBe("^0.27.1"); - }); - - it("falls back to dist/extensions memory-lancedb manifest for flattened bundles", () => { - const modulePath = path.join( - "/usr/lib/node_modules/openclaw/dist", - "lancedb-runtime-3m75WU-W.js", - ); - const distPackagePath = path.join("/usr/lib/node_modules/openclaw/dist", "package.json"); - const extensionPackagePath = path.join( - "/usr/lib/node_modules/openclaw/dist/extensions/memory-lancedb", - "package.json", - ); - const readPackageJson = mapReader([ - [distPackagePath, { dependencies: {} }], - [ - extensionPackagePath, - { - dependencies: { "@lancedb/lancedb": "^0.27.1" }, - }, - ], - ]); - - expect(resolveLanceDbDependencySpec(modulePath, readPackageJson)).toBe("^0.27.1"); - }); - - it("walks parent directories to support nested dist chunk paths", () => { - const modulePath = path.join( - "/usr/lib/node_modules/openclaw/dist/chunks/runtime", - "lancedb-runtime-3m75WU-W.js", - ); - const extensionPackagePath = path.join( - "/usr/lib/node_modules/openclaw/dist/extensions/memory-lancedb", - "package.json", - ); - const readPackageJson = mapReader([ - [ - extensionPackagePath, - { - dependencies: { "@lancedb/lancedb": "0.27.2" }, - }, - ], - ]); - - expect(resolveLanceDbDependencySpec(modulePath, readPackageJson)).toBe("0.27.2"); - }); - - it("throws when no candidate package manifest declares @lancedb/lancedb", () => { - const modulePath = path.join( - "/usr/lib/node_modules/openclaw/dist", - "lancedb-runtime-3m75WU-W.js", - ); - const readPackageJson = mapReader([ - [path.join("/usr/lib/node_modules/openclaw/dist", "package.json"), null], - ]); - - expect(() => resolveLanceDbDependencySpec(modulePath, readPackageJson)).toThrow( - 'memory-lancedb package.json is missing "@lancedb/lancedb"', - ); - }); -}); diff --git a/extensions/memory-lancedb/lancedb-runtime.ts b/extensions/memory-lancedb/lancedb-runtime.ts index 15900d3c53d..02e613f51f8 100644 --- a/extensions/memory-lancedb/lancedb-runtime.ts +++ b/extensions/memory-lancedb/lancedb-runtime.ts @@ -1,11 +1,3 @@ -import { spawn } from "node:child_process"; -import fs from "node:fs"; -import { createRequire } from "node:module"; -import os from "node:os"; -import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import { resolveStateDir } from "./api.js"; - type LanceDbModule = typeof import("@lancedb/lancedb"); export type LanceDbRuntimeLogger = { @@ -13,211 +5,18 @@ export type LanceDbRuntimeLogger = { warn?: (message: string) => void; }; -type RuntimeManifest = { - name: string; - private: true; - type: "module"; - dependencies: Record; -}; - -type PackageJsonWithDependencies = { - dependencies?: Record; -}; - -type ReadPackageJson = (manifestPath: string) => PackageJsonWithDependencies | null; - type LanceDbRuntimeLoaderDeps = { - env: NodeJS.ProcessEnv; platform: NodeJS.Platform; arch: NodeJS.Architecture; - resolveStateDir: (env?: NodeJS.ProcessEnv, homedir?: () => string) => string; - runtimeManifest: RuntimeManifest; importBundled: () => Promise; - importResolved: (resolvedPath: string) => Promise; - resolveRuntimeEntry: (params: { runtimeDir: string; manifest: RuntimeManifest }) => string | null; - installRuntime: (params: { - runtimeDir: string; - manifest: RuntimeManifest; - env: NodeJS.ProcessEnv; - logger?: LanceDbRuntimeLogger; - }) => Promise; }; -function defaultReadPackageJson(manifestPath: string): PackageJsonWithDependencies | null { - try { - return JSON.parse(fs.readFileSync(manifestPath, "utf8")) as PackageJsonWithDependencies; - } catch { - return null; - } -} - -function buildMemoryLanceDbManifestCandidates(modulePath: string): string[] { - const moduleDir = path.dirname(modulePath); - const candidates = new Set(); - candidates.add(path.join(moduleDir, "package.json")); - - let cursor = moduleDir; - while (true) { - candidates.add(path.join(cursor, "extensions", "memory-lancedb", "package.json")); - const parent = path.dirname(cursor); - if (parent === cursor) { - break; - } - cursor = parent; - } - - return [...candidates]; -} - -export function resolveLanceDbDependencySpec( - modulePath: string, - readPackageJson: ReadPackageJson = defaultReadPackageJson, -): string { - for (const manifestPath of buildMemoryLanceDbManifestCandidates(modulePath)) { - const lanceDbSpec = readPackageJson(manifestPath)?.dependencies?.["@lancedb/lancedb"]; - if (lanceDbSpec) { - return lanceDbSpec; - } - } - throw new Error('memory-lancedb package.json is missing "@lancedb/lancedb"'); -} - -const MEMORY_LANCEDB_RUNTIME_MANIFEST: RuntimeManifest = (() => { - const lanceDbSpec = resolveLanceDbDependencySpec(fileURLToPath(import.meta.url)); - return { - name: "openclaw-memory-lancedb-runtime", - private: true, - type: "module", - dependencies: { - "@lancedb/lancedb": lanceDbSpec, - }, - }; -})(); - -function resolveRuntimeDir(stateDir: string): string { - return path.join(stateDir, "plugin-runtimes", "memory-lancedb", "lancedb"); -} - -function readRuntimeManifest(filePath: string): RuntimeManifest | null { - try { - return JSON.parse(fs.readFileSync(filePath, "utf8")) as RuntimeManifest; - } catch { - return null; - } -} - -function manifestsMatch(actual: RuntimeManifest | null, expected: RuntimeManifest): boolean { - if (!actual) { - return false; - } - return JSON.stringify(actual) === JSON.stringify(expected); -} - -function defaultResolveRuntimeEntry(params: { - runtimeDir: string; - manifest: RuntimeManifest; -}): string | null { - const runtimePackagePath = path.join(params.runtimeDir, "package.json"); - if (!manifestsMatch(readRuntimeManifest(runtimePackagePath), params.manifest)) { - return null; - } - try { - const runtimeRequire = createRequire(runtimePackagePath); - return runtimeRequire.resolve("@lancedb/lancedb"); - } catch { - return null; - } -} - -function collectSpawnOutput(params: { - command: string; - args: string[]; - cwd: string; - env: NodeJS.ProcessEnv; -}): Promise<{ code: number | null; stdout: string; stderr: string; error?: Error }> { - return new Promise((resolve) => { - const child = spawn(params.command, params.args, { - cwd: params.cwd, - env: params.env, - shell: process.platform === "win32", - stdio: ["ignore", "pipe", "pipe"], - }); - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (chunk: Buffer | string) => { - stdout += chunk.toString(); - }); - child.stderr.on("data", (chunk: Buffer | string) => { - stderr += chunk.toString(); - }); - child.on("error", (error) => { - resolve({ code: null, stdout, stderr, error }); - }); - child.on("close", (code) => { - resolve({ code, stdout, stderr }); - }); - }); -} - -async function defaultInstallRuntime(params: { - runtimeDir: string; - manifest: RuntimeManifest; - env: NodeJS.ProcessEnv; - logger?: LanceDbRuntimeLogger; -}): Promise { - const runtimePackagePath = path.join(params.runtimeDir, "package.json"); - const currentManifest = readRuntimeManifest(runtimePackagePath); - if (!manifestsMatch(currentManifest, params.manifest)) { - await fs.promises.rm(path.join(params.runtimeDir, "node_modules"), { - recursive: true, - force: true, - }); - await fs.promises.rm(path.join(params.runtimeDir, "package-lock.json"), { force: true }); - } - - await fs.promises.mkdir(params.runtimeDir, { recursive: true }); - await fs.promises.writeFile( - runtimePackagePath, - `${JSON.stringify(params.manifest, null, 2)}\n`, - "utf8", - ); - - const install = await collectSpawnOutput({ - command: "npm", - args: ["install", "--omit=dev", "--silent", "--ignore-scripts", "--package-lock=false"], - cwd: params.runtimeDir, - env: params.env, - }); - if (install.error) { - const spawnError = install.error as NodeJS.ErrnoException; - throw new Error( - spawnError.code === "ENOENT" - ? "npm is required to install the LanceDB runtime but was not found on PATH" - : install.error.message, - ); - } - if ((install.code ?? 0) !== 0) { - const detail = install.stderr.trim() || install.stdout.trim(); - throw new Error(detail || `npm exited with code ${install.code ?? "unknown"}`); - } - - const resolved = defaultResolveRuntimeEntry({ - runtimeDir: params.runtimeDir, - manifest: params.manifest, - }); - if (!resolved) { - throw new Error("installed LanceDB runtime is missing the @lancedb/lancedb entry"); - } - params.logger?.info?.(`memory-lancedb: installed LanceDB runtime under ${params.runtimeDir}`); - return resolved; -} - -function defaultImportResolved(resolvedPath: string): Promise { - return import(pathToFileURL(resolvedPath).href); -} - -function buildLoadFailureMessage(prefix: string, error: unknown): string { - return `memory-lancedb: ${prefix}. ${String(error)}`; +function buildLoadFailureMessage(error: unknown): string { + return [ + "memory-lancedb: bundled @lancedb/lancedb dependency is unavailable.", + "Install or repair the memory-lancedb plugin package dependencies, then restart OpenClaw.", + String(error), + ].join(" "); } function isUnsupportedNativePlatform(params: { @@ -239,87 +38,31 @@ function buildUnsupportedNativePlatformMessage(params: { } export function createLanceDbRuntimeLoader(overrides: Partial = {}): { - load: (logger?: LanceDbRuntimeLogger) => Promise; + load: (_logger?: LanceDbRuntimeLogger) => Promise; } { const deps: LanceDbRuntimeLoaderDeps = { - env: overrides.env ?? process.env, platform: overrides.platform ?? process.platform, arch: overrides.arch ?? process.arch, - resolveStateDir: overrides.resolveStateDir ?? resolveStateDir, - runtimeManifest: overrides.runtimeManifest ?? MEMORY_LANCEDB_RUNTIME_MANIFEST, importBundled: overrides.importBundled ?? (() => import("@lancedb/lancedb")), - importResolved: overrides.importResolved ?? defaultImportResolved, - resolveRuntimeEntry: overrides.resolveRuntimeEntry ?? defaultResolveRuntimeEntry, - installRuntime: overrides.installRuntime ?? defaultInstallRuntime, }; let loadPromise: Promise | null = null; return { - async load(logger?: LanceDbRuntimeLogger): Promise { + async load(_logger?: LanceDbRuntimeLogger): Promise { if (!loadPromise) { - loadPromise = (async () => { - try { - return await deps.importBundled(); - } catch (bundledError) { - if (isUnsupportedNativePlatform({ platform: deps.platform, arch: deps.arch })) { - throw new Error( - buildUnsupportedNativePlatformMessage({ - platform: deps.platform, - arch: deps.arch, - }), - { cause: bundledError }, - ); - } - const runtimeDir = resolveRuntimeDir( - deps.resolveStateDir(deps.env, () => - deps.env.HOME?.trim() ? deps.env.HOME : os.homedir(), - ), - ); - const existingRuntime = deps.resolveRuntimeEntry({ - runtimeDir, - manifest: deps.runtimeManifest, - }); - if (existingRuntime) { - try { - return await deps.importResolved(existingRuntime); - } catch { - // Reinstall below when the cached runtime is incomplete or stale. - } - } - if (deps.env.OPENCLAW_NIX_MODE === "1") { - throw new Error( - buildLoadFailureMessage( - "failed to load LanceDB and Nix mode disables auto-install", - bundledError, - ), - { cause: bundledError }, - ); - } - logger?.warn?.( - `memory-lancedb: bundled LanceDB runtime unavailable (${String(bundledError)}); installing runtime deps under ${runtimeDir}`, - ); - const installedEntry = await deps.installRuntime({ - runtimeDir, - manifest: deps.runtimeManifest, - env: deps.env, - logger, - }); - try { - return await deps.importResolved(installedEntry); - } catch (runtimeError) { - throw new Error( - buildLoadFailureMessage( - "failed to load LanceDB after installing runtime deps", - runtimeError, - ), - { cause: runtimeError }, - ); - } - } - })().catch((error) => { + loadPromise = deps.importBundled().catch((error) => { loadPromise = null; - throw error; + if (isUnsupportedNativePlatform({ platform: deps.platform, arch: deps.arch })) { + throw new Error( + buildUnsupportedNativePlatformMessage({ + platform: deps.platform, + arch: deps.arch, + }), + { cause: error }, + ); + } + throw new Error(buildLoadFailureMessage(error), { cause: error }); }); } return await loadPromise; diff --git a/extensions/msteams/src/sdk.test.ts b/extensions/msteams/src/sdk.test.ts index d9ba09cfddd..f44a882a60f 100644 --- a/extensions/msteams/src/sdk.test.ts +++ b/extensions/msteams/src/sdk.test.ts @@ -60,7 +60,7 @@ const jwtMockImpl = { }; vi.mock("jsonwebtoken", () => ({ - // Match jsonwebtoken@9 under dynamic ESM import from staged runtime deps: + // Match jsonwebtoken@9 under dynamic ESM import from plugin package deps: // Node exposes decode as a named export, while verify is only on default. decode: jwtMockImpl.decode, default: jwtMockImpl, diff --git a/extensions/qa-lab/src/bundled-plugin-staging.ts b/extensions/qa-lab/src/bundled-plugin-staging.ts index 0d988d7c356..27d33c82757 100644 --- a/extensions/qa-lab/src/bundled-plugin-staging.ts +++ b/extensions/qa-lab/src/bundled-plugin-staging.ts @@ -16,10 +16,6 @@ const QA_CLI_METADATA_ENTRY_BASENAMES = Object.freeze([ "cli-metadata.mjs", "cli-metadata.cjs", ]); -const QA_RUNTIME_DEPS_ARTIFACT_BASENAMES = new Set([ - ".openclaw-runtime-deps.json", - ".openclaw-runtime-deps-stamp.json", -]); function assertSafeQaBundledPluginId(pluginId: string) { if (!QA_BUNDLED_PLUGIN_ID_PATTERN.test(pluginId)) { @@ -316,14 +312,6 @@ async function seedQaStagedBuiltTreeRoots(params: { } } -function shouldStageQaBundledPluginPath(sourcePath: string) { - const basename = path.basename(sourcePath); - return ( - !QA_RUNTIME_DEPS_ARTIFACT_BASENAMES.has(basename) && - !basename.startsWith(".openclaw-runtime-deps-copy-") - ); -} - export async function resolveQaRuntimeHostVersion(params: { repoRoot: string; allowedPluginIds: readonly string[]; @@ -426,10 +414,7 @@ export async function createQaBundledPluginsDir(params: { if (!sourceDir) { throw new Error(`qa bundled plugin not found: ${pluginId}`); } - await fs.cp(sourceDir, path.join(bundledPluginsDir, pluginId), { - recursive: true, - filter: shouldStageQaBundledPluginPath, - }); + await fs.cp(sourceDir, path.join(bundledPluginsDir, pluginId), { recursive: true }); } await symlinkQaStagedDirEntry({ sourcePath: path.join(stagedRoot, "dist"), diff --git a/extensions/qa-lab/src/gateway-child.test.ts b/extensions/qa-lab/src/gateway-child.test.ts index 95d17b17fcc..7d6aa13c501 100644 --- a/extensions/qa-lab/src/gateway-child.test.ts +++ b/extensions/qa-lab/src/gateway-child.test.ts @@ -934,57 +934,6 @@ describe("qa bundled plugin dir", () => { ).resolves.toBeTruthy(); }); - it("skips legacy dependency debris while staging built bundled plugins", async () => { - const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-legacy-deps-")); - cleanups.push(async () => { - await rm(repoRoot, { recursive: true, force: true }); - }); - await writeFile( - path.join(repoRoot, "package.json"), - JSON.stringify({ name: "openclaw", type: "module" }, null, 2), - "utf8", - ); - const pluginDir = path.join(repoRoot, "dist", "extensions", "qa-channel"); - await mkdir(path.join(pluginDir, ".openclaw-runtime-deps-copy-active", "node_modules"), { - recursive: true, - }); - await writeFile( - path.join(pluginDir, "package.json"), - JSON.stringify({ name: "@openclaw/qa-channel", type: "module" }, null, 2), - "utf8", - ); - await writeFile(path.join(pluginDir, "index.js"), "export const ok = true;\n", "utf8"); - await writeFile(path.join(pluginDir, ".openclaw-runtime-deps.json"), "{}\n", "utf8"); - await writeFile(path.join(pluginDir, ".openclaw-runtime-deps-stamp.json"), "{}\n", "utf8"); - await writeFile( - path.join(pluginDir, ".openclaw-runtime-deps-copy-active", "node_modules", "transient.js"), - "export {};\n", - "utf8", - ); - const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-legacy-deps-target-")); - cleanups.push(async () => { - await rm(tempRoot, { recursive: true, force: true }); - }); - - const { bundledPluginsDir } = await __testing.createQaBundledPluginsDir({ - repoRoot, - tempRoot, - allowedPluginIds: ["qa-channel"], - }); - - const stagedPluginDir = path.join(bundledPluginsDir, "qa-channel"); - await expect(readFile(path.join(stagedPluginDir, "index.js"), "utf8")).resolves.toContain("ok"); - await expect(lstat(path.join(stagedPluginDir, ".openclaw-runtime-deps.json"))).rejects.toThrow( - /ENOENT/u, - ); - await expect( - lstat(path.join(stagedPluginDir, ".openclaw-runtime-deps-stamp.json")), - ).rejects.toThrow(/ENOENT/u); - await expect( - lstat(path.join(stagedPluginDir, ".openclaw-runtime-deps-copy-active")), - ).rejects.toThrow(/ENOENT/u); - }); - it("preserves dist-runtime-only root chunks when dist also exists", async () => { const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-mixed-runtime-")); cleanups.push(async () => { diff --git a/scripts/check-gateway-watch-regression.mjs b/scripts/check-gateway-watch-regression.mjs index 03e612ace34..a8d59516ebd 100644 --- a/scripts/check-gateway-watch-regression.mjs +++ b/scripts/check-gateway-watch-regression.mjs @@ -207,23 +207,6 @@ function snapshotTree(rootName) { return stats; } -export function isIgnoredDistRuntimeWatchPath(entry) { - return ( - entry === "dist-runtime/extensions/node_modules" || - entry.startsWith("dist-runtime/extensions/node_modules/") - ); -} - -function summarizeDistRuntimeAddedPaths(added) { - const addedPaths = added.filter((entry) => entry.startsWith("dist-runtime/")); - const ignoredDependencyAddedPaths = addedPaths.filter(isIgnoredDistRuntimeWatchPath); - const topologyAddedPaths = addedPaths.filter((entry) => !isIgnoredDistRuntimeWatchPath(entry)); - return { - ignoredDependencyAddedPaths, - topologyAddedPaths, - }; -} - function writeSnapshot(snapshotDir) { ensureDir(snapshotDir); const pathEntries = [...listTreeEntries("dist"), ...listTreeEntries("dist-runtime")]; @@ -684,10 +667,9 @@ async function main() { const post = writeSnapshot(postDir); const diff = writeDiffArtifacts(options.outputDir, preDir, postDir); - const distRuntimeAddedPathSummary = summarizeDistRuntimeAddedPaths(diff.added); - const distRuntimeAddedPaths = distRuntimeAddedPathSummary.topologyAddedPaths.length; - const distRuntimeIgnoredDependencyAddedPaths = - distRuntimeAddedPathSummary.ignoredDependencyAddedPaths.length; + const distRuntimeAddedPaths = diff.added.filter((entry) => + entry.startsWith("dist-runtime/"), + ).length; const distRuntimeFileGrowth = distRuntimeAddedPaths; const distRuntimeByteGrowth = distRuntimeAddedPaths === 0 @@ -721,7 +703,6 @@ async function main() { distRuntimeByteGrowth, distRuntimeByteGrowthMax: options.distRuntimeByteGrowthMax, distRuntimeAddedPaths, - distRuntimeIgnoredDependencyAddedPaths, addedPaths: diff.added.length, removedPaths: diff.removed.length, watchExit: watchResult.exit, diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index eaefe57b3e1..6e16a7655b3 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -512,13 +512,7 @@ function toScopedIncludePattern(arg, cwd) { } function isSkippedImportGraphDirectory(name) { - return ( - name === ".git" || - name === "dist" || - name === "node_modules" || - name === "vendor" || - name.startsWith(".openclaw-runtime-deps") - ); + return name === ".git" || name === "dist" || name === "node_modules" || name === "vendor"; } function listImportGraphFiles(cwd, directory, files = []) { diff --git a/src/commands/channel-setup/plugin-install.ts b/src/commands/channel-setup/plugin-install.ts index c786c729267..8ea2569626e 100644 --- a/src/commands/channel-setup/plugin-install.ts +++ b/src/commands/channel-setup/plugin-install.ts @@ -76,7 +76,6 @@ function loadChannelSetupPluginRegistry(params: { workspaceDir?: string; onlyPluginIds?: string[]; activate?: boolean; - installRuntimeDeps?: boolean; forceSetupOnlyChannelPlugins?: boolean; }): PluginRegistry { const autoEnabled = applyPluginAutoEnable({ config: params.cfg, env: process.env }); @@ -94,8 +93,7 @@ function loadChannelSetupPluginRegistry(params: { logger: createPluginLoaderLogger(log), onlyPluginIds: params.onlyPluginIds, includeSetupOnlyChannelPlugins: true, - forceSetupOnlyChannelPlugins: - params.forceSetupOnlyChannelPlugins ?? params.installRuntimeDeps === false, + forceSetupOnlyChannelPlugins: params.forceSetupOnlyChannelPlugins, activate: params.activate, }); } @@ -163,7 +161,6 @@ export function loadChannelSetupPluginRegistrySnapshotForChannel(params: { channel: string; pluginId?: string; workspaceDir?: string; - installRuntimeDeps?: boolean; forceSetupOnlyChannelPlugins?: boolean; }): PluginRegistry { const scopedPluginId = resolveScopedChannelPluginId({ diff --git a/src/commands/channels.add.test.ts b/src/commands/channels.add.test.ts index aa6747d0572..66f3b38e326 100644 --- a/src/commands/channels.add.test.ts +++ b/src/commands/channels.add.test.ts @@ -520,7 +520,7 @@ describe("channelsAddCommand", () => { ); expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1); expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith( - expect.objectContaining({ installRuntimeDeps: false }), + expect.objectContaining({ forceSetupOnlyChannelPlugins: true }), ); expect(registryRefreshMocks.refreshPluginRegistryAfterConfigMutation).toHaveBeenCalledWith( expect.objectContaining({ @@ -554,7 +554,7 @@ describe("channelsAddCommand", () => { expect(ensureChannelSetupPluginInstalled).not.toHaveBeenCalled(); expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1); expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith( - expect.objectContaining({ installRuntimeDeps: false }), + expect.objectContaining({ forceSetupOnlyChannelPlugins: true }), ); expectExternalChatEnabledConfigWrite(); }); diff --git a/src/commands/channels/add.ts b/src/commands/channels/add.ts index 1441cd7e7da..def686a0005 100644 --- a/src/commands/channels/add.ts +++ b/src/commands/channels/add.ts @@ -296,7 +296,7 @@ export async function channelsAddCommand( channel: channelId, ...(pluginId ? { pluginId } : {}), workspaceDir: resolveWorkspaceDir(), - installRuntimeDeps: false, + forceSetupOnlyChannelPlugins: true, }); return ( snapshot.channelSetups.find((entry) => entry.plugin.id === channelId)?.plugin ?? diff --git a/src/flows/channel-setup.test.ts b/src/flows/channel-setup.test.ts index ed8c0ba5ad4..3eb5968cfa3 100644 --- a/src/flows/channel-setup.test.ts +++ b/src/flows/channel-setup.test.ts @@ -471,7 +471,7 @@ describe("setupChannels workspace shadow exclusion", () => { channel: "external-chat", pluginId: "external-chat", workspaceDir: "/tmp/openclaw-workspace", - installRuntimeDeps: false, + forceSetupOnlyChannelPlugins: true, }), ); expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenNthCalledWith( @@ -480,7 +480,6 @@ describe("setupChannels workspace shadow exclusion", () => { channel: "external-chat", workspaceDir: "/tmp/openclaw-workspace", forceSetupOnlyChannelPlugins: true, - installRuntimeDeps: true, }), ); expect(getChannelSetupPlugin).not.toHaveBeenCalled(); diff --git a/src/flows/channel-setup.ts b/src/flows/channel-setup.ts index f48c02e6e0d..3f5996c77ad 100644 --- a/src/flows/channel-setup.ts +++ b/src/flows/channel-setup.ts @@ -156,7 +156,6 @@ export async function setupChannels( channel: ChannelChoice, pluginId?: string, setup?: { - installRuntimeDeps?: boolean; forceReload?: boolean; forceSetupOnlyChannelPlugins?: boolean; }, @@ -171,8 +170,7 @@ export async function setupChannels( channel, ...(pluginId ? { pluginId } : {}), workspaceDir: resolveWorkspaceDir(), - installRuntimeDeps: setup?.installRuntimeDeps ?? false, - forceSetupOnlyChannelPlugins: setup?.forceSetupOnlyChannelPlugins, + forceSetupOnlyChannelPlugins: setup?.forceSetupOnlyChannelPlugins ?? true, }); const plugin = snapshot.channelSetups.find((entry) => entry.plugin.id === channel)?.plugin ?? @@ -442,7 +440,6 @@ export async function setupChannels( await loadScopedChannelPlugin(channel, undefined, { forceReload: true, forceSetupOnlyChannelPlugins: true, - installRuntimeDeps: true, }); } const adapter = getVisibleSetupFlowAdapter(channel); diff --git a/src/plugins/bundled-dir.test.ts b/src/plugins/bundled-dir.test.ts index 87df61e8246..a7c9fcf4953 100644 --- a/src/plugins/bundled-dir.test.ts +++ b/src/plugins/bundled-dir.test.ts @@ -167,7 +167,7 @@ afterEach(() => { describe("resolveBundledPluginsDir", () => { it.each([ [ - "prefers the staged runtime bundled plugin tree from the package root", + "prefers the runtime bundled plugin tree from the package root", { prefix: "openclaw-bundled-dir-runtime-", hasDistRuntimeExtensions: true, diff --git a/src/plugins/loader-channel-setup.ts b/src/plugins/loader-channel-setup.ts index f86ece78e69..c9cef5d9f8d 100644 --- a/src/plugins/loader-channel-setup.ts +++ b/src/plugins/loader-channel-setup.ts @@ -122,10 +122,7 @@ export function loadBundledRuntimeChannelPlugin(params: { } } -export function resolveSetupChannelRegistration( - moduleExport: unknown, - params: { installRuntimeDeps?: boolean } = {}, -): { +export function resolveSetupChannelRegistration(moduleExport: unknown): { plugin?: ChannelPlugin; setChannelRuntime?: (runtime: PluginRuntime) => void; usesBundledSetupContract?: boolean; @@ -146,14 +143,10 @@ export function resolveSetupChannelRegistration( typeof setupEntryRecord.loadSetupPlugin === "function" ) { try { - const setupLoadOptions = - params.installRuntimeDeps === false ? { installRuntimeDeps: false } : undefined; - const loadedPlugin = setupEntryRecord.loadSetupPlugin(setupLoadOptions); + const loadedPlugin = setupEntryRecord.loadSetupPlugin(); const loadedSecrets = typeof setupEntryRecord.loadSetupSecrets === "function" - ? (setupEntryRecord.loadSetupSecrets(setupLoadOptions) as - | ChannelPlugin["secrets"] - | undefined) + ? (setupEntryRecord.loadSetupSecrets() as ChannelPlugin["secrets"] | undefined) : undefined; if (loadedPlugin && typeof loadedPlugin === "object") { const mergedSecrets = mergeChannelPluginSection( diff --git a/test/scripts/check-gateway-watch-regression.test.ts b/test/scripts/check-gateway-watch-regression.test.ts index fbbf718b5a7..e0ba1f709c4 100644 --- a/test/scripts/check-gateway-watch-regression.test.ts +++ b/test/scripts/check-gateway-watch-regression.test.ts @@ -4,7 +4,6 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { hasGatewayReadyLog, - isIgnoredDistRuntimeWatchPath, shouldRefreshBuildStampForRestoredArtifacts, writeBuildAndRuntimePostBuildStamps, } from "../../scripts/check-gateway-watch-regression.mjs"; @@ -14,24 +13,6 @@ import { } from "../../scripts/lib/local-build-metadata-paths.mjs"; describe("check-gateway-watch-regression", () => { - it("ignores top-level dist-runtime extension dependency debris", () => { - expect(isIgnoredDistRuntimeWatchPath("dist-runtime/extensions/node_modules")).toBe(true); - expect( - isIgnoredDistRuntimeWatchPath( - "dist-runtime/extensions/node_modules/playwright-core/index.js", - ), - ).toBe(true); - }); - - it("keeps plugin runtime graph paths counted", () => { - expect(isIgnoredDistRuntimeWatchPath("dist-runtime/extensions/openai/index.js")).toBe(false); - expect( - isIgnoredDistRuntimeWatchPath( - "dist-runtime/extensions/openai/node_modules/openclaw/index.js", - ), - ).toBe(false); - }); - it("recognizes current and legacy gateway ready logs", () => { expect(hasGatewayReadyLog("[gateway] http server listening (0 plugins, 0.8s)")).toBe(true); expect(hasGatewayReadyLog("[gateway] ready (0 plugins, 0.8s)")).toBe(true);