From aa8ac6cad92d064e395960dd45be254d86592c1e Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Fri, 16 Oct 2026 14:54:43 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20v2.8.10=20=E2=80=94=20the=20server=20sta?= =?UTF-8?q?rts=20again=20on=20plugin=20installs=20(js-yaml=20is=20a=20runt?= =?UTF-8?q?ime=20dependency)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v2.8.9 crashed at start on every plugin install with "Cannot find module 'js-yaml'": server/lib/lane-detect.js requires it, but it was only a devDependency that redoc used to pull in, and v2.8.8 dropped redoc. The plugin installs with --omit=dev. This box hid it: Debian's global /usr/share/nodejs has js-yaml. Reproduced with a pruned install and the global paths off; fixed by moving js-yaml (and argparse in the lock) to dependencies. New runtime-deps.test.js fails on any server/ require that is not a dependency. --- .../screens.snapshot.test.tsx.snap | 2 +- openapi.yaml | 2 +- package-lock.json | 8 +-- package.json | 4 +- server/__tests__/runtime-deps.test.js | 68 +++++++++++++++++++ 5 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 server/__tests__/runtime-deps.test.js diff --git a/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap b/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap index d69f9f03..e242e399 100644 --- a/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap +++ b/client/src/pages/__tests__/__snapshots__/screens.snapshot.test.tsx.snap @@ -7239,7 +7239,7 @@ exports[`screen snapshots > Settings — about 1`] = ` - UI build v2.8.9 (rebuild client to match) + UI build v2.8.10 (rebuild client to match)

diff --git a/openapi.yaml b/openapi.yaml index a040a083..5077b430 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3,7 +3,7 @@ openapi: 3.0.3 info: title: Agent Dashboard for Claude Code API - version: 2.8.9 + version: 2.8.10 description: HTTP API for real-time Claude Code session monitoring, agent lifecycle tracking, analytics, pricing, hooks ingestion, and workflow intelligence. contact: name: Nguyễn Ngọc Trí Vĩ diff --git a/package-lock.json b/package-lock.json index 043cff9b..bacd86f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agenthub", - "version": "2.8.9", + "version": "2.8.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agenthub", - "version": "2.8.9", + "version": "2.8.10", "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { @@ -14,6 +14,7 @@ "adm-zip": "^0.6.1", "cors": "^2.8.5", "express": "^4.21.2", + "js-yaml": "^4.1.0", "multer": "^2.0.0", "swagger-ui-express": "^5.0.1", "systray2": "^2.1.4", @@ -27,7 +28,6 @@ }, "devDependencies": { "concurrently": "^9.1.2", - "js-yaml": "^4.1.0", "prettier": "^3.8.1" }, "engines": { @@ -215,7 +215,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-flatten": { @@ -1099,7 +1098,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "dev": true, "funding": [ { "type": "github", diff --git a/package.json b/package.json index e0efb296..b679d355 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agenthub", - "version": "2.8.9", + "version": "2.8.10", "bin": { "agenthub": "bin/agenthub.js" }, @@ -75,6 +75,7 @@ "adm-zip": "^0.6.1", "cors": "^2.8.5", "express": "^4.21.2", + "js-yaml": "^4.1.0", "multer": "^2.0.0", "swagger-ui-express": "^5.0.1", "systray2": "^2.1.4", @@ -88,7 +89,6 @@ }, "devDependencies": { "concurrently": "^9.1.2", - "js-yaml": "^4.1.0", "prettier": "^3.8.1" }, "engines": { diff --git a/server/__tests__/runtime-deps.test.js b/server/__tests__/runtime-deps.test.js new file mode 100644 index 00000000..2b2c2024 --- /dev/null +++ b/server/__tests__/runtime-deps.test.js @@ -0,0 +1,68 @@ +/** + * @file runtime-deps.test.js + * @description Every package the server code requires is a runtime dependency. + * A plugin install runs `npm install --omit=dev`, so a package listed only in + * devDependencies — or one that arrived only as some other package's + * dependency — is missing there. v2.8.9 crashed at start on every plugin + * install: `js-yaml` (server/lib/lane-detect.js) was a devDependency that + * `redoc` used to pull in, and dropping `redoc` took it away. Machines with + * Debian's global /usr/share/nodejs never saw it, so this checks the manifest + * instead of trusting a local `require` to fail. + * @author Nguyễn Ngọc Trí Vĩ + */ + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const { builtinModules } = require("node:module"); + +const ROOT = path.join(__dirname, "..", ".."); +const pkg = require(path.join(ROOT, "package.json")); + +// Required only behind a runtime check, never on a plain Node server. +const GUARDED = new Set(["electron"]); + +function sourceFiles(dir) { + const out = []; + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, e.name); + if (e.isDirectory()) { + if (!["node_modules", "__tests__", "vendor"].includes(e.name)) out.push(...sourceFiles(p)); + } else if (/\.(c?js|mjs)$/.test(e.name)) out.push(p); + } + return out; +} + +/** Bare package names `file` requires, comments left out. */ +function requiredPackages(file) { + const code = fs + .readFileSync(file, "utf8") + .replace(/\/\*[\s\S]*?\*\//g, "") + .replace(/(^|[^:"'`\\])\/\/.*$/gm, "$1"); + const names = new Set(); + for (const m of code.matchAll(/\brequire\(\s*["']([^"'./][^"']*)["']\s*\)/g)) { + const spec = m[1]; + if (spec.startsWith("node:")) continue; + const name = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0]; + if (!builtinModules.includes(name)) names.add(name); + } + return names; +} + +describe("runtime dependencies", () => { + it("every package server/ requires is in dependencies or optionalDependencies", () => { + const runtime = new Set([ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.optionalDependencies || {}), + ]); + const missing = []; + for (const file of sourceFiles(path.join(ROOT, "server"))) { + for (const name of requiredPackages(file)) { + if (!runtime.has(name) && !GUARDED.has(name)) + missing.push(`${path.relative(ROOT, file)}: ${name}`); + } + } + assert.deepEqual(missing, []); + }); +});