/** * 验证「端口已被占用」这条路径 —— 也就是**事故本身那一刻**的状态。 * * 为什么单独一个文件:之前我把它塞在一条 `node -e` 里,进程在 * 「await 端口探测」那步被 30s 超时掐断(Command aborted), * 于是这条路径**从来没真正跑完过**。而它恰恰是唯一会决定 * 「子进程会不会被打死」的分支。 * * 两层断言: * 1. 进程存活到结束(事故根因:不加 on("error") 时 Node 抛未捕获异常) * 2. 该进程没有抢占已占用的端口(语义正确性:让给真正的占用者) */ import { createServer } from "node:http"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); const AGENT = "/root/.pi/agent"; const target = process.argv[2] ?? "pi-a2a"; const PORT = Number(process.argv[3] ?? 14779); const srvKey = target === "pi-acp" ? "__piAcpServer" : "__piA2aServer"; // 先占住端口,模拟「父进程已经监听」 const blocker = createServer(() => {}); await new Promise((r) => blocker.listen(PORT, "127.0.0.1", r)); console.log(`SETUP: 端口 ${PORT} 已被本进程占用(模拟父进程)`); process.env.PI_A2A_PORT = String(PORT); process.env.PI_ACP_PORT = String(PORT); delete process.env.PI_SUBAGENT_CHILD; // 走「非子进程但端口被占」这条分支 delete process.env.PI_SUBAGENT_PARENT_DEPTH; const jiti = require(`${AGENT}/npm/node_modules/jiti/lib/jiti.mjs`).createJiti(import.meta.url, { interopDefault: true, fsCache: false, moduleCache: false, }); const factory = (await jiti.import(`${AGENT}/extensions/${target}/index.ts`)).default; const tools = []; factory({ registerTool: (t) => tools.push(t.name), on: () => {}, sendUserMessage: () => {} }); // 扩展的端口探测是异步的(300ms 超时),给它时间 await new Promise((r) => setTimeout(r, 1200)); const srv = globalThis[srvKey]; const problems = []; if (srv && srv.listening) { problems.push(`抢占了已占用端口 ${PORT}(语义错误)`); } if (srv && srv.address()) { problems.push("仍报告了一个 bound address(应为空)"); } if (tools.length === 0) { problems.push("工具未注册 —— 扩展功能被降级掉了(出站工具必须仍可用)"); } // 断言 1:占位者仍能应答(没被踢掉) const alive = await new Promise((r) => { const req = blocker.listening; r(req); }); if (!alive) problems.push("占用者不再监听"); console.log(`OBSERVED: ${target} 创建了 server=${srv !== undefined}, 工具=[${tools.join(",")}]`); if (problems.length) { console.log(`RESULT=FAIL ${problems.join("; ")}`); blocker.close(); process.exit(1); } console.log(`RESULT=PASS 进程存活且未抢占已占用端口;出站工具仍注册`); blocker.close(); process.exit(0);