uu88s/dsh-session-handoff ↗★ 0

@uu88s/dsh-session-handoff

复制会话ID或将整个会话交接给Codex 适合需要将当前会话无缝迁移、恢复至Codex Agent继续对话的用户。

套件
@uu88s/dsh-session-handoff
相容性
待驗證
版本
0.1.0
授權
MIT
最近更新
2026年9月27日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:uu88s/dsh-session-handoff

@uu88s/dsh-session-handoff — 会话交接

DSH Web 插件。做两件事:

  1. 复制会话 id:在侧边栏会话行右侧的 hover 图标或 … 菜单里,一键复制 DSH 会话 id。
  2. 交接给 codex:把整个 DSH 会话(对话文本 + 工具调用 + 工具输出 + 文件变更清单)转写成一个 codex 能 resume 的会话,写进 codex 的会话库并登记进它的索引,最后给出可粘贴的恢复命令 codex resume 。

只复制 id 是不够的:codex 的 resume 只认自己库里的会话。交接 = 读源会话 → 展平成中间表示 → 写成目标 agent 的原生记录 → 登记进目标索引 → 给出恢复凭据。

安装

下面的写法都实测过,任选其一:

# 跟随 main
dsh plugin --profile web add github:uu88s/dsh-session-handoff

# 锁定版本标签
dsh plugin --profile web add github:uu88s/dsh-session-handoff#v0.1.0

# 源码归档 —— 含 test/ 与 docs/,可本地跑验收
dsh plugin --profile web add https://codeload.github.com/uu88s/dsh-session-handoff/tar.gz/refs/tags/v0.1.0

# Release 资产 —— 只含发布白名单里的 15 个文件
dsh plugin --profile web add https://github.com/uu88s/dsh-session-handoff/releases/download/v0.1.0/uu88s-dsh-session-handoff-0.1.0.tgz

或者先下载再本地安装(plugin_manager 的 install_bundle,target 传绝对目录):

curl -L -o handoff.tar.gz https://codeload.github.com/uu88s/dsh-session-handoff/tar.gz/refs/tags/v0.1.0
tar -xzf handoff.tar.gz
dsh plugin --profile web add /绝对路径/到/dsh-session-handoff-0.1.0

装完在 dsh --profile web --dump-config 的组合树里能看到 - id: session-handoff / name: '@uu88s/dsh-session-handoff'。

包内容

package.json 的 files 白名单(共 15 个文件):

index.js  client.js  package.json  cordis.patch.yml  README.md  LICENSE
lib/{zstd-frames,dsh-session,ir,codex-rollout,codex-store,handoff-log,handoff}.mjs
locale/{zh,en}.json

test/ 与 docs/ 不进包,但都在仓库里,克隆源码后可直接跑。

源码:;问题反馈:。

安装后 client 半边改动可免刷新生效(依赖 dsh-client-hmr 的轮询);host 半边改 JS 需要重启 DSH。

用法

入口位置作用
会话行 hover 图标侧边栏会话行右侧交接给 codex
会话行 … 菜单同上① 复制 DSH 会话 id ② 交接给 codex
会话头部「交接」按钮会话标题右侧交接给 codex
/handoff 命令输入框[] [--dry-run] [--undo [--force]]
handoff_session 工具模型可调用{sessionId?, dryRun?, codexHome?}

交接是两步式:先预演(列出将新建的 rollout 文件与将插入的索引行),确认后才写入。完成后 Toast 上给三个按钮:复制恢复命令、撤销这次交接、关闭。

安全性

  • 只增不改:永不覆盖已存在的文件(flag: 'wx'),永不改写索引里已有的行;同一会话只对应一个新的目标会话。
  • 预检:写入前校验 codex 家目录、state_*.sqlite、threads 表、必需列、schema 版本(_sqlx_migrations 不得高于已核实的 57),并用 BEGIN IMMEDIATE 探测库是否被占用;预检失败直接报错,不会悄悄建目录。
  • 可撤销:撤销 = 删掉刚建的 rollout 文件 + 删掉刚插入的 threads 行(按 id + rollout_path 双条件),并在交接记录里留痕。文件被外部改动过则拒绝撤销(需 force)。
  • 留痕:每次交接/撤销都追加到 $DSH_HOME/session-handoff/handoffs.jsonl,用于撤销与重复交接检测。

保真度(v1)

