- Accueil
- Plugins
- Améliorations UI
- dsh-message-ops
dsh-message-ops
240xu/dsh-message-ops
Roll back, branch, and export any DSH conversation — from the UI or straight from the agent via a message_ops tool. Non-destructive: logs stay append-only and recoverable. 消息回滚/删除/分支/导出四合一,内置 message_ops agent 工具;非破坏语义,日志只追加可恢复。
Installer
dsh plugin --profile web add github:240xu/dsh-message-opsREADME
@240xu/dsh-message-ops
DSH web 插件:消息回滚 + 消息删除 + 消息分支 三合一。在会话头部添加分支图标按钮, 侧栏会话行 "..." 菜单注入「消息操作」项,打开统一操作对话框:
- 回滚(Revert):遮蔽所选消息及其后的全部可见内容(DSH 原生 surface replace 语义)。 日志 append-only,原事件完整保留,语境可通过重新发送恢复。
- 删除(Delete):仅遮蔽所选的那一条消息,其余内容不变(同样是 surface replace)。
- 分支(Branch):把所选消息(含)之前的全部事件复制为一个新会话,
新会话 header 携带
parentSession=<原会话 id>,原会话一个字节都不动 —— 唯一的非破坏操作。
安装
零 npm 依赖(只用 node:fs / node:zlib / node:path / node:url;zstd 压缩走
Node 内建 zlib.zstdCompressSync,需 Node ≥ 23.5,Termux 与 Windows 通用)。
# Termux / Linux
dsh plugin --profile web add @240xu/dsh-message-ops
# Windows
dsh plugin --profile web add @240xu/dsh-message-ops
# 或本地目录:
dsh plugin --profile web add file:C:/path/to/dsh-message-ops
重启 DSH web 实例后生效。
安全设计
- 回滚/删除不重写日志:通过一条承载
surfaceOp: replace的 system 事件实现, 约束由 DSH 引擎assertProvenance校验,违规如实上报(HTTP 409)。 - 回滚/删除前必须勾选风险确认;运行中的会话一律拒绝(提示先停止)。
- 分支先写临时文件再原子 rename,半写日志不会被会话扫描读到。
端点
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/message-ops/messages?sessionId=<id> | 消息级列表(磁盘真相 + 可见性标注 + running 状态) |
| POST | /api/message-ops/revert | {sessionId, seq} 回滚到该条(含) |
| POST | /api/message-ops/delete | {sessionId, seq} 仅遮蔽该条 |
| POST | /api/message-ops/branch | {sessionId, upToSeq} 分支新会话,返回 {newId, dir, keptEvents} |
| GET | /api/message-ops/export?sessionId=<id>&seq=<可选> | 导出 Markdown(seq ≤ 上界,缺省全部),附件下载 |
| POST | /api/message-ops/restore | {sessionId, seq} 回滚恢复:seq 为某次 revert/delete 标记事件的 seq |
0.2.4 UI GAP(ux-scout dsh-ui-spec G-M1)
- S6 违规修复:回滚/删除成功后不再 900ms 裸
location.reload()——改为 devkit 标准 toast(window.__dshDevkit.toast探测,无 devkit 时降级为对话框 内完成文案)+ done 态新增「刷新页面」按钮由用户手动刷新;与 branch 路径 (refreshList 不刷新页面)行为拉齐。
0.2.3 格式兼容(compat-audit + 用户实测紧急修复)
- P0 旧单帧
session.jsonl.zstd读取必崩:readSessionFile旧实现把帧 0 整段JSON.parse——旧单帧格式整个文件是一帧、解压出多行 NDJSON,直接抛异常, 与「旧格式兼容」声明矛盾。现改为逐行扫描统一路径:全部帧解压后按行扫描, 首个type:"session"行作 header、其余行作事件,v3 多帧 / v4 多帧 / 旧单帧 三种格式一条代码路径(撕裂行仍按完整前缀语义跳过)。 - P0 v4 会话格式(用户实测 3080 服务暴露):DSH 已升级 v4 持久化
(
session.v4.jsonl.zstd,结构同 v3,仅文件名与version数字不同)。findSessionDirs探测序列改为 v4 → v3 → 旧单帧(此前只找 v3,最新会话 全部 "session log not found");readSessionFile不做 version 硬校验;applyBranch分支写回沿用读到的原格式版本(session.v<version>.jsonl.zstd), 不再写死 v3。 - dedup:
listMessages内联文本提取收敛到session-file.messageText单点(ops-core 转出保持导入面兼容),消除双实现漂移面。 - 回归:40 项测试全绿,含旧单帧/ v4 fixture 往返、listMessages、 findSessionDirs 三格式发现与 v4 分支写回;真实 v4 会话日志已实测通过。
0.2.2 前端收尾(评审 M1/M2)
- M2 观察范围收窄:侧栏行菜单注入的
MutationObserver仍观察document.body(菜单由宿主 React 动态渲染,安装期无稳定锚点),但回调改为 精确过滤——只有新增节点本身是(或包含)[role=menu]时才调度探测;聊天流 渲染、流式 chunk 等海量 mutation 零探测成本;同帧多次命中合并为一次探测。 - M1 分批渲染:消息列表先渲 50 条,顶部「显示更多(剩余 N 条)」按钮每次 渐进展开 50 条(上限仍为最近 200 条),避免 200 行单选列表一次性进 DOM; 打开新会话时分页重置。
0.2.1 评审修复(架构评审 arch-review)
- P0 信任围栏:全部 6 条
/api/message-ops/*路由接入三层信任判定 (回环 Host 挡 DNS rebinding +sec-fetch-site: cross-site拒绝 + Origin 同源校验),非回环/跨站请求一律 403。写操作(revert/delete/branch/restore) 另要求Content-Type: application/json(text/plain 绕预检的洞 → 415)。 为何不引入一次性 CSRF token:浏览器对所有 POST(含 text/plain)都附带 Origin 头,Origin 同源校验已覆盖跨站 POST;自定义头天然触发预检、与 Origin 校验等价——token 只增加握手复杂度而不增加安全性,围栏已足够(论证见src/ops-core.js的isJsonContentType注释)。 - P1 写端 replace 拼写前向兼容:
applySurfaceReplace写入时先按当前 运行时{startSeq,endSeq}形状写;若引擎报invalid replace surfaceOp(validateNext 在事件入 log 前抛出,无半写风险)自动降级{start,end}重试并记住拼写。当前与未来 dsh cohort 都不炸;读端双拼写兼容已统一收敛到session-file.readReplaceOp单点(computeShadowed / planRestore 共用)。 - P1 大日志让出:新增
readSessionFileAsync,逐帧解压每 8 帧setImmediate让出事件循环;messages/export/restore 路由改走该路径。 帧数超过 500 阈值的 export 在 Markdown 末尾追加耗时提示(partial 语义, 内容完整无截断)。 - P2:
readJsonBody加 1MB 上限(超限 413);findSessionDirs多 project slug 命中同一 id 时显式报 409 歧义而非静默取第一个;branch 产物 不进会话注册表——需刷新会话列表后才可见(此为宿主扫描行为,见 branch 命令 返回后请刷新列表)。
0.2.0 新增
Agent 工具 message_ops
模型侧可直接调用五操作:action: list | revert | delete | branch | restore | export,参数
sessionId(必填)、seq、upToSeq。工具与 HTTP 路由共用同一套核心逻辑
(src/ops-core.js),行为与错误语义完全一致;失败以文本形式返回,不中断回合。
工具注册是容错的:本插件 inject 保持为空(tools 是可选增强而非硬依赖,
cordis 的 inject 是硬依赖声明,缺服务会让整个插件永不加载)。apply 时探测
ctx.get('tools'),有则注册;没有则通过 ctx.inject(['tools'], …) 等它出现;
@deepseek-ai/dsh-tools 包不可解析时静默跳过工具,HTTP 面完全不受影响。
因此 @deepseek-ai/dsh-tools 以 peerDependencies 形式声明(^0.1.0-rc.6)。
导出 Markdown
GET /api/message-ops/export?sessionId=<id>&seq=<可选上界(含)>,Content-Disposition
附件下载。user/assistant/system 消息按角色小节展开(## [seq N] role),工具调用折叠为
单行引用(含 80 字符参数摘要);带 parentSession 的分支会话在头部标注来源。
回滚恢复(restore,重放语义)
POST /api/message-ops/restore 传 {sessionId, seq},seq 指向一次 revert/delete 落定的
标记事件(surfaceOp: replace、携带 sourceEventSeqs)。
引擎能力查证结论(见 src/ops-core.js 头部注释):安装的运行时
@deepseek-ai/dsh-session 的 SurfaceOp 只有 append 与 replace 两个变体,
不存在「解除遮蔽」操作——surface replace 是 append-only 的永久遮蔽。因此恢复实现为
重放(replay)而非解除遮蔽:把被遮蔽区间的 user/assistant 消息文本重新 append 为新
事件,文本加 [恢复] 前缀,并先 append 一条 system 说明;tool/call 等不可安全重放的事件
跳过并在结果中计数。原遮蔽区间保持不可见(磁盘日志原样保留)。
语义差异须知:恢复出来的消息 seq 是新的、时间戳是新的、措辞带 [恢复] 标记,且不是
「回到当时」——后续上下文(中间隔着的其他对话)不会被抹掉。若想「干净地回到某点」,
请用 branch 从该 seq 分叉。
兼容性备注:运行时引擎当前 replace 形状为 {op:'replace', startSeq, endSeq};
dsh-src 仓库较新副本已改名为 start/end。本插件写入用 startSeq/endSeq(对齐安装的
运行时),restore 读取端两种拼写都兼容,前向迁移无需改动。
开发
npm test # 20 个零依赖测试(node --test,Node 26 起目录参数已弃用,改用 glob)
node --check src/*.js
格式兼容性已对真实会话日志验证(多帧 v3 格式;旧单帧 session.jsonl.zstd 读取走同一帧扫描路径)。
License
MIT
0.3.0 · 用户 UI 回撤体系(官方槽集成)
针对 dsh 0.2.0-rc.2 的用户级操作面(agent 工具保留,但 UI 为主推入口):
- 每条 AI 消息旁的原生回撤按钮:注入官方
conversation.chat.assistant-actions槽(0.2.0 正式扩展点,条目收持久化 messageId)——「回滚到此 / 删除此条 / 从此分支」直接出现在消息操作行,与官方点赞/点踩并列;0.1.x 无此槽自动 no-op。 messageId→seq 反查经/api/message-ops/messages(每会话索引缓存)。 - 分支双路径:0.2.0+ 优先官方
sessions.fork({sessionId, atSeq, increaseTitle})(子会话自动进宿主列表,成功后可一键uiWorkspace.openSession打开); 官方 fork 失败或 0.1.x 回退磁盘级 applyBranch(原路径保留)。 - 恢复(restore)补完:选中 revert/delete 落定的 replace 标记行时,对话框
提供「恢复」操作(重放语义:新 seq +
[恢复]前缀,不可重放事件计数跳过), 确认弹窗内联语义说明(评审缺口销账)。 - 官方已有但易混淆的:0.2.0 原生分支按钮(仅限已完成轮次最后一条消息)与 本插件的「任意位置分支/回滚」互补不冲突。
0.3.1 · 槽按钮重构(opencode 式一键 + 去重)
- 一键立即执行:回滚/删除点击即生效(无对话框),devkit toast 反馈;宿主 0.2.0 chat store 原生响应 surface replace——遮蔽即时呈现,无需刷新页面。
- 与官方去重:槽内移除分支按钮——官方 0.2.0 已在消息操作行自带分支 (限已完成轮次最后一条);任意位置分支保留在对话框(官方没有的能力)。
- 原生观感:按钮改用官方 primitives 图标(IconClockOutline/IconTrashOutline, require 失败回退内联 SVG);S3 命中区 ≥44px;S11 双提交防护(busy 互斥)。
0.3.2 · 图标规范化
- 槽按钮去掉文字 emoji——0.2.0+ 直接用官方 primitives 图标(与原生操作行 1:1 同款); 回退路径的 SVG 也改为官方 IconClockOutline/IconTrashOutline 的精确 path 数据 (1px stroke,与 Regular 变体一致),不再手绘。
- 字典文案去 emoji(label 仅用于 tooltip/aria,不参与渲染)。
Plugins associés
dsh-web (dsh-task-board)
zhu1090093659/dsh-web
dsh-web (dsh-web-all)
zhu1090093659/dsh-web
dsh-web-ui (dsh-task-board)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-web-ui-all)
zhu1090093659/dsh-web-ui