fix: harden ACP plugin tools bridge (#56867) (thanks @joe2643)

This commit is contained in:
Peter Steinberger
2026-03-29 21:03:43 +01:00
parent e24091413c
commit 73477eee4c
14 changed files with 612 additions and 47 deletions

View File

@@ -6,37 +6,57 @@
import { execSync } from "node:child_process";
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
const isGlobal = process.env.npm_config_global === "true";
if (!isGlobal) {
process.exit(0);
}
export const BUNDLED_PLUGIN_INSTALL_TARGETS = [
{
pluginId: "acpx",
sentinelPath: join("node_modules", "acpx", "package.json"),
},
];
const __dirname = dirname(fileURLToPath(import.meta.url));
const extensionsDir = join(__dirname, "..", "dist", "extensions");
const DEFAULT_EXTENSIONS_DIR = join(__dirname, "..", "dist", "extensions");
// Extensions whose runtime deps include platform-specific binaries and therefore
// cannot be pre-bundled. Add entries here if new extensions share this pattern.
const NEEDS_INSTALL = ["acpx"];
export function createNestedNpmInstallEnv(env = process.env) {
const nextEnv = { ...env };
delete nextEnv.npm_config_global;
delete nextEnv.npm_config_prefix;
return nextEnv;
}
for (const ext of NEEDS_INSTALL) {
const extDir = join(extensionsDir, ext);
if (!existsSync(join(extDir, "package.json"))) {
continue;
export function runBundledPluginPostinstall(params = {}) {
const env = params.env ?? process.env;
if (env.npm_config_global !== "true") {
return;
}
// Skip if already installed (node_modules/.bin present).
if (existsSync(join(extDir, "node_modules", ".bin"))) {
continue;
}
try {
execSync("npm install --omit=dev --no-save --package-lock=false", {
cwd: extDir,
stdio: "pipe",
});
console.log(`[postinstall] installed bundled plugin deps: ${ext}`);
} catch (e) {
// Non-fatal: gateway will surface the missing dep via doctor.
console.warn(`[postinstall] could not install deps for ${ext}: ${String(e)}`);
const extensionsDir = params.extensionsDir ?? DEFAULT_EXTENSIONS_DIR;
const exec = params.execSync ?? execSync;
const pathExists = params.existsSync ?? existsSync;
const log = params.log ?? console;
for (const target of BUNDLED_PLUGIN_INSTALL_TARGETS) {
const extDir = join(extensionsDir, target.pluginId);
if (!pathExists(join(extDir, "package.json"))) {
continue;
}
if (pathExists(join(extDir, target.sentinelPath))) {
continue;
}
try {
exec("npm install --omit=dev --no-save --package-lock=false", {
cwd: extDir,
env: createNestedNpmInstallEnv(env),
stdio: "pipe",
});
log.log(`[postinstall] installed bundled plugin deps: ${target.pluginId}`);
} catch (e) {
// Non-fatal: gateway will surface the missing dep via doctor.
log.warn(`[postinstall] could not install deps for ${target.pluginId}: ${String(e)}`);
}
}
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
runBundledPluginPostinstall();
}

View File

@@ -15,7 +15,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
*
* Each entry: { src: repo-root-relative source, dest: dist-relative dest }
*/
const STATIC_EXTENSION_ASSETS = [
export const STATIC_EXTENSION_ASSETS = [
// acpx MCP proxy — co-deployed alongside the acpx index bundle so that
// `path.resolve(dirname(import.meta.url), "mcp-proxy.mjs")` resolves correctly
// at runtime (see extensions/acpx/src/runtime-internals/mcp-agent-command.ts).
@@ -25,15 +25,19 @@ const STATIC_EXTENSION_ASSETS = [
},
];
function copyStaticExtensionAssets() {
for (const { src, dest } of STATIC_EXTENSION_ASSETS) {
const srcPath = path.join(ROOT, src);
const destPath = path.join(ROOT, dest);
if (fs.existsSync(srcPath)) {
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.copyFileSync(srcPath, destPath);
export function copyStaticExtensionAssets(params = {}) {
const rootDir = params.rootDir ?? ROOT;
const assets = params.assets ?? STATIC_EXTENSION_ASSETS;
const fsImpl = params.fs ?? fs;
const warn = params.warn ?? console.warn;
for (const { src, dest } of assets) {
const srcPath = path.join(rootDir, src);
const destPath = path.join(rootDir, dest);
if (fsImpl.existsSync(srcPath)) {
fsImpl.mkdirSync(path.dirname(destPath), { recursive: true });
fsImpl.copyFileSync(srcPath, destPath);
} else {
console.warn(`[runtime-postbuild] static asset not found, skipping: ${src}`);
warn(`[runtime-postbuild] static asset not found, skipping: ${src}`);
}
}
}