liangmianya/dsh-synapse
liangmianya/dsh-synapseDeepSeek Harness 的可视化非线性对话工作区:把会话、追问与分支变成可浏览、可拖拽的对话地图。
从 DSH 会话中删除一个已结束的轮次而不删除会话本身——最后一条助手回复旁的垃圾桶按钮会把该轮的提问、这条回复与工具记录从对话界面和模型上下文中移除,而 Session id、标题、之后的轮次与只追加日志保持不变;删除以持久 tombstone 记录,刷新后依然生效。
English | 中文
从 DeepSeek Harness 的对话中删除一个完整且已结束的 Turn(轮次),不删除也不替换整个 Session:该轮的提问、助手回复与工具记录会从后续模型上下文中移除,而 Session id、标题、工作区关联、之后的轮次以及原始 append-only 事件日志全部保留。每个已完成的顶层轮次,其最后一条助手回复旁会出现一个垃圾桶按钮。
本仓库是 hanshenmesen/dsh-turn-delete 的独立延续版。上游主分支暂未合并本仓库的兼容性修复,因此这里独立维护,并跟进 DeepSeek Harness
0.1.5-rc.2的 surface 规则与0.2.0-rc.1的插件兼容性闸门。
确认后删除中间轮次,同时保留原 Session 与后续轮次。演示录制于 DeepSeek Harness 0.1.0-rc.7,操作方式与当前版本一致。
目标轮次尚未结束,或已与其他历史一起被折叠进 compaction 摘要时,插件会拒绝删除:摘要是整体替换,无法单独摘除其中某一轮。这种拒绝会如实说明原因;插件内部出错时会报告「删除失败」并附带原因,不会再误报成「任务正在运行」。
从本地目录(开发调试):
dsh plugin --profile web add -w link:/absolute/path/to/dsh-turn-delete
从 GitHub 安装(私有仓库需要本机已有对应凭据):
dsh plugin --profile web add github:TiJun-Prime/dsh-turn-delete
从预构建的 GitHub Release 安装(发布 Release 之后可用):
dsh plugin --profile web add https://github.com/TiJun-Prime/dsh-turn-delete/releases/latest/download/dsh-turn-delete.tgz
安装后重启 dsh web(桌面版重启应用)。卸载:
dsh plugin --profile web remove dsh-turn-delete
{ op: 'replace', startSeq, endSeq };tombstone 载体由 assistant/message 改为空内容的 system/message——0.1.5 明令禁止在 assistant/message 上携带 sourceEventSeqs,而空 system 节点既声明来源、又不投影出任何模型消息。旧版本写入的 tombstone 仍会被识别。assertSystemHeadRewrite 保护 surface 节点 0,第一轮的区间曾在某些会话里包含该节点而直接失败。AGENT_BUSY,新增 DELETE_FAILED;只有 Agent 确实非 idle 时才提示任务正在运行。.bin shim 问题),npm run check 增加「用真实内核经 HTTP 路由删除构建产物中的中间轮次」的 smoke 测试。DeepSeek Harness 0.2.0-rc.1 起,profile 启动时会用 evaluatePluginCompatibility() 检查每个插件的 peerDependencies:凡是名字等于 @deepseek-ai/dsh 或以其为前缀的 peer,都要求运行中的内核版本满足其 semver 范围(includePrerelease: true);不满足的插件不报错、也不加载——loader 条目停在 enabled: false,插件面板显示「已进入 profile bundle 层但本次未能热挂载;重启后生效」,而内核日志里没有任何相关行。
两个坑:
^0.1.5-rc.2)在 0.2.0-rc.1 上判定为 false → 直接不加载。>=0.1.5-rc.2 <0.3.0 同样判定为 false(该范围内没有任何稳定版元组),必须写成 ^0.1.5-rc.2 || ^0.2.0-rc.1 这类联合范围。0.2.1 因此把 5 个 @deepseek-ai/dsh-* peer 的范围改为 ^0.1.5-rc.2 || ^0.2.0-rc.1,一份包同时支持两条内核线;代码无需改动(surface 事件、system/message 形状、data-turn-tail / data-chat-flow-kind DOM 契约在 0.2.0-rc.1 上均已逐项核对未变;slot 契约有一处收紧,见 0.2.3 一节)。
升级内核后发现按钮消失时,可在插件面板里重新启用本插件(会写入 profile patch 行并热挂载),或直接重启应用;0.2.1 已可被闸门接受。
id —— 按钮不出现的真正原因0.2.1 让插件重新通过兼容闸门、宿主半恢复挂载之后,浏览器端的删除按钮仍然不出现,而且插件面板上看不出任何问题。真正的原因在浏览器控制台里:
Error: list slot "conversation.chat.turnTail" requires options.id
0.2.0-rc.1 收紧了 slot 契约:kind: "list" 的槽在 register() 时强制要求 options.id。本插件的 turn-tail 注册当时写作 { name: 'conversation.chat.turnTail', select: selectDeletedTurn }(在 0.1.x 上合法),于是 apply(ctx) 在第二个注册处抛错 → cordis 回滚整个插件 fiber → 先前注册成功的 conversation.chat.assistant-actions(删除按钮本体)一并被撤销。表现就是「插件已加载、日志无异常、按钮不见了」。
0.2.3 的改动:
id:turn-tail 用 id: 'turn-delete'(另加 order: 50)。id 是槽内单元键——新的 id 并列渲染,复用已有 id 则替换该格;order 只控制排序。id,并断言 turn-tail 的 select 仍按 turn 数据选取(deleted → { hidden: true, turn },未删除 → null)。顺带澄清两个容易混淆的 manifest 字段:
dsh.client.immediately 与是否加载无关:manifest 里的每个动态客户端包都会被 create。false 只是不写进行数据。dsh.client.inject 只接受动态客户端包名。写进非动态包(例如 @deepseek-ai/dsh-client-ui-primitives,其 package.json 没有 dsh.client 字段、也没有 lib/client.js)不会报错,但也不会生效——未知项被静默跳过,0.2.3 已移除。0.2.4 让按钮回来了,但点删除会弹出:
删除失败:插件内部出错或与当前内核不兼容。 — format v4 message requires a producer-owned source kind
而且那个会话之后每次发消息都报同一句。原因在 0.2.0-rc.1 的会话格式 v4对消息 source 的校验规则:
v4 按「角色」而不是「生产者」校验 source:system/message 的 source 必须是 { kind: "system-prompt" },assistant/message 必须是 { kind: "model", provider, model }。用真实 0.2.0-rc.1 内核逐项实测(内存中造 3 轮、删中间轮、再逐个替换 source 回放):
| tombstone source | 行准入 | 会话回放 |
|---|---|---|
{ kind: 'plugin', plugin: 'dsh-turn-delete' }(0.1.x 写法) | ✗ format v4 message requires a producer-owned source kind | ✗ message must have system-prompt source |
{ kind: 'plugin:dsh-turn-delete' }(v3→v4 迁移的改写形式) | ✓ | ✗ must have system-prompt source |
{ kind: 'runtime-context' } | ✓ | ✗ must have system-prompt source |
{ kind: 'system-prompt' } | ✓ | ✓ |
因此 0.2.5 让 tombstone 的 source 跟随会话自身的格式版本:session.header.version >= 4 时写 { kind: 'system-prompt' }(即内核自己的空系统消息),v3 仍写 { kind: 'plugin', plugin: 'dsh-turn-delete' }——0.1.x 的 seed 校验器恰好相反(seed system/message … message must have plugin source),一刀切必然弄坏另一条内核线。
身份识别随之改为结构化:v4 上不再有生产者字段可用,所以「空内容的 system/message + surfaceOp: replace」本身就是 tombstone;旧日志里的 plugin 形状与更早的 assistant/message 形状继续被识别,历史删除不会复活。
0.2.5 验证证据(真实 0.2.0-rc.1 内核,全内存、不碰用户会话):删除中间轮 → receipt { turn: 2, seq: 18 },tombstone source { kind: 'system-prompt' },模型面 ["q1","a1","q3","a3"],并用 Session.create(id, seed, header, …) 重建整条日志成功(tombstone 仍在 seq 18、被删轮次仍不在模型面);三组负对照(legacy plugin、plugin:<name>、runtime-context)全部被同一校验器拒绝。
被写坏的会话(内存里挂着一条无法落盘的 pending 事件)不需要修磁盘:关掉该会话重新打开、或重启应用即可恢复——磁盘上的日志从未被污染。
0.2.3 修好 slot 注册后重新加载,删除按钮依然不出现:宿主半状态是 enabled: true, fiberPhase: "active",浏览器控制台里最初那条 requires options.id 也消失了,但动作行里什么都没有。第二个(也是最后一个)原因不在 slot,而在图标:
0.2.0-rc.1 把 @deepseek-ai/dsh-client-ui-primitives 里所有带尺寸后缀的图标改名成了「笔画粗细」变体。
| 0.1.x | 0.2.0-rc.1 |
|---|---|
IconTrashOutline16 | IconTrashOutlineRegular(1px 描边)/ IconTrashOutlineMedium(1.3px) |
本插件此前直接 import { IconTrashOutline16 };在 0.2.0-rc.1 上这个导出不存在,而构建产物是 CJS interop(x = __toESM(require(...))),读出来是 undefined。把 undefined 当组件渲染会让 React 抛 Element type is invalid,整条动作行渲染失败——注册是成功的、宿主侧没有任何报错,所以从外面看只是「按钮不见了」。
0.2.4 的改动:
src/client/trash-icon.tsx:按 IconTrashOutlineRegular → IconTrashOutline → IconTrashOutline16 → IconTrashOutlineMedium 的顺序在运行时解析当前内核真正导出的图标名,一份 bundle 同时服务两条内核线;一个都解析不到时渲染内置的 16px SVG 兜底——以后再改名最多是换图形,不会再让按钮消失。null、兜底图形可渲染。Button / Modal / Tooltip 的 props 已在两条内核线上逐项核对一致,无需改动。
Host 端注册 POST /dsh-turn-delete:取得目标 Agent 的维护租约 → 校验目标轮次是一个完整闭合、且仍可独立删除的 surface 区间 → 追加持久替换事件 → 等待 sessions.flush() 完成后才返回成功。
替换事件是一条空内容的 system/message,携带 surfaceOp: { op: 'replace', startSeq, endSeq } 以及它所遮蔽的 sourceEventSeqs。
浏览器端使用公开的 conversation.chat.assistant-actions 与 conversation.chat.turnTail slot:一个 Conversation definition 把持久 tombstone 投影到目标 Turn 的 Location data 上,标记组件据此隐藏相邻 Turn tail 之间的 Chat 行。DeepSeek Harness 目前还没有公开「整轮可见性」扩展点,所以这层呈现兼容被限制在行范围内;模型上下文的删除完全不依赖它。
0.1.5-rc.2 与 0.2.0-rc.1(surface 替换使用 { op: 'replace', startSeq, endSeq })peerDependencies 声明为 ^0.1.5-rc.2 || ^0.2.0-rc.1。内核升到未列出的版本时,0.2.0-rc.1 之后的 Harness 会拒绝加载本插件(见上一节);此时需要更新本包,或在插件面板里为该精确版本显式授予兼容豁免。
DeepSeek Harness 仍处于 developer preview,surface API 会在版本之间变化。升级 Harness 后,建议新建一个含三个短轮次的临时 Session,并删除中间轮次做一次验证。
npm install
npm run check
dsh plugin --profile web add -w link:/absolute/path/to/dsh-turn-delete
npm run check 会:类型检查 → 构建 Host/Browser 两半 → 运行单元与 UI 测试(真实内核对象 + jsdom)→ 用构建产物经真实 HTTP 路由对真实内核做一次删除 smoke → 校验 npm 包内容。
与所有 DSH 插件一样,本包以 Harness 进程的权限运行,安装前应审阅源码。删除只改变后续模型上下文,原始 append-only 事件会为审计与回放保留。
MIT