dsh-rewind-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SiriLee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # dsh-rewind
2
+
3
+ DeepSeek Harness 插件:**同一会话窗口的 in-place 对话回退**(Claude Code `/rewind` 语义)。主交互为**用户消息旁的「回退」按钮**,点击后选择回退模式;命令仅作辅助。
4
+
5
+ > 状态:v0.1.0 已实现(host + client 双面插件)。交互以 Claude Code 行为为参考,并贴合 dsh Web 实际 UI(利用现有 DOM 锚点与运行时快照,纯插件、不改仓库核心)。
6
+
7
+ ## 实现状态(v0.1.0)
8
+
9
+ - ✅ host 端 `/rewind` 命令(两步文本引导 + 直接执行 + `preview` 影响清单)
10
+ - ✅ host 端变更台账(`tools/execute` 捕获 before、`tools/post-execute` 提交),按会话隔离
11
+ - ✅ **与其他审批类插件共存**:捕获在 around-dispatch 阶段,`tools/pre-execute` 被 `ask` 短路(如 dsh-edit-approval)后批准仍能记录;被拒绝的调用不留 pending 残留
12
+ - ✅ **路径按会话 cwd 解析**(复刻 `dsh-tool-fs` 的 session-cwd 规则),相对路径台账/还原指向真实文件;台账记录解析后的 display path
13
+ - ✅ **fs 服务动态获取**(`ctx.inject(['fs'])`):fs 后挂载也不失效,无 fs 部署时命令仍可用
14
+ - ✅ 同窗口 in-place 回退:追加标记节点 + `surfaceOp: replace` 替换目标点之后的 surface(真实 `dsh-session` 集成测试通过)
15
+ - ✅ client 端「回退」按钮(MutationObserver 注入用户消息行操作区)+ 模式选择浮层(含 both 模式影响清单确认)
16
+ - ✅ 测试:纯函数单测 + 真实 `dsh-session` 集成测试 + `verify-host` 端到端(14 项,含审批短路/会话 cwd 场景)
17
+ - ⏳ 二期:快捷键、git-first 快照式文件回退、命令路径的 client 两步浮层接管
18
+
19
+ ## 安装
20
+
21
+ > 发布名:**`dsh-rewind-plugin`**(npm 上的 `dsh-rewind` 已被功能重叠的既有包占用)。
22
+ > 本地 / GitHub 路径不受影响(仓库仍为 `SiriLee/dsh-rewind`)。
23
+
24
+ ```sh
25
+ # 本地 checkout
26
+ dsh plugin --profile web add /home/slev/workspace/projects/dsh-rewind
27
+
28
+ # 或 GitHub(git 安装会运行 prepare 构建;需按提示在 profile 的
29
+ # pnpm-workspace.yaml 中 allowBuilds 授权)
30
+ dsh plugin --profile web add github:SiriLee/dsh-rewind#<commit-sha>
31
+
32
+ # 或 npm 发布后(预构建产物,无需授权)
33
+ dsh plugin --profile web add dsh-rewind-plugin
34
+ ```
35
+
36
+ `cordis.patch.yml` 插入一行 `dsh-rewind-plugin`(node 半面 = host 插件;行名必须
37
+ 等于包名);包声明 `dsh.bundle` + `dsh.client`,浏览器半面由
38
+ `dsh-client-modules` 从 `exports["./client"]` 解析并注入 web roster。
39
+
40
+ ## 发布(维护者,CI + Trusted Publishing)
41
+
42
+ 发布走 GitHub Actions + npm **Trusted Publishing**(OIDC,无 npm token):
43
+
44
+ ```sh
45
+ npm version patch # 或 minor / major;同步更新 README 版本说明
46
+ git push --tags # push v<version> tag → 触发 .github/workflows/publish.yml
47
+ ```
48
+
49
+ - workflow:`v*` tag 或手动 `workflow_dispatch` 触发;`id-token: write` 让 npm
50
+ registry 校验 GitHub OIDC token 后授权发布(Node 24 自带 npm ≥11.5.1,
51
+ OIDC 必需);自动生成 SLSA provenance;版本已发布则幂等跳过;tag 自动建
52
+ GitHub Release。
53
+ - **一次性 npm 侧配置**(仓库内无法代做;配置**不需要包先存在**,发布后
54
+ `npmjs.com/package/dsh-rewind-plugin` 才会出现):
55
+ 1. 浏览器打开 [npmjs.com → sirilee → Access Tokens](https://www.npmjs.com/settings/sirilee/access-tokens),
56
+ 顶部 **Enable Trusted Publishing**;
57
+ 2. **Add publisher** → GitHub → repo `SiriLee/dsh-rewind`、branch `main`
58
+ (与 workflow 的 `environment: npm` 对应时可选 environment)。
59
+ - 质量门禁(PR / push main):`.github/workflows/ci.yml` 跑 typecheck + 测试
60
+ + 构建 + `verify-host`。
61
+
62
+ ## 使用
63
+
64
+ - 每条用户消息 hover 出现「↶ 回退」按钮:点击 → 选择「仅回退对话」或
65
+ 「回退对话和代码」(后者先展示影响清单再确认)。
66
+ - 键盘流:`/rewind` → 选消息 → `/rewind <序号> chat|both`;`/rewind preview <目标>`
67
+ 只输出影响清单不执行。
68
+ - 回退后:模型上下文从目标消息重新开始;会话日志与可见对话完整保留
69
+ (append-only);标记节点不渲染为气泡(非 append surface 事件),结果以命令节点呈现。
70
+
71
+ ## 已知限制(v0.1)
72
+
73
+ - 台账只覆盖插件运行期间、经 `write` / `edit` / `str_replace_editor` 的变更;
74
+ bash 或外部程序的修改无法还原(二期可加 git-first 快照层)。
75
+ - 文件删除走 `processPath` 直删(本地 backend);sandbox/远程 backend 下还原
76
+ 可能受限。
77
+ - 回退本身可再回退(标记进入日志),但文件还原动作不再重新入台账。
78
+
79
+ ## 背景与定位
80
+
81
+ 社区 rewind 类插件(`dsh-recall-plugin`、`dsh-checkpoint-rewind`、`dsh-turn-rewind`)均为 **fork 路线**(回退 = fork 出新会话,用户切换会话继续),且没有「仅回退对话 / 对话+代码」的选项。本插件提供:**在当前会话窗口内**改写模型上下文 + 可选还原工作区文件。
82
+
83
+ ## 交互设计(Claude Code 式两步:先选消息,再选模式)
84
+
85
+ 所有入口(按钮、命令)都遵循同一流程:**第一步选择要回退到的 user 消息,第二步选择回退模式**。不会在一开始就要求指定模式。
86
+
87
+ ### 1. 用户消息旁的「回退」按钮(主入口)
88
+
89
+ - 在你**发送过的每条用户消息**下方/旁边显示「↶ 回退」按钮(hover 出现,与现有 clock/copy 操作并排)。
90
+ - **第一步(目标即已确定)**:点击某条消息旁的按钮,回退目标就是这条消息——无需再选。
91
+ - **第二步(模式选择浮层)**:弹出小型浮层(非新页面),选项:
92
+ - **仅回退对话** —— 只回退模型上下文,不动工作区文件
93
+ - **回退对话和代码** —— 对话回退 + 工作区文件还原到该消息之前
94
+ - **取消**
95
+ - 选「回退对话和代码」时,浮层内先显示将受影响的内容清单(将还原/删除的文件名与数量),确认后执行。
96
+ - 执行结果以一条对话内消息呈现(如「已回退到 seq N,移除 M 条上下文;还原 2 个文件」)。
97
+
98
+ ### 2. 命令(辅助入口,面向键盘流与 headless;同样两步)
99
+
100
+ ```
101
+ /rewind 第一步:列出最近的 user 消息(序号 + 时间 + 内容预览),等待选择
102
+ /rewind <序号|@seq> 第二步:对选中的消息展示模式选项,等待选择
103
+ /rewind <序号|@seq> chat 执行:仅回退对话
104
+ /rewind <序号|@seq> both 执行:对话 + 代码
105
+ /rewind preview <目标> 只输出影响清单,不执行
106
+ ```
107
+
108
+ - 分步示例:`/rewind` → 返回「1. 14:02 … / 2. 13:47 …」→ 输 `/rewind 1` → 返回「回退到消息 1:/rewind 1 chat 或 /rewind 1 both」→ 输 `/rewind 1 both` 执行。
109
+ - 若输入直接带全参数(`/rewind 1 both`),等价于跳过前两步直接执行——高级用法,不强制。
110
+ - UI 按钮与命令共享同一套 host 端回退逻辑(`/rewind @seq <mode>`)。
111
+
112
+ ## 回退机制(host 端,全部公开 API)
113
+
114
+ ### 3. 同窗口 in-place 对话回退
115
+
116
+ - `Session.append('user/message', marker, { surfaceOp: { op:'replace', start, end }, sourceEventSeqs })`:在当前会话日志内追加回退标记节点,把目标点之后的所有 surface 节点从模型上下文替换掉。
117
+ - 效果:当前窗口上下文从目标点重新开始;**不产生新会话、不切换窗口**;原始日志完整保留(append-only 审计不变),仅不再进入模型上下文。
118
+ - 依赖:`@deepseek-ai/dsh-session`(`Session.append`、`foldSurface`)、`@deepseek-ai/dsh-llm`(`createUserMessage`)、`@deepseek-ai/dsh-commands`(命令注册)、`@deepseek-ai/dsh-agent`(`Agent.status` idle 守卫)。
119
+
120
+ ### 4. 文件回退:变更台账
121
+
122
+ - 在 `tools/execute`(around-dispatch 阶段)读取目标文件 before,`tools/post-execute` 记录
123
+ `{ 消息锚点 seq, 文件路径(解析后), before, after }`。捕获放在 execute 而非
124
+ pre-execute:**审批类插件(如 dsh-edit-approval)在 `tools/pre-execute` 返回 `ask`
125
+ 会短路后续监听器**,但批准后 dispatch 阶段必然执行——共存的写操作照样入台账;
126
+ 被拒绝的调用不 dispatch,不会留下 pending 残留。
127
+ - 相对路径按**会话 cwd** 解析(与 `dsh-tool-fs` 同规则,`src/session-cwd.ts`),
128
+ 台账记录解析后的 display path,preview/还原始终指向真实文件。
129
+ - 回退「对话和代码」时,把目标点之后发生的变更**逆序还原**(内容写回 before、新建文件删除)。
130
+ - 边界(已知限制):台账只覆盖**插件运行期间、经写类工具**的变更;bash 命令或外部程序的修改不在台账内,无法还原(二期可加 git-first 快照层)。
131
+
132
+ ### 5. 安全守卫
133
+
134
+ - agent 运行中拒绝执行(`status !== 'idle'`)。
135
+ - 文件还原是破坏性操作:UI 选择「对话和代码」时需经影响清单确认;命令路径用 `preview` 先行查看。
136
+ - 回退本身可再回退(回退动作同样进入台账/日志)。
137
+
138
+ ## 客户端实现要点(纯插件,无源码补丁)
139
+
140
+ - 按钮注入锚点:用户行 `[data-chat-flow-kind="user"]`(行容器 `data-chat-anchor-key` 为节点 key);用 MutationObserver 跟踪新增行。
141
+ - 消息 seq 获取:从行元素的 `data-chat-anchor-key` → 运行时快照 `session.getSnapshot().chat.nodes.get(key)` → `UserMessageNode.seq`(DOM 只用于定位,数据取自 runtime,不解析 DOM 文本)。
142
+ - **两步选择浮层(命令与按钮共用)**:输入 `/rewind` 或点击消息旁按钮时,客户端接管交互——第一步展示最近 user 消息列表(序号 + 时间 + 预览),选中后第二步展示模式选项(仅回退对话 / 回退对话和代码 / 取消);确认后调 `session.command('/rewind @<seq> <mode>')` 执行。host 端分步文本引导作为无客户端/headless 场景的降级。
143
+ - 执行结果以命令节点出现在对话中。
144
+ - 注入按钮与「在新对话中分支」等官方操作并排,样式遵循 dsh 设计 token。
145
+
146
+ ## 明确不包含(本期)
147
+
148
+ - 快捷键(esc+esc 回退等)——独立的快捷键插件,二期。
149
+ - 压缩(`/compact`)——官方已有。
150
+ - fork/分支回退——官方已有(「在新对话中分支」)。
151
+ - 快照式文件回退(git-first)——二期(台账方案先行)。
152
+
153
+ ## 目录结构(实际)
154
+
155
+ ```
156
+ src/index.ts host 插件:/rewind 命令 + tools/execute|post-execute 台账(fs 动态注入)
157
+ src/rewind.ts planRewind 纯函数(目标解析、surface 范围计算、候选列表)
158
+ src/ledger.ts 变更台账(记录、查询、逆序还原、影响清单;按会话 cwd 解析)
159
+ src/session-cwd.ts 会话 cwd 解析(复刻 dsh-tool-fs 规则,可单测)
160
+ src/client/index.ts client 插件:消息行「回退」按钮 + 模式选择浮层
161
+ src/client/popover.ts 浮层 DOM(含 both 模式影响清单确认)
162
+ src/client/locales.ts zh/en 文案(LocaleNamespaceMap 合并)
163
+ src/client/styles.ts 注入样式(dsh 设计 token)
164
+ scripts/build.mjs esbuild 构建:lib/index.js(host ESM)+ lib/client.js(loader 闭包)
165
+ scripts/verify-host.mjs 端到端验证(真实 cordis + dsh-session,14 项断言)
166
+ tests/ rewind/ledger 单测 + 真实 dsh-session 集成测试
167
+ cordis.patch.yml bundle patch(插入 dsh-rewind-plugin 一行,双面)
168
+ package.json dsh.bundle + dsh.client 声明、optional peerDependencies
169
+ ```
170
+
171
+ ## 参考:deepseek-harness 接口文档
172
+
173
+ 本地 fork:`../../oss/deepseek-harness/` · 官方仓库:[github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)
174
+
175
+ ### 子系统文档(`docs/subsystems/`)
176
+
177
+ - [session.md](../../oss/deepseek-harness/docs/subsystems/session.md) — `Session` / `SessionStore` / 事件模型(`Session.append`、`surfaceOp`、`sourceEventSeqs`)
178
+ - [core.md](../../oss/deepseek-harness/docs/subsystems/core.md) — `Agent`(`status`、`session`)等核心类型
179
+ - [commands.md](../../oss/deepseek-harness/docs/subsystems/commands.md) — 命令注册(`ctx.commands.register`、`CommandInvocation`、`CommandResult`)
180
+ - [tools.md](../../oss/deepseek-harness/docs/subsystems/tools.md) — 工具执行 seam(`tools/pre-execute` / `tools/post-execute`、`ToolExecution`)
181
+ - [session-query.md](../../oss/deepseek-harness/docs/subsystems/session-query.md) — 会话查询/`foldSurface` 相关只读接口
182
+ - 根目录目录:`docs/persistence-catalog.md`(`SessionEventMap` 全量事件)、`docs/tool-catalog.md`(工具清单)、`docs/config-catalog.md`(配置清单)
183
+
184
+ ### 关键源码(`packages/`)
185
+
186
+ | 接口 | 文件 |
187
+ |---|---|
188
+ | `Session.append`、surface 校验 | [packages/core/session/src/index.ts](../../oss/deepseek-harness/packages/core/session/src/index.ts) |
189
+ | `foldSurface`、替换规则 | [packages/core/session/src/surface.ts](../../oss/deepseek-harness/packages/core/session/src/surface.ts) |
190
+ | `SessionEventMap`、`SurfaceOp` | [packages/core/session/src/types.ts](../../oss/deepseek-harness/packages/core/session/src/types.ts) |
191
+ | `createUserMessage`、`MessageSource` | [packages/llm/llm/src/message.ts](../../oss/deepseek-harness/packages/llm/llm/src/message.ts) |
192
+ | `CommandDefinition`、`CommandInvocation` | [packages/interaction/commands/src/index.ts](../../oss/deepseek-harness/packages/interaction/commands/src/index.ts) |
193
+ | `Agent`(`status`/`session`) | [packages/core/agent/src/runtime-types.ts](../../oss/deepseek-harness/packages/core/agent/src/runtime-types.ts) |
194
+ | `tools/pre-execute` / `execute` / `post-execute` | [packages/core/tools/src/index.ts](../../oss/deepseek-harness/packages/core/tools/src/index.ts) |
195
+ | 客户端 DOM 锚点(`data-chat-flow-kind`/`data-chat-anchor-key`) | [packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx](../../oss/deepseek-harness/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx) |
196
+ | 用户气泡渲染 | [packages/client/ui-conversation/src/client/chat/MessageItem.tsx](../../oss/deepseek-harness/packages/client/ui-conversation/src/client/chat/MessageItem.tsx) |
197
+ | 客户端 `SessionFace`(`command`/`cancel`) | [packages/client/runtime/src/client/contract/session.ts](../../oss/deepseek-harness/packages/client/runtime/src/client/contract/session.ts) |
198
+ | 客户端 `PendingWait`(`respond`) | [packages/client/runtime/src/client/sessions/pending.ts](../../oss/deepseek-harness/packages/client/runtime/src/client/sessions/pending.ts) |
@@ -0,0 +1,10 @@
1
+ # dsh-rewind-plugin bundle patch: one row, dual-face.
2
+ # The node half is the package's default export (host plugin: /rewind command
3
+ # + tools/execute|post-execute ledger); the browser half is served from
4
+ # ./client (dsh.client declaration in package.json) and injected into the web
5
+ # roster. The row name MUST equal the package name — the client-modules node
6
+ # half resolves `require.resolve('<row-name>/package.json')` to find the
7
+ # package and its dsh.client declaration.
8
+ - insert:
9
+ - id: dsh-rewind-plugin
10
+ name: 'dsh-rewind-plugin'