Per-message delete for DSH: remove a user message, one reply step, or a whole assistant reply from the derived model context through the official surface-replace contract, and hide it from the visible transcript without rewriting the append-only log.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-delete-turn
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:DDDMUC/dsh-delete-turn
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
This plugin publishes its README in Chinese only.
DeepSeek Harness 消息删除插件 —— 把一条消息从模型上下文里真正拿掉,同时从当前转录里隐藏它。 助手回复挂在官方 conversation.chat.assistant-actions 槽位;用户消息、注入上下文与工具调用卡由 DOM 增强补上入口。确认后经官方 surface-replace 契约追加替换事件:模型后续看到的历史里不再有这条内容,而原始会话日志一个字节都不改写。
中文
为什么需要它
DSH 的会话日志是 append-only 的:说错话、发错提示词、模型答偏了,这些内容会一直留在模型上下文里,污染后续每一轮。官方只提供了「压缩」(/compact)这种整段摘要机制,没有单条消息级别的删除。
这个插件把删除放回消息本身:
- 用户消息 → 删这一条;
- 思考卡 / 工具调用卡 → 删这一步(该步的
assistant/message与它请求的tool/result一起走,工具配对永不悬空); - 助手回复(官方操作条)→ 删这条回复连同它的思考、工具调用与注入上下文(你的提问保留);
- 用户消息行 → 还有第二个入口:删掉这一整轮(含提问、注入上下文与全部回复);
- 注入上下文行、失败回合行 → 同样有删除入口。
- 任意多行 → 勾选后一次删掉(多选删除;入口在 Session 标题栏右上角,相邻的窗口会合成一次范围替换,一次请求要么全落、要么全不落)。
特性
- 模型上下文级删除 —— 追加一条官方
surfaceOp: { op: 'replace', startSeq, endSeq }替换事件,被遮蔽的内容不再进入deriveMessages();与宿主/compact同一套官方契约 - 整轮删除(新) —— 删掉一轮(含你的提问、注入上下文、全部回复与工具结果):一次普通的范围替换落地,后面的轮次原地保留、连编号都不变——不重放、不重编号、日志只多一条事件
- 多选删除(新) —— 入口常驻在 Session 标题栏右上角(电源按钮附近):点它进入多选,行内随之出现勾选框,勾几行、点底部浮条的「删除选中」,一次请求带走全部;相邻的窗口在宿主侧融合成一条替换事件,非相邻的按 surface 顺序依次落地;任何一次拒绝都不会只删掉一半。助手最终回答段同样能勾——它没有行级垃圾桶(那个范围属于回复动作条),勾它删的是这一条回复、保留你的提问。
- 转录级隐藏 —— 客户端按
data-chat-flow-*锚点与官方useChat快照定位行,删除后折叠退场;刷新、重启 DSH、换标签页后依旧隐藏 - 日志即台账 —— 隐藏依据直接从日志里的替换事件重建(替换事件的消息 source 标记为本插件),不依赖 localStorage、不需要预检,也不会和其它插件(如压缩)的替换混淆
- 原生视觉 —— 复用官方 primitives 的 Modal / Button 与主题 token,明暗主题自动适配;图标与全部文案为原创
- 中英双语 —— 弹窗、提示、错误原因随系统语言即时切换
- 安全边界 —— 只接受回环地址且 Host 为本机的请求;目标必须是当前 surface 节点、不能碰系统提示词头、回合进行中拒绝、已被删除的目标拒绝并给出机器原因码
截图
安装
# 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" 段落
使用
- 悬停任意消息行,点击行尾的垃圾桶按钮;助手回复的按钮在官方操作条(复制 / 分叉旁边)。
- 确认弹窗会说明这次删除的影响范围,点「删除」。用户消息行上还有一个「删除这一轮」入口:它删的是整轮(含你的提问、注入上下文与全部回复),后面的轮次原地保留、直接接上。
- 目标行折叠退场;模型上下文在下一轮请求重建时不再包含它。
- 想一次删多条:点 Session 标题栏右上角(电源按钮附近)的勾选按钮进入多选——行内随即出现勾选框,点任意行勾选/取消,底部浮条报出已选条数;点「删除选中」确认后一次性落地,点「退出多选」或再点标题栏那个按钮退出。助手最终回答段也能勾,勾它删的是这一条回复,不删你的提问。
工作原理
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 集合
整轮删除(mode: 'turn'):
一次普通的 surface-replace:窗口 = 这一轮的全部表面节点
(提问 + 注入上下文 + 每个 step 的 assistant/message 与 tool/result)
落一条 turn-less 载体,后面的轮次原地保留、编号不变
—— 不重放、不重编号、不碰代理循环,日志只多一条事件
多选删除(POST 带 targets: [{ mode, seq? / turn? }, ...]):
planBatch:每个目标各自 planRange,全部对着**同一份删除前 surface** 算
按 surface 顺序排序;重叠或相邻的窗口合并成一条 replace(一个扇区)
写入前一次性校验全部窗口(foreign / stale),任何一条不干净就整体拒绝
→ 每个扇区一条载体,按顺序落地;要么全落,要么一条都不落
设计要点:
- 为什么载体是 turn-less 的零宽空格 user 消息(绝不能开合成回合):删除的替换载体只能是不带回合的
user/message。开一个「合成 turn+step」来安放对模型隐身的空system/message会损坏日志:agent loop 只从它自己开的回合推进回合号,外部开掉的回合号会被它的下一个真实回合复用(冷读报turn/start does not open the expected turn),而任何按回合号做隐藏的客户端都会把被复用的那个真实回合整轮吞掉——此故障已在真实会话里复现(消息「被吞掉」)。因此载体退回 turn-less 形状,内容用单个零宽空格(空内容数组会被网关 400user message must have content,可读标记会被模型复述;零宽空格对校验器非空、对模型无字面文本)。代价:模型可能把这条载体读成一条空白 user 消息。历史日志里已经写入的簿记回合(旧版本产生)由客户端按/state的markerTurns隐藏。 - 为什么不读 React fiber / CSS 哈希类名:行定位只用官方
data-chat-flow-*锚点与官方useChat标准 hook,宿主 UI 重构不会静默失效。 - 为什么刷新后仍然隐藏:隐藏台账不是浏览器本地状态,而是日志里替换事件的可重放推导;宿主
/state路由在每次打开会话时重建它。 - 整轮删除不重放、不重编号:只遮蔽这一轮自己的表面节点,后面的轮次一条不动——日志的轮号、后续轮的内容、它们对其他事件的引用都不受影响,也不需要任何跨轮操作。
- 多选的入口在标题栏,不在行里:多选是一个模式,而行内按钮只在悬停时存在——把入口藏在一次悬停之后,就等于藏起来(首版就是这么做的,用户找不到)。因此常驻入口是 Session 标题栏的
conversation.session.header.utilities条目,行内勾选框只在模式打开后出现。 - 没有行级入口的行也能勾:助手最终回答段的范围是「整轮回复」,这份入口在回复动作条上,所以行上不再放垃圾桶(否则同一个范围出现两个按钮);多选态下它仍拿到一个勾选框,指向
reply目标——只有该 turn 出现在view.replyTurns里才给,勾它删这条回复、保留提问。 - 多选是一次请求,也是一次全有或全无:窗口在宿主侧算,客户端只发目标;相邻窗口融合后一次落地更少的事件(两轮相邻时是 1 条而不是 2 条),且所有窗口共用一次校验,正常路径下不会出现「删了一半」(只有在两次 append 之间日志被别处改动、第二个窗口被拒的极端情况下,路由才会明确返回
partial,界面随即重读状态显示真实结果)。 - 广告即承诺:
/state报出turnSeqs(现在哪些行可以整轮删除),它由路由规划用的同一个函数算出(turnableSeqs对每个候选调用planRange),所以界面上的入口不会变成一次注定被拒的点击。
已知限制
- append-only 语义下没有「反删除」:被遮蔽的内容无法真正恢复,删除不可撤销(原始日志仍在,可用官方工具自行重建会话)。
- 回合进行中删除会先停止当前生成:走官方
sessionController.cancel(等价于你按停止键,且保留排队的输入),等这一轮真正收尾(turn/end落地)后再删;停止超时(默认 15 秒)、停止被拒、或正在压缩(压缩没有"停止"语义)时仍会拒绝并给出原因。 - 系统提示词头(surface 节点 0)不可删除。
- 多选删除是一次请求:所有窗口在写入前一起校验(foreign / stale),所以不会出现「删了一半」;但它删掉的东西同样不可撤销。
- 助手操作条的删除范围是整条回复;要只删某一步,请用思考卡 / 工具卡上的按钮。
- 过程行(「已思考」「用时 N 秒」)不单独提供删除入口:它的范围同样是整条回复,与操作条重复,因此只保留操作条那一个。
- 已经被官方压缩(
/compact)移出模型上下文的内容不再显示删除入口:它已经不在上下文里,转录用意保留;入口只在内容仍可删时才出现。 - 整轮删除也删掉你的提问:它删的是整轮(提示词 + 注入上下文 + 全部回复步骤);只删回复请用助手操作条,只删提示词请用用户行上的垃圾桶。
- 这一轮的窗口里如果夹着别的插件留下的、既不是本插件占位符也不是静默载体的节点(例如某个可读的注入内容),不会静默地一并删掉:第一次请求只回报"还会多删 N 项",客户端据此弹出强制确认(主按钮变成「一并删除」),你再确认一次才会带
force真正执行。系统提示词头(surface 节点 0)本身不可删除;含它的那一轮可以整轮删除(头会被排除在窗口外、留在原位)。 - 宿主侧插件树仅在 DSH 启动时加载:安装、更新插件后必须完全重启 DSH。
更新日志
(工作区) —— 会话 id 校验放宽 + 空会话按空状态处理。
- 会话 id 不再只认 UUID:命名会话(例如
bili-whale,~/.dsh/storages/<profile>/sessions/bili-whale.json)是完全合法的会话,但这里的校验只接受 UUID 形式,于是对它的每个请求都被 400 拒掉 —— 面板上就是「状态加载失败,稍后重试。」。现在按 DSH 自己的语法(宿主侧 schema 就是z.string().min(1))放宽为/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,仍然挡住/、\、..、空白与控制字符。 - 查不到会话按空状态处理:没有日志的会话(刚建的命名会话)返回空状态而不是 404 —— 面板正常加载、显示"没有可操作的内容",日志一出现下一次刷新就接上。
见 CHANGELOG.md(中文 / English)。
验证
cd dsh-delete-turn
npm test # 86 例:纯逻辑 + 宿主路由 + 真实校验器契约 + 客户端 DOM
node tools/verify-real-session.mjs <session.v4.jsonl.zstd> [--turn N]
npm test里的test/contract.test.js与tools/verify-real-session.mjs需要已安装的@deepseek-ai/dsh-session/@deepseek-ai/dsh-session-format-catalog(插件本身不 import 它们)。解析顺序见tools/dsh-modules.mjs:DSH_SESSION_DIR→ 本仓库 / 父目录 / 兄弟仓库的node_modules→~/.npm/_npx/*/node_modules(npx 安装的 DSH)→ 裸包名。解析不到时测试会明确失败(而不是静默跳过),因为那意味着契约没有被验证。tools/verify-real-session.mjs不会写它拿到的文件:它解码日志、跑一次严格冷读、用解出来的事件建一个真实Session,然后对这个会话驱动插件的真实 HTTP 路由,最后再跑一次严格冷读并打印折叠前后的节点序列。拿真实会话试之前先cp到/tmp(本仓库的验收就是这么做的)。
兼容性
- 实测 DSH
0.1.6-alpha.2、0.1.7-alpha.2与0.2.0-rc.1(web 与 desktop profile,Safari / WebKit 与 Chromium 内核均验证)。 - 会话格式 v3 与 v4 都支持:v4 迁移会把插件 source 展平为
plugin:dsh-delete-turn,隐藏台账会同时识别{ kind: 'plugin', plugin: ... }与{ kind: 'plugin:...' }两代形状。 - 宿主半区零运行时依赖,全部服务经 cordis ctx 解析;缺少
sessionQuery时回退到 live 会话快照。 - 不修改 DSH 官方源码,不写任何私有事件类型。
License
MIT
日志里的标注
每次删除追加的载体除了 surfaceOp,还会在 source 里写:
{ "kind": "plugin:dsh-delete-turn", "op": "delete", "turn": 3,
"deletedTurn": 3, "deleteIndex": 1 }
deletedTurn/deleteIndex只有整轮删除才写(行内删除不会让轮次消失);有了这两个字段,任何读者都能还原「可见轮 ↔ 日志轮」:
可见轮(L) = L − #{ 已删的日志轮 < L } 日志轮(V) = 第 V 个没被删的正整数同一批里删多轮时,每条载体各占一个递增的
deleteIndex。
English
Why
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:
- user message → remove that one message;
- reasoning card / tool card → remove that step (the step's
assistant/messageand thetool/resultit requested leave together, so tool pairs never dangle); - assistant reply (official action strip) → remove the whole reply attempt with its reasoning, tool calls and injected context (your prompt stays);
- user message → a second entry: delete this whole turn (prompt, injected context and every reply step included);
- injected-context rows and failed-turn rows get an entry too;
- any number of rows → tick them and delete them together (multi-select; the entry sits in the Session header's top-right corner, neighbouring windows fuse into one range replace, and one request lands all of them or none). A turn's answer text is selectable too: it carries no row-level trash (that scope belongs to the reply action strip), so ticking it deletes just that reply and keeps your prompt.
Features
- Context-level delete — appends the official
surfaceOp: { op: 'replace', startSeq, endSeq }intent; shadowed content no longer reachesderiveMessages(). Same contract as/compact. - Turn delete (new) — remove one whole turn (your prompt included, plus its injected context and every reply step and tool result) as one ordinary range replace: the later turns stay exactly where they are, under their own numbers — no replay, no renumbering, one extra event.
- Multi-select (new) — the entry lives in the Session header's top-right corner (next to the power control): it turns the mode on, every row then shows a tick box, and one request takes them all from the bottom bar's “Delete selected”. Neighbouring windows fuse into one range replace on the host, distant ones land in surface order, and a refusal never leaves half of the batch behind
- Transcript-level hide — rows are located through official
data-chat-flow-*anchors and the officialuseChatsnapshot, then collapse out. The hide survives a reload, a DSH restart and other tabs. - The log is the ledger — hidden seqs are re-derived from the replacement events themselves (their message source is marked with this plugin), so there is no localStorage sidecar, no preflight, and no confusion with compaction replacements.
- Native look — official primitives (Modal / Button) and theme tokens; icon and all copy are original.
- Bilingual — zh/en dictionaries follow the active locale.
- Safety boundary — loopback-only routes; targets must be current surface nodes, the system-prompt head is protected, a running turn is refused, and already-deleted targets fail with a machine code.
Screenshots
Install
# 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
Usage
- Hover a message row and click the trash action at its end; the assistant action sits in the official action strip next to copy/branch.
- The dialog states the exact scope; click Delete. A user row also carries “Delete this whole turn”, which removes the whole turn (prompt included) as one range replace: the later turns keep their place and their numbers.
- The row collapses away; the model context stops containing it when the next request is rebuilt.
- To remove several things at once: click the tick control in the Session header's top-right corner to enter multi-select — every row then shows a tick box; tick or untick rows, watch the bottom bar's count, then click “Delete selected”. “Leave multi-select”, or that same header control, ends the mode and drops the marks. A turn's answer text can be ticked as well — that deletes the reply and keeps your prompt.
How it works
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
Turn delete (mode: 'turn'):
ONE ordinary surface replace over this turn's own surface nodes
(prompt + injected context + every step's assistant/message and tool/result)
landing one turn-less carrier; later turns keep their place and their numbers
- no replay, no renumbering, no loop state touched, one extra event
Multi-select (POST carries targets: [{ mode, seq? / turn? }, ...]):
planBatch: each target planned by planRange against the SAME pre-delete surface
sorted by surface order; overlapping or adjacent windows fuse into one replace
every window validated together (foreign / stale) before the first append
→ one carrier per window, landed in order; all of them or none
Design notes:
- Why the carrier is a turn-less zero-width user message (never open a synthetic turn): the replacement carrier can only be a
user/messagewith no turn bracket. Opening a synthetic turn+step to host a model-invisible emptysystem/messagecorrupts 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 turnon 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 400user 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 viamarkerTurnsfrom/state. - Why no React fiber or CSS-module hashing: rows are addressed through official
data-chat-flow-*anchors and the officialuseChatstandard hook, so a host UI refactor cannot silently detach the actions. - Why a reload stays hidden: the ledger is not browser state; it is a replay of the replacement events in the log, rebuilt by the host
/stateroute. - A turn delete neither replays nor renumbers: it shadows this turn's own surface nodes and nothing else, so the log's numbering, every later turn's content and their references to other events are untouched — and no cross-turn operation is needed.
- The multi-select door is in the header, not in a row: multi-select is a mode, and a row action only exists while its row is hovered — hiding the door behind a hover hides it outright (the first cut did exactly that and the user could not find it). The always-visible entry is a
conversation.session.header.utilitiesitem; the per-row tick boxes only exist once the mode is on. - A row without a scope of its own can still be ticked: a turn's answer text belongs to the reply scope, whose entry lives in the reply action strip, so no trash is drawn on the row (two buttons for one scope would be a lie); the mode still gives it a tick box aimed at
reply, offered only when the turn is inview.replyTurns, and ticking it deletes that reply while the prompt survives. - A batch is one request and one all-or-nothing step: the windows are planned host-side, the client only ships targets; fusing neighbours means fewer events (two adjacent turns cost 1 instead of 2), and one shared validation pass makes a half-landed batch the exceptional case: only when the log moves between two appends does the route answer
partial, and the UI then re-reads the state to show what actually landed. - Advertised = accepted:
/statepublishesturnSeqs(the rows that can be turn-deleted right now), computed by the SAME function the route plans with (turnableSeqstriesplanRangeon each candidate), so an entry can never become a click that is bound to fail.
Known limitations
- Append-only semantics offer no un-delete: shadowed content cannot truly be restored, and deletion is irreversible (the original log survives; official tooling can rebuild a session from it).
- Deleting during a running turn stops the generation first: it goes through the official
sessionController.cancel(the same stop the UI's own button uses, keeping the queued inbox), waits for the turn to actually close (turn/endlanded), and only then deletes. A stop that times out (15 s default), a refused stop, or a running compaction (which has no stop semantics) is still refused with a reason. - The system-prompt head (surface node 0) is protected.
- A multi-select batch is a single request: every window is validated before anything is appended, so a batch cannot land half of itself — but what it removes is just as irreversible as a single delete.
- The assistant action strip deletes the whole reply attempt; use the reasoning/tool card to remove a single step.
- The process/disclosure row (“Thinking”, “N s”) carries no entry of its own: its scope is the whole reply, which the action strip already covers, so the duplicate was removed.
- Content already removed from the model context by official compaction (
/compact) no longer offers a delete action: it is not in the context any more and the transcript keeps it on purpose. - A turn delete also removes YOUR prompt: it deletes the whole turn (prompt + injected context + every reply step). To remove only the reply use the assistant action strip; to remove only the prompt use the trash action on the user row.
- If the turn's window holds a node that is neither this plugin's placeholder nor a silent carrier (some readable injected content, say), it is never retired silently: the first request only reports how many unrelated nodes it would additionally remove, and the client turns that into a forced confirmation (the primary button becomes "Delete all of it"); only a second,
force-carrying retry removes them. The system-prompt head (surface node 0) itself cannot be deleted; the turn that introduced it can be turn-deleted - the head is excluded from the window and stays in place. - The host plugin tree loads at DSH startup only: fully restart DSH after installing or updating the plugin.
Verification (how to reproduce the claims above)
cd dsh-delete-turn
npm test # 86 cases: pure logic + host route + real-validator contract + client DOM
node tools/verify-real-session.mjs <session.v4.jsonl.zstd> [--turn N]
test/contract.test.jsandtools/verify-real-session.mjsneed an installed@deepseek-ai/dsh-session/@deepseek-ai/dsh-session-format-catalog(the plugin itself never imports them). Lookup order lives intools/dsh-modules.mjs:DSH_SESSION_DIR→ the repo's / its parent's / the sibling repo'snode_modules→~/.npm/_npx/*/node_modules(an npx-installed DSH) → the bare specifier. When nothing resolves the test fails loudly instead of skipping, because that would mean the contract is unverified.tools/verify-real-session.mjsnever writes the file it is given: it decodes the log, runs one strict cold read, builds a realSessionfrom the decoded events, drives the plugin's actual HTTP route against it, then cold-reads the result again and prints the folded surface before and after.cpa real session to/tmpfirst, exactly as this repository's own acceptance run did.
Changelog
See CHANGELOG.md.
Compatibility
- Verified against DSH
0.1.6-alpha.2,0.1.7-alpha.2and0.2.0-rc.1(web and desktop profiles; WebKit and Chromium engines). - Both session formats v3 and v4 are supported: the v4 migration flattens plugin sources to
plugin:dsh-delete-turn, and the hidden ledger recognizes both{ kind: 'plugin', plugin: ... }and{ kind: 'plugin:...' }shapes. - The host half has zero runtime dependencies and resolves every service through the cordis context; it falls back to the live session snapshot when
sessionQueryis absent. - No DSH source is modified and no private event type is written.
License
MIT
Links
More in this category
ranxianglei/billion-context★ 4050
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
Minglink/dsh-infinite-gen-4★ 3898
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
liangmianya/dsh-synapse★ 658
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
MichengAI/dsh-archive-manager★ 288
Adds an archived-sessions page in Settings to search, restore, and delete archived DeepSeek Harness sessions by workspace.
Nwflower/dsh-chat-import★ 280
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
SiriLee/dsh-rewind★ 240
Conversation rewind: one click back to any earlier user message, in the same window — no new branch, no leftover copy; the target message is refilled into the composer, with optional workspace-file restore — the full Claude Code /rewind semantics.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.