历史开发记录
本页保留原始中文技术记录。计划中的版本和能力不代表已发布的 npm 功能;请先查阅当前版本说明。
V3.1 Windows ConPTY 与终端可靠性开发记录
日期:2026-10-11。稳定基线:origin/main 的 0b924f8(V3.0.0 Web-first)。开发分支:codex/v3.1-conpty-reliability。本次不发布版本,npm 版本仍为 3.0.0,node-pty 仍为 1.1.0,没有生产依赖或 lockfile 变更。
2026-10-13 合并审查补录:以下策略结论和测试记录描述原开发分支,早于 PR #118。整合
main后,Windowsauto沿用 #118 的隔离 Host 原生能力探测:验证成功使用 native,失败才 fallback;显式native严格运行,显式stdio不启动原生探测。保留 #118 的换行处理、异步有界清理、Host 单一元数据写入和退出确认。审查另修复失联状态轮询按历史 PID 杀进程的问题,真实独立进程回归已先复现误杀再验证修复。原始 node-pty worker 自然退出问题仍是历史探针的已知限制,不能把该探针视为已通过;产品 Host 退出释放资源的路径另行验收。本次用户已授权创建 PR 并合并,不发布新版本。
结论与验收边界
当前保持 Windows Node 22/24 的 auto → stdio-fallback 策略。新增 进程级、严格 native 实验,失败不降级。macOS arm64 Node 22.23.3 / 24.17.0 已通过真实 Native PTY 集成测试。授权手动 CI 已验证 Windows 22/24 的产品 Native Host 交互与生命周期;原始库自然退出的 worker 清理仍失败,因此不改变默认策略。Ubuntu Native 已通过。最终 Runner 明细见验收补录。
原来的 Windows 限制并不能证明 Node 22 的 ABI 不兼容。已读取的历史 CI 日志证明 Session/目录清理失败;没有足够的原生崩溃栈定位到 ConPTY 或 Node 的单一根因。本次不会把修复普通 Session 生命周期问题描述成已修复 Windows 原生崩溃。
历史证据与故障分类
限制最早出现在 500c752 / PR #17,最后一个子提交说明为 fix: stabilize Windows Node 22 session cleanup。
通过 gh 获取了以下日志:
- 32867508239,
2e0f39a3,尚未限制 Windows native:external Adapter 注册 fixture 最后rmSync出现EBUSY。 - 32868040172,
3aa8b166,尚未限制 Windows native:同样的目录EBUSY,另有 MCP persistent Session 布尔断言失败。普通 Session open/write/read/close 测试在同一 Windows Node 22 作业中通过。 4cb639cd随后引入 Windows Node 22+ fallback;历史成功不等于真实 ConPTY 验收。
| 分类 | 已有证据 / 目前处理 |
|---|---|
| 加载失败 | Windows 的 require('node-pty') 仅加载 JS 入口,conpty.node 在 WindowsPtyAgent 构造时才加载。独立探针显式检查绑定;缺少依赖的独立 checkout 测试证明 auto 可诊断 fallback、native 必须失败。历史日志未证明 ABI 加载失败。 |
| 初始化失败 | 公开的 spawn 同时完成 pseudo console 和进程创建,不能仅凭调用失败精确划分。已按 node-pty 的已知错误模板分类;原始探针把调用前阶段与绑定阶段分开。严格 Windows 实验拒绝不支持 ConPTY 的旧 build,防止 WinPTY 冒充 ConPTY。 |
| 启动失败 | 旧 Manager 超过 5 秒后仍返回 starting。未绑定 endpoint 的 Host fixture 已复现,并修复为明确失败与进程回收。稳定性窗口仍保留。 |
| 运行时错误 | 已注入真实 PTY 对象的 EPIPE 事件,验证错误监听、脱敏固定原因及退出;这是故障注入,不是 Windows 自然崩溃复现。正常 native keyboard / ANSI / UTF-8 / resize 使用真实 PTY。 |
| 关闭清理失败 | 历史 Windows EBUSY 已确认。新测试同时检查 CLI、后代、Host 存活和目录删除;macOS 已通过。忽略 SIGTERM 的未就绪 Host 在本机复现泄漏,现加入有界 SIGKILL 升级。Windows 句柄释放仍需真实 Runner 验收。 |
| 测试/启动误判 | 同一历史作业已有 native Session 测试通过,但后续 fixture 清理失败;清理异常可能覆盖实际断言。V3.0 的 Windows 独立 executable 文件、stdio close/drain、IPC init 消费顺序修复全部保留。新 raw probe 比较 attached 与 detached,避免仅测库 import 或 HTTP 200。 |
node-pty 1.1.0 的源码使用 N-API,并且 Windows exit callback 有 Napi::Error::Fatal / assert 路径。源码存在这些路径不能证明本次历史失败经过它们。没有证据要求升级 node-pty,因此暂不升级。
实现范围
| 文件 | 改动 |
|---|---|
pty-runtime.mjs | 严格实验模式、固定的脱敏 failure stage/code、平台/Node 信息;native stream 错误处理;stdio UTF-8 decoder;UTF-8 安全尾部截断;截断事实;清理定时器;Windows tree kill 顺序。 |
session-host.mjs | 将 backend、diagnostics、cols/rows 持久化;close 返回 active 时不提前关闭 endpoint,允许 Manager 升级清理。 |
session-manager.mjs | 公共记录与 durable 记录保留诊断/尺寸;捕获 owned fork 的异常退出;启动超时明确失败并回收;未就绪 Host 的有界强制退出。旧 terminal 记录的 PID 不用于强制杀进程,避免 PID 复用伤及其他进程。 |
workspace-task-runtime.mjs | 双终端的既有 Session view 添加 backend、diagnostics、cols/rows。任务/角色/协议保持不变。 |
session-service.mjs | 无需修改;既有 forwarding 自动带出新增 Session 字段。 |
app.js / terminal-model.mjs | 现有 footer 显示 Native PTY / stdio fallback / unknown;tooltip 提供脱敏诊断。Resize 仅在后端确认后记为同步,允许失败后相同尺寸重试,并修复 pending A→B→A。 |
.github/workflows/web-ci.yml | 增加仅手动的 native_pty_regression,不扩大日常矩阵;保留 windows_regression。 |
诊断不复制 argv、env、Cookie、Token、登录凭据或会话内容。新增的失败原因均为固定代码。已有会话输出仍沿用既有脱敏和有界读取;没有新日志收集系统。
API 只添加字段,不改变现有操作或 schemaVersion。旧记录缺少这些字段时返回 null,UI 显示 unknown,不能据此声称 native。nativeStatus 区分 pending/running/unavailable/disabled/exited/failed,failure 包括 selection/load/initialization/spawn/runtime/exit/host/cleanup 阶段。
最小复现与运行方法
先在独立开发 checkout 执行 npm ci。探针只启动仓库内离线 Node fixture,不调用 Codex、Antigravity 或模型服务,不读取登录凭据。
# 原始 node-pty:显式绑定加载、TTY、输入、中文/ANSI、尺寸、自然退出和目录释放
node scripts/diagnose-native-pty.mjs
node scripts/diagnose-native-pty.mjs --attached
# 正常日常回归:真实 fallback、缺少绑定、错误注入、UTF-8、前端尺寸队列
npm run test:pty
# 必须是 native,绝不把 fallback 的通过记成 native 通过
npm run test:pty:nativediagnose-native-pty.mjs 在独立 worker 中调用原始 node-pty,阶段通过 IPC 回传,忽略普通 stdio 时仍可观察进度。20 秒监督超时、worker exit code/signal、最后阶段、CLI 存活和临时目录删除都进入 JSON。成功需要 worker 自然退出,不能用 process.exit(0) 隐藏 native worker/handle 泄漏。绑定、TTY、尺寸和子进程事实都来自实际库或实际子进程,不来自产品 backend 字符串。
scripts/pty-probes/terminal-probe.cjs 在 raw mode 接收 Enter、Backspace、四个方向键、Ctrl+C,并计算大粘贴内容 SHA-256;使用 alternate screen、clear/home/color 重绘。子进程查询真实 console window size;Windows 不一定发送 Node resize event,因此还轮询 getWindowSize()。正常 /exit 和异常 /crash 分别退出 0 / 9;/spawn 创建后代,验证进程树释放。
实验开关:
# POSIX:仅本次启动进程及其 owned Host 使用
COORDINATE_AGENTS_PTY_MODE=native node bin/coordinate-agents.mjs web --root <offline-test-root># PowerShell:当前进程环境;结束测试后清除
$env:COORDINATE_AGENTS_PTY_MODE = 'native'
node bin/coordinate-agents.mjs web --root <offline-test-root>
Remove-Item Env:COORDINATE_AGENTS_PTY_MODE支持 auto(默认、保持平台政策)、native(Node >=20、严格无 fallback)、stdio(显式兼容后端);拼写错误直接拒绝。不要写入用户 config,不设置系统环境变量。已发布的 V3.0.0 不含此开关;本开发分支不做任何发布操作。实验应使用独立 root,因为已有 Session 持久化与复用契约不会自动重启一个历史 fallback Session。
浏览器离线 QA 入口:node scripts/pty-probes/native-browser-server.mjs。它输出临时 URL/directory,建立独立用户配置、真实 executable 和双终端 fixture;SIGTERM/SIGINT 会关闭 owned Sessions 并删除临时目录。此入口仅供人工/ego-browser QA,不进入 npm 白名单或日常自动测试发现。
测试结果
本机:macOS arm64,OS release 27.0.0。运行记录的摘要见 v3.1-local-evidence.json。
npm ci:通过,node-pty 1.1.0。npm run check:web:Web 57 项 + Shared 81 项通过。npm run test:package:7 项通过;真实 pack 后独立 consumer 安装生产依赖,运行离线双终端、角色提示、重连、重启、归档和清理。精简 files 白名单、Plugin 缺省与版本独立测试通过。npm run test:legacy:114 项通过。npm run test:adapters:33 项通过。npm run test:pty:native:macOS Node 22/24 各 6 项通过,零 skip;增加真实 Host uncaught error 的固定原因与资源释放测试。- 真实 ego-browser/xterm:中文与绿色 ANSI 输出、alternate screen 重绘、Backspace/方向键、Ctrl+C、大粘贴 53,248 字节(子进程 SHA-256 验证)、尺寸 61×32 → 76×47(xterm/API/子进程一致)、浏览器刷新后原 Session IDs 保留,通过。这里只代表 macOS 浏览器。
npm run test:full:最终安全清理实现c12933c的完整回归 408/408、零 skip,通过。后续只增加初始化前平台诊断和调整专用 fixture,另以 Session 9/9、Native 6/6 和 tarball 7/7 验证。初次扩大回归主动停止,不计为通过;另一次 407 项回归中,obsolete benchmark, performance report, and test-support paths are absent from repository root失败(406/407),原因是临时新建了受既有布局约束禁止的根目录。改放scripts/pty-probes后保留原断言并通过,未放宽布局测试。Native 接受测试通过显式入口运行,支持 Node 18 的 full suite 不会误启动严格 native 实验或常驻 QA server。
Session 集成测试不仅检查输出,还检查真实 backend、三个标准流的 TTY、initial 120×30、resize 103×31、Session Manager 重建后的重连/复用、CLI 0/9、Host SIGKILL 后不能复用、强制关闭后代,以及 Host/CLI 退出后临时 root 可删除。普通 shell 子进程不是模型代理;退出测试不推断 HTTP 200 为成功。
手动 CI 与默认策略的闸门
授权后使用 CLI:
git push -u origin codex/v3.1-conpty-reliability
gh workflow run web-ci.yml --ref codex/v3.1-conpty-reliability -f native_pty_regression=true矩阵只在手动输入为 true 时启用:Windows Node 22/24、Ubuntu Node 24、macOS Node 22 的严格 native;Windows Node 20 同时验证 native 与默认既有行为;Windows Node 18 验证稳定 fallback 与严格 native 拒绝。Node 18 没有“跳过失败的 native 断言”:它执行相应兼容策略断言,不属于 native 验收平台。
交付状态(首次本地验收)
已完成且有测试证据:历史回溯;两个独立启动方式的 raw MRP;严格模式和诊断贯穿 Session/Workspace;macOS Node 22/24 native 键盘/ANSI/UTF-8/resize/生命周期;本机 xterm QA;UTF-8、尺寸队列、Host 启动超时缺陷修复;四组要求的回归;独立 tarball 安装。
已实现但缺少真实平台验证:Windows 的真实浏览器/xterm 端到端显示与高输出长期压力;不同 Windows build、架构与第三方 TUI 的长期稳定性。Windows ConPTY Host 与 Ubuntu native 的 Runner 证据已取得,见补录。
尚未完成:定位历史 Windows 原生崩溃的确定根因(历史日志不足);Windows Node 22/24 原始库资源释放的完整验收;长时间高输出/多次重启压力验收;将 Windows native 设为默认的决策。仅在真实平台结果充分且没有未解决故障时才考虑默认切换。当前不具备默认切换条件。
没有修改 npm/Plugin 版本、Tag、Release、dist-tag、日常 CI 矩阵或 Web-first 架构;没有创建 PR、合并 main、发布 npm、引入新协议/框架或恢复旧分发包。
Runner 验收补录
第一轮 38138450785,提交 aaaf21d;第二轮 38139026280,提交 726630a。两轮 overall 均为 failure,未把失败平台当作通过。
Windows Node 22.23.3 / 24.21.0,build 10.0.26100、x64:原始探针真实 conpty.node 加载、TTY、输入、中文、ANSI、resize 和 CLI 0 退出全部到达 complete,但独立 worker 20 秒后仍活着,timedOut:true、childAlive:false、failureStage:cleanup。同一作业的产品 Native Session Host open/write/read/resize、重连、正常/异常退出、Host SIGKILL、后代关闭和目录删除通过。因此不能把问题归为“绑定加载失败”或“Node 22 无法创建 ConPTY”。Node 20.20.2 的 raw probe 出现同样资源清理故障,进一步排除了它仅特有于 Node 22/24 的假设。
node-pty 1.1.0 源码中的实际路径:WindowsPtyAgent._$onProcessExit() → _flushDataAndCleanUp() → _cleanUpProcess() 只销毁 outSocket;ConoutConnection 的 worker 在 conoutSocketWorker.js 中创建并监听一个 server,自然退出路径没有调用 worker dispose。这解释了“CLI 退出、TTY 交互正常、独立库进程仍留存”的复现。它是目前有源码和运行证据的资源释放障碍;它不能证明历史 PR #17 的每一个 EBUSY 或断言错误都来自同一原因。
第二轮的 pty.kill() after onExit 对比能令 raw worker 自然退出。然而 WindowsPtyAgent.kill() 会异步启动 console-list helper,再向返回的 PIDs 发信号;helper 超时 5 秒后退回原 PID,且没有已退出/身份校验。已退出 PID 可能被重用,因此该方法不能作为安全 disposal。实验选项和 CI 对比步骤已移除,不进入生产修复。当前 stable registry 的 node-pty 仍为 1.1.0,没有经证明可替代的稳定升级。
生产路径保留独立 Host:在 drained terminal fact 持久化之后退出 Host,释放其 ConPTY worker/文件句柄。Windows force-close 和 stream-failure 使用当前 owned process tree;不在 taskkill 完成后再调用会延迟按旧 PID 发信号的 node-pty kill。另有固定码 uncaughtExceptionMonitor,保留 Node 默认崩溃行为,不吞掉异常、不写原始栈或私密错误值。
Ubuntu Node 24 第一轮因为新增的 error observer 与 node-pty 对 POSIX 正常 EIO/errno 5 的处理不一致而失败。修正为同样的正常读端结束分类后,第二轮完整 Native suite、原有 Session 回归与 raw 两种启动方式都通过;没有通过跳过断言处理。Windows 18 fallback 和 macOS 22 Native 两轮均通过。Windows 20 的第二、三轮原有 Session 回归通过,但专门的 unready fixture 提前退出。仅限制 Unix SIGTERM hook 没有修复早退。随后 fixture 改为与真实 Host 一样 await init 消费后再断开 IPC,并打开专门 fixture 的诊断;聚焦作业验证通过。此前早退的具体机制没有足够日志,不能将其归为生产 ConPTY 故障。
最后一轮安全实现验证为 38139799801,提交 c12933c,overall 为 failure:Windows 22/24 各 5/6;Windows 20 为 4/6(raw 清理和专用 fixture),Ubuntu/macOS 各 6/6;原有 Session/前端回归每个平台 14/14。Existing platform policy and Session regressions 使用 always() 独立执行,确保 raw probe 失败不会阻止既有行为回归。Windows raw 的资源释放断言依然保留,预期会阻止 native 完整验收变绿。
独立 tarball 在 macOS Node 22/24 下还以 COORDINATE_AGENTS_PTY_MODE=native 强制模式完成了两组 7/7 安装验收,包括 Web 进程停止后新 Web 进程重连原 Session。它们不是 fallback 验收。Windows 上的独立 tarball native 安装未单独执行,不能外推为已验证。
最终产品验收与默认策略:
| 环境 | 实验中的实际 backend / 结果 | 默认 auto / 决策 |
|---|---|---|
| Windows 22.23.3 | node-pty / 真实 ConPTY;产品 Host 5 项通过,raw 自然资源释放 1 项失败 | stdio-fallback;不切换 |
| Windows 24.21.0 | node-pty / 真实 ConPTY;产品 Host 5 项通过,raw 自然资源释放 1 项失败 | stdio-fallback;不切换 |
| Windows 20.20.2 | 原有 Session/前端回归 14/14;Native Host 实测通过;raw 清理失败。超时 fixture 后续聚焦验证通过 | 原有 node-pty 策略保留 |
| Windows 18 | fallback 与严格 native 拒绝的 9 项策略、14 项原有回归通过 | stdio-fallback 保留 |
| Ubuntu 24.21.0 | 原生 PTY 6/6 + 14/14,通过 | node-pty 保留 |
| macOS Node 22 | 原生 PTY 6/6 + 14/14,通过;本机 22/24 和 native tarball 另有证据 | node-pty 保留 |
聚焦 38140458643(cc7ad99)只筛选 Windows 20 unready Host 一项,1 项通过、其余 5 项未选中;它不是完整 Native acceptance,不能抵消整套矩阵的失败。新增 host_startup_diagnostic=true 入口仅运行该一项,避免为 fixture 诊断反复启动完整矩阵,不影响日常 CI。
最终代码审查无剩余重要安全问题。新增初始平台/Node 诊断在 Host 模块尚未加载时就落盘,故 native abort 早于 Runtime 构造也能保留环境事实;Host 的失败记录继承它。错误标识采用有限白名单,未知标识统一为 UNKNOWN,测试包含秘密样式的大写 error.code,避免用字母正则伪称安全脱敏。
已完成且有证据:受控真实 ConPTY 恢复入口、实际 backend 可观测性、真实交互与 Host 生命周期的 Windows 22/24 验证、macOS/Linux 回归、Windows 18/20 既有行为、Native tarball 本机独立安装、代码审查与技术报告。
已实现但缺少真实平台验证:Windows 浏览器实机/xterm 完整端到端重绘;Windows 独立 native tarball 安装;不同 build/ARM64、长时间高输出和大量重启压力。
尚未完成:原始 node-pty 自然退出后的完整 worker/handle disposal;历史 PR #17 原生崩溃的确定根因;Windows Node 22/24 完整 native gate 变绿与默认切换。当前不具备默认切换条件,开发成果保留在分支,不执行发布或合并。
最终补充说明:e0a065f 只补充 Runtime 尚未构造时的平台诊断。其后本机 Session 9/9、Native 6/6、tarball 7/7 通过;完整 408 项与跨平台全套矩阵的执行提交仍明确记录为 c12933c,不将其说成重新运行了每个后续诊断字段变更。
初始化前诊断的最终 Windows Node 20 聚焦验证:38142027731(e0a065f),通过。该作业仍只验证超时、环境诊断与 owned Host 回收,不计为完整 Native acceptance。