From 505d140aeb350286f79191b83cea9ec674171ba4 Mon Sep 17 00:00:00 2001 From: Josh Lehman Date: Wed, 18 Mar 2026 10:55:25 -0700 Subject: [PATCH] fix: stabilize build dependency resolution (#49928) * build: mirror uuid for msteams Add uuid to both the msteams bundled extension and the root package so the workspace build can resolve @microsoft/agents-hosting during tsdown while standalone extension installs also have the runtime dependency available. Regeneration-Prompt: | pnpm build failed because @microsoft/agents-hosting 1.3.1 requires uuid in its published JS but does not declare it in its package manifest. The msteams extension dynamically imports that package, and the workspace build resolves it from the root dependency graph. Mirror uuid into the root package for workspace builds and keep it in extensions/msteams/package.json so standalone plugin installs also resolve it. Update the lockfile to match the manifest changes. * build: prune stale plugin dist symlinks Remove stale dist and dist-runtime plugin node_modules symlinks before tsdown runs. These links point back into extension installs, and tsdown's clean step can traverse them on rebuilds and hollow out the active pnpm dependency tree before plugin-sdk declaration generation runs. Regeneration-Prompt: | pnpm build was intermittently failing in the plugin-sdk:dts phase after earlier build steps had already run. The symptom looked like missing root packages such as zod, ajv, commander, and undici even though a fresh install briefly fixed the problem. Investigate the build pipeline step by step rather than patching TypeScript errors. Confirm whether rebuilds mutate node_modules, identify the first step that does it, and preserve existing runtime-postbuild behavior. The key constraint is that dist and dist-runtime plugin node_modules links are intentional for runtime packaging, so do not remove that feature globally. Instead, make rebuilds safe by deleting only stale symlinks left in generated output before invoking tsdown, so tsdown cleanup cannot recurse back into the live pnpm install tree. Verify with repeated pnpm build runs. --- extensions/msteams/package.json | 3 ++- package.json | 1 + pnpm-lock.yaml | 6 ++++++ scripts/tsdown-build.mjs | 34 +++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index 6365de0b725..c29afcfebbb 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -5,7 +5,8 @@ "type": "module", "dependencies": { "@microsoft/agents-hosting": "^1.3.1", - "express": "^5.2.1" + "express": "^5.2.1", + "uuid": "^11.1.0" }, "openclaw": { "extensions": [ diff --git a/package.json b/package.json index 124f51927db..5b7887dcef4 100644 --- a/package.json +++ b/package.json @@ -718,6 +718,7 @@ "tar": "7.5.11", "tslog": "^4.10.2", "undici": "^7.24.4", + "uuid": "^11.1.0", "ws": "^8.19.0", "yaml": "^2.8.2", "zod": "^4.3.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0447e4ef9bc..73e329eedb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,6 +196,9 @@ importers: undici: specifier: ^7.24.4 version: 7.24.4 + uuid: + specifier: ^11.1.0 + version: 11.1.0 ws: specifier: ^8.19.0 version: 8.19.0 @@ -477,6 +480,9 @@ importers: express: specifier: ^5.2.1 version: 5.2.1 + uuid: + specifier: ^11.1.0 + version: 11.1.0 extensions/nextcloud-talk: dependencies: diff --git a/scripts/tsdown-build.mjs b/scripts/tsdown-build.mjs index 871e89ddbf0..79f24ea65b8 100644 --- a/scripts/tsdown-build.mjs +++ b/scripts/tsdown-build.mjs @@ -1,6 +1,8 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; const logLevel = process.env.OPENCLAW_BUILD_VERBOSE ? "info" : "warn"; const extraArgs = process.argv.slice(2); @@ -8,6 +10,38 @@ const INEFFECTIVE_DYNAMIC_IMPORT_RE = /\[INEFFECTIVE_DYNAMIC_IMPORT\]/; const UNRESOLVED_IMPORT_RE = /\[UNRESOLVED_IMPORT\]/; const ANSI_ESCAPE_RE = new RegExp(String.raw`\u001B\[[0-9;]*m`, "g"); +function removeDistPluginNodeModulesSymlinks(rootDir) { + const extensionsDir = path.join(rootDir, "extensions"); + if (!fs.existsSync(extensionsDir)) { + return; + } + + for (const dirent of fs.readdirSync(extensionsDir, { withFileTypes: true })) { + if (!dirent.isDirectory()) { + continue; + } + const nodeModulesPath = path.join(extensionsDir, dirent.name, "node_modules"); + try { + if (fs.lstatSync(nodeModulesPath).isSymbolicLink()) { + fs.rmSync(nodeModulesPath, { force: true, recursive: true }); + } + } catch { + // Skip missing or unreadable paths so the build can proceed. + } + } +} + +function pruneStaleRuntimeSymlinks() { + const cwd = process.cwd(); + // runtime-postbuild links dist/dist-runtime plugin node_modules back into the + // source extensions. Remove only those symlinks up front so tsdown's clean + // step cannot traverse into the active pnpm install tree on rebuilds. + removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist")); + removeDistPluginNodeModulesSymlinks(path.join(cwd, "dist-runtime")); +} + +pruneStaleRuntimeSymlinks(); + function findFatalUnresolvedImport(lines) { for (const line of lines) { if (!UNRESOLVED_IMPORT_RE.test(line)) {