mapleru/dsh-codex-bridge:用一個本地文件郵箱連接 DSH 會話和 Codex CLI 線程

前言

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

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