diff --git a/package.json b/package.json index 80570e35e37..1060144332f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openclaw", - "version": "2026.4.23-beta.1", + "version": "2026.4.23-beta.2", "description": "Multi-channel AI gateway with extensible messaging integrations", "keywords": [], "homepage": "https://github.com/openclaw/openclaw#readme", diff --git a/scripts/openclaw-npm-postpublish-verify.ts b/scripts/openclaw-npm-postpublish-verify.ts index fab26bc2416..e3e41551506 100644 --- a/scripts/openclaw-npm-postpublish-verify.ts +++ b/scripts/openclaw-npm-postpublish-verify.ts @@ -11,6 +11,7 @@ import { rmSync, } from "node:fs"; import { builtinModules } from "node:module"; +import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { isAbsolute, join, relative } from "node:path"; import { pathToFileURL } from "node:url"; @@ -25,7 +26,6 @@ import { } from "./lib/bundled-plugin-root-runtime-mirrors.mjs"; import { runInstalledWorkspaceBootstrapSmoke } from "./lib/workspace-bootstrap-smoke.mjs"; import { parseReleaseVersion, resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts"; -import { createRequire } from "node:module"; type InstalledPackageJson = { version?: string; @@ -302,6 +302,8 @@ export function collectInstalledRootDependencyManifestErrors(packageRoot: string ]; } const missingImporters = new Map>(); + const bundledExtensionRuntimeDependencyOwners = + collectBundledExtensionRuntimeDependencyOwners(packageRoot); for (const filePath of distFiles) { const fileStat = lstatSync(filePath); @@ -324,7 +326,12 @@ export function collectInstalledRootDependencyManifestErrors(packageRoot: string if ( !dependencyName || NODE_BUILTIN_MODULES.has(dependencyName) || - declaredRuntimeDeps.has(dependencyName) + declaredRuntimeDeps.has(dependencyName) || + isBundledExtensionOwnedRuntimeImport({ + dependencyName, + ownersByDependency: bundledExtensionRuntimeDependencyOwners, + source, + }) ) { continue; } @@ -342,6 +349,35 @@ export function collectInstalledRootDependencyManifestErrors(packageRoot: string .toSorted((left, right) => left.localeCompare(right)); } +function collectBundledExtensionRuntimeDependencyOwners( + packageRoot: string, +): Map> { + const ownersByDependency = new Map>(); + const { manifests } = readBundledExtensionPackageJsons(packageRoot); + for (const { id, manifest } of manifests) { + for (const dependencyName of collectRuntimeDependencySpecs(manifest).keys()) { + const owners = ownersByDependency.get(dependencyName) ?? new Set(); + owners.add(id); + ownersByDependency.set(dependencyName, owners); + } + } + return ownersByDependency; +} + +function isBundledExtensionOwnedRuntimeImport(params: { + dependencyName: string; + ownersByDependency: Map>; + source: string; +}): boolean { + const owners = params.ownersByDependency.get(params.dependencyName); + if (!owners) { + return false; + } + return [...owners].some((pluginId) => + params.source.includes(`//#region extensions/${pluginId}/`), + ); +} + export function resolveInstalledBinaryPath(prefixDir: string, platform = process.platform): string { return platform === "win32" ? join(prefixDir, "openclaw.cmd") diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index 444534ca382..e689025afd5 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -27769,6 +27769,6 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { tags: ["advanced", "url-secret"], }, }, - version: "2026.4.23-beta.1", + version: "2026.4.23-beta.2", generatedAt: "2026-03-22T21:17:33.302Z", }; diff --git a/test/release-check.test.ts b/test/release-check.test.ts index 73d70049058..c147ca872a3 100644 --- a/test/release-check.test.ts +++ b/test/release-check.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest"; import { listBundledPluginPackArtifacts } from "../scripts/lib/bundled-plugin-build-entries.mjs"; import { listPluginSdkDistArtifacts } from "../scripts/lib/plugin-sdk-entries.mjs"; import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../scripts/lib/workspace-bootstrap-smoke.mjs"; +import { collectInstalledRootDependencyManifestErrors } from "../scripts/openclaw-npm-postpublish-verify.ts"; import { collectAppcastSparkleVersionErrors, collectBundledExtensionManifestErrors, @@ -277,6 +278,62 @@ describe("bundled plugin root runtime mirrors", () => { } }); + it("does not require root deps for root chunks sourced from the owning installed plugin", () => { + const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-root-owned-installed-")); + + try { + mkdirSync(join(tempRoot, "dist", "extensions", "memory-lancedb"), { recursive: true }); + writeFileSync( + join(tempRoot, "package.json"), + `{"name":"openclaw","dependencies":{}}\n`, + "utf8", + ); + writeFileSync( + join(tempRoot, "dist", "extensions", "memory-lancedb", "package.json"), + `{"name":"@openclaw/memory-lancedb","dependencies":{"@lancedb/lancedb":"^0.27.2"}}\n`, + "utf8", + ); + writeFileSync( + join(tempRoot, "dist", "lancedb-runtime-7TYK-Pto.js"), + `//#region extensions/memory-lancedb/lancedb-runtime.ts\nimport("@lancedb/lancedb");\n`, + "utf8", + ); + + expect(collectInstalledRootDependencyManifestErrors(tempRoot)).toEqual([]); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + + it("still requires root deps for root-owned installed chunks", () => { + const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-root-owned-installed-missing-")); + + try { + mkdirSync(join(tempRoot, "dist", "extensions", "memory-lancedb"), { recursive: true }); + writeFileSync( + join(tempRoot, "package.json"), + `{"name":"openclaw","dependencies":{}}\n`, + "utf8", + ); + writeFileSync( + join(tempRoot, "dist", "extensions", "memory-lancedb", "package.json"), + `{"name":"@openclaw/memory-lancedb","dependencies":{"@lancedb/lancedb":"^0.27.2"}}\n`, + "utf8", + ); + writeFileSync( + join(tempRoot, "dist", "root-runtime.js"), + `import("@lancedb/lancedb");\n`, + "utf8", + ); + + expect(collectInstalledRootDependencyManifestErrors(tempRoot)).toEqual([ + "installed package root is missing declared runtime dependency '@lancedb/lancedb' for dist importers: root-runtime.js. Add it to package.json dependencies/optionalDependencies.", + ]); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); + it("does not compare root mirror versions for plugin manifest deps", () => { expect( collectBundledPluginRootRuntimeMirrorErrors({