Superpowers辅助工具链:可验证的工程契约体系
1. 为什么 Superpowers 的辅助工具不是“锦上添花”而是项目存活的命脉Superpowers 这个项目很多人第一眼看到的是它那个炫酷的、类似 Unity 的可视化编辑器界面是它支持实时协作、多端预览的“超能力”标签。但真正把它从一个漂亮的原型拖进工业级开发流水线里的从来不是主编辑器那几块 UI 组件而是藏在scripts/、tools/、bin/这些目录深处的一堆.js、.sh、.bat文件——它们才是让整个项目在 Node.js 生态里不散架、不卡死、不误判的底层筋骨。我第一次接手 Superpowers 的 CI 构建任务时就栽在这上面。当时只改了core/下一行 TypeScript 类型定义本地npm run dev跑得飞起可一推到 GitHub Actions构建直接失败Error: Cannot find module graphviz。排查了三小时才发现不是依赖没装而是build-graph.js这个脚本在 CI 环境里调用dot命令时压根没走PATH查找逻辑而是硬编码了/usr/local/bin/dot——而 Ubuntu runner 默认只装了graphviz-cli二进制名是dot但路径在/usr/bin/。这个细节文档里没写报错日志里只甩出一句“module not found”根本看不出是路径问题。后来翻源码才明白build-graph.js根本不是用require(graphviz)这种 npm 包而是用child_process.spawn()直接调系统命令行它依赖的是 Graphviz 的 CLI 工具链不是 JS 库。这就是 Superpowers 辅助工具设计的第一个精妙之处它不做抽象封装而是做精准控制。它不假装自己能跨平台兼容所有 Graphviz 安装方式而是把“你必须装好 dot 命令”这件事变成一个明确、可验证、可调试的契约。你在scripts/check-prerequisites.js里能看到它用which dot和dot -V双重校验在build-graph.js开头它会先执行spawnSync(dot, [-V])失败就process.exit(1)并打印清晰错误“Graphviz CLI not found. Please install Graphviz and ensure dot is in your PATH.” —— 不是模糊的“依赖缺失”而是直指操作系统的环境配置。第二个精妙在于它对 Git Worktrees 的深度绑定。Superpowers 的插件生态极度依赖git worktree实现“主干开发 插件并行迭代 版本快照归档”三合一。它的scripts/sync-plugins.js不是简单地git pull而是先遍历.git/worktrees/目录检查每个 worktree 的HEAD是否指向远程origin/main再比对git status --porcelain输出是否为空。一旦发现某个插件 worktree 有未提交变更它不会强行覆盖而是抛出带插件名的警告“Plugin sp-websocket has uncommitted changes in worktree at ./plugins/sp-websocket. Skipping sync.” —— 把 Git 的原子性语义原封不动地映射到构建流程中。这背后是极强的工程克制它拒绝用fs.copyFileSync()做暴力同步因为那会抹掉开发者正在调试的临时修改它也不用git stash自动暂存因为 stash 有冲突风险且不可审计。它选择“看见问题、明确阻断、提示人介入”把自动化和人工判断的边界划得清清楚楚。这种设计让团队新人第一天就能看懂“为什么我的插件没更新上去”而不是陷入npm run build报错的迷宫。第三个常被忽略的点是 WebSocket 工具链的“非对称职责划分”。Superpowers 的编辑器前端通过 WebSocket 连接后端服务codex但它的辅助脚本从不碰 WebSocket 协议本身。scripts/start-dev-server.js只负责启动codex进程并监听其 stdout 中是否出现WebSocket server listening on ws://字样而真正的连接健康检查交给独立的tools/ws-healthcheck.js—— 它用ws库建立真实连接发送{type:ping}等待{type:pong}响应超时 5 秒就退出并返回非零状态码。这种拆分让“服务启动”和“服务可用”成为两个可独立验证的阶段CI 流程可以先跑start-dev-server.js再并行跑ws-healthcheck.js失败时能准确定位是进程没起来还是网络/认证层出了问题。所以当你在热搜里看到“superpowers安装”“websocket连接失败”“graphviz安装教程”这些词高频共现时别只当它们是用户搜索习惯的偶然叠加。它们恰恰暴露了 Superpowers 辅助工具链的真实作用域它不是让你“装完就能用”的傻瓜式向导而是为你搭建了一套可观察、可中断、可验证的工程契约体系。你装 Node.js它校验 v16你配 Graphviz它校验dot -V输出你启 WebSocket 服务它用真实连接测通断。每一个脚本都是一份写给操作系统和团队成员的、不容含糊的承诺书。2.scripts/目录下的四类核心脚本从环境校验到发布打包的完整闭环Superpowers 的scripts/目录表面看是十几个.js文件的集合实则是一个精密咬合的齿轮组。我把它们按职责划分为四类环境准备类、开发支撑类、构建发布类、质量保障类。每一类都解决一个具体、不可妥协的工程问题且彼此之间有严格的执行顺序依赖。下面我逐个拆解不仅告诉你“它做什么”更说清“为什么必须这样设计”。2.1 环境准备类check-prerequisites.js与setup-dev-env.js的双重保险这类脚本是所有工作的起点目标只有一个确保你的机器不是“裸奔状态”。check-prerequisites.js是守门员setup-dev-env.js是急救包二者缺一不可。check-prerequisites.js的核心逻辑极其朴素用process.version检查 Node.js 版本正则匹配/v(\d)\.(\d)\.(\d)/要求主版本 ≥16因 Superpowers 大量使用fs.promises和AbortController用which(git)检查 Git 是否在 PATH对 Graphviz它不满足于which(dot)而是执行spawnSync(dot, [-V], { encoding: utf8 })捕获输出并用正则/Graphviz version (\d\.\d\.\d)/提取版本号要求 ≥2.40因旧版 dot 不支持-Tsvg:cairo渲染选项最关键的是 WebSocket 依赖检查它尝试require(ws)但仅用于验证模块存在性不实际创建连接——因为真正的连接健康度留给ws-healthcheck.js去做。提示这个脚本的输出设计很讲究。成功时只打印绿色[✓] Node.js v18.17.0失败时用红色[✗] Graphviz: dot command not found. Run brew install graphviz (macOS) or sudo apt install graphviz (Ubuntu).。它不解释原理只给可执行的修复命令把认知负担降到最低。setup-dev-env.js则是兜底方案。当check-prerequisites.js发现缺失项时它不自动安装避免权限和路径污染而是生成一个dev-env-setup.sh脚本。这个生成脚本的精妙在于它根据当前 OS 自动适配包管理器。检测到 macOS 且有 Homebrew就写brew install node git graphviz检测到 Ubuntu/Debian就写sudo apt update sudo apt install -y nodejs npm git graphviz甚至对 Windows它会生成一个 PowerShell 脚本调用winget install OpenJS.NodeJS Git.Git Graphviz.Graphviz。它不越俎代庖但把“下一步该敲什么命令”精确到字符级别。2.2 开发支撑类start-dev-server.js与watch-plugins.js的协同心跳这是让开发者能“边写边看”的核心。start-dev-server.js启动codex后端服务watch-plugins.js监控插件变更并热重载二者通过 IPC 通道联动。start-dev-server.js的关键不在启动本身而在进程生命周期管理。它用child_process.spawn()启动codex但设置了stdio: [ignore, pipe, pipe]将子进程的 stdout/stderr 重定向到自己的流中。然后它启动一个lineReader逐行解析codex的日志输出。当匹配到WebSocket server listening on ws://时它向父进程即npm run dev的 shell发送process.send({ type: server-ready, port: 15900 })若 30 秒内未匹配到它调用child.kill(SIGTERM)并process.exit(1)。这确保了npm run dev不会卡在“等待服务启动”上无限期挂起。watch-plugins.js的精妙则体现在变更感知的粒度控制。它不监听整个plugins/目录而是读取plugins/.worktrees.json由sync-plugins.js维护只对其中列出的每个插件 worktree 路径调用chokidar.watch()。监听事件也做了分级add/change事件触发npm run build仅构建该插件unlink事件不立即响应而是等待 2 秒防抖避免编辑器保存临时文件触发误判最关键的是ready事件当 chokidar 完成初始扫描后它会向codex发送一个POST /api/plugins/reload请求强制后端重新加载插件清单。这保证了前端编辑器侧的插件列表永远与磁盘上的最新 worktree 状态一致。2.3 构建发布类build-core.js与package-release.js的原子化交付构建不是“把代码编译成二进制”而是“把意图转化为可部署产物”的严谨过程。build-core.js负责产出中间产物package-release.js负责组装最终发布包二者通过约定的dist/目录交接。build-core.js的核心是多目标并行构建。它用p-map库并发执行三个子任务tsc -b tsconfig.core.json编译核心 TS 代码rollup -c rollup.config.js打包前端资源CSS/JSnode scripts/build-graph.js生成架构图 SVG用于文档。每个子任务失败整个构建立即终止。它还内置了缓存机制检查dist/core/目录下是否存在package.json和index.js若存在且tsconfig.core.json的mtime早于dist/core/index.js的mtime则跳过 TS 编译——这使二次构建速度提升 70%。package-release.js则体现“发布即契约”的思想。它不直接压缩dist/而是先执行cp -r dist/core ./release/core再cp -r plugins/*/dist ./release/plugins/最后cp LICENSE README.md ./release/。完成后它运行sha256sum ./release/**/* ./release/SHA256SUMS生成校验文件。最关键的是版本注入它读取package.json的version用sed -i s/version: .*/version: 1.8.0/ ./release/core/package.jsonmacOS或sed -i s/version: .*/version: 1.8.0/ ./release/core/package.jsonLinux统一替换。这确保了无论从哪个分支构建发布包内的version字段都与 Git Tag 严格一致杜绝了“构建产物版本号与 Tag 不符”的线上事故。2.4 质量保障类test-integration.js与lint-staged.js的防御性编程质量不是测试覆盖率数字而是“让错误在抵达用户前就被拦截”的层层关卡。test-integration.js是端到端防线lint-staged.js是代码提交前哨。test-integration.js的设计哲学是“最小可行验证”。它不模拟全量用户操作而是聚焦三个黄金路径WebSocket 连接链路启动codex用ws库连接ws://localhost:15900发送{type:auth,token:test}验证是否收到{type:auth-success}插件加载链路在连接成功后发送{type:get-plugins}验证响应中是否包含sp-websocket插件信息实时协作链路启动两个 WebSocket 客户端Client A 发送{type:scene-update,data:{...}}验证 Client B 是否在 500ms 内收到相同消息。每个路径失败脚本立即process.exit(1)并打印失败步骤的完整请求/响应日志。它不追求 100% 覆盖但确保最核心的三条链路 100% 可用。lint-staged.js则把质量左移到 Git 提交瞬间。它的配置不是全局的而是按文件类型精准打击*.ts文件先eslint --fix再prettier --write最后tsc --noEmit --skipLibCheck做类型检查*.json文件只运行prettier --write*.md文件运行markdownlint-cli2 --fix。它甚至处理了编辑器格式化冲突当prettier和eslint对缩进有分歧时它强制eslint的indent规则优先因为 Superpowers 的 TS 代码规范以 ESLint 为准。这避免了“我刚用 Prettier 格式化完提交时 ESLint 又报错”的挫败感。这四类脚本共同构成一个闭环环境校验 → 开发支撑 → 构建发布 → 质量保障。它们之间没有冗余每个环节的输出都是下一个环节的明确输入。你无法绕过check-prerequisites.js直接运行build-core.js因为后者依赖前者验证的dot命令你也无法跳过test-integration.js就执行package-release.js因为 CI 配置强制要求所有测试通过才能进入打包阶段。这种环环相扣的设计正是 Superpowers 能在多年迭代中保持稳定性的底层密码。3.tools/目录的隐藏战场Graphviz 图谱生成与 WebSocket 健康探针的实战细节如果说scripts/目录是 Superpowers 的“前台业务流程”那么tools/目录就是它的“后台基础设施引擎”。这里没有花哨的 UI只有直面操作系统和网络协议的硬核工具。其中build-graph.jsGraphviz 图谱生成和ws-healthcheck.jsWebSocket 健康探针是最具代表性的两个它们的设计思路、实现细节和踩坑经验值得单独深挖。3.1build-graph.js如何用 Graphviz 将代码依赖关系可视化为可维护的架构图build-graph.js的使命很纯粹把core/目录下 TypeScript 模块间的import关系转换成一张 SVG 格式的依赖图谱。但这张图不是装饰品而是工程师理解系统耦合度、识别循环依赖、评估重构影响范围的核心依据。它的精妙始于对 Graphviz 的“非标准用法”。首先它不生成.dot源文件再调用dot编译而是直接构造 Graphviz 的 DOT 语言字符串通过 stdin 输入给dot进程。这样做有两个硬性好处一是避免磁盘 I/O生成临时.dot文件再删除二是规避 Windows 下文件路径转义的坑。核心代码片段如下const dotInput digraph G { rankdirLR; node [shapebox, stylefilled, fillcolor#f0f8ff]; edge [color#4a90e2, arrowheadvee]; // 模块节点 core/app [labelapp.ts]; core/scene [labelscene.ts]; core/ui [labelui.ts]; // 依赖边 core/app - core/scene; core/app - core/ui; core/scene - core/ui; }; const dotProcess spawn(dot, [-Tsvg, -o, docs/architecture.svg], { stdio: [pipe, ignore, pipe] }); dotProcess.stdin.write(dotInput); dotProcess.stdin.end();这段代码的关键在于spawn的参数[-Tsvg, -o, docs/architecture.svg]明确指定了输出格式和路径而stdio: [pipe, ignore, pipe]让dot从 stdin 读取 DOT 代码错误信息从 stderr 捕获。这比fs.writeFileSync(temp.dot, dotInput); execSync(dot -Tsvg temp.dot -o docs/architecture.svg);更健壮因为后者在execSync超时时会留下垃圾文件。其次它对 TypeScript 的import解析采用白名单驱动的 AST 分析而非正则匹配。它用typescript-eslint/typescript-estree解析每个.ts文件遍历ImportDeclaration节点提取source.value如./scene。但关键过滤逻辑在isRelevantImport()函数里function isRelevantImport(sourceValue) { // 只分析 core/ 内部模块排除 node_modules 和绝对路径 if (sourceValue.startsWith(node_modules) || sourceValue.startsWith(/)) return false; // 排除相对路径中的 .. 上溯防止越界 if (sourceValue.includes(..)) return false; // 白名单只保留 core/ 下的模块路径 return sourceValue.startsWith(./) || sourceValue.startsWith(../); }这个白名单策略让图谱只反映核心模块间的直接依赖剔除了types/node、lodash等第三方依赖的噪音使图表真正服务于架构决策。最后它对循环依赖的检测不是靠 Graphviz 的cycleshape属性那只是视觉提示而是在生成 DOT 代码前用 Tarjan 算法在内存中构建依赖图并检测强连通分量。当检测到core/scene→core/ui→core/scene这样的环时它不会静默忽略而是将相关节点的fillcolor设为醒目的#ffcccc浅红并在 SVG 图片下方生成文字报告WARNING: Circular dependency detected between core/scene and core/ui. Refactor to break the cycle.。这把抽象的算法结果转化成了工程师一眼能懂的行动指令。3.2ws-healthcheck.js一个 120 行脚本如何扛住生产环境的 WebSocket 连接压力ws-healthcheck.js是 Superpowers 健康检查体系中最“薄”也最“韧”的一层。它只有 120 行代码却要应对 WebSocket 连接中所有可能的失败场景DNS 解析失败、TCP 连接超时、TLS 握手失败、WebSocket 协议升级失败、认证 Token 无效、服务端主动关闭等。它的设计是“用最简代码覆盖最多故障面”的典范。它的主干逻辑异常清晰async function healthCheck() { const ws new WebSocket(ws://${host}:${port}/, { headers: { Authorization: Bearer ${token} } }); let timeoutId; const timeoutPromise new Promise((_, reject) { timeoutId setTimeout(() reject(new Error(Connection timeout)), 5000); }); try { await Promise.race([ new Promise((resolve, reject) { ws.onopen () resolve(connected); ws.onerror (err) reject(err); ws.onclose (e) reject(new Error(Closed with code ${e.code}: ${e.reason})); }), timeoutPromise ]); // 连接成功后立即发送 ping 并等待 pong const pingPromise new Promise((resolve, reject) { ws.onmessage (event) { try { const data JSON.parse(event.data); if (data.type pong) resolve(pong-received); } catch (e) { reject(e); } }; ws.send(JSON.stringify({ type: ping })); setTimeout(() reject(new Error(No pong received)), 2000); }); await Promise.race([pingPromise, timeoutPromise]); console.log([✓] WebSocket health check passed); process.exit(0); } catch (error) { console.error([✗] WebSocket health check failed: ${error.message}); process.exit(1); } finally { clearTimeout(timeoutId); ws.close(); } }这段代码的精妙之处在于对Promise.race()的三次嵌套使用第一次race判断连接是否在 5 秒内建立第二次race判断是否在 2 秒内收到pong避免长连接空闲超时finally块确保无论成功失败ws.close()都会被调用防止连接泄漏。它对错误的分类处理更是直击痛点onerror事件捕获底层网络错误如net::ERR_CONNECTION_REFUSED直接rejectonclose事件捕获服务端关闭原因e.code是标准 WebSocket 错误码如1006表示异常关闭4001是 Superpowers 自定义的认证失败码e.reason是服务端传来的文本描述JSON.parse失败捕获非法消息格式这往往是服务端 Bug 的征兆。我在一次生产部署中就靠它快速定位了问题ws-healthcheck.js报错[✗] WebSocket health check failed: Closed with code 4001: Invalid token format。而前端日志只显示WebSocket connection closed。正是因为这个脚本把4001错误码和Invalid token format原文完整打印出来我们立刻意识到是codex服务的 JWT 解析逻辑有 bug而不是网络或证书问题。10 分钟内就修复了避免了更大范围的影响。这两个工具一个把代码结构变成可视资产一个把网络连接变成可量化指标。它们不追求功能繁多但每行代码都直指要害是 Superpowers 工程师日常调试、发布、运维时最信赖的“瑞士军刀”。4.bin/目录与 Git Worktrees 的深度整合如何让插件开发像写 Git Commit 一样自然bin/目录在 Superpowers 中是个低调但至关重要的存在。它不像scripts/那样被npm run调用也不像tools/那样提供通用能力而是作为开发者工作流的快捷入口把 Git Worktrees 的强大能力封装成几个简单的命令行工具。sp-worktree、sp-plugin-sync、sp-release-diff这三个脚本共同构成了 Superpowers 插件生态的“操作系统内核”。4.1sp-worktree不只是git worktree add而是插件开发的“沙盒创建器”sp-worktree的核心价值是把git worktree这个 Git 高级特性变成插件开发者无需记忆命令的“一键沙盒”。它的设计逻辑是每个插件都应该有一个独立、隔离、可随时丢弃的开发环境。当你执行sp-worktree create sp-websocket时它做的远不止git worktree add plugins/sp-websocket origin/main智能路径规划它检查plugins/目录是否存在若不存在则mkdir -p plugins/检查sp-websocket名称是否符合^[a-z][a-z0-9-]*$正则强制小写字母开头只含小写、数字、短横线不符合则报错模板注入在plugins/sp-websocket/下它会cp -r tools/plugin-template/* .复制一份标准化的插件骨架包括package.json预设name: sp-websocket、src/index.ts含export default class WebSocketPlugin extends Plugin { ... }、README.md含开发指南Git 初始化执行git init然后git remote add origin https://github.com/superpowers/sp-websocket.git如果该仓库存在最后git fetch origin并git checkout -b main origin/main依赖链接最关键的一步它运行npm link ../core将本地core/目录软链接到插件的node_modules/中确保插件开发时能实时使用最新的核心 API无需反复npm install。这使得sp-worktree create不是一个 Git 命令包装器而是一个插件项目初始化向导。开发者不需要知道git worktree的语法只需要记住sp-worktree create plugin-name就能获得一个开箱即用、与主干完全隔离、又与核心实时联动的开发沙盒。4.2sp-plugin-sync如何用 Git Worktrees 实现“零冲突”的插件版本管理sp-plugin-sync解决的是插件生态中最棘手的问题如何让多个插件开发者在不互相干扰的前提下共享同一套主干代码并能随时回滚到任意历史版本它的答案是用 Git Worktrees 为每个插件维护一个“版本快照分支”。它的执行流程是典型的“三步走”快照拉取遍历plugins/.worktrees.json对每个插件 worktree执行git -C path fetch origin获取所有远程分支版本对齐读取plugins/.worktrees.json中该插件的targetBranch字段如targetBranch: v1.2.0然后执行git -C path checkout v1.2.0状态清理执行git -C path reset --hard HEAD清除所有本地修改并git -C path clean -fdx删除未跟踪文件如node_modules/、dist/。这个流程的精妙在于它把“版本管理”从“人脑记忆”变成了“配置文件驱动”。plugins/.worktrees.json的结构如下{ sp-websocket: { path: plugins/sp-websocket, targetBranch: v1.2.0, lastSynced: 2023-10-15T08:23:45Z }, sp-graphql: { path: plugins/sp-graphql, targetBranch: main, lastSynced: 2023-10-15T08:23:45Z } }当团队决定将sp-websocket升级到v1.3.0时只需修改 JSON 中的targetBranch字段再运行sp-plugin-sync所有开发者本地的sp-websocketworktree 就会自动切换到新版本且不会影响sp-graphql的main分支开发。这彻底消除了“我升级了插件但同事的环境还卡在旧版”的协作摩擦。4.3sp-release-diff用 Git Worktrees 自动生成发布说明的魔法sp-release-diff是 Superpowers 发布流程中最具“生产力魔法”的工具。它利用 Git Worktrees 的并行能力自动生成专业、准确、可审计的发布说明Release Notes让每次git tag都不再是一次手动填表的苦差事。它的原理是在发布前用git worktree创建一个临时工作区检出上一个 Tag再与当前main分支做差异分析。执行sp-release-diff v1.1.0 v1.2.0时它会创建临时 worktreegit worktree add /tmp/sp-release-diff-v1.1.0 v1.1.0在临时 worktree 中运行git log v1.1.0..v1.2.0 --oneline --no-merges --format- %s (%an) /tmp/changelog.md提取所有非合并提交过滤出core/和plugins/目录的变更git diff --name-only v1.1.0 v1.2.0 | grep -E ^(core|plugins)/ | sort -u生成 Markdown 格式输出## v1.2.0 (2023-10-15) ### Core Changes - Add WebSocket authentication middleware (John Doe) - Optimize scene rendering pipeline (Jane Smith) ### Plugin Updates - sp-websocket: Add support for binary message types (John Doe) - sp-graphql: Fix schema introspection timeout (Jane Smith)这个输出直接复制粘贴就能作为 GitHub Release 的正文。它不依赖任何外部服务不解析 commit message 的语义如 conventional commits而是基于 Git 的原始数据确保了 100% 的准确性。我在一次紧急 hotfix 发布中用它 30 秒内就生成了完整的 release notes而手动整理至少要 15 分钟。bin/目录下的这三个工具共同证明了一个道理最好的开发者工具不是功能最全的而是最懂你工作流的。它们不试图替代 Git而是把 Git Worktrees 的能力翻译成插件开发者每天都在做的具体动作创建新插件、同步插件版本、发布新版本。当你习惯了sp-worktree create你就再也不会想回到git clone cd npm install的原始时代。5. 从源码到实践一个真实案例——如何用 Superpowers 工具链诊断并修复 WebSocket 连接失败理论终须落地。我来分享一个最近在客户现场遇到的真实案例它完美串联了前面讲到的所有工具check-prerequisites.js的环境校验、ws-healthcheck.js的精准诊断、sp-plugin-sync的版本回滚以及build-graph.js的架构分析。这个案例就是 Superpowers 辅助工具设计价值的终极证明。5.1 问题现象编辑器前端疯狂报错后端日志一片空白客户反馈Superpowers 编辑器打开后左下角持续显示Connecting to server...10 秒后弹出错误WebSocket connection to ws://127.0.0.1:15900/ failed: Connection closed before receiving a handshake response.。更诡异的是codex进程明明在运行ps aux | grep codex能看到但curl http://localhost:15900/health返回503 Service Unavailable且codex的 stdout 日志里没有任何关于 WebSocket 启动的记录只有一行Server started on http://localhost:15900。这是一个典型的“症状明显、原因隐蔽”的问题。前端报 WebSocket 连接失败但后端日志却显示 HTTP 服务正常WebSocket 服务仿佛凭空消失。常规思路会去查codex的配置文件、防火墙、端口占用但这次我决定从 Superpowers 的工具链入手。5.2 第一步用check-prerequisites.js排除环境幻觉我首先怀疑是 Node.js 版本或 Graphviz 导致的隐性崩溃。执行node scripts/check-prerequisites.js输出[✓] Node.js v18.17.0 [✓] Git 2.39.2 [✗] Graphviz: dot command not found. Run brew install graphviz (macOS) or sudo apt install graphviz (Ubuntu).客户机器上确实没装 Graphviz但问题是codex启动失败跟 Graphviz 有什么关系check-prerequisites.js只是校验它不会导致codex崩溃。我继续往下查。5.3 第二步用ws-healthcheck.js定位连接失败的精确环节我跳过前端直接用工具链的“探针”打穿问题node tools/ws-healthcheck.js --host 127.0.0.1 --port 15900 --token test输出[✗] WebSocket health check failed: Connection timeout这确认了问题在连接建立阶段而非认证或消息收发。但timeout太笼统。我修改ws-healthcheck.js在new WebSocket()后加一行console.log(Attempting to connect to ws://127.0.0.1:15900/)再运行发现日志卡在这一行之后无输出。这说明WebSocket