🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

会话回退 LUDIWUSI12138/dsh-sqy-rewind

DeepSeek Harness
LUDIWUSI1213816 天前有提交已提供中文文档扫描:含敏感能力

把 DSH 会话退回到你已发出的某一轮对话之前。 模型可见的 surface…

30.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/21 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add LUDIWUSI12138/dsh-sqy-rewind
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
数据截至 2026/11/02·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 16 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-sqy-rewind(未发布到 npm,仅可源码安装)
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/12 08:17:35

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 12 处,展示前 12 条)
  • 读取本地文件
    entry.js:255
    if (existsSync(path)) {
  • 读取本地文件
    entry.js:256
    const parsed = JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    entry.js:274
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    entry.js:275
    writeFileSync(path, JSON.stringify(store))
  • 发起网络请求
    lib/client.js:343
    fetch(`${ROUTE}/api/state?sessionId=${encodeURIComponent(sessionId)}`, { cache: 'no-store' })
  • 发起网络请求
    lib/client.js:584
    fetch(`${ROUTE}/api/rewind`, {
  • 读取本地文件
    build-client.mjs:17
    const id = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name
  • 读取本地文件
    build-client.mjs:23
    const source = readFileSync(join(root, 'src', 'client.js'), 'utf8')
  • 写入本地文件
    build-client.mjs:28
    mkdirSync(join(root, 'lib'), { recursive: true })
  • 写入本地文件
    build-client.mjs:30
    writeFileSync(join(root, 'lib', 'client.js'), bundle)
  • 发起网络请求
    src/client.js:341
    fetch(`${ROUTE}/api/state?sessionId=${encodeURIComponent(sessionId)}`, { cache: 'no-store' })
  • 发起网络请求
    src/client.js:582
    fetch(`${ROUTE}/api/rewind`, {
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 回退会话至指定轮次
  • 清除该轮后模型记忆

把 DSH 会话回退到你已发送的某一轮之前。

README

由 DeepSeek 最新模型翻译生成
dsh-sqy-rewind 把 DSH 会话退回到你已发出的某一轮对话之前。 模型可见的 surface 就地替换——对模型来说那一轮从未发生过——界面则把被回退的轮次藏起来。DSH 自身源码不动,原始会话日志一条不删。 把当前会话退回某一轮对话发起之前。模型历史用 surface replace 就地遮蔽(那一轮等于没发生过),界面同步把被回退的轮次藏起来。不改 DSH 本体源码,原始日志一条不删。 长什么样 每条用户消息下面,和「复制」并排的回退图标 点第一次后弹出的「再点一次确认回退」提示 每条用户消息下面,和「复制」并排多一个回退图标: - 第一次点:弹出「再点一次确认回退」提示;点页面任意空白处立刻收起。提示不会自己超时消失,也不带按钮——再点一下这个回退图标就是确认 - 第二次点:执行回退 - 之后:从那一轮起(含它自己)全部从界面上消失,模型也不再记得 按钮挂在用户消息上是有意的:点哪条提问,就退回到那条提问之前。 ui-chat 把用户消息的操作行硬编码成 MessageIconActions,没有开 slot,所以这一半只能 DOM 注入,不能像助手消息那样注册 slot。见下文。
展开完整文档(README 剩余 1 节)
dsh-sqy-rewind

把 DSH 会话退回到你已发出的某一轮对话之前。 模型可见的 surface 就地替换——对模型来说那一轮从未发生过——界面则把被回退的轮次藏起来。DSH 自身源码不动,原始会话日志一条不删。

把当前会话退回某一轮对话发起之前。模型历史用 surface replace 就地遮蔽(那一轮等于没发生过),界面同步把被回退的轮次藏起来。不改 DSH 本体源码,原始日志一条不删。

长什么样

每条用户消息下面,和「复制」并排的回退图标

点第一次后弹出的「再点一次确认回退」提示

每条用户消息下面,和「复制」并排多一个回退图标:

- 第一次点:弹出「再点一次确认回退」提示;点页面任意空白处立刻收起。提示不会自己超时消失,也不带按钮——再点一下这个回退图标就是确认
- 第二次点:执行回退
- 之后:从那一轮起(含它自己)全部从界面上消失,模型也不再记得

按钮挂在用户消息上是有意的:点哪条提问,就退回到那条提问之前。

ui-chat 把用户消息的操作行硬编码成 MessageIconActions,没有开 slot,所以这一半只能 DOM 注入,不能像助手消息那样注册 slot。见下文。

它到底做了什么

| 层 | 动作 |
| 模型历史 | 追加一条 user/message,带 surfaceOp: { op: 'replace', startSeq, endSeq };surface 折叠把 [startSeq, endSeq] 区间内所有模型可见节点丢掉,只留这条标记 |
| 界面 | 插件自己按 turn 号把被回退的轮次 display: none(DSH 不会替你隐藏,见下) |
| 日志 | 原样保留。被遮蔽的事件仍在 session.v4.jsonl.zstd 里,只是不再进模型上下文 |

为什么载体必须是 user/message

第一版用空 content 的 developer/message,理由是 deriveEventMessage 把空 developer 消息映射成 null,标记就不会进模型上下文。这条路是死的,装上就报:

developer/message turn must be a non-negative safe integer

两条硬约束挡在这里(都在 dsh-session-format 的 V4 接受检查里):

1. developer 消息必须带正数 turn/step,而关系检查要求这两个坐标匹配一个开放的 turn 和 step。回退发生在轮次结束之后,根本没有开放的 turn/step 可以匹配。
2. 同一条检查明确拒绝 source.kind === "plugin" 的 developer 消息——第一版正好用的就是这个。

user/message 是唯一不被轮次状态绑住的 surface 类型,所以改用它。

代价:user 消息必须带 content,于是标记是一行中文

(用户已回退这一轮对话)

这行字是回退在模型上下文里留下的唯一痕迹,模型看到它会知道这一轮被撤销了。

好在它不会在界面上出现:ui-chat 的 messageDefinition.match 要求
isAppendSurfaceEvent(event),也就是 event.surfaceOp === "append";我们的标记带的是对象形式的
surfaceOp,所以它根本装配不出消息节点,客户端不需要为它做任何隐藏。

为什么界面要插件自己藏

DSH 的 transcript 是故意用 append 来源的事件拼的。官方在 dsh-session/lib/types/surface.js 里写得很直白:

The model-visible surface deliberately shadows replaced ranges, so it is the wrong
source for a human transcript — a landed replacement would erase conversation the
user already saw. Append-origin events are that transcript's durable source material;
replacement copies stay model-only.

也就是说 surface replace 只让模型失忆,用户看过的消息不会消失。
而且客户端的事件流要求 seq 连续,所以「Host 端把被遮蔽的事件从分页窗口里过滤掉」这条路走不通——会造成 seq 空洞,触发 gap repair,最后把整条流搞失败。

所以这个插件在 DOM 层按 turn 号隐藏。范围由 Host 从已落地的标记事件里读回来,因此刷新页面后依然有效,不需要客户端存任何状态。

限制(重要)

- 回退不可逆。 surface 折叠是单向的:被遮蔽的节点从 surface 里永久移除,DSH 没有「取消 replace」的 API。日志还在,但没有任何官方手段能把它们放回模型上下文。这就是按钮要确认两次的原因。
- 模型上下文里会留一行中文说明(见上)。做不到「完全没发生过」——载体类型决定的,绕不过去。
- 如果目标轮次的起点已经被压缩(compaction)吸收,回退会被拒绝。 压缩摘要遮蔽掉的那些节点已经不在 surface 上了。想「回到那一轮之前」就得连摘要一起遮蔽,而那会让模型只剩系统提示 ——等于把上下文清空。插件选择拒绝,而不是悄悄做这件事。
实测:一个 720 事件的会话里,第 3 轮的 turn/start(seq 76)落在压缩摘要遮蔽的 [15, 547] 区间内,这一轮就无法精确回退;同一台机器上另外两个会话(10 轮 / 23 轮)回退正常。
- 边界是按「模型可见顺序」找的,不是按 seq 比的。 这点反直觉但很关键:压缩标记带着它被写入时的 seq,却坐在它所替换的历史位置上,所以 surface 上一个 seq 更大的节点可以排在一个 seq 更小的
节点前面。实测的节点序列长这样:[0, 550, 470, 473, ...]。按 seq 找边界会把压缩摘要一起遮蔽,
模型会瞬间失忆。tools/verify-entry.mjs 里有这两个场景的回归测试。
- 按钮是 DOM 注入的,不是官方 slot。 ui-chat 没给用户消息的操作行留 slot,所以插件用 MutationObserver 往 div[data-clock="start"] 里插按钮,React 重渲染冲掉后会自动补回来。
这是这一半唯一「非官方」的地方。
- 只影响模型历史和本插件隐藏的那些轮次。 被隐藏的 DOM 节点还在页面里(只是 display:none)。
- 依赖 turnOutline 投影(dsh-session-turn-outline)。没有它就算不出轮次边界,插件会拒绝回退而不是猜一个位置切下去。
- 回退会先停掉正在跑的一轮。 用 agent.cancel({ kind: 'user' }, { keepInbox: true }) + whenIdle(),和界面上的停止按钮是同一条链路。否则 agent 会在标记之后继续追加节点,回退就不成立。
- 用的是非弃用 API:ctx.sessionQuery.readSurface()、ctx.sessionProjections.stateOf()、
session.surface.nodes、session.append()。没有用 eventAt / snapshotEvents / ownEvents
(这三个已弃用)。

安装

方式一:DSH 插件市场(推荐)

被 awesome-dsh-plugin 收录后,在 DSH 里打开插件市场搜 dsh-sqy-rewind 即可一键安装。市场的安装源被限制在注册表内,所以"被收录"是别人能一键装的前提。

方式二:dsh CLI

dsh plugin --profile desktop add github:LUDIWUSI12138/dsh-sqy-rewind

方式三:从源码装(开发用)

git clone https://github.com/LUDIWUSI12138/dsh-sqy-rewind.git
cd dsh-sqy-rewind

node tools/install.mjs --profile desktop --dry-run # 先看它要做什么
node tools/install.mjs --profile desktop # 真装

--profile 默认取 $DSH_PROFILE,没有就用 desktop;DSH_HOME 默认 ~/.dsh。

前置条件

| 项 | 要求 |
| DSH | 桌面版,实测 0.2.0-rc.2 |
| Node | 22.19+ 或 24+(与 package.json 的 engines 一致) |
| 平台 | Windows 实测通过。安装器用 junction,POSIX 上 Node 会退化成普通符号链接,未实测 |

它改三处(改之前全部备份到 ~/.dsh/.dsh-rollback-dsh-sqy-rewind-<时间戳>/):

| 位置 | 改动 | 少了会怎样 |
| <profile>/node_modules/dsh-sqy-rewind | 指向本包的 junction | Host 半根本加载不到 |
| <profile>/cordis.patch.yml | 追加一行 - insert: | 插件不被注册(bundle 自带的 patch 一般已覆盖它,这行是保险) |
| <profile>/package.json | 写入 dependencies 和 dsh.profile.bundles | 浏览器半不会进前端 bundle:按钮缺失,或只在个别会话里出现 |

第三项最容易漏,也是「按钮时有时无」的根因:只加 cordis.patch.yml 的 insert 行能加载 Host 半,但浏览器半不会被打进前端 bundle,于是界面这一半根本没运行。
cordis.patch.yml 是无 BOM 的 UTF-8 且别行带中文,所以安装器用 Node 读写而不是 PowerShell。

装完必须重启 DSH:Host 半(entry.js)靠热加载换不掉,运行中的进程持有旧模块。
只改了浏览器半(lib/client.js)的话,刷新页面就够。

验证装好了

重启后随便打开一个会话,每条你发出的消息下面应该和「复制」并排多一个回退图标。
没有的话跑一次干跑,看最后两行:

node tools/install.mjs --profile desktop --dry-run

dep 和 bundle 都应该是 already registered。

卸载

node tools/install.mjs --profile desktop --uninstall

三处改动都会撤销,package.json 的其它字段原样保留。

改代码之后

node --check entry.js
node --check src/client.js
node build-client.mjs # src/client.js -> lib/client.js(没有打包器,包装即构建)
node tools/verify-entry.mjs # Host 半:stub context 跑 apply(),再用假请求打一遍接口

tools/verify-entry.mjs 是改 entry.js 之后最该跑的东西:它不起 DSH、不占端口,用 stub context 调 apply(),检查路由只认领 /api 前缀(认领整个 /plugins/dsh-sqy-rewind 会挡住 shell 自己的 client.js),然后走一遍非回环 403、缺参数 400、坏 JSON 400、未知路径 404、非活动会话 404,最后真的跑几次回退,断言落地事件的类型、user/message 载体(以及它不是 developer 消息)、replace 的三个键、sourceEventSeqs 对被遮蔽节点的完整覆盖、要隐藏的 turn 区间,以及「我们的标记」和「compaction 摘要」不会被认混(两者都是带 replace 的 user/message)。

entry.js 不能靠刷新验证,所以这是重启之前唯一便宜的关卡。

结构

| 文件 | 作用 |
| entry.js | Host 半:回退逻辑 + /plugins/dsh-sqy-rewind/api/* |
| src/client.js | 浏览器半源码:DOM 注入按钮 + DOM 隐藏 |
| lib/client.js | 构建产物(window.__ModuleLoader__.load 包装,勿手改) |
| build-client.mjs | 上面那个包装 |
| tools/install.mjs | 安装/卸载/干跑 |
| tools/verify-entry.mjs | Host 半桩测试 |

接口

GET /plugins/dsh-sqy-rewind/api/state?sessionId=<id>
-> { ok: true, marks: [{ startSeq, endSeq, fromTurn, toTurn }] }

POST /plugins/dsh-sqy-rewind/api/rewind
body: { sessionId, turn }
-> { ok: true, startSeq, endSeq, turn, fromTurn, toTurn }

turn 就是 DOM 上 [data-chat-turn] 的值,浏览器半不用反查 messageId。

只监听回环来源;非回环一律 403。

房子规则

代码注释与文档用英文,插件里面向用户的文案用中文。
预览分享卡片长什么样 ↓LUDIWUSI12138/dsh-sqy-rewind
分享 · DeepSeek Harness Hub
LUDIWUSI12138/dsh-sqy-rewind

把 DSH 会话退回到你已发出的某一轮对话之前。 模型可见的 surface…

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部