@bitqyh/dsh-codex-bridge
Codex ↔ DSH 跨 agent 接力:会话采集、自动存档、中断恢复提示。
一个包,一条命令安装。
只想用中断恢复? 直接在项目目录跑
dsh-codex-bridge resume --from-agent codex即可,不需要注册插件、 装 profile 或重启 DSH。 下面「安装」段落装的是事件插件(会话采集/自动存档/中断提示), 那套需要注册与重启;它不是使用resume的前置条件。
安装
dsh plugin --profile desktop add @bitqyh/dsh-codex-bridge
然后跑安装命令(不会自动改文件,见下方说明):
npx @bitqyh/dsh-codex-bridge doctor # 先只读检查
npx @bitqyh/dsh-codex-bridge setup # 看计划(不写文件)
npx @bitqyh/dsh-codex-bridge setup --yes # 真正安装 DSH 侧
npx @bitqyh/dsh-codex-bridge setup-codex --yes # 安装 Codex 侧
本包的
cordis.patch.yml已自带 UI 入口(「交接」按钮),dsh plugin add装完重启 DSH 即可使用,不需要再手动改 profile patch。 只有想额外启用事件扫描插件(中断提示)时才需要下面这一步。
最后(可选,仅事件插件):
- 在
~/.dsh/profiles/desktop/cordis.patch.yml加:
- insert:
- id: dsh-codex-bridge-events
name: '@bitqyh/dsh-codex-bridge'
- 重启 DSH
- Codex 里
/hooks重新信任(改了命令哈希会失效,且静默跳过)
为什么不用 postinstall 自动装
两个原因,都实测过:
1. 技术上跑不起来。 DSH profile 用 pnpm 11,pnpm-workspace.yaml 的
allowBuilds 白名单会拦掉未批准的安装脚本:
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: ...
本机白名单只有 cloudflared,所以 postinstall 根本不会执行。
2. 产品上不该这么做。 安装要往
~/Library/Application Support/ai-memory/hooks/dsh/ 写 13 个文件
(7 个 hook 脚本 + 6 个工具脚本)、覆盖 hooks.json——都在项目目录之外。
装一个 npm 包不预期会改这些。
所以改成显式命令:默认只打印计划,加 --yes 才执行,且改任何文件前先备份。
命令
| 命令 | 作用 |
|---|---|
dsh-codex-bridge resume |
构建中断会话的恢复上下文(只读) |
dsh-codex-bridge doctor |
只读检查两侧状态 |
dsh-codex-bridge setup |
安装 DSH 侧(先看计划) |
dsh-codex-bridge setup --yes |
真正执行 |
dsh-codex-bridge setup-codex |
安装 Codex 侧(合并,保留原有其他事件) |
dsh-codex-bridge paths |
打印所有相关路径 |
选项:--server=<url> 覆盖 ai-memory 地址(默认 http://127.0.0.1:49374)。
接续已有聊天
没有「已完成的任务」——只有「本轮已收尾」和「因故中断」,后续都还会有新需求。 所以接续既用于中断恢复,也用于上一轮正常结束之后的后续需求。
三种入口,按顺手程度选:
1. DSH 界面按钮:点输入框下方的「交接」→ 按聊天名称选 → 看预览 → 点「插入当前对话」,内容进草稿,自己确认后再发送。
列表按具体项目分组(组名就是项目名),未归属的合成「非项目会话」放最后; 组顺序按组内最新会话时间倒序。搜索作用于全部会话,展开后行数只增不减。 分组依据与它的边界见下面「项目归属」一节。
只插入草稿,绝不自动发送。 按钮只调 insertText,不碰 setDraft/paste/submit。
2. Codex skill:说「用接续任务接着 DSH 没做完的任务」,或 $resume-task。
3. 命令行(备用入口):
在同一项目目录运行
dsh-codex-bridge resume --from-agent codex,先核验现场,再继续。
反向(从 DSH 切到 Codex)把参数改成 --from-agent dsh。
候选里每个聊天带一个本轮标识:本轮已收尾 / 因故中断 / 未收尾/需核验。
它说的是这一轮怎么结束的,不是任务完成度。徽标是可接续聊天数。
# 备用 agent 在项目目录里跑;只读,不写 handoff/DB,不调用模型
dsh-codex-bridge resume --from-agent codex
# 两边工作目录不一致时,显式给路径
dsh-codex-bridge resume --from-agent codex --cwd '<实际项目路径>'
# 只有出现多个可接续来源时才需要指定(必须完整 native id,不猜 latest)
dsh-codex-bridge resume --from-agent codex --session <完整native-id>
输出是一份机械提取的恢复上下文:定位信息、任务目标(人类原话)、已确认成功 的操作、失败/未知/待审批、最后一条助手可见输出、接续前必须先核验的清单。
它是证据,不是新授权。 备用 agent 按你当前的指令继续任务时,用的是它自己的
模型和工具;resume 本身只读日志。
退出码:0 ready、2 no-candidate、3 needs-selection、4 incomplete。
incomplete 表示信息不足(缺目标、未知 scope、片段无法排序),不要当通过。
它提供什么
| 能力 | 说明 |
|---|---|
| 会话采集 | prompt、工具调用、轮次边界,上报 ai-memory |
| 自动存档 | 每轮结束刷新可恢复快照(零 token) |
| 中断检测 | 限速扫描磁盘日志,发现额度中断/崩溃的会话并注入提示 |
| 手动恢复 | resume 只读解析原生日志,构建中断会话的恢复上下文;可离线、无 DB 降级,不依赖服务运行 |
| 「交接」按钮 | 输入框下方按具体项目分组列出可接续的 Codex 聊天,只把恢复内容插入草稿 |
和已有插件的区别
市场里已有一批「把外部会话带进 DSH」的插件。本插件的定位是 不导入、不切换上下文、不写回——只把会话列清楚,并诚实标注归属判断有多可靠。
与 dsh-codex-history(平铺列表 + 导入)、dsh-assembly.resume(按路径末段命名分组 +
导入会话)、dsh-takeover(新建会话接管)、task-passport(任务状态搬运)的逐条对比
见 COMPARISON.md。
前置条件
分两种,按你要用的能力看:
① 采集 / 自动存档 / 普通 handoff — 需要已安装并运行 ai-memory 服务 (本包不包含):
launchctl list | grep ai-memory
curl -s http://127.0.0.1:49374/healthz # → {"status":"ok"}
没装见 https://github.com/akitaonrails/ai-memory
② resume 手动恢复 — 不需要服务运行:
- 直接读本机原生日志(Codex
~/.codex/sessions、DSH sessions 目录)。 - 没有 ai-memory 数据库也能用,按原生日志降级构建,正文会标 「缺 DB 校验」,提醒你现场核验。
- DSH 日志是 zstd 压缩,需要本机有
zstd命令。 - 缺点:DB 侧的时间线/存档信息拿不到,只有日志里写下来的内容。
配置
{
scriptPath: "", // 留空自动解析(已安装位置 → 包内自带)
throttleSeconds: 600, // 扫描限速窗口
sinceHours: 24, // 只看最近 N 小时有活动的会话
maxReported: 3, // 最多提示几条
enabled: true,
}
设计要点
为什么挂 UserPromptSubmit 而不是 SessionStart
常驻会话不会重新触发 SessionStart。实测一个长期会话:
| 钩子 | 触发次数 |
|---|---|
| PreToolUse / PostToolUse | 821 / 821 |
| UserPromptSubmit | 63 |
| SessionStart | 0 |
用户日常把 agent 挂后台,挂 SessionStart 基本不会跑。
为什么 hooks 全用脚本模式
DSH 钩子在会话沙箱里运行。workspace-write 模式下写不了
~/Library/Application Support/ai-memory,而 ai-memory 原生二进制
先落 spool 再发送,写不进去就整条丢弃:
ai-memory hook warning: failed to spool lifecycle event
实测某会话 762 次钩子调用里 744 次这样丢失。所以本包生成的
hooks.json 7 个事件全部走脚本直连。
为什么插件只提示、不自动写交接
恢复摘要需要理解「他做到哪一步」,机械解析给不了。所以: 插件负责发现和提示,接手的 agent 读 raw 后自己写恢复交接。
包内结构
dsh-codex-bridge/
├── src/index.js 事件插件(中断扫描 + 注入)
├── src/ui-server.js 「交接」按钮的后端(只读接口)
├── src/ui-client.js 「交接」按钮的前端(分组渲染)
├── bin/cli.mjs 安装/诊断/恢复命令
├── cordis.patch.yml 自带 UI 入口
├── assets/hooks/ 7 个 hook 脚本
├── assets/tools/ 存档/扫描/恢复/验收工具
├── tests/ 回归测试(54+19+8+28+52 项)
└── test-plugin.mjs 11 项插件逻辑测试
测试
npm install # 先装 peer 依赖(@deepseek-ai/dsh-llm)
npm test # 全部 188 项
node test-plugin.mjs # 11 项:插件逻辑
node tests/ui-tests.mjs # 16 项:交接按钮前端
python3 tests/batch1-tests.py # 54 项:存档语义 + session 选择
python3 tests/batch2-tests.py # 19 项:验收工具 fixture
python3 tests/batch3-tests.py # 8 项:中断恢复扫描
python3 tests/batch4-resume-tests.py # 28 项:resume 恢复上下文
python3 tests/project-scope-tests.py # 52 项:项目归属判定(含歧义)
从 GitHub 克隆后直接
npm test会报Cannot find package '@deepseek-ai/dsh-llm'——它声明在peerDependencies(装进 DSH profile 时由宿主提供)。 只在仓库里跑测试需要先npm install,devDependencies会把它装上。 已作为插件装进 profile 的开发副本不需要,因为它会沿目录树解析到 profile 的node_modules。
插件逻辑测试覆盖:注册事件、只注入可操作项、活跃会话不提示、去重、限速、 脚本缺失静默、开关、downstream 非 enter 时不注入。
项目归属:怎么分组,以及它不知道什么
「交接」列表按每个具体项目各自一组显示,未归属的合成「非项目会话」放最后。 组顺序按组内最新一条会话的时间倒序(最近在做的项目排最前),组内仍按时间倒序。
分组依据是「目录匹配」,不是会话归属。 这点必须说清楚:
| 依据 | 含义 | 代码里的 project_basis |
|---|---|---|
threads.project_id |
Codex 记录的会话所属项目,权威 | thread |
project_roots.path + cwd 路径边界匹配 |
只能说明 cwd 落在某个登记目录内 | dir |
| 元数据读不到 | 归属未知,不等于「确认非项目」 | unavailable |
| 目录同时登记给多个项目 | 归属未知,不按注册表行序猜一个 | ambiguous |
| 都不命中 | 确实没有依据 | none |
优先级 thread > dir > ambiguous / unavailable > none。当前 Codex 版本的
threads.project_id 整列为空,所以实际跑的是 dir;将来该列有值时自动接管。
几个容易误解的点:
threads.project_id全空不代表这些会话不属于任何项目,只说明这一版 schema 没写入这个字段。所以元数据不可读时标「未知」,不降级成「确认非项目」。- 不把「cwd 不是 home」当作项目,也不按目录名或日期形状(如
2026-11-01)推断—— 日期目录只有在project_roots里登记过才算项目。 - 路径边界匹配:
/a/b不匹配/a/bc;嵌套取最长根;尾斜杠归一。 - 同一目录登记给多个项目时不猜:注册表里两个根路径相同,谁先出现只取决于
SQLite 返回顺序。这时取「第一个」等于让行序静默决定归属,所以标
ambiguous并归入非项目组。(真实案例:.../Liner-transformer同时挂在「论文复现」和 「PPT制作」下。) - 元数据不可用、或目录歧义时,列表会分别单独提示,且这些会话没有被跳过 ——不计入「N 个日志无法确认归属,已跳过」的统计。
默认读 ~/.codex/state_5.sqlite,可用环境变量 BRIDGE_CODEX_STATE_DB
或 --db 覆盖。
边界
- 不包含 ai-memory 服务本身。采集、存档、普通 handoff 需要它;
resume读原生日志不依赖它,缺 DB 时降级并明确标注。 - Codex 侧需要重新信任钩子(信任哈希绑定命令),改完不信任会静默跳过。
这条只影响采集,不影响
resume。 - 插件只发现和提示中断,不自动写恢复交接。
- 中断期间不会有任何自动动作(进程已死),只能等下次有 agent 活着时补。
- 没有后台派发、没有自动切换模型;切换由你手动决定,
resume只负责 把上一个 agent 的现场整理成可核验的文本。 - 未落盘的信息无法重建:日志里没有的事件,恢复上下文里也不会有。
- 源会话如果还活着,先暂停/确认停止再接手,避免两边同时改同一个任务。
No comments yet. Be the first to write one.