Dot 连不上 Codex 以及 WebSocket 代理问题修复

最近 codex 新添加了 dots 的功能,但是使用起来的时候还是有诸多的不习惯,以及我想让 Dot 帮我协调电脑上的 Codex,比如读取一个已有会话,再把需要继续处理的内容发过去时,发现经常遇到连接失败。有时电脑显示在线,但是 Dot 仍然无法读取会话,只能通过浏览本地的文件间接查看;使用时,桌面端偶尔还会弹出恢复会话失败的提示

这里主要记录如何解决该问题,相关的代码可以查看 codex-durable-relay

最常见的报错是:

Codex app tool request failed
MCP -32000

但这本身没有给出多少信息,把工具失败的时间和桌面日志对齐后,在相关记录中看到了:

hostKind=durable
open_timeout / ECONNREFUSED
initialized=false
remote_unavailable

这里的 durable 是日志中用于区分这类云端会话连接的标识。说明相关 WebSocket 没能完成初始化,后面的会话读取或恢复才返回通用工具错误

同时,在这几次失败的调用里,工具在操作目标会话之前,会先读取发起请求的任务。前面这一步就失败了,后面自然无法继续。所以表面上看是“Dot 不能给本地 Codex 发消息”,实际故障可能发生在更早的云端任务读取环节

WebSocket 可以简单理解为一条持续保持的双向连接。连接建立后,双方可以在同一条通道上继续交换数据。它是通用网络协议。“系统代理已开启”也并不等于“所有程序发出的连接都由代理接管”,系统代理提供的是一套代理设置,应用使用的网络实现还需要读取并采用它。有些组件使用浏览器的网络栈,有些使用 Node.js 或其他网络库;即使都在同一个桌面应用里,代理处理方式也可能不同

我们先做了一个不携带账号凭据的网络对照:

探测方式 当时观察到的结果
不显式使用代理,连接目标域名 出现连接超时;部分后续测试能建立 TCP,但 TLS 被重置
显式通过已有本地代理连接同一域名 能完成 TLS 验证,并收到 HTTP 响应

接着,我们检查了当时安装版本的相关连接实现。公共 durable WebSocket 的连接分支没有显式接入系统代理;所使用的 ws 连接方式,也不能简单靠默认的全局代理处理来覆盖。不过 ws 本身支持代理。它的官方 FAQ说明,可以通过 https-proxy-agent 或 socks-proxy-agent 接入。问题在于应用是否为相应分支配置了合适的连接器,而不是 WebSocket 协议天生不能使用代理

公开讨论中我们也看到了类似的代理路径问题,例如 openai/codex #49829

发现代理接管可能有缺口之后,TUN 自然成为候选方案,TUN 通常通过虚拟网络接口,在更底层接管符合路由规则的流量。因此,即使某个程序没有采用系统代理,它的连接仍有机会被送进代理

TUN 不等于所有流量都必须经过同一个节点,也可以按规则分流。不过,它会涉及更大范围的网络接管。我当时不想为了一个应用中的一条连接,改变整台电脑的网络处理方式,于是让大模型尝试能不能像配置 Codex 的代理那样,做一个范围更明确的调整

我们沿着这个方向检查了几种办法:

  • 环境变量和 Node 的环境代理开关。 对当时这条使用自定义 createConnection 的路径,单加 NODE_USE_ENV_PROXY=1 并不能补上代理处理。
  • Codex 的其他代理配置。 例如 respect_system_proxy 属于另一部分网络处理,但不能说明它能控制这条桌面 durable 连接。
  • 只修改 DNS 或目标 IP。 我们在单次进程测试中指定公共 DNS 返回的地址,仍遇到 TLS 重置。因此,仅把域名换到另一个公网 IP,并不足以解决当时的问题。
  • 模型的 WebSocket 设置。 supports_websockets=false 影响的是模型服务相关传输,不能用来关闭或修复桌面应用管理云端会话的这条 WebSocket。

