dsh-rewind-plugin 0.2.0 → 0.2.2
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/README.md +195 -188
- package/README.zh.md +214 -0
- package/assets/screenshots/guard-hint.png +0 -0
- package/assets/screenshots/impact-list.png +0 -0
- package/assets/screenshots/mode-popover.png +0 -0
- package/assets/screenshots/rewind-button.png +0 -0
- package/docs/harness-reference.md +34 -0
- package/lib/client.js +140 -89
- package/lib/index.js +66 -35
- package/lib/types/client/hidden.d.ts +33 -0
- package/lib/types/client/locales.d.ts +6 -2
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/snapshot.d.ts +17 -3
- package/package.json +4 -3
- package/scripts/build.mjs +0 -89
- package/scripts/verify-host.mjs +0 -206
package/README.md
CHANGED
|
@@ -1,212 +1,219 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[简体中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
In-place conversation rewind for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): the Claude Code `/rewind` semantics inside the **same session window** — cut the model context back to an earlier user message, and optionally restore workspace files from **disk-persisted before-backups**.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **Status:** published to npm (`dsh-rewind-plugin`, v0.2.0) via GitHub Actions Trusted Publishing + Sigstore provenance. Targets the web profile (`dsh --profile web`). Interaction mirrors Claude Code's rewind, adapted to dsh's real web UI.
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
|
|
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
|
-
- ✅ **还原走 `node:fs` 直写**(不经 fs 服务):文件内容真正落盘;符号链接跳过并警告
|
|
16
|
-
- ✅ client 端「回退」按钮(MutationObserver 注入用户消息行操作区)+ 模式选择浮层(含 both 模式影响清单确认)+ 手动 `/rewind` 输入拦截
|
|
17
|
-
- ✅ 测试:纯函数单测 + 真实 `dsh-session` 集成测试 + `verify-host` 端到端(18 项,含审批短路/会话 cwd/新建文件删除/重启持久化场景)
|
|
18
|
-
- ⏳ 二期:快捷键、bash/外部修改的快照覆盖(整树快照)
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
10
|
+
[](https://github.com/SiriLee/dsh-rewind/blob/main/LICENSE)
|
|
19
11
|
|
|
20
|
-
##
|
|
12
|
+
## Table of contents
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
- [✨ Features](#-features)
|
|
15
|
+
- [📸 Screenshots](#-screenshots)
|
|
16
|
+
- [How it works](#how-it-works)
|
|
17
|
+
- [📦 Install](#-install)
|
|
18
|
+
- [Usage](#usage)
|
|
19
|
+
- [Behavior details & limitations](#behavior-details--limitations)
|
|
20
|
+
- [Not included](#not-included)
|
|
21
|
+
- [Compatibility](#compatibility)
|
|
22
|
+
- [Development](#development)
|
|
23
|
+
- [Publishing](#publishing)
|
|
24
|
+
- [Directory layout](#directory-layout)
|
|
25
|
+
- [License](#license)
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
# 本地 checkout
|
|
27
|
-
dsh plugin --profile web add /home/slev/workspace/projects/dsh-rewind
|
|
27
|
+
## ✨ Features
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
| Feature | Description |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| In-place rewind | Rewind to **any** user message from a per-message ↶ button: the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript — no new session, no window switch |
|
|
32
|
+
| Time-travel semantics | Rewinding to a message withdraws **that message too**; its text is offered back in the composer so you can edit and re-send it |
|
|
33
|
+
| Claude-Code-style file restore | Write-class edits are backed up **before** they happen and persisted on disk; "conversation and code" restores files to their pre-edit content and deletes files created after the target |
|
|
34
|
+
| Impact preview | "Conversation and code" first shows the exact restore / delete list for confirmation (the option is hidden when there are no tracked changes — like Claude Code's code-restore visibility) |
|
|
35
|
+
| Approval-plugin coexistence | Capture runs at the `tools/execute` around-dispatch stage, so another plugin's pre-execute approval short-circuit (e.g. dsh-edit-approval) cannot skip the backup, and a denied call never records |
|
|
36
|
+
| Paths resolved by session cwd | Relative paths are backed up and restored against the **real** file using the fs-tools session-cwd rule; the resolved display path is what gets recorded |
|
|
37
|
+
| Restores write real files | Restore goes through plain `node:fs` directly to the filesystem; symbolic and hard links are skipped with a warning (no clobber through a shared inode) |
|
|
38
|
+
| Survives host restart | Backups live on disk under `~/.dsh/rewind-snapshots/<session>/<anchor seq>/`, newest 100 message groups per session |
|
|
39
|
+
| Localized | `zh` / `en` copy registered into the dsh locale system |
|
|
40
|
+
|
|
41
|
+
## 📸 Screenshots
|
|
42
|
+
|
|
43
|
+
All screenshots render at a fixed width, so their size stays consistent
|
|
44
|
+
regardless of caption length.
|
|
45
|
+
|
|
46
|
+
<table>
|
|
47
|
+
<tr>
|
|
48
|
+
<td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="Per-message ↶ rewind button"><br><sub>Per-message ↶ rewind button</sub></td>
|
|
49
|
+
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="Mode-selection popover"><br><sub>Mode-selection popover</sub></td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr>
|
|
52
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="Impact list"><br><sub>"Conversation and code" impact list</sub></td>
|
|
53
|
+
<td align="center"><img src="assets/screenshots/guard-hint.png" width="440" alt="Manual /rewind guard hint"><br><sub>Manual /rewind guard hint</sub></td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
Two halves work together: the **conversation rewind** (in-place, same window) and the **checkpoint file restore** (Claude-Code-style before-backups).
|
|
60
|
+
|
|
61
|
+
### 1. Conversation rewind
|
|
62
|
+
|
|
63
|
+
The plugin appends an **empty-content marker** `assistant/message` into the session log whose `surfaceOp: { op: 'replace', start, end }` replaces every surface node after the target message with the marker:
|
|
64
|
+
|
|
65
|
+
- The marker carries `sourceEventSeqs` covering every shadowed node, and the `Session.append` surface rules validate the cut (only a contiguous range on the current surface).
|
|
66
|
+
- Because the marker is **empty**, the harness derives it to `null` — it never enters the model context and never renders as conversation content. The agent and the user both see the conversation exactly as it was at the target.
|
|
67
|
+
- The append-only log is **untouched** — the audit trail keeps every withdrawn event; only the model-visible surface is cut, so the next request derives its context from the target onward.
|
|
68
|
+
|
|
69
|
+
A running turn (LLM thinking / streaming) is force-stopped first (`cancel({ kind: 'user' })`) and the rewind waits for quiescence; if it can't stop, the rewind is aborted with an error.
|
|
70
|
+
|
|
71
|
+
### 2. Checkpoint file restore
|
|
32
72
|
|
|
33
|
-
|
|
73
|
+
The plugin tracks the write-class tools — `write`, `edit`, `str_replace_editor` (mutating commands `create` / `str_replace` / `insert`):
|
|
74
|
+
|
|
75
|
+
1. **Before-capture** at `tools/execute` (the around-dispatch stage): the target file is read and the resolved path + content are held in a pending map. This stage only runs after any pre-execute approval gate let the call through — so an `ask` short-circuit (dsh-edit-approval) **cannot skip** the backup, and a denied call never records.
|
|
76
|
+
2. **Disk commit** at `tools/post-execute`: the before-backup is written under the turn's **anchor message seq** (`~/.dsh/rewind-snapshots/<session>/<anchor seq>/<callId>.json`).
|
|
77
|
+
3. **Restore** (`/rewind @<seq> both`): every backup anchored at or after the target applies — modified files are written back to their **earliest** captured before-state, files created after the target are deleted, symbolic / hard links are skipped. Writes go through plain `node:fs`, independent of the fs service.
|
|
78
|
+
4. A failed tool body that **throws** skips `tools/post-execute`; a `tools/result` safety net clears the pending capture so nothing leaks in memory.
|
|
79
|
+
|
|
80
|
+
Backups persist across host restarts, bounded to the newest 100 anchor groups per session.
|
|
81
|
+
|
|
82
|
+
## 📦 Install
|
|
83
|
+
|
|
84
|
+
Published to npm — the registry path is the recommended one. **Restart dsh web (`--profile web`) after installing.**
|
|
85
|
+
|
|
86
|
+
### Option A: registry (recommended)
|
|
87
|
+
|
|
88
|
+
```sh
|
|
34
89
|
dsh plugin --profile web add dsh-rewind-plugin
|
|
35
90
|
```
|
|
36
91
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
92
|
+
### Option B: local checkout (authors / contributors)
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
cd dsh-rewind
|
|
96
|
+
npm install # devDeps come from the npm registry; no harness checkout needed
|
|
97
|
+
npm run build # full build: lib/ (host ESM + client bundle + .d.ts)
|
|
98
|
+
dsh plugin --profile web add /path/to/dsh-rewind # link install
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Option C: GitHub (pin a commit for reproducibility)
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
dsh plugin --profile web add github:SiriLee/dsh-rewind#<commit-sha>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
First run fails: pnpm blocks git dependencies from running build scripts. Follow
|
|
108
|
+
the CLI hint to add an `allowBuilds` key to the profile's `pnpm-workspace.yaml`
|
|
109
|
+
(e.g. `$DSH_HOME/profiles/web/pnpm-workspace.yaml`), then retry. pnpm then runs
|
|
110
|
+
the plugin's `prepare` (full build) and installs it into the profile.
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
### Rewind via the per-message button
|
|
115
|
+
|
|
116
|
+
1. **Hover** any user message you sent — a **↶ rewind** button appears in its action row.
|
|
117
|
+
2. **Click it.** The target is that message (step one is done). A small popover opens (step two):
|
|
118
|
+
- **Rewind conversation only** — cut the model context back to before the message; workspace files stay untouched.
|
|
119
|
+
- **Rewind conversation and code** — same context cut, plus workspace files restored to their state before the message. An impact list (files to restore / delete) is shown first, then you confirm.
|
|
120
|
+
- The "conversation and code" option is **hidden** when no tracked file changes exist after the target (matching Claude Code's behavior).
|
|
121
|
+
3. The rewind executes as an in-session command; a result message confirms (e.g. "已撤回 seq N 及之后内容;还原 M 个文件"), and the withdrawn message's text is filled back into the composer for editing and re-sending.
|
|
122
|
+
|
|
123
|
+
### Rewinds are time-travel
|
|
124
|
+
|
|
125
|
+
Rewinding to a message **withdraws** it and everything after it — the transcript and the agent's context both return to before the message. The command result says so, and the message's text is offered back in the composer.
|
|
126
|
+
|
|
127
|
+
### Manual `/rewind` is blocked
|
|
128
|
+
|
|
129
|
+
`/rewind` exists only as the button's internal channel. Typing `/rewind` (bare or with arguments) into the composer is **intercepted** — submitting shows a transient hint pointing at the ↶ button.
|
|
130
|
+
|
|
131
|
+
## Behavior details & limitations
|
|
40
132
|
|
|
41
|
-
|
|
133
|
+
- Only **write-class tools** running while the plugin is active are tracked (`write` / `edit` / `str_replace_editor`). Changes made by `bash`, other tools, or external programs are not backed up and cannot be restored — the same limitation as Claude Code, which also defers such rollbacks to the user's git.
|
|
134
|
+
- If a before-capture read fails (e.g. a permission error), that change is simply not backed up and a `both` rewind cannot restore it — the plugin logs a warning but **does not block the write**.
|
|
135
|
+
- File restore/delete writes through the **real local filesystem**; under sandbox / remote backends path resolution may be restricted.
|
|
136
|
+
- Symbolic links and hard links are not written through (they share the inode with another name; a restore would clobber both) — they are skipped and reported.
|
|
137
|
+
- A rewind can itself be rewound (its marker enters the log), but the file-restore action is not re-backed up.
|
|
138
|
+
- The ↶ button is injected on user messages rendered in the **current session view**; switch to another session before rewinding it.
|
|
139
|
+
- When no tracked file changes exist after the target, the mode popover offers only "conversation only" (Claude Code hides code-restore options the same way).
|
|
42
140
|
|
|
43
|
-
|
|
44
|
-
|
|
141
|
+
## Not included
|
|
142
|
+
|
|
143
|
+
- Keyboard shortcuts (esc+esc to open the rewind menu) — planned as a follow-up.
|
|
144
|
+
- `/compact` — provided by the harness.
|
|
145
|
+
- Fork / branch rewind — the harness's built-in "branch in new chat".
|
|
146
|
+
- Whole-tree / git-first snapshots covering bash and external edits — **deliberately not implemented**, in line with Claude Code's native rewind (which also defers such rollbacks to the user's git).
|
|
147
|
+
|
|
148
|
+
## Compatibility
|
|
149
|
+
|
|
150
|
+
- Node.js `^22.19.0 || >=24.0.0`.
|
|
151
|
+
- DeepSeek Harness web profile (`dsh --profile web`); peer `@deepseek-ai/*` packages are resolved by the harness at runtime.
|
|
152
|
+
|
|
153
|
+
> [!WARNING]
|
|
154
|
+
> This project and DeepSeek Harness are both in developer preview. Pin exact
|
|
155
|
+
> versions in reproducible environments and review the behavior notes above.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
45
158
|
|
|
46
159
|
```sh
|
|
47
|
-
npm
|
|
48
|
-
|
|
160
|
+
npm install # devDeps from the npm registry
|
|
161
|
+
npm run typecheck # tsc on both compilation surfaces (host + client)
|
|
162
|
+
npm test # vitest: rewind / snapshot / hidden / session-cwd / integration (46 cases)
|
|
163
|
+
npm run build # esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
|
|
164
|
+
node scripts/verify-host.mjs # boot the BUILT host artifact end-to-end (18 checks)
|
|
49
165
|
```
|
|
50
166
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
`npmjs.com/package/dsh-rewind-plugin` 才会出现):
|
|
57
|
-
1. 打开 [dsh-rewind-plugin 包页](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
58
|
-
→ 右上角 **settings** → **Trusted Publisher** → **Add**;
|
|
59
|
-
2. 字段:Provider **GitHub Actions** · Organization or user **`SiriLee`** ·
|
|
60
|
-
Repository **`dsh-rewind`**(GitHub 仓库名,与 npm 包名可不同)·
|
|
61
|
-
Workflow filename **`publish.yml`**(只填文件名)· Environment **留空**
|
|
62
|
-
(本 workflow 无 `environment` 块,OIDC subject 携带 ref,此为已验证组合)·
|
|
63
|
-
**Allowed actions:`npm publish`**(2026-05-20 起必选)。
|
|
64
|
-
- 质量门禁(PR / push main):`.github/workflows/ci.yml` 跑 typecheck + 测试
|
|
65
|
-
+ 构建 + `verify-host` + tarball 完整性检查(`lib/` 与 `LICENSE` 必须在包内)。
|
|
66
|
-
- 包内附带 `.d.ts` 类型声明(`exports` 已声明 `types`,源码级 `./src/*` 亦可导入)。
|
|
67
|
-
|
|
68
|
-
## 使用
|
|
69
|
-
|
|
70
|
-
- 每条用户消息 hover 出现「↶ 回退」按钮:点击 → 选择「仅回退对话」或
|
|
71
|
-
「回退对话和代码」(后者先展示影响清单再确认)。
|
|
72
|
-
- **回退 = 撤回(时间回溯)**:对**任意**用户消息回退(使用消息旁的 ↶ 按钮),
|
|
73
|
-
效果是**撤回该消息及它之后的所有内容**(含 agent 回复、工具调用)——对话界面与
|
|
74
|
-
Agent 上下文都回到这条消息发送之前;**该消息的文本自动填入输入框(编辑区)**,
|
|
75
|
-
可直接修改后重发。命令结果提示"已撤回 seq N 及之后内容"。
|
|
76
|
-
- **手动 `/rewind` 不支持**:在输入框手动输入 `/rewind`(含裸命令)会被 client 拦截并
|
|
77
|
-
提示改用按钮——`/rewind` 命令仅作为按钮的内部调用通道存在。
|
|
78
|
-
- **回退后前端与 Agent 一致**:回退标记是空内容消息(deriveMessages 会跳过,模型
|
|
79
|
-
上下文无任何标记噪音);client 端隐藏被撤回范围内的消息行与 `/rewind` 命令结果,
|
|
80
|
-
可见对话即"撤回点之前的内容"。会话日志(append-only 审计)不受影响。
|
|
81
|
-
|
|
82
|
-
## 已知限制(v0.2)
|
|
83
|
-
|
|
84
|
-
- checkpoint 只覆盖**插件运行期间、经 `write` / `edit` / `str_replace_editor` 的变更**;
|
|
85
|
-
bash 或外部程序的修改无法还原(与 Claude Code 相同的限制,二期可加整树快照层)。
|
|
86
|
-
备份按消息分组**落盘**(每会话保留最近 100 组,最旧先清理),dsh 重启不丢失。
|
|
87
|
-
- 文件删除/还原走真实路径直删直写(本地 backend);sandbox/远程 backend 下路径解析
|
|
88
|
-
可能受限。
|
|
89
|
-
- 回退本身可再回退(标记进入日志),但文件还原动作不再记录新备份。
|
|
90
|
-
- 回退按钮只出现在**当前会话**渲染的用户消息行上(DOM 注入范围即当前视图);
|
|
91
|
-
subagent/分屏等非当前会话的对话需要先切到该会话再回退。
|
|
92
|
-
|
|
93
|
-
## 背景与定位
|
|
94
|
-
|
|
95
|
-
社区 rewind 类插件(`dsh-recall-plugin`、`dsh-checkpoint-rewind`、`dsh-turn-rewind`)均为 **fork 路线**(回退 = fork 出新会话,用户切换会话继续),且没有「仅回退对话 / 对话+代码」的选项。本插件提供:**在当前会话窗口内**改写模型上下文 + 可选还原工作区文件。
|
|
96
|
-
|
|
97
|
-
## 交互设计(按钮两步;手动命令不支持)
|
|
98
|
-
|
|
99
|
-
按钮流程遵循两步:**第一步选择要回退到的 user 消息,第二步选择回退模式**。手动
|
|
100
|
-
`/rewind` 命令不支持输入——client 端在输入框拦截所有手动 `/rewind` 并提示改用按钮。
|
|
101
|
-
|
|
102
|
-
### 1. 用户消息旁的「回退」按钮(主入口)
|
|
103
|
-
|
|
104
|
-
- 在你**发送过的每条用户消息**下方/旁边显示「↶ 回退」按钮(hover 出现,与现有 clock/copy 操作并排)。
|
|
105
|
-
- **第一步(目标即已确定)**:点击某条消息旁的按钮,回退目标就是这条消息——无需再选。
|
|
106
|
-
- **第二步(模式选择浮层)**:弹出小型浮层(非新页面),选项:
|
|
107
|
-
- **仅回退对话** —— 只回退模型上下文,不动工作区文件
|
|
108
|
-
- **回退对话和代码** —— 对话回退 + 工作区文件还原到该消息之前
|
|
109
|
-
- **取消**
|
|
110
|
-
- 选「回退对话和代码」时,浮层内先显示将受影响的内容清单(将还原/删除的文件名与数量),确认后执行。
|
|
111
|
-
- 执行结果以一条对话内消息呈现(如「已回退到 seq N,移除 M 条上下文;还原 2 个文件」)。
|
|
112
|
-
|
|
113
|
-
### 2. 命令(仅作按钮内部通道,不支持手动输入)
|
|
114
|
-
|
|
115
|
-
- **手动输入 `/rewind`(含裸命令)会被 client 端在输入框直接拦截**,并提示改用消息旁
|
|
116
|
-
的 ↶ 按钮。`/rewind` 命令仅作为按钮的内部调用通道存在
|
|
117
|
-
(`/rewind @seq chat|both`、`/rewind preview @seq both`)。
|
|
118
|
-
- UI 按钮与命令共享同一套 host 端回退逻辑(`/rewind @seq <mode>`)。
|
|
119
|
-
|
|
120
|
-
## 回退机制(host 端,全部公开 API)
|
|
121
|
-
|
|
122
|
-
### 3. 同窗口 in-place 对话回退
|
|
123
|
-
|
|
124
|
-
- `Session.append('assistant/message', { turn, step, message: 空标记 }, { surfaceOp: { op:'replace', start, end }, sourceEventSeqs })`:追加**空内容**标记(deriveMessages 跳过 → 模型上下文无噪音),把目标及其之后的所有 surface 节点从模型上下文替换掉。
|
|
125
|
-
- 效果:当前窗口上下文从目标点重新开始;**不产生新会话、不切换窗口**;原始日志完整保留(append-only 审计不变),仅不再进入模型上下文。
|
|
126
|
-
- 依赖:`@deepseek-ai/dsh-session`(`Session.append`、`foldSurface`)、`@deepseek-ai/dsh-llm`(`createUserMessage`)、`@deepseek-ai/dsh-commands`(命令注册)、`@deepseek-ai/dsh-agent`(`Agent.status` idle 守卫)。
|
|
127
|
-
|
|
128
|
-
### 4. 文件回退:Claude Code 式 checkpoint(写前备份,按消息分组落盘)
|
|
129
|
-
|
|
130
|
-
- 在 `tools/execute`(around-dispatch 阶段)读取目标文件**写前备份**(before;文件不存在记
|
|
131
|
-
为「新建」),`tools/post-execute` 将备份按**当前轮用户消息 seq(锚点)**落盘提交到
|
|
132
|
-
`~/.dsh/rewind-snapshots/<会话>/<锚点 seq>/`。捕获放在 execute 而非 pre-execute:
|
|
133
|
-
**审批类插件(如 dsh-edit-approval)在 `tools/pre-execute` 返回 `ask` 会短路后续监听器**,
|
|
134
|
-
但批准后 dispatch 阶段必然执行——共存的写操作照样入备份;被拒绝的调用不 dispatch,
|
|
135
|
-
不会留下残留。
|
|
136
|
-
- 相对路径按**会话 cwd** 解析(与 `dsh-tool-fs` 同规则,`src/session-cwd.ts`),
|
|
137
|
-
备份记录解析后的 display path,preview/还原始终指向真实文件。
|
|
138
|
-
- 回退「对话和代码」到消息 N 时:对锚点 ≥ N 的每条备份取**该文件最早一条**——内容写回
|
|
139
|
-
before、新建文件删除(与 Claude Code 的 rewind 语义一致)。恢复用 `node:fs` 直写真实
|
|
140
|
-
文件,不经过 fs 服务;符号链接跳过并在结果中提示。
|
|
141
|
-
- 持久化:备份在磁盘上,**dsh 重启后仍可还原**;每会话保留最近 100 个消息分组,最旧先清理。
|
|
142
|
-
- 边界(已知限制):只覆盖**插件运行期间、经写类工具**的变更;bash 命令或外部程序的修改
|
|
143
|
-
不在备份内,无法还原(与 Claude Code 相同的限制,二期可加整树快照层)。
|
|
144
|
-
|
|
145
|
-
### 5. 安全守卫
|
|
146
|
-
|
|
147
|
-
- agent 运行中(LLM 思考/输出)执行回退时**自动强制停止**当前回合(`cancel({kind:'user'})`),
|
|
148
|
-
等待 quiescence 后回退;停止超时/失败则中止并报错。无需先手动停止。
|
|
149
|
-
- 文件还原是破坏性操作:UI 选择「对话和代码」时需经影响清单确认;命令路径用 `preview` 先行查看。
|
|
150
|
-
- 回退本身可再回退(回退动作同样进入会话日志),但文件还原动作不再记录新备份。
|
|
151
|
-
|
|
152
|
-
## 客户端实现要点(纯插件,无源码补丁)
|
|
153
|
-
|
|
154
|
-
- 按钮注入锚点:用户行 `[data-chat-flow-kind="user"]`(行容器 `data-chat-anchor-key` 为节点 key);用 MutationObserver 跟踪新增行。
|
|
155
|
-
- 消息 seq 获取:从行元素的 `data-chat-anchor-key` → 运行时快照 `session.getSnapshot().chat.nodes.get(key)` → `UserMessageNode.seq`(DOM 只用于定位,数据取自 runtime,不解析 DOM 文本)。
|
|
156
|
-
- **按钮两步选择浮层**:点击消息旁按钮时,客户端接管交互——目标即该消息,第二步展示模式选项(仅回退对话 / 回退对话和代码 / 取消);确认后调 `session.command('/rewind @<seq> <mode>')` 执行。**手动 `/rewind` 被 client 整体拦截**(含裸命令):输入框 guard 阻止提交并提示改用按钮;`/rewind` 命令仅作为按钮内部调用通道存在。
|
|
157
|
-
- 执行结果以命令节点出现在对话中。
|
|
158
|
-
- 注入按钮与「在新对话中分支」等官方操作并排,样式遵循 dsh 设计 token。
|
|
159
|
-
|
|
160
|
-
## 明确不包含(本期)
|
|
161
|
-
|
|
162
|
-
- 快捷键(esc+esc 回退等)——独立的快捷键插件,二期。
|
|
163
|
-
- 压缩(`/compact`)——官方已有。
|
|
164
|
-
- fork/分支回退——官方已有(「在新对话中分支」)。
|
|
165
|
-
- 快照式文件回退(整树/git-first,覆盖 bash 与外部修改)——二期(checkpoint 方案先行)。
|
|
167
|
+
`prepare` runs the full build, so git installs and `npm pack` / `npm publish`
|
|
168
|
+
always produce a complete `lib/` and the `LICENSE`.
|
|
169
|
+
|
|
170
|
+
Maintainers: see [docs/harness-reference.md](docs/harness-reference.md) for the
|
|
171
|
+
DeepSeek Harness interface reference (subsystem docs + key source index).
|
|
166
172
|
|
|
167
|
-
##
|
|
173
|
+
## Publishing
|
|
174
|
+
|
|
175
|
+
Releases go out through GitHub Actions Trusted Publishing (OIDC, no stored
|
|
176
|
+
`NPM_TOKEN`):
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
npm version patch && git push origin main --tags # triggers .github/workflows/publish.yml
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- The workflow verifies the tag matches `package.json`, runs typecheck + tests +
|
|
183
|
+
a full build + artifact verification, publishes with `--provenance`
|
|
184
|
+
(Sigstore), and creates a GitHub Release. It is **idempotent** — an already
|
|
185
|
+
published version is skipped. CI (`.github/workflows/ci.yml`) runs the same
|
|
186
|
+
checks on every push / PR, plus a `npm pack --dry-run` sanity check that the
|
|
187
|
+
tarball carries `lib/` and `LICENSE`.
|
|
188
|
+
- One-time npm-side configuration (cannot be done from this repo): open
|
|
189
|
+
[dsh-rewind-plugin](https://www.npmjs.com/package/dsh-rewind-plugin) →
|
|
190
|
+
**settings → Trusted Publisher → Add**, with Provider **GitHub Actions** ·
|
|
191
|
+
Organization or user **`SiriLee`** · Repository **`dsh-rewind`** (the GitHub
|
|
192
|
+
repo, not the npm name) · Workflow filename **`publish.yml`** · Environment
|
|
193
|
+
**empty** · Allowed actions **`npm publish`**. Once configured, pushes of
|
|
194
|
+
`v<version>` tags publish automatically.
|
|
195
|
+
|
|
196
|
+
## Directory layout
|
|
168
197
|
|
|
169
198
|
```
|
|
170
|
-
src/index.ts
|
|
171
|
-
src/rewind.ts
|
|
172
|
-
src/snapshot.ts
|
|
173
|
-
src/session-cwd.ts
|
|
174
|
-
src/client/index.ts
|
|
175
|
-
src/client/popover.ts
|
|
176
|
-
src/client/
|
|
177
|
-
src/client/
|
|
178
|
-
|
|
179
|
-
scripts/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
199
|
+
src/index.ts host plugin: /rewind command + checkpoint pipeline (tools/execute|post-execute)
|
|
200
|
+
src/rewind.ts pure planning: target resolution, surface range, candidate listing
|
|
201
|
+
src/snapshot.ts checkpoint store (disk before-backups, restore/preview, bounded prune)
|
|
202
|
+
src/session-cwd.ts session-cwd resolution (fs-tools rule)
|
|
203
|
+
src/client/index.ts client plugin: per-message ↶ button + manual /rewind guard
|
|
204
|
+
src/client/popover.ts mode-selection popover (both-mode impact confirm)
|
|
205
|
+
src/client/hidden.ts withdrawn-span computation (hiddenSeqsOf), pure
|
|
206
|
+
src/client/locales.ts zh / en copy (LocaleNamespaceMap)
|
|
207
|
+
src/client/styles.ts injected styles (dsh design tokens)
|
|
208
|
+
scripts/build.mjs esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
|
|
209
|
+
scripts/verify-host.mjs end-to-end host verification (18 checks)
|
|
210
|
+
tests/ vitest suites (rewind / snapshot / hidden / session-cwd / integration, 46 cases)
|
|
211
|
+
docs/harness-reference.md maintainer docs: DeepSeek Harness interface reference
|
|
212
|
+
assets/screenshots/ UI screenshots
|
|
213
|
+
cordis.patch.yml bundle patch (mounts the dual-face plugin row)
|
|
214
|
+
package.json dsh.bundle + dsh.client manifests, optional peerDependencies
|
|
183
215
|
```
|
|
184
216
|
|
|
185
|
-
##
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
### 子系统文档(`docs/subsystems/`)
|
|
190
|
-
|
|
191
|
-
- [session.md](../../oss/deepseek-harness/docs/subsystems/session.md) — `Session` / `SessionStore` / 事件模型(`Session.append`、`surfaceOp`、`sourceEventSeqs`)
|
|
192
|
-
- [core.md](../../oss/deepseek-harness/docs/subsystems/core.md) — `Agent`(`status`、`session`)等核心类型
|
|
193
|
-
- [commands.md](../../oss/deepseek-harness/docs/subsystems/commands.md) — 命令注册(`ctx.commands.register`、`CommandInvocation`、`CommandResult`)
|
|
194
|
-
- [tools.md](../../oss/deepseek-harness/docs/subsystems/tools.md) — 工具执行 seam(`tools/pre-execute` / `tools/post-execute`、`ToolExecution`)
|
|
195
|
-
- [session-query.md](../../oss/deepseek-harness/docs/subsystems/session-query.md) — 会话查询/`foldSurface` 相关只读接口
|
|
196
|
-
- 根目录目录:`docs/persistence-catalog.md`(`SessionEventMap` 全量事件)、`docs/tool-catalog.md`(工具清单)、`docs/config-catalog.md`(配置清单)
|
|
197
|
-
|
|
198
|
-
### 关键源码(`packages/`)
|
|
199
|
-
|
|
200
|
-
| 接口 | 文件 |
|
|
201
|
-
|---|---|
|
|
202
|
-
| `Session.append`、surface 校验 | [packages/core/session/src/index.ts](../../oss/deepseek-harness/packages/core/session/src/index.ts) |
|
|
203
|
-
| `foldSurface`、替换规则 | [packages/core/session/src/surface.ts](../../oss/deepseek-harness/packages/core/session/src/surface.ts) |
|
|
204
|
-
| `SessionEventMap`、`SurfaceOp` | [packages/core/session/src/types.ts](../../oss/deepseek-harness/packages/core/session/src/types.ts) |
|
|
205
|
-
| `createUserMessage`、`MessageSource` | [packages/llm/llm/src/message.ts](../../oss/deepseek-harness/packages/llm/llm/src/message.ts) |
|
|
206
|
-
| `CommandDefinition`、`CommandInvocation` | [packages/interaction/commands/src/index.ts](../../oss/deepseek-harness/packages/interaction/commands/src/index.ts) |
|
|
207
|
-
| `Agent`(`status`/`session`) | [packages/core/agent/src/runtime-types.ts](../../oss/deepseek-harness/packages/core/agent/src/runtime-types.ts) |
|
|
208
|
-
| `tools/pre-execute` / `execute` / `post-execute` | [packages/core/tools/src/index.ts](../../oss/deepseek-harness/packages/core/tools/src/index.ts) |
|
|
209
|
-
| 客户端 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) |
|
|
210
|
-
| 用户气泡渲染 | [packages/client/ui-conversation/src/client/chat/MessageItem.tsx](../../oss/deepseek-harness/packages/client/ui-conversation/src/client/chat/MessageItem.tsx) |
|
|
211
|
-
| 客户端 `SessionFace`(`command`/`cancel`) | [packages/client/runtime/src/client/contract/session.ts](../../oss/deepseek-harness/packages/client/runtime/src/client/contract/session.ts) |
|
|
212
|
-
| 客户端 `PendingWait`(`respond`) | [packages/client/runtime/src/client/sessions/pending.ts](../../oss/deepseek-harness/packages/client/runtime/src/client/sessions/pending.ts) |
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
[MIT](LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# dsh-rewind
|
|
2
|
+
|
|
3
|
+
[English](README.md)
|
|
4
|
+
|
|
5
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:**同一会话窗口的 in-place 对话回退**(Claude Code `/rewind` 语义)——把模型上下文剪回更早的一条用户消息,并可基于**落盘的写前备份**还原工作区文件。
|
|
6
|
+
|
|
7
|
+
> **状态**:已发布 npm(`dsh-rewind-plugin`,v0.2.0),经 GitHub Actions Trusted Publishing + Sigstore provenance 构建发布。目标为 web 配置档(`dsh --profile web`)。交互以 Claude Code 的 rewind 为参考,并贴合 dsh Web 实际 UI。
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
10
|
+
[](https://github.com/SiriLee/dsh-rewind/blob/main/LICENSE)
|
|
11
|
+
|
|
12
|
+
## 目录
|
|
13
|
+
|
|
14
|
+
- [✨ 功能特性](#-功能特性)
|
|
15
|
+
- [📸 截图](#-截图)
|
|
16
|
+
- [工作原理](#工作原理)
|
|
17
|
+
- [📦 安装](#-安装)
|
|
18
|
+
- [使用](#使用)
|
|
19
|
+
- [行为细节与限制](#行为细节与限制)
|
|
20
|
+
- [明确不包含](#明确不包含)
|
|
21
|
+
- [兼容性](#兼容性)
|
|
22
|
+
- [开发](#开发)
|
|
23
|
+
- [发布](#发布)
|
|
24
|
+
- [目录结构](#目录结构)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
## ✨ 功能特性
|
|
28
|
+
|
|
29
|
+
| 特性 | 说明 |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| 同窗口原地回退 | 在**任意**用户消息旁点 ↶ 按钮:该消息及之后全部内容(agent 回复、工具调用)从模型上下文**与**渲染对话中撤回——不新建会话、不切换窗口 |
|
|
32
|
+
| 时间回溯语义 | 回退到某消息会**连同撤回该消息本身**;其文本自动填入输入框,可修改后重发 |
|
|
33
|
+
| Claude Code 式文件还原 | 写类编辑在执行**前**被备份并落盘;「回退对话和代码」把文件还原到编辑前内容、删除目标之后新建的文件 |
|
|
34
|
+
| 影响清单确认 | 「回退对话和代码」先展示要还原/删除的文件清单再确认(目标之后无跟踪变更时不显示该选项,对齐 Claude Code 的 code-restore 可见性) |
|
|
35
|
+
| 与审批类插件共存 | 捕获在 `tools/execute`(around-dispatch 阶段):其他插件的 pre-execute 审批短路(如 dsh-edit-approval)无法跳过备份,被拒绝的调用也不会记录 |
|
|
36
|
+
| 路径按会话 cwd 解析 | 相对路径按 fs-tools 的 session-cwd 规则解析到**真实文件**,记录解析后的 display path |
|
|
37
|
+
| 还原直写真实文件 | 还原走纯 `node:fs` 直接落盘;符号链接与硬链接跳过并警告(不通过共享 inode 误伤) |
|
|
38
|
+
| 重启后仍可还原 | 备份落在磁盘 `~/.dsh/rewind-snapshots/<会话>/<锚点 seq>/`,每会话保留最近 100 组 |
|
|
39
|
+
| 本地化 | `zh` / `en` 文案,注册进 dsh 的 locale 体系 |
|
|
40
|
+
|
|
41
|
+
## 📸 截图
|
|
42
|
+
|
|
43
|
+
所有截图以固定宽度渲染,尺寸不随说明文字长短变化。
|
|
44
|
+
|
|
45
|
+
<table>
|
|
46
|
+
<tr>
|
|
47
|
+
<td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="用户消息旁的 ↶ 回退按钮"><br><sub>用户消息旁的 ↶ 回退按钮</sub></td>
|
|
48
|
+
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="模式选择浮层"><br><sub>模式选择浮层</sub></td>
|
|
49
|
+
</tr>
|
|
50
|
+
<tr>
|
|
51
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="影响清单"><br><sub>「回退对话和代码」影响清单</sub></td>
|
|
52
|
+
<td align="center"><img src="assets/screenshots/guard-hint.png" width="440" alt="手动 /rewind 拦截提示"><br><sub>手动 /rewind 拦截提示</sub></td>
|
|
53
|
+
</tr>
|
|
54
|
+
</table>
|
|
55
|
+
|
|
56
|
+
## 工作原理
|
|
57
|
+
|
|
58
|
+
两部分协同:**对话回退**(同窗口 in-place)与 **checkpoint 文件还原**(Claude Code 式写前备份)。
|
|
59
|
+
|
|
60
|
+
### 1. 对话回退
|
|
61
|
+
|
|
62
|
+
插件向会话日志追加一条**空内容标记** `assistant/message`,其 `surfaceOp: { op: 'replace', start, end }` 把目标消息之后的全部 surface 节点替换为标记本身:
|
|
63
|
+
|
|
64
|
+
- 标记携带 `sourceEventSeqs` 覆盖所有被遮蔽节点,`Session.append` 的 surface 规则校验切割合法性(仅限当前 surface 上的连续区间)。
|
|
65
|
+
- 因为标记**内容为空**,harness 会将其派生为 `null`——永不进入模型上下文、也永不渲染成对话内容。agent 与用户看到的对话都回到目标消息当时的样子。
|
|
66
|
+
- append-only 日志**不被改写**——审计轨迹完整保留每条被撤回的事件,只有模型可见的 surface 被剪掉,下一条请求从目标消息起派生上下文。
|
|
67
|
+
|
|
68
|
+
若 agent 正在运行(LLM 思考/流式输出),会先强制停止(`cancel({ kind: 'user' })`)并等待 quiescence 再回退;停不下来则中止并报错。
|
|
69
|
+
|
|
70
|
+
### 2. Checkpoint 文件还原
|
|
71
|
+
|
|
72
|
+
插件跟踪写类工具:`write`、`edit`、`str_replace_editor`(变更子命令 `create` / `str_replace` / `insert`):
|
|
73
|
+
|
|
74
|
+
1. **写前备份**(`tools/execute`,around-dispatch 阶段):读取目标文件,把解析后的路径与内容放入 pending 表。此阶段只在任何 pre-execute 审批门放行之后运行——所以审批 `ask` 短路(dsh-edit-approval)**无法跳过**备份,被拒绝的调用也不会记录。
|
|
75
|
+
2. **落盘提交**(`tools/post-execute`):备份按当前轮**锚点消息 seq** 写入 `~/.dsh/rewind-snapshots/<会话>/<锚点 seq>/<callId>.json`。
|
|
76
|
+
3. **还原**(`/rewind @<seq> both`):锚点 ≥ 目标的每条备份生效——被修改的文件写回其**最早一次**捕获的 before 内容,目标之后新建的文件被删除,符号/硬链接跳过。写入走纯 `node:fs`,不经 fs 服务。
|
|
77
|
+
4. 工具体**抛异常**会跳过 `tools/post-execute`;`tools/result` 兜底清掉 pending,避免内存泄漏。
|
|
78
|
+
|
|
79
|
+
备份跨 host 重启持久化,每会话有界保留最近 100 组锚点。
|
|
80
|
+
|
|
81
|
+
## 📦 安装
|
|
82
|
+
|
|
83
|
+
已发布 npm——推荐走 registry 路径。**装完重启 dsh web(`--profile web`)。**
|
|
84
|
+
|
|
85
|
+
### 方式 A:registry(推荐)
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
dsh plugin --profile web add dsh-rewind-plugin
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 方式 B:本地 checkout(作者 / 贡献者)
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
cd dsh-rewind
|
|
95
|
+
npm install # devDeps 来自 npm registry,无需 harness checkout
|
|
96
|
+
npm run build # 完整构建:lib/(host ESM + client bundle + .d.ts)
|
|
97
|
+
dsh plugin --profile web add /path/to/dsh-rewind # link 安装
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 方式 C:GitHub(pin commit,可复现)
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
dsh plugin --profile web add github:SiriLee/dsh-rewind#<commit-sha>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
首次安装会失败:pnpm 默认禁止 git 依赖执行构建脚本。按 CLI 提示在 profile 的
|
|
107
|
+
`pnpm-workspace.yaml`(如 `$DSH_HOME/profiles/web/pnpm-workspace.yaml`)中加
|
|
108
|
+
`allowBuilds` 后重试;pnpm 随后会执行插件的 `prepare`(完整构建)并装入 profile。
|
|
109
|
+
|
|
110
|
+
## 使用
|
|
111
|
+
|
|
112
|
+
### 通过消息旁的按钮回退
|
|
113
|
+
|
|
114
|
+
1. **hover** 任意你发送过的用户消息——操作行出现 **↶ 回退** 按钮。
|
|
115
|
+
2. **点击它。** 目标即这条消息(第一步完成)。弹出小浮层(第二步):
|
|
116
|
+
- **仅回退对话** —— 把模型上下文剪回这条消息之前;工作区文件不动。
|
|
117
|
+
- **回退对话和代码** —— 同样的上下文裁剪,并把工作区文件还原到该消息之前的状态。先显示影响清单(待还原/删除的文件),确认后执行。
|
|
118
|
+
- 目标之后**没有**跟踪的文件变更时,该选项**不显示**(对齐 Claude Code 行为)。
|
|
119
|
+
3. 回退以一条会话内命令执行;结果消息确认(如「已撤回 seq N 及之后内容;还原 M 个文件」),被撤回消息的文本自动填入输入框,可编辑后重发。
|
|
120
|
+
|
|
121
|
+
### 回退 = 撤回(时间回溯)
|
|
122
|
+
|
|
123
|
+
回退到某消息会**撤回该消息及它之后的所有内容**——渲染对话与 agent 上下文都回到这条消息之前。命令结果会说明,且该消息文本会填回输入框供重发。
|
|
124
|
+
|
|
125
|
+
### 手动 `/rewind` 不支持
|
|
126
|
+
|
|
127
|
+
`/rewind` 命令仅作为按钮的内部调用通道存在。在输入框手动输入 `/rewind`(含裸命令)会被**拦截**——提交时弹出临时提示,指向消息旁的 ↶ 按钮。
|
|
128
|
+
|
|
129
|
+
## 行为细节与限制
|
|
130
|
+
|
|
131
|
+
- 只跟踪**插件运行期间、经写类工具**的变更(`write` / `edit` / `str_replace_editor`)。`bash`、其他工具或外部程序的修改不在备份内、无法还原——与 Claude Code 相同,官方同样不覆盖,此类回退交由用户 git 处理。
|
|
132
|
+
- 写前备份读取失败时(如权限错误)该次变更不会入备份,`both` 回退无法还原它——插件会在日志中警告,但**不会阻塞写操作**。
|
|
133
|
+
- 文件还原/删除直写**真实本地文件系统**;sandbox / 远程 backend 下路径解析可能受限。
|
|
134
|
+
- 符号链接与硬链接不写入(它们与另一名字共享 inode,还原会互相污染)——跳过并在结果中提示。
|
|
135
|
+
- 回退本身可再回退(标记进入日志),但文件还原动作不再记录新备份。
|
|
136
|
+
- ↶ 按钮只出现在**当前会话视图**渲染的用户消息行上;回退其他会话前先切换到该会话。
|
|
137
|
+
- 目标之后没有跟踪的文件变更时,模式浮层只显示「仅回退对话」(Claude Code 同样隐藏 code-restore 选项)。
|
|
138
|
+
|
|
139
|
+
## 明确不包含
|
|
140
|
+
|
|
141
|
+
- 快捷键(esc+esc 打开回退菜单)——规划中的后续项。
|
|
142
|
+
- `/compact` —— harness 已内置。
|
|
143
|
+
- fork / 分支回退 —— harness 内置的「在新对话中分支」。
|
|
144
|
+
- 整树 / git-first 快照(覆盖 bash 与外部修改)——**明确不做**,与 Claude Code 原生 rewind 保持一致(官方同样不覆盖,把此类回退交给用户 git)。
|
|
145
|
+
|
|
146
|
+
## 兼容性
|
|
147
|
+
|
|
148
|
+
- Node.js `^22.19.0 || >=24.0.0`。
|
|
149
|
+
- DeepSeek Harness web 配置档(`dsh --profile web`);peer `@deepseek-ai/*` 包由 harness 运行时解析。
|
|
150
|
+
|
|
151
|
+
> [!WARNING]
|
|
152
|
+
> 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
|
|
153
|
+
> 并阅读上述行为说明。
|
|
154
|
+
|
|
155
|
+
## 开发
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
npm install # devDeps 来自 npm registry
|
|
159
|
+
npm run typecheck # tsc 双面编译(host + client)
|
|
160
|
+
npm test # vitest:rewind / snapshot / hidden / session-cwd / 集成(46 例)
|
|
161
|
+
npm run build # esbuild:lib/index.js(host ESM)+ lib/client.js(loader 闭包)+ .d.ts
|
|
162
|
+
node scripts/verify-host.mjs # 端到端验证构建产物(18 项检查)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`prepare` 执行完整构建,所以 git 安装与 `npm pack` / `npm publish` 总会产出完整的
|
|
166
|
+
`lib/` 与 `LICENSE`。
|
|
167
|
+
|
|
168
|
+
维护者参考:[docs/harness-reference.md](docs/harness-reference.md) 收录 DeepSeek
|
|
169
|
+
Harness 接口文档(子系统文档 + 关键源码索引)。
|
|
170
|
+
|
|
171
|
+
## 发布
|
|
172
|
+
|
|
173
|
+
通过 GitHub Actions Trusted Publishing(OIDC,无存储 `NPM_TOKEN`)发布:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
npm version patch && git push origin main --tags # 触发 .github/workflows/publish.yml
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- workflow 校验 tag 与 `package.json` 版本一致,跑 typecheck + 测试 + 完整构建 +
|
|
180
|
+
产物验证,以 `--provenance`(Sigstore)发布,并创建 GitHub Release。**幂等**——
|
|
181
|
+
已发布的版本会跳过。CI(`.github/workflows/ci.yml`)在每次 push / PR 跑相同检查,
|
|
182
|
+
外加 `npm pack --dry-run` 校验 tarball 含 `lib/` 与 `LICENSE`。
|
|
183
|
+
- 一次性 npm 侧配置(仓库内无法代做):打开
|
|
184
|
+
[dsh-rewind-plugin](https://www.npmjs.com/package/dsh-rewind-plugin) →
|
|
185
|
+
**settings → Trusted Publisher → Add**,Provider **GitHub Actions** ·
|
|
186
|
+
Organization or user **`SiriLee`** · Repository **`dsh-rewind`**(GitHub 仓库名,
|
|
187
|
+
与 npm 包名可不同)· Workflow filename **`publish.yml`** · Environment
|
|
188
|
+
**留空** · Allowed actions **`npm publish`**。配置好后 push `v<version>` tag
|
|
189
|
+
即自动发布。
|
|
190
|
+
|
|
191
|
+
## 目录结构
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
src/index.ts host 插件:/rewind 命令 + checkpoint 流水线(tools/execute|post-execute)
|
|
195
|
+
src/rewind.ts 纯函数规划:目标解析、surface 范围、候选列表
|
|
196
|
+
src/snapshot.ts checkpoint 存储(磁盘写前备份、还原/preview、有界清理)
|
|
197
|
+
src/session-cwd.ts session-cwd 解析(fs-tools 规则)
|
|
198
|
+
src/client/index.ts client 插件:消息行 ↶ 按钮 + 手动 /rewind 拦截
|
|
199
|
+
src/client/popover.ts 模式选择浮层(both 模式影响清单确认)
|
|
200
|
+
src/client/hidden.ts 被撤回区间计算(hiddenSeqsOf),纯函数
|
|
201
|
+
src/client/locales.ts zh / en 文案(LocaleNamespaceMap)
|
|
202
|
+
src/client/styles.ts 注入样式(dsh 设计 token)
|
|
203
|
+
scripts/build.mjs esbuild:lib/index.js(host ESM)+ lib/client.js(loader 闭包)+ .d.ts
|
|
204
|
+
scripts/verify-host.mjs 端到端验证构建产物(18 项检查)
|
|
205
|
+
tests/ vitest 套件(rewind / snapshot / hidden / session-cwd / 集成,46 例)
|
|
206
|
+
docs/harness-reference.md 维护者文档:DeepSeek Harness 接口参考
|
|
207
|
+
assets/screenshots/ 界面截图
|
|
208
|
+
cordis.patch.yml bundle patch(挂载双面插件行)
|
|
209
|
+
package.json dsh.bundle + dsh.client 声明、optional peerDependencies
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
[MIT](LICENSE)
|
|
Binary file
|
|
Binary file
|