From 5eabb6e6973628d64e72f45e20ed6707c40b420e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 09:39:11 +0100 Subject: [PATCH] build(deps): internalize bundled plugin runtime deps --- package.json | 22 ++++++ pnpm-lock.yaml | 66 ++++++++++++++++ scripts/root-dependency-ownership-audit.mjs | 75 +++++++++++++++++++ .../root-dependency-ownership-audit.test.ts | 69 +++++++++++++++++ 4 files changed, 232 insertions(+) diff --git a/package.json b/package.json index 09172b31cfc..6b44ac9f2f5 100644 --- a/package.json +++ b/package.json @@ -1642,33 +1642,55 @@ }, "dependencies": { "@agentclientprotocol/sdk": "0.21.0", + "@anthropic-ai/sdk": "0.92.0", + "@anthropic-ai/vertex-sdk": "^0.16.0", + "@aws-sdk/client-bedrock": "3.1041.0", + "@aws-sdk/client-bedrock-runtime": "3.1041.0", + "@aws-sdk/credential-provider-node": "3.972.39", + "@aws/bedrock-token-generator": "^1.1.0", "@clack/prompts": "^1.3.0", + "@google/genai": "^1.51.0", + "@grammyjs/runner": "^2.0.3", + "@grammyjs/transformer-throttler": "^1.2.1", + "@homebridge/ciao": "^1.3.7", "@lydell/node-pty": "1.2.0-beta.12", "@mariozechner/pi-agent-core": "0.71.1", "@mariozechner/pi-ai": "0.71.1", "@mariozechner/pi-coding-agent": "0.71.1", "@mariozechner/pi-tui": "0.71.1", "@modelcontextprotocol/sdk": "1.29.0", + "@mozilla/readability": "^0.6.0", + "@slack/bolt": "^4.7.2", + "@slack/types": "^2.20.1", + "@slack/web-api": "^7.15.1", "ajv": "^8.20.0", "chalk": "^5.6.2", "chokidar": "^5.0.0", "commander": "^14.0.3", "croner": "^10.0.1", "dotenv": "^17.4.2", + "express": "5.2.1", "file-type": "22.0.1", "global-agent": "^4.1.3", + "grammy": "^1.42.0", "https-proxy-agent": "^9.0.0", "ipaddr.js": "^2.3.0", "jiti": "^2.6.1", "json5": "^2.2.3", "jszip": "^3.10.1", + "linkedom": "^0.18.12", "markdown-it": "14.1.1", + "minimatch": "10.2.5", + "node-edge-tts": "^1.2.10", "openai": "^6.35.0", + "openshell": "0.1.0", + "pdfjs-dist": "^5.7.284", "playwright-core": "1.59.1", "proxy-agent": "^8.0.1", "qrcode": "1.5.4", "sqlite-vec": "0.1.9", "tar": "7.5.13", + "tokenjuice": "0.7.0", "tslog": "^4.10.2", "typebox": "1.1.37", "undici": "8.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcb3013412d..9c4965b293e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,9 +46,39 @@ importers: '@agentclientprotocol/sdk': specifier: 0.21.0 version: 0.21.0(zod@4.4.1) + '@anthropic-ai/sdk': + specifier: 0.92.0 + version: 0.92.0(zod@4.4.1) + '@anthropic-ai/vertex-sdk': + specifier: ^0.16.0 + version: 0.16.0(zod@4.4.1) + '@aws-sdk/client-bedrock': + specifier: 3.1041.0 + version: 3.1041.0 + '@aws-sdk/client-bedrock-runtime': + specifier: 3.1024.0 + version: 3.1024.0 + '@aws-sdk/credential-provider-node': + specifier: 3.972.39 + version: 3.972.39 + '@aws/bedrock-token-generator': + specifier: ^1.1.0 + version: 1.1.0 '@clack/prompts': specifier: ^1.3.0 version: 1.3.0 + '@google/genai': + specifier: ^1.51.0 + version: 1.51.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.1)) + '@grammyjs/runner': + specifier: ^2.0.3 + version: 2.0.3(grammy@1.42.0) + '@grammyjs/transformer-throttler': + specifier: ^1.2.1 + version: 1.2.1(grammy@1.42.0) + '@homebridge/ciao': + specifier: ^1.3.7 + version: 1.3.7 '@lydell/node-pty': specifier: 1.2.0-beta.12 version: 1.2.0-beta.12 @@ -67,6 +97,18 @@ importers: '@modelcontextprotocol/sdk': specifier: 1.29.0 version: 1.29.0(zod@4.4.1) + '@mozilla/readability': + specifier: ^0.6.0 + version: 0.6.0 + '@slack/bolt': + specifier: ^4.7.2 + version: 4.7.2(@types/express@5.0.6) + '@slack/types': + specifier: ^2.20.1 + version: 2.20.1 + '@slack/web-api': + specifier: ^7.15.1 + version: 7.15.1 ajv: specifier: ^8.20.0 version: 8.20.0 @@ -85,12 +127,18 @@ importers: dotenv: specifier: ^17.4.2 version: 17.4.2 + express: + specifier: 5.2.1 + version: 5.2.1 file-type: specifier: 22.0.1 version: 22.0.1 global-agent: specifier: ^4.1.3 version: 4.1.3 + grammy: + specifier: ^1.42.0 + version: 1.42.0 https-proxy-agent: specifier: ^9.0.0 version: 9.0.0 @@ -106,12 +154,27 @@ importers: jszip: specifier: ^3.10.1 version: 3.10.1 + linkedom: + specifier: ^0.18.12 + version: 0.18.12 markdown-it: specifier: 14.1.1 version: 14.1.1 + minimatch: + specifier: 10.2.5 + version: 10.2.5 + node-edge-tts: + specifier: ^1.2.10 + version: 1.2.10 openai: specifier: ^6.35.0 version: 6.35.0(ws@8.20.0)(zod@4.4.1) + openshell: + specifier: 0.1.0 + version: 0.1.0 + pdfjs-dist: + specifier: ^5.7.284 + version: 5.7.284 playwright-core: specifier: 1.59.1 version: 1.59.1 @@ -127,6 +190,9 @@ importers: tar: specifier: 7.5.13 version: 7.5.13 + tokenjuice: + specifier: 0.7.0 + version: 0.7.0 tslog: specifier: ^4.10.2 version: 4.10.2 diff --git a/scripts/root-dependency-ownership-audit.mjs b/scripts/root-dependency-ownership-audit.mjs index 66aa99a105b..8ab8b8bc0e0 100644 --- a/scripts/root-dependency-ownership-audit.mjs +++ b/scripts/root-dependency-ownership-audit.mjs @@ -135,6 +135,57 @@ function collectExtensionDependencyDeclarations(repoRoot) { return declarations; } +function collectExcludedPackagedExtensionDirs(rootPackageJson) { + const excluded = new Set(); + for (const entry of rootPackageJson.files ?? []) { + if (typeof entry !== "string") { + continue; + } + const match = /^!dist\/extensions\/([^/]+)\/\*\*$/u.exec(entry); + if (match?.[1]) { + excluded.add(match[1]); + } + } + return excluded; +} + +function collectInternalizedBundledExtensionRuntimeDependencies(repoRoot, rootPackageJson) { + const dependencies = new Map(); + const extensionsRoot = path.join(repoRoot, "extensions"); + if (!fs.existsSync(extensionsRoot)) { + return dependencies; + } + + const excluded = collectExcludedPackagedExtensionDirs(rootPackageJson); + for (const entry of fs.readdirSync(extensionsRoot, { withFileTypes: true })) { + if (!entry.isDirectory() || excluded.has(entry.name)) { + continue; + } + const packageJsonPath = path.join(extensionsRoot, entry.name, "package.json"); + const manifestPath = path.join(extensionsRoot, entry.name, "openclaw.plugin.json"); + if (!fs.existsSync(packageJsonPath) || !fs.existsSync(manifestPath)) { + continue; + } + const packageJson = readJson(packageJsonPath); + if (packageJson?.openclaw?.bundle?.includeInCore === false) { + continue; + } + for (const section of ["dependencies", "optionalDependencies"]) { + for (const depName of Object.keys(packageJson[section] ?? {})) { + const existing = dependencies.get(depName) ?? []; + existing.push(`${entry.name}:${section}`); + dependencies.set(depName, existing); + } + } + } + + for (const values of dependencies.values()) { + values.sort((left, right) => left.localeCompare(right)); + } + + return dependencies; +} + function sectionSetContainsCore(sectionSet) { return sectionSet.has("src") || sectionSet.has("packages") || sectionSet.has("ui"); } @@ -190,6 +241,16 @@ export function classifyRootDependencyOwnership(record) { }; } + if ( + record.internalizedBundledRuntimeOwners?.length > 0 && + sectionSetIsSubsetOf(sections, new Set(["extensions", "test"])) + ) { + return { + category: "root_owned_extension_runtime", + recommendation: `keep at root while bundled plugin runtime dependencies are internalized; owners: ${record.internalizedBundledRuntimeOwners.join(", ")}`, + }; + } + if (sectionSetIsSubsetOf(sections, new Set(["extensions", "test"]))) { return { category: "extension_only_localizable", @@ -219,6 +280,7 @@ export function collectRootDependencyOwnershipAudit(params = {}) { sections: new Set(), files: new Set(), declaredInExtensions: [], + internalizedBundledRuntimeOwners: [], spec: rootDependencies[depName], }, ]), @@ -249,6 +311,15 @@ export function collectRootDependencyOwnershipAudit(params = {}) { } } + const internalizedBundledRuntimeDependencies = + collectInternalizedBundledExtensionRuntimeDependencies(repoRoot, rootPackageJson); + for (const [depName, owners] of internalizedBundledRuntimeDependencies) { + const record = records.get(depName); + if (record) { + record.internalizedBundledRuntimeOwners = owners; + } + } + return [...records.values()] .map((record) => { const classification = classifyRootDependencyOwnership({ @@ -262,6 +333,7 @@ export function collectRootDependencyOwnershipAudit(params = {}) { fileCount: record.files.size, sampleFiles: [...record.files].slice(0, 5), declaredInExtensions: record.declaredInExtensions, + internalizedBundledRuntimeOwners: record.internalizedBundledRuntimeOwners, category: classification.category, recommendation: classification.recommendation, }; @@ -301,6 +373,9 @@ function printTextReport(records) { if (record.declaredInExtensions.length > 0) { details.push(`extensions=${record.declaredInExtensions.join(",")}`); } + if (record.internalizedBundledRuntimeOwners.length > 0) { + details.push(`internalized=${record.internalizedBundledRuntimeOwners.join(",")}`); + } console.log(`- ${record.depName}@${record.spec} :: ${details.join(" | ")}`); console.log(` ${record.recommendation}`); } diff --git a/test/scripts/root-dependency-ownership-audit.test.ts b/test/scripts/root-dependency-ownership-audit.test.ts index 4bca04b17f1..41a725e38ff 100644 --- a/test/scripts/root-dependency-ownership-audit.test.ts +++ b/test/scripts/root-dependency-ownership-audit.test.ts @@ -251,4 +251,73 @@ describe("collectRootDependencyOwnershipCheckErrors", () => { ]); expect(collectRootDependencyOwnershipCheckErrors(records)).toEqual([]); }); + + it("allows runtime deps for bundled plugins that are still packaged in core", () => { + const repoRoot = makeTempRepo(); + writeRepoFile( + repoRoot, + "package.json", + JSON.stringify({ + dependencies: { "vendor-sdk": "^1.0.0" }, + files: ["dist/", "!dist/extensions/externalized/**"], + }), + ); + writeRepoFile( + repoRoot, + "extensions/internal/package.json", + JSON.stringify({ dependencies: { "vendor-sdk": "^1.0.0" } }), + ); + writeRepoFile(repoRoot, "extensions/internal/openclaw.plugin.json", JSON.stringify({})); + writeRepoFile( + repoRoot, + "extensions/internal/src/setup.ts", + 'const sdk = await import("vendor-sdk");\n', + ); + + const records = collectRootDependencyOwnershipAudit({ repoRoot, scanRoots: ["extensions"] }); + + expect(records).toMatchObject([ + { + category: "root_owned_extension_runtime", + depName: "vendor-sdk", + internalizedBundledRuntimeOwners: ["internal:dependencies"], + sections: ["extensions"], + }, + ]); + expect(collectRootDependencyOwnershipCheckErrors(records)).toEqual([]); + }); + + it("keeps excluded bundled plugin deps localizable", () => { + const repoRoot = makeTempRepo(); + writeRepoFile( + repoRoot, + "package.json", + JSON.stringify({ + dependencies: { "vendor-sdk": "^1.0.0" }, + files: ["dist/", "!dist/extensions/externalized/**"], + }), + ); + writeRepoFile( + repoRoot, + "extensions/externalized/package.json", + JSON.stringify({ dependencies: { "vendor-sdk": "^1.0.0" } }), + ); + writeRepoFile(repoRoot, "extensions/externalized/openclaw.plugin.json", JSON.stringify({})); + writeRepoFile( + repoRoot, + "extensions/externalized/src/setup.ts", + 'const sdk = await import("vendor-sdk");\n', + ); + + const records = collectRootDependencyOwnershipAudit({ repoRoot, scanRoots: ["extensions"] }); + + expect(records).toMatchObject([ + { + category: "extension_only_localizable", + depName: "vendor-sdk", + internalizedBundledRuntimeOwners: [], + sections: ["extensions"], + }, + ]); + }); });