最后,我们选择了一个更具体的做法:保留现有代理,让一个小程序只负责把这个固定目标的连接送进去,核心思路就是 Codex 仍然连接原来的云端域名。我们通过一条受控的 hosts 映射,让这个域名在本机指向回环地址;本机中继接到连接后,再通过已有代理的 HTTP CONNECT 隧道,把字节转发给真正的服务端

链路大致如下:

Codex 连接 codex-cloud-backend.chatgpt.com:443
                    │
                    │ 本机 hosts 映射
                    ▼
          中继监听 127.0.0.2:443
                    │
                    │ 向已有代理请求 HTTP CONNECT
                    ▼
       本地代理 127.0.0.1:7890(默认端口)
                    │
                    ▼
      codex-cloud-backend.chatgpt.com:443

127.0.0.2 是本机回环地址。这里的 7890 是默认的本地代理端口,公开版本支持在安装时指定其他端口

这与前面“只换 DNS 或公网 IP”的尝试有一个区别:前者仍然是在尝试直接访问某个公网地址;这里则把连接先交给本机中继,再明确通过已有代理建立上游连接。

中继只搬运字节,不负责理解聊天内容,也不需要实现一套 WebSocket 协议。TLS 握手仍由原客户端与目标服务完成,域名、SNI 和证书校验保留。我们没有安装自签名证书,也没有关闭证书验证

它也只允许转发到固定目标,而不是一个可以随意指定目的地的通用代理。不过,hosts 是整机生效的,所以其他使用系统解析访问同一个域名的程序,也会受到这条映射影响

通过最初的短时测试得到了这样的结果:

  1. 启用中继后,桌面日志从初始化失败变为 initialized=true、connected
  2. 桌面进程的实际连接到达了本机中继
  3. 原生会话读取接口成功读到了此前失败的同一个云端会话
  4. 测试到期撤回后,hosts 恢复原始内容,监听端口释放;随后 durable 的 open_timeout 再次出现

说明方案应该是有效的

网络恢复之后,我在桌面端向 Dot 创建的云端任务输入消息,又遇到了另一种错误:

invalid turn/start params:
AbsolutePathBuf deserialized without a base path

对应错误码是 -32602。这次服务端在约 0.22 秒内就返回了“参数无效”,与之前连接建立超时的故障阶段明显不同。

日志中还出现了 Linux 目录前缀与 Windows 路径混在一起的现象。当时的请求还附带了 workspaceWrite 和一个显式可写目录;writableRoots 等权限路径也可能涉及跨平台解析。

我们找到的相近公开案例提到了切换权限选项。于是,在正确打开原云端任务、恢复网络连接后,我把该会话输入框附近的权限选项从“自定义”改成了 “帮我批准(Approve for me)”,再提交消息发现成功了

此外,打开任务的入口也曾造成误判。某次不带主机信息的链接让客户端用 hostId=local 去找云端任务,于是报找不到记录。后来验证过的链接形式是:

codex://threads/<会话ID>?hostId=durable

实际使用时,应优先从原任务页面进入,或使用应用复制出的完整链接。带对主机参数解决的是“去哪里找任务”,不代表网络、访问权限和消息提交也都正常

最后完善了下上面的思路,现在采用的流程是:

登录 Windows
    ↓
普通用户启动器开始等待本地代理
    ↓
通过代理的 TLS 预检成功
    ↓
启动本机中继监听
    ↓
受保护的维护任务核验进程身份,接入自有 hosts 映射
    ↓
Codex 按自身的重连机制恢复连接

如果代理还没准备好,就继续等待,不提前把域名导向一个没有监听的地址。运行中遇到上游波动,会检查恢复情况,而不是不断切换 hosts

权限也做了分工:日常转发由普通用户进程完成,需要高权限的 hosts 维护和生命周期清理由固定的受保护组件处理。程序文件和特权脚本放在受保护的安装目录里,避免长期从普通可写目录加载高权限代码

posted @ 2026-10-10 16:26  rdcamelot  阅读(183)  评论(1)    收藏  举报