deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
DDDMUC/dsh-delete-turn
Per-message delete for DeepSeek Harness: remove a user message, one reply step, or a whole assistant reply from the derived model context via the official surface-replace contract, and hide it from the visible transcript without rewriting the append-only log.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:DDDMUC/dsh-delete-turn
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
DeepSeek Harness 消息删除插件 —— 把一条消息从模型上下文里真正拿掉,同时从当前转录里隐藏它。 助手回复挂在官方 conversation.chat.assistant-actions 槽位;用户消息、注入上下文与工具调用卡由 DOM 增强补上入口。确认后经官方 surface-replace 契约追加替换事件:模型后续看到的历史里不再有这条内容,而原始会话日志一个字节都不改写。
DSH 的会话日志是 append-only 的:说错话、发错提示词、模型答偏了,这些内容会一直留在模型上下文里,污染后续每一轮。官方只提供了「压缩」(/compact)这种整段摘要机制,没有单条消息级别的删除。
这个插件把删除放回消息本身:
assistant/message 与它请求的 tool/result 一起走,工具配对永不悬空);surfaceOp: { op: 'replace', startSeq, endSeq } 替换事件,被遮蔽的内容不再进入 deriveMessages();与宿主 /compact 同一套官方契约data-chat-flow-* 锚点与官方 useChat 快照定位行,删除后折叠退场;刷新、重启 DSH、换标签页后依旧隐藏
# npm 安装(web profile)
dsh plugin --profile web add dsh-delete-turn
# 本地工作区(开发)
dsh plugin --profile web add link:/path/to/dsh-delete-turn
安装后需要完全重启 DSH 进程(宿主侧插件树只在启动时读取)。浏览器端 bundle 由宿主按请求动态 serve,重启后硬刷新页面即可生效。核实是否加载:
dsh --profile web --dump-config # 应出现 "# == dsh-delete-turn" 段落
UI(官方槽按钮 / DOM 增强按钮)
→ 确认弹窗
→ POST /dsh-delete-turn/delete { sessionId, mode, seq? / messageId? / turn? }
宿主:
sessionQuery.readSession() 读完整日志(live 优先)
自实现 surface fold → 当前 surface 节点 + 历史替换遮蔽集
校验(当前节点 / 区间干净 / 回合已闭合 / 不碰系统头)
session.append('user/message', 短标记占位, {
surfaceOp: { op: 'replace', startSeq, endSeq },
sourceEventSeqs: [被遮蔽的全部 seq],
})
等待官方 session/flush 持久化检查点
→ 返回 { hidden: [{ seq, mode }] }
客户端:
useChat 快照把 data-chat-flow-key 映射到节点,按 hidden 集合折叠行
GET /dsh-delete-turn/state 在每次打开会话时重建 hidden 集合
设计要点:
user/message。开一个「合成 turn+step」来安放对模型隐身的空 system/message 会损坏日志:agent loop 只从它自己开的回合推进回合号,外部开掉的回合号会被它的下一个真实回合复用(冷读报 turn/start does not open the expected turn),而任何按回合号做隐藏的客户端都会把被复用的那个真实回合整轮吞掉——此故障已在真实会话里复现(消息「被吞掉」)。因此载体退回 turn-less 形状,内容用单个零宽空格(空内容数组会被网关 400 user message must have content,可读标记会被模型复述;零宽空格对校验器非空、对模型无字面文本)。代价:模型可能把这条载体读成一条空白 user 消息。历史日志里已经写入的簿记回合(旧版本产生)由客户端按 /state 的 markerTurns 隐藏。data-chat-flow-* 锚点与官方 useChat 标准 hook,宿主 UI 重构不会静默失效。/state 路由在每次打开会话时重建它。/compact)移出模型上下文的内容不再显示删除入口:它已经不在上下文里,转录用意保留;入口只在内容仍可删时才出现。0.1.7 —— 重新 apply 时清理自己的注入节点(互操作契约 I3)。只修 Bug,交互语义不变。
WeakMap,而上一次 apply 注入的宿主还留在行里,于是同一条行上叠出多个删除按钮——真机 CDP 只读探针曾在同一行数到 3 个 .dshdt-action-host。现在:①注入前先按命名空间属性 [data-dshdt-action-host="1"] 查一次已有宿主,找到就复用同一个节点(不 remove + re-insert),上一版留下、还没有属性的旧节点按 .dshdt-action-host 兜底认领并补上属性;②行内宿主与思考卡宿主分别用 data-dshdt-action-host / data-dshdt-think-action 认领,行不会把思考卡里的步骤按钮抢走;③ctx.effect 的清理函数里按命名空间属性全局扫掉本插件注入的宿主(槽根节点也带上了命名空间标记),只删自己的节点,宿主的按钮与兄弟插件的节点一律不动。新增 4 条 test/client.test.js 用例覆盖「apply → dispose → 再 apply」与两个实例同时在场的时序。data-dshdt-action-host="1" (a reasoning host additionally data-dshdt-think-action="1"), an injection adopts the host a previous apply left in the row instead of adding a second one, and the ctx.effect teardown sweeps every node this plugin injected, matched by its own namespace, while host and sibling nodes are left alone. 4 new test/client.test.js cases cover apply → dispose → apply.0.1.6 —— 隐藏归因(互操作契约 I4)。只修 Bug,交互语义不变。
row.style.display 清成 ''——那一行若正被 dsh-edit-turn(data-dshet-hidden)或 dsh-rerun-turn(data-dsrr-hidden)按归属属性隐藏着,本插件一恢复就把别人的隐藏一并抹掉(行「复活」)。现在按契约 §4 在本地拷入 foreignHideOn(row,'dshdt'):交还自己那份归属属性与折叠样式之前先确认没有别的归属属性,有则保持 display:none,等对方自己解除;轮次导航标记同理,别人声明隐藏的回合不再留下跳转点。新增 test/client.test.js(用 DOM stub 加载真实 client bundle,8 例)。foreignHideOn(row, 'dshdt') (contract §4) leaves display:none in force while data-dshet-hidden / data-dsrr-hidden is present, so a sibling's hide survives until the sibling lifts it; the turn-navigation rail no longer keeps a jump mark for a turn another plugin declared hidden. New test/client.test.js loads the real client bundle against a DOM stub (8 cases).0.1.6-alpha.2、0.1.7-alpha.2 与 0.2.0-rc.1(web 与 desktop profile,Safari / WebKit 与 Chromium 内核均验证)。plugin:dsh-delete-turn,隐藏台账会同时识别 { kind: 'plugin', plugin: ... } 与 { kind: 'plugin:...' } 两代形状。sessionQuery 时回退到 live 会话快照。MIT
A DSH session log is append-only: a wrong prompt or a bad answer stays in the model context and pollutes every later turn. The only official tool is whole-range compaction (/compact); there is no per-message delete.
This plugin puts deletion back on the message itself:
assistant/message and the tool/result it requested leave together, so tool pairs never dangle);surfaceOp: { op: 'replace', startSeq, endSeq } intent; shadowed content no longer reaches deriveMessages(). Same contract as /compact.data-chat-flow-* anchors and the official useChat snapshot, then collapse out. The hide survives a reload, a DSH restart and other tabs.
# npm (web profile)
dsh plugin --profile web add dsh-delete-turn
# local workspace (development)
dsh plugin --profile web add link:/path/to/dsh-delete-turn
Then fully restart the DSH process (the host plugin tree is read at startup only). The browser bundle is served dynamically, so a hard refresh after the restart is enough. Verify:
dsh --profile web --dump-config # expect a "# == dsh-delete-turn" section
UI (official slot action / DOM-enhanced action)
→ confirmation dialog
→ POST /dsh-delete-turn/delete { sessionId, mode, seq? / messageId? / turn? }
Host:
sessionQuery.readSession() reads the complete log (live-preferred)
local surface fold → current surface nodes + historical shadowed seqs
validate (current node / clean window / closed turn / protected head)
session.append('user/message', short marker placeholder, {
surfaceOp: { op: 'replace', startSeq, endSeq },
sourceEventSeqs: [every shadowed seq],
})
await the official session/flush durability checkpoint
→ { hidden: [{ seq, mode }] }
Browser:
useChat snapshot maps data-chat-flow-key to nodes; hidden seqs collapse rows
GET /dsh-delete-turn/state rebuilds the hidden set on every session open
Design notes:
user/message with no turn bracket. Opening a synthetic turn+step to host a model-invisible empty system/message corrupts the log: the agent loop advances its turn counter only from the turns it opens itself, so its next real turn reuses the number this plugin burned (turn/start does not open the expected turn on the next cold read), and any turn-number-keyed client hiding then swallows that reused real turn — reproduced in production as messages “being eaten”. The carrier therefore stays turn-less, with a single zero-width space: truly empty content is refused by the gateway with 400 user message must have content, and a readable marker gets quoted back by the model; a zero-width space is non-empty for every validator and carries no literal text. The cost: the model may read it as one blank user message. Bookkeeping turns already written to historical logs (older versions) are hidden client-side via markerTurns from /state.data-chat-flow-* anchors and the official useChat standard hook, so a host UI refactor cannot silently detach the actions./state route./compact) no longer offers a delete action: it is not in the context any more and the transcript keeps it on purpose.0.1.6-alpha.2, 0.1.7-alpha.2 and 0.2.0-rc.1 (web and desktop profiles; WebKit and Chromium engines).plugin:dsh-delete-turn, and the hidden ledger recognizes both { kind: 'plugin', plugin: ... } and { kind: 'plugin:...' } shapes.sessionQuery is absent.MIT
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。