前言¶
DSH 适合跑流程、管文件、执行具体任务;Codex CLI 在跨文件推理、架构取舍、长上下文约束一致性上更强。实际工作中经常遇到同一个问题需要两种能力:DSH 把任务推进到需要架构判断的节点,把问题连同背景交给 Codex,Codex 在自己的线程里分析,把结论送回 DSH,后续追问继续落在同一线程。
常见的做法是自己拼脚本、开 HTTP 服务、或者让两个助手在同一个上下文里互相聊。前者容易丢消息、重启后要重来,后者没有边界——谁发起、谁回复、跑了多少轮、能不能停,都说不清。下面介绍的插件解决的是前者:用本地文件做中转,把可靠性问题放在协议层处理。
这是什么¶
mapleru/dsh-codex-bridge 是一个面向 DSH Desktop 的 Cordis Host bundle,由 Mapleru 维护,版本 0.1.0,MIT 许可证。它把一个指定的 DSH 会话和一个专用的 Codex CLI 会话通过本地纯文本文件邮箱双向连接起来。
DSH 会话 ──codex_bridge_send──▶ inbox-codex.jsonl ──▶ codex exec / exec resume
▲ │
└────────────── inbox-dsh.jsonl ◀── 最终答复 / send.mjs ──┘
阶段一只做可靠收发、恢复和停止。它不共享 Codex 桌面聊天上下文,不模拟键盘输入,不修改 Codex 内部数据库,也不新增 HTTP 服务或端口。
运行要求来自 README:DSH Desktop(测试于 0.2.0-rc.2)、Node.js ≥ 22、Codex CLI(测试于 0.166.0-alpha.6)。测试命令 npm test 覆盖 82 个用例,不需要联网,也不需要真实的 Codex。
核心能力¶
两个工具分别给谁用¶
DSH 侧拿到两个工具,只对配置在 routes 里的会话可见,条件是 when: ({agent}) => routeSessions.has(agent.id):
codex_bridge_send(text, replyTo?, route?),返回{status, id, hops, route, queueDepth},失败时返回{status: "busy"|"rejected"|"error", code}。成功只表示消息已写入邮箱,不代表 Codex 已读或已回复。codex_bridge_status(route?),返回实例、ready 状态及原因、各路由的 Codex 会话 ID、当前消息、子进程、队列深度、停车数、最后错误代码、STOP 状态。
普通 DSH 的文本回答不会自动转发,只有模型显式调用发送工具才会产生消息。Codex 侧拿到复制到邮箱根目录的 send.mjs 和 ctl.mjs,用来发消息、看状态、停止、恢复、查询回执。
投递语义¶
- 至少一次投递,重启后可以从游标位置恢复,不丢未消费的消息。
- 无效记录连同原始行和原因写入
parked.jsonl,不会永久堵住队列。 - 达到
maxHops(默认 6)停车,不再调用模型;maxAttempts(默认 3)只在尚未看到任何 Codex 事件时允许重试。 maxQueueDepth(默认 20)、maxMessageBytes(默认 32768)、maxMailboxBytes(默认 8388608)分别给队列、单条消息、单个邮箱文件设上限,满了返回busy,不覆盖未消费数据。STOP是一个文件,存在即暂停新投递和新调用,可以用ctl.mjs stop/resume切换。
绑定到 Codex 桌面线程¶
Codex 桌面端打开某个线程时会持有独占写锁,此时 codex exec resume <thread> 必然失败并报 thread-store conflict: thread <id> already has an active writer。这不是线程丢失。transport: auto(默认)看到这条错误后会自动改用 codex queue,把消息排进同一个线程,再从该线程的 rollout JSONL(~/.codex/sessions/.../rollout-*-<id>.jsonl)读回最终答复,答复以普通 reply 写回邮箱。README 给出的实测数据是入队到答复约 11 s。
代价要说清楚:被排队的回合在桌面线程自己的工作目录和权限配置里执行,而不是 codexWorkingDirectory;绑定桌面聊天等于把该聊天的执行环境交给桥。queue 只保证已入队,期间桥无法中止该回合,超时按 delivery-uncertain 停车,不自动重跑。如果线程真的不存在(no rollout found),仍然按 session-lost 停车,不会新建线程替代,需要运维用 ctl.mjs reset-session <route> 明确解除绑定。
按会话路由¶
不配 routes 时是单路由,一个 DSH 会话绑定一个 Codex 线程(default)。要对接多组会话,按下面的结构配置,每个路由各自绑定一个 DSH 会话和一个 Codex 线程,线程绑定写在 mailbox/routes.json,互不复用。
- DSH 到 Codex:工具按调用方会话自动选路由;同一会话拥有多条路由时必须显式传
route。 - Codex 到 DSH:用
send.mjs --route <id>指定;多路由下不写route会被停车为route-required。 - 回复继承父消息的路由;显式写了不同路由会被拒绝(
route-mismatch)。 - 旧配置(只有
dshSessionId)自动迁移成default路由,session.json的线程绑定也会迁移进routes.json。
安装与启用¶
当前版本的 plugin_manager 是 Host 工具,不是命令行。安装时会写入 profile 的 package.json 和 cordis.patch.yml。
plugin_manager({ action: "list_bundles" })
plugin_manager({ action: "install_bundle", target: "file:<本包目录或包名>" })
plugin_manager({ action: "set_bundle", target: "dsh-codex-bridge", enabled: false })
plugin_manager({ action: "set_bundle", target: "dsh-codex-bridge", enabled: true })
plugin_manager({ action: "remove_bundle", target: "dsh-codex-bridge" })
package.json 声明了 dsh.bundle.patch: ./cordis.patch.yml,补丁里已经有一条 insert(id: codex-bridge)。安装后不要再手工加一条 insert,否则同一个插件会加载两次。
安装完编辑该条目的 config 填入运行参数,examples/cordis.patch.example.yml 是一份完整的配置示例;修改 profile 后按当前 Host 的 HMR/重启规则生效。
配置要点¶
所有运行参数来自 Cordis 配置中 cordis.patch.yml 的 config:。必填项缺失时插件保持 ready:false 并报告原因,不会让宿主崩溃。
两个必填项决定了安全边界:codexExecutable 是 Codex CLI 的绝对路径,启动时会验证文件、版本和能力;codexWorkingDirectory 是 Codex 的工作目录,也是邮箱的沙箱根。root 是邮箱根目录,必须绝对路径,且 path.relative(codexWorkingDirectory, root) 不能逃出该目录。这是刻意设计:邮箱要由 Codex 在 workspace-write 沙箱内写入,不能靠扩大成全盘写权限来解决,否则等于绕过 Codex 的沙箱限制。
其余可调项包括投递来源 deliverySource(webhook 显示为外部通知消息,user 走 prompt() 并保留幂等 requestId)、投递时机 deliveryMode(followup 等当前回合结束再送达,steer 立即送达并开新回合)、单次调用超时 codexTimeoutMs(默认 900000)、轮询间隔 pollIntervalMs(默认 2000)、watcher 防抖 debounceMs(默认 300)、投递失败退避基数 retryBaseMs(默认 5000)、宿主退出收尾上限 shutdownTimeoutMs(默认 4000,小于宿主的 5 s 强制退出)、可选模型 model。
典型用法¶
安装并配好参数后,先看一次状态确认实例 ready:
node .\codex-bridge\tools\ctl.mjs status
在 Codex 侧往 DSH 发一条消息,正文永远走文件或 stdin,不拼进命令行:
node .\codex-bridge\tools\send.mjs --to dsh --file .\msg.txt
'状态如何?' | node .\codex-bridge\tools\send.mjs --to dsh
node .\codex-bridge\tools\send.mjs --to dsh --file .\reply.txt --reply-to <消息ID>
退出码:0 已入队或重复,2 busy,3 拒绝,4 错误;stdout 只输出一行 JSON。
DSH 侧由模型显式调用 codex_bridge_send 把问题发给 Codex,需要时把它挡在当前回合之后(deliveryMode: followup),用 codex_bridge_status 查看队列和停车情况。
出问题时的运维命令:
node .\codex-bridge\tools\ctl.mjs parked
node .\codex-bridge\tools\ctl.mjs stop
node .\codex-bridge\tools\ctl.mjs resume
node .\codex-bridge\tools\ctl.mjs retry <消息ID> [--confirm-uncertain]
node .\codex-bridge\tools\ctl.mjs receipt <你发出的消息ID>
node .\codex-bridge\tools\ctl.mjs reset-session [route]
适用场景与注意¶
适合的场景是明确的:一个 DSH 会话在流程中遇到需要 Codex 能力的问题,要把问题连同背景送过去,再把结论接回来继续推进。不适合用来做两个助手之间的自动对话,消息有来源、有路由、有往返上限,天然是显式调用的模型。
需要注意几点:
- 邮箱是纯文本,
from字段不是身份认证,不要把密钥放进消息内容。 - 插件以当前 DSH 进程的权限运行,安装前应该自己检查源码和许可证(MIT)。
queue模式会把被绑定的桌面线程的执行环境交给桥,确认聊天记录里的权限配置符合预期再启用。- 目录页属于社区站点,与 DeepSeek / 幻方无官方从属关系。
更多细节见 README 的“安全”一节和 examples/ 下的协作目录示例。
结尾¶
这个插件的价值在于把“跨工具调用”从临时脚本变成了一组有游标、有停车、有回执、可恢复、可停止的明确规则。它不试图让两个模型合并成一个上下文,而是把各自的边界划清楚:DSH 管流程和文件,Codex 管一个问题一次深度分析,中间用本地文件做可靠投递。
目录页:https://www.skillhub.cn/plugins/Mapleru/dsh-codex-bridge
GitHub:https://github.com/Mapleru/dsh-codex-bridge