内容是否转写
用户 / 助手对话文本✅(模型上下文 + 界面转录两条通道都写)
codex 界面里显示的历史✅ 用户 / 助手消息;工具活动不进界面(不伪造 codex 的工具条目,见 ADR-0003)
工具调用(名称 + 参数,参数超 2 KB 截断并标注)✅
工具输出(单条超 8 KB 截断,标注原 DSH 会话 id)✅
文件变更清单(从工具参数里抽取的路径)✅(汇总进前言 + 计划)
reasoning / 思考过程❌
附件图片、系统提示快照❌(附件仅以绝对路径占位一行)

交接会话的 cwd 默认等于源会话的 cwd(同一工作区继续干活);originator 标记为 dsh-session-handoff,cli_version 标记为 0.1.0-dsh-handoff,便于日后区分来源。

交接是快照:源会话后来长了怎么办

交接在写入那一刻固化源会话当时的内容,之后源会话继续加消息,目标线程不会跟着变长(目标是一个独立会话,codex 不会回读 DSH 日志)。

要拿到后来的内容,重新交接一次:每次交接都新造一个目标会话 id,不会覆盖或追加到旧目标线程(见 ADR-0001)。旧目标线程可以留着继续用,也可以 /handoff --undo 撤掉。

实测(源会话 session-e181bce7-…):

交接时刻目标线程写入规模界面转录
17:162e61001f-…783 行 / 1.3 MB8 轮 / 32 条
18:05(源会话已变长)40c65068-…1053 行 / 1.75 MB11 轮 / 39 条

第一次交接之后才在源会话里出现的文字在 2e61001f 的转录里查不到,在重新交接出来的 40c65068 里查得到——两条通道(模型上下文 / 界面转录)都带上了新内容。

适配器范围

v1 只实现 codex(本机唯一可 resume 的 CLI agent)。适配器接缝只有四件事:读源会话 / 展平成中间表示 / 写目标记录 / 给出恢复凭据;zcode、codebuddy 等属于 v2。

反方向(外部会话 → DSH)由官方 dsh-chat-import 负责,本插件不重复实现。

测试与验收

test/ 不进发布包,这几个脚本都不需要浏览器,也不需要正在运行的 DSH:

脚本做什么写盘?
node test/smoke.mjs [sessionId]读本机会话日志 → 中间表示 → rollout 行(逐条校验行类型、配对、时间戳、arguments 可解析)→ 目标库预检 → 预演只读
node test/e2e.mjs [sessionId]在 node 里装一个最小 React + 渲染器,真渲染客户端注册的组件、按真实路径点击按钮(复制 id / 开始写入 / 复制恢复命令 / 撤销 / 错误态)→ 宿主路由 → 写一次真会话 → 再撤销写一次并撤销
node test/acceptance.mjs [--dry|--undo]真写一次并独立校验 rollout 首行与索引行(含两条通道与 history_mode),打印 codex resume 写一次(--dry 不写)
node test/probe-transcript.mjs …用 codex 自己的 app-server 问「这条会话的转录里有什么」(轮次 + 可见条目)——界面通道的验收判据只读
node test/probe-schema.mjs只读打开 state_5.sqlite:迁移版本、threads 列、必需列、触发器、本插件不认识的列及原生取值只读

已实测(2026-09-27,codex-cli 0.157.1):

  • codex exec resume --json "…" 返回 {"type":"thread.started","thread_id":""} 与 turn.completed input_tokens=306867 —— codex 把转写出来的三十万 token 历史读了进去并正常回答(模型上下文通道)。
  • node test/probe-transcript.mjs 返回 historyMode paginated、8 轮 / 32 条可见条目,内容就是源会话里的用户/助手消息(界面转录通道)。

已知限制

  • 界面转录只还原用户 / 助手消息:codex 的工具条目(TurnItem::CommandExecution 等)枚举取值只能靠猜,而 rollout 是整行反序列化的,猜错一条会让整个会话在 resume 时直接崩——所以工具调用只留在模型上下文里,界面里不显示。reasoning 同样不写。
  • 写入依赖 codex 的 TurnItem 载荷形状与 history_mode 语义(ADR-0003)。升级 codex 后除了用 probe-schema 复核 threads 表,还要用 probe-transcript 复核界面转录;形状对不上时宁可停下报错,不硬写。
  • 目标侧必须存在可用的 codex 状态库;若目标 agent 正在运行,登记可能遇到 SQLITE_BUSY(预检会先探测)。
  • 撤销不会动 DSH 源会话,也不会动 ~/.codex/session_index.jsonl(它只是名字索引,不是 resume 入口)。

许可

MIT