让没做完的任务清单跨回合保留。DSH 在每个回合开始时清空 todos 投影,指望模型重写整份清单,于是回合一旦被中断,计划虽然durable 地留在会话日志里,界面上却永久消失——而 todo_write 只写不读,用户和模型都拿不回来。本插件只注册一个普通的 session/event 监听器,按会话记住最后一份清单;当 turn/start 到来而那份清单仍有未完成项时,把它补折回去,投影就不再显示空计划。不 fork tool-todo、不注册任何 waterfall:插件换不掉投影(注册表对 stateVersion 不一致直接抛错),追加折叠所反应的那条事件才是够得着的缝。刻意收得很窄——只管未完成的清单、每次回合开始最多补一条、失败即放行。已用真实会话上的受控 A/B 验证:两次运行内容相同,唯一差别是插件装没装,都用真实的投影定义折叠。零依赖。
安装
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:apex-mochen/dsh-todo-continuity
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
让没做完的任务清单跨回合保留。
DSH 在每个回合开始时清空 todos 投影,指望模型把整份清单重写一遍。可一旦某个回合被中断,
清单虽然durable 地留在会话日志里,界面上却永久消失——而 todo_write 是只写的,你和模型都
读不回来。本插件在回合开始、且上一份清单仍有未完成项时,把它补折回去。
它针对的行为
packages/todo/tool-todo/src/index.ts:130-145,源码自己把这段写成有意设计:
// Standing-plan fold: latest whole todo/write list, cleared by the next
// turn/start (turn/end keeps the finished checklist visible); null before the
// first write or after a later turn begins; every other event returns the
// same state reference.
apply: (state, event) => {
if (event.type === 'todo/write') return event.data.todos
if (event.type === 'turn/start') return null
return state
},
stateVersion: 2,
"每轮重写"确实是刻意的——工具描述就叫模型每次发送完整清单。问题出在中断这种情形:下一个
turn/start 照样把清单清掉,而 todo_write(:203-222)只追加快照并返回计数,没有任何读回的
途径。计划并没有丢——它在日志里——但界面上拿不到,模型也恢复不了。
这是 DSH discussion #6520 社区核实清单的追加第 21 条。
它做什么
只注册一个普通的 session/event 监听器。它按会话记住最后一份 todo/write 清单;当 turn/start
到来、而记住的那份清单仍有未完成项时,就把这份清单作为一条新的 todo/write 追加回去。投影是
日志的纯函数,所以"补上折叠所反应的那条事件"就能恢复状态,别处什么都不用动。
刻意做得很窄:
- 只管未完成的清单:全部
completed的清单交给内置行为,插件在它不该管的场景里不改变任何东西。 - 每次 turn/start 最多补一条事件。
- 不 fork。它不 disable、也不替换
tool-todo——它也做不到:投影注册表对重复 key 只在stateVersion相同时才引用计数,不同直接抛错(session-projection/src/index.ts:276-283), 第一个定义永远获胜。追加事件才是够得着的那条缝。 - 不注册任何 waterfall(
pre-execute/pre-step/llm/stream都没有),因此没有next()义务,不可能吞掉别的插件的行为。 - 失败即放行:补不上时,调用方拿到的就是原本的内置行为。
已验证
真实会话上的受控 A/B——两次内容完全相同的 headless 运行,唯一差别是插件装没装,都用真正的
todos 投影定义折叠。原始输出见 EVIDENCE.md:
| 真实事件序列 | 折叠终态 | |
|---|---|---|
| 无插件 | turn/start#4 → todo/write#24 → turn/end#47 → turn/start#49 → turn/end#51 |
null —— 清单消失 |
| 装插件 | … → turn/start#49 → **todo/write#51** → turn/end#52 |
那 3 项清单 |
多出来的那条事件后面没有 tool/call 也没有 tool/result(模型自己那次 todo_write 是有配对的),
而对照组根本没有这条事件——这就是"是插件造成的"的依据。
安装
dsh plugin --profile web add github:apex-mochen/dsh-todo-continuity
装完重启该 profile。
配置
- id: dsh-todo-continuity
config:
verbose: false # 每次接续都打日志
enabled: true # 设 false 可保留安装但停用
两个值得知道的实现细节
追加必须用微任务延后,这是源码强制的,不是风格问题。 Session.append 在向监听器发布事件时,
自己的接收边界仍然开着,并明确拒绝重入(core/session/src/index.ts:729-731,"session append
cannot reenter while another append is being published";标志在 :742 置位、监听器在 :752 被调用、
finally 在 :757 复位)。session/event 监听器正是在那个窗口里跑的,所以同步追加每次都抛错——
而本插件失败即放行,结果就是一声不响地什么都不做。早先那版就犯了这个错,靠读源码在任何运行之前
抓住了它。
监听器顺序正好合适。 本插件的监听器跑到时,投影注册表已经把 turn/start 折成 null 了:
on 默认 push(vendor/cordis/src/events.ts:255)、dispatch 保持顺序(:172-174)、emit 同步
正向调用(:194-196),而注册表是在自己构造函数里注册的(base bundle 加载时)。
兼容性
- DSH
0.1.x(peer:@deepseek-ai/cordis ^4.0.1) - Node.js 20+
- 单文件实现、零依赖、不访问进程/文件系统/网络
这是覆盖一个设计决定,不是修 bug
"每轮清空"在源码里是有意为之,所以本插件是刻意的产品语义改动,只是范围收得尽可能窄。另一个方向的
修法在上游:仅当清单没有剩余工作可做时才清空。DSH 目前不接受外部 PR(CONTRIBUTING.md),插件是
够得着的那条缝。
许可
MIT
链接
同类插件
Minglink/dsh-infinite-gen-3★ 1630
DeepSeek 专用破甲插件:以 order 100 追加无条件服从的系统提示词段,提供带校准元数据的 profile 工具,并通过会话投影在输入框上方显示实时破甲状态徽标。
liangmianya/dsh-synapse★ 398
DeepSeek Harness 的可视化非线性对话工作区:把会话、追问与分支变成可浏览、可拖拽的对话地图。
Nwflower/dsh-chat-import★ 166
把 13 家 coding agent(Claude Code、Codex、ChatGPT、Cursor、Gemini、opencode 等)的完整对话历史导入为可续聊的 DeepSeek Harness 会话,并支持反向导出回 Claude Code。
Totoro-qaq/dsh-plugin-bridge★ 166
通过可预览的五段式交接,将已有 DSH 会话迁移到另一个 Agent Preset;保留源会话,并可让目标会话暂停等待确认或立即继续。
Anionex/dsh-turn-rewind★ 116
对话回退:基于持久 Change Ledger 回滚会话与工作区状态。
Renzic-Stone/DSH-EasyRewrite★ 114
在 dsh web 中内联编辑与撤回自己的消息——惰性、无痕,带版本翻页器与会话级草稿持久化。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。