dsh-retrace 0.3.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/HUMANS.txt ADDED
@@ -0,0 +1,18 @@
1
+ /* HUMANS.TXT */
2
+ /* dsh-retrace — an open-source plugin for DeepSeek Harness */
3
+
4
+ /* Team */
5
+ OfferKuai — AI Job Application Assistant
6
+ Founder: Zhaofeng (Yaming)
7
+ Contact: contact@offerkuai.com
8
+ Website: https://www.offerkuai.com
9
+
10
+ /* Mission */
11
+ Users need results, not repeated conversations.
12
+
13
+ /* Acknowledgements */
14
+ Thanks to all test users for their feedback and suggestions
15
+ Thanks to the open-source community for their technical contributions
16
+
17
+ /* License */
18
+ MIT — see LICENSE
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 dsh-retrace contributors
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,317 @@
1
+ <div align="center">
2
+
3
+ # 🧭 dsh-retrace
4
+
5
+ **Retrace · 回溯** — Recall · Edit-and-resend · Regenerate, plus **in-conversation
6
+ versioning**: a timeline of every rewind, artifact rollback, and a fork map of the
7
+ paths your conversation explored. A Harness enhancement plugin for the
8
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web GUI and
9
+ Desktop app (both share the same Web frontend).
10
+
11
+ [![npm version](https://img.shields.io/npm/v/dsh-retrace)](https://www.npmjs.com/package/dsh-retrace)
12
+ [![License: MIT](https://img.shields.io/npm/l/dsh-retrace)](https://github.com/azmavethy/dsh-retrace/blob/main/LICENSE)
13
+ [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4A90D9)](https://github.com/topics/dsh-plugin)
14
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/azmavethy/dsh-retrace/pulls)
15
+
16
+ **English** · [简体中文](./README.zh.md)
17
+
18
+ </div>
19
+
20
+ DeepSeek Harness stores every conversation as an **append-only event log**, so there is
21
+ no built-in "undo". `dsh-retrace` brings back the three moves every chat deserves —
22
+ **撤回 (recall)**, **编辑重发 (edit-and-resend)**, **重新生成 (regenerate)** — and then
23
+ goes further: because a recall only rewinds the **context**, while files the agent
24
+ already changed stay changed, retrace versions your conversation **and its artifacts**
25
+ in one place.
26
+
27
+ Recall / edit **remove the target messages from the conversation view and the model
28
+ context** — that is exactly the effect you see. What stays untouched is the underlying
29
+ **durable transcript**: it remains append-only, old events are never rewritten or deleted,
30
+ and the plugin merely appends one valid replacement event (the same `replace` primitive
31
+ the built-in compaction uses) to rewind the surface — so the log keeps a full audit trail
32
+ of every rewind. On top of that trail, retrace records version boundaries, touched files
33
+ and (optionally) git state, and lets you roll back artifacts or jump back to any point
34
+ in the conversation — all **inside the same session**, no session-switching.
35
+
36
+ > 🚧 **Roadmap in progress** — the **P0 version-data service is live** (version
37
+ > boundaries, touched-file windows, content-addressed artifact snapshots,
38
+ > `session/projection` push + HTTP query channels, config wiring). Timeline &
39
+ > artifact rollback (P1) and the fork map (P2) are being built per [PLAN.md](./PLAN.md).
40
+ > Recall / edit / regenerate are live today.
41
+
42
+ ---
43
+
44
+ ## ✨ Features
45
+
46
+ | Action | Where | What happens |
47
+ | --- | --- | --- |
48
+ | **↩ 撤回** (recall) | hover any assistant reply, or the row under any user message | Removes the **whole exchange round** (the input **and** the agent's output, tool rows included) from both the model context and the conversation view; the input text is echoed into the composer so you can re-ask or re-edit immediately. A small transient notice marks the rewind and disappears once you keep typing. |
49
+ | **✎ 编辑重发** (edit & re-send) | row under any user message | The old message and its reply are rewound and hidden. By default the conversation **starts fresh** (earlier messages are hidden too and excluded from context); the edited text is sent and the agent answers. A collapsed **"original input"** reference sits right under the new message — click to expand, configurable off. |
50
+ | **↻ 重新生成** (regenerate) | hover any assistant reply | The reply (and everything after it) is rewound and hidden, then the original prompt is re-sent so the agent answers again. |
51
+
52
+ **Why it's different**
53
+
54
+ - 🎯 **Whole-round recall** — one click removes the input *and* its output (including tool rows), not just a single bubble.
55
+ - 🖥️ **Web + Desktop** — the same plugin covers both surfaces of DeepSeek Harness.
56
+ - 🔒 **Removed from view & context, not from the log** — recalled/edited messages disappear from the conversation view and the model context, while the durable transcript is never rewritten or deleted; the plugin only appends valid, typed session events (the same `replace` primitive the built-in compaction uses), so the log keeps a full audit trail.
57
+ - 🧠 **View ⇄ context in sync** — the conversation view always reflects exactly what the agent sees.
58
+ - ⚡ **Try in 30 seconds** — the dynamic form installs in your current session with no rebuild.
59
+
60
+ ---
61
+
62
+ ## 🚀 Quick start
63
+
64
+ > Requires DeepSeek Harness with the `dsh` CLI. Installs the plugin as a profile
65
+ > bundle and automatically rebuilds the Web client:
66
+
67
+ ```sh
68
+ # DSH Desktop (desktop profile)
69
+ dsh plugin --profile desktop add dsh-retrace
70
+
71
+ # standalone Web (`dsh web` / web profile)
72
+ dsh plugin --profile web add dsh-retrace
73
+ ```
74
+
75
+ > ⚠️ **Restart after install.** A running app keeps the previously loaded bundle
76
+ > in memory, so **quit and reopen DSH Desktop** (or restart the `dsh` process for
77
+ > a standalone Web deployment) before the plugin activates.
78
+
79
+ That's it — after the restart, hover any assistant reply, or any user message,
80
+ and use ↩ / ✎ / ↻.
81
+
82
+ ---
83
+
84
+ ## 📦 Installation
85
+
86
+ ### 1. Profile bundle (recommended)
87
+
88
+ The package declares a `dsh.bundle` manifest, so it installs through the official
89
+ plugin path into any profile:
90
+
91
+ ```sh
92
+ dsh plugin --profile <name> add dsh-retrace
93
+ ```
94
+
95
+ > ⚠️ **Restart required.** The install writes the new files and re-renders the
96
+ > profile composition, but a running app does **not** hot-reload bundles — quit
97
+ > and reopen **DSH Desktop** (or restart the `dsh` process for a standalone Web
98
+ > deployment) to load the plugin. To uninstall:
99
+ > `dsh plugin --profile <name> remove dsh-retrace` (then restart again).
100
+
101
+ It also shows up in [dsh-market](https://github.com/dsh-market/dsh-market) for
102
+ one-click install from inside Settings (same restart applies).
103
+
104
+ ### 2. Manual install (no `dsh` CLI)
105
+
106
+ The same result with plain file edits and `pnpm` — exactly the steps
107
+ `dsh plugin add` performs for you:
108
+
109
+ 1. Open the profile manifest (defaults: `~/.dsh/profiles/desktop` on DSH
110
+ Desktop, `~/.dsh/profiles/web` for standalone Web) and add **both** the
111
+ dependency and the bundle-layer entry:
112
+
113
+ ```json
114
+ {
115
+ "dependencies": {
116
+ "dsh-retrace": "^0.2.0"
117
+ },
118
+ "dsh": {
119
+ "profile": {
120
+ "bundles": [
121
+ "@deepseek-ai/dsh-base",
122
+ "@deepseek-ai/dsh-web-app",
123
+ "dsh-retrace"
124
+ ]
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ (Keep whatever entries your profile already has; only add the two
131
+ `dsh-retrace` lines.)
132
+
133
+ 2. Install inside the profile directory:
134
+
135
+ ```sh
136
+ cd ~/.dsh/profiles/<name> && pnpm install
137
+ ```
138
+
139
+ 3. Restart DSH Desktop / the `dsh` process (see above).
140
+
141
+ For local development, point the dependency at a checkout instead of the
142
+ registry: `"dsh-retrace": "file:/path/to/dsh-retrace"` — or let
143
+ `dsh` do it: `dsh plugin --profile <name> add /path/to/dsh-retrace`.
144
+
145
+ ### 3. npm package + composition (classic)
146
+
147
+ ```sh
148
+ npm i dsh-retrace
149
+ ```
150
+
151
+ Add the package to the harness composition (`cordis.yml` of the app/deployment you use):
152
+
153
+ ```yaml
154
+ - name: 'dsh-retrace'
155
+ ```
156
+
157
+ The client half is picked up automatically from the package's `dsh.client` metadata and
158
+ bundled into the Web client (a client-module rebuild happens automatically when the
159
+ composition changes). The Host half registers the same-origin HTTP route
160
+ `/api/plugins/retrace/*` for the browser UI.
161
+
162
+ ### 4. Dynamic plugin (current session — no install, no rebuild)
163
+
164
+ Use the **dynamic** entries shipped in the package. In the session where you want the
165
+ feature:
166
+
167
+ 1. Open the plugin editor and define a new plugin from `lib/dynamic-host.js`
168
+ (Host half) and `lib/dynamic-client.js` (Client half).
169
+ 2. Approve and run the Client half.
170
+ 3. Done — hover any assistant reply, or any user message, and use ↩ / ✎ / ↻.
171
+
172
+ The dynamic host registers the same operations behind the package-private
173
+ `harness.handle` RPC (`retrace.recall` / `retrace.editAndResend` /
174
+ `retrace.regenerate`).
175
+
176
+ ---
177
+
178
+ ## ⚙️ Settings → General
179
+
180
+ | Setting | Default | Description |
181
+ | --- | --- | --- |
182
+ | **编辑后显示原提问对照** | on | A collapsed "original input" reference under the re-sent message showing the **most recent** replaced text (reference only — never sent to the model). |
183
+ | **编辑后从新对话开始** | on | After editing, hide earlier messages too so the conversation looks like a fresh start (the whole surface is rewound before re-sending). |
184
+
185
+ ---
186
+
187
+ ## 🧠 How it works
188
+
189
+ ```
190
+ durable transcript (append-only) model context & view
191
+ ┌────────────────────────────────┐ ┌──────────────────┐
192
+ │ ... target message │ │ … target message │
193
+ │ ↓ shadow span │ │ ↓ rewind │
194
+ │ [target … last surface node] │ ─────▶ │ (empty replace │
195
+ │ ↳ one replacement │ │ = context cut) │
196
+ │ assistant/message (empty)│ └──────────────────┘
197
+ │ ↳ optional original-input │ agent.followup(new prompt)
198
+ └────────────────────────────────┘ → next turn rebuilds request
199
+ ```
200
+
201
+ 1. **Host core** (`lib/host-core.js`, zero runtime imports) locates the target
202
+ message in the session's live surface, computes the shadow span
203
+ `[message … last surface node]`, and appends one replacement
204
+ `assistant/message` with an **empty** body — a valid surface node that
205
+ derives to *no* model message, so the LLM context simply rewinds.
206
+ 2. **Edit / regenerate** additionally call `agent.followup(...)` with the
207
+ (new) prompt text; the agent's next turn builds its request from the
208
+ rewound `session.deriveMessages()`.
209
+ 3. **Client** (`lib/client.js`) registers:
210
+ - a `user-actions` conversation node under every user message
211
+ (编辑 / 撤回 row with an inline editor); recall echoes the text into the
212
+ composer,
213
+ - the `recall-marker` node renderer: a notice row that injects CSS hiding
214
+ every shadowed message row from the flow (view and model context stay in
215
+ sync), plus the optional original-input comparison block,
216
+ - the `retrace` entry in the `conversation.chat.assistant-actions`
217
+ strip (撤回 / 重新生成),
218
+ - two preference toggles under Settings → General.
219
+
220
+ > Two different layers are at play: the **durable transcript** (append-only; old
221
+ > events are never rewritten or deleted) and the **model-visible surface** (rewound
222
+ > by an appended replacement event). So the old events stay in the log as an audit
223
+ > trail — but they are **synchronized out of both the model context and the visible
224
+ > conversation**, and the view always reflects what the agent actually sees.
225
+ > Persistence, projections and the transcript remain consistent because the plugin
226
+ > only appends valid, typed session events.
227
+
228
+ ---
229
+
230
+ ## ⚠️ Requirements & limitations
231
+
232
+ - Only **user messages** can be edited; recall works on user and assistant
233
+ messages. Tool results are shadowed along with the recalled range but are not
234
+ themselves recall targets.
235
+ - The agent must be **idle**: while a reply is streaming you must stop it
236
+ (⏹) before recalling or editing. The Host rejects with `agent-busy`
237
+ otherwise.
238
+ - Recall/edit operate on the **active model surface**: a message that was
239
+ already compacted away or previously recalled is rejected
240
+ (`target-shadowed`).
241
+ - Regenerate re-sends only the **text** of the original prompt; prompts that
242
+ carried images fall back to the text-only content.
243
+
244
+ ---
245
+
246
+ ## 🗺️ Roadmap
247
+
248
+ Built per [PLAN.md](./PLAN.md):
249
+
250
+ - **P1 — Timeline & artifact rollback**: an in-session version timeline (messages,
251
+ thinking, touched files), artifact snapshots (git-first, snapshot-fallback, opt-in),
252
+ rollback with dry-run preview, and jump-to-conversation navigation.
253
+ - **P2 — Fork map**: a flow graph of the conversation's turns with fork points at every
254
+ rewind, thinking flow per turn, branch-intent cards, and version comparison.
255
+ - More locales beyond 简体中文 / English.
256
+
257
+ ---
258
+
259
+ ## 🛠️ Development
260
+
261
+ ```sh
262
+ # structure
263
+ lib/host-core.js # transport-neutral host logic (no imports)
264
+ lib/index.js # published Host: harness RPC + HTTP route
265
+ lib/client.js # client SOURCE (React via import; pluggable transport)
266
+ lib/client.bundle.js # BUILT client bundle — the self-registering loader entry
267
+ # (`window.__ModuleLoader__.load`) served by client-modules
268
+ lib/dynamic-host.js # GENERATED dynamic Host half (from lib/host-core.js)
269
+ lib/dynamic-client.js # GENERATED dynamic Client half (from lib/client.js)
270
+ scripts/build-client.mjs # bundle lib/client.js → lib/client.bundle.js
271
+ scripts/generate-dynamic.mjs # generate both dynamic entries from the canonical sources
272
+ scripts/check-dynamic.mjs # syntax-check the dynamic entries (function bodies)
273
+ test/ # vitest suite: host-core ops + generated-entry smoke tests
274
+ .github/workflows/ # CI (syntax + build-sync + tests) and npm publish (v* tags)
275
+ cordis.patch.yml # dsh.bundle profile patch layer
276
+ ```
277
+
278
+ ```sh
279
+ pnpm install # install dev dependencies (vitest, esbuild)
280
+ pnpm check # syntax-check sources AND the generated dynamic entries
281
+ pnpm build # regenerate lib/dynamic-*.js + lib/client.bundle.js
282
+ pnpm test # run the host-core unit tests
283
+ npm pack --dry-run # verify the published file list
284
+ ```
285
+
286
+ > ⚠️ **Generated files.** `lib/dynamic-host.js`, `lib/dynamic-client.js` and
287
+ > `lib/client.bundle.js` are built artifacts generated from `lib/host-core.js`
288
+ > and `lib/client.js` — never edit them by hand. CI fails when a committed
289
+ > artifact is stale (`git diff --exit-code`), so run `pnpm build` before
290
+ > committing. The dynamic client reuses the same client source as the published
291
+ > one and only swaps the transport (`host.call` vs the HTTP route) via
292
+ > `__setMessageEditorWire`.
293
+
294
+ PRs and issues are welcome — see [CONTRIBUTING](./CONTRIBUTING.md) (coming soon)
295
+ and the [issue tracker](https://github.com/azmavethy/dsh-retrace/issues).
296
+
297
+ ---
298
+
299
+ ## 📚 Ecosystem
300
+
301
+ Listed on the [dsh-plugin topic](https://github.com/topics/dsh-plugin) and
302
+ installable from [dsh-market](https://github.com/dsh-market/dsh-market). For a
303
+ curated overview of the DeepSeek Harness plugin ecosystem, see
304
+ [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin).
305
+
306
+ ---
307
+
308
+ ## 👥 Team
309
+
310
+ Built by the [OfferKuai](https://www.offerkuai.com) team — an AI job application
311
+ assistant on a mission that "users need results, not repeated conversations".
312
+ Founder: Zhaofeng (Yaming). This plugin is released as open source for the
313
+ DeepSeek Harness community.
314
+
315
+ ## 📄 License
316
+
317
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,284 @@
1
+ <div align="center">
2
+
3
+ # 🧭 dsh-retrace
4
+
5
+ **Retrace · 回溯** —— 在 **撤回 · 编辑重发 · 重新生成** 之上,更进一步:
6
+ 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 对话提供
7
+ **单会话内的版本化**——每一次回退的时间线、产物回退、以及对话走过的分叉路径图。
8
+ 同时支持 **Web 端** 与 **桌面客户端**(两者共用同一套 Web 前端)。
9
+
10
+ [![npm version](https://img.shields.io/npm/v/dsh-retrace)](https://www.npmjs.com/package/dsh-retrace)
11
+ [![License: MIT](https://img.shields.io/npm/l/dsh-retrace)](https://github.com/azmavethy/dsh-retrace/blob/main/LICENSE)
12
+ [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4A90D9)](https://github.com/topics/dsh-plugin)
13
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/azmavethy/dsh-retrace/pulls)
14
+
15
+ **简体中文** · [English](./README.md)
16
+
17
+ </div>
18
+
19
+ DeepSeek Harness 的对话是「只追加(append-only)」的事件日志,本身没有撤销能力。
20
+ `dsh-retrace` 先为对话补上聊天本该有的三个操作 —— **撤回**、**编辑重发**、
21
+ **重新生成**;再往前一步:撤回只回退了**上下文**,而智能体已经改过的**产物文件**
22
+ 不会自动还原——retrace 把对话**和它的产物**放在一起做版本化。
23
+
24
+ 撤回/编辑后,目标消息会**从对话视图和模型上下文中移除**——你看到的"删除"正是这个
25
+ 效果。但底层的**持久化日志不会被改写或删除**:它始终保持只追加,旧事件原样保留,
26
+ 插件只是在日志末尾追加一条合法的替换事件(与内置压缩使用的 `replace` 原语一致)来
27
+ 回退对话表面,因此日志保留每一次回退的完整审计痕迹。在这条痕迹之上,retrace 记录
28
+ 版本边界、触碰文件与(可选的)git 状态,支持产物回退与跳转到对话任意位置——全部
29
+ 发生在**同一会话内**,不换会话。
30
+
31
+ > 🚧 **路线图进行中** —— 时间线与产物回退(P1)、分叉图(P2)正在按
32
+ > [PLAN.md](./PLAN.md) 开发;撤回/编辑/重新生成当前已可用。
33
+
34
+ ---
35
+
36
+ ## ✨ 功能
37
+
38
+ | 操作 | 入口 | 效果 |
39
+ | --- | --- | --- |
40
+ | **↩ 撤回** | 悬停任意助手回复;或用户消息下方的操作行 | **移除整轮对话**(该条输入及其对应的输出、工具行一并消失),从模型上下文与对话视图中同步清除,并把输入原文**回显到输入框**方便立即修改后重发;一条短暂提示标记回退点,你继续输入后自动消失。 |
41
+ | **✎ 编辑重发** | 用户消息下方的操作行 | 回退并隐藏旧消息及其回复。默认**从新对话开始**(此前的消息一并隐藏、不再进入上下文),发送修改后的文本让智能体作答;新消息下方有一个折叠的「原提问」对照,点击展开、可配置关闭。 |
42
+ | **↻ 重新生成** | 悬停任意助手回复 | 回退并隐藏该回复及其后内容,重新发送原提问,让智能体重新作答。 |
43
+
44
+ **为什么与众不同**
45
+
46
+ - 🎯 **整轮撤回** —— 一键移除输入 *和* 它的输出(含工具行),而不只是单条气泡。
47
+ - 🖥️ **Web + Desktop 双端** —— 同一插件覆盖 DeepSeek Harness 两种界面。
48
+ - 🔒 **删除的是视图与上下文,不是日志** —— 被撤回/编辑的消息从对话视图和模型上下文中
49
+ 消失,但持久化日志从不被改写或删除;插件只追加合法、带类型的会话事件(与内置压缩
50
+ 使用的 `replace` 原语一致),日志保留完整审计痕迹。
51
+ - 🧠 **视图 ⇄ 上下文同步** —— 对话视图永远反映智能体真正看到的内容。
52
+ - ⚡ **30 秒上手** —— 动态插件形式无需重建即可在当前会话试用。
53
+
54
+ ---
55
+
56
+ ## 🚀 快速开始
57
+
58
+ > 需要带 `dsh` CLI 的 DeepSeek Harness。以 profile bundle 方式安装,并自动重建 Web 客户端:
59
+
60
+ ```sh
61
+ # DSH Desktop(desktop profile)
62
+ dsh plugin --profile desktop add dsh-retrace
63
+
64
+ # 独立 Web 部署(`dsh web` / web profile)
65
+ dsh plugin --profile web add dsh-retrace
66
+ ```
67
+
68
+ > ⚠️ **安装后需要重启。** 运行中的应用仍在内存中保留之前加载的 bundle,请**退出并
69
+ > 重新打开 DSH Desktop**(独立 Web 部署则重启 `dsh` 进程)后插件才会生效。
70
+
71
+ 重启后,悬停任意助手回复或用户消息,即可使用 ↩ / ✎ / ↻。
72
+
73
+ ---
74
+
75
+ ## 📦 安装
76
+
77
+ ### 1. Profile bundle(推荐)
78
+
79
+ 包声明了 `dsh.bundle` 清单,可通过官方插件路径安装到任意 profile:
80
+
81
+ ```sh
82
+ dsh plugin --profile <name> add dsh-retrace
83
+ ```
84
+
85
+ > ⚠️ **安装后需要重启。** 安装会写入新文件并重新生成 profile 组合,但运行中的应用
86
+ > **不会**热加载 bundle —— 请**退出并重新打开 DSH Desktop**(独立 Web 部署则重启
87
+ > `dsh` 进程)来加载插件。卸载:`dsh plugin --profile <name> remove
88
+ > dsh-retrace`(卸载后同样需要重启)。
89
+
90
+ 同时可在 [dsh-market](https://github.com/dsh-market/dsh-market) 里一键安装
91
+ (安装后同样需要重启)。
92
+
93
+ ### 2. 手动安装(不依赖 `dsh` CLI)
94
+
95
+ 用纯文件编辑 + `pnpm` 装进同一个 profile —— 也就是 `dsh plugin add` 帮你做的那些步骤:
96
+
97
+ 1. 打开 profile 清单(默认位置:DSH Desktop 为 `~/.dsh/profiles/desktop`,
98
+ 独立 Web 为 `~/.dsh/profiles/web`),同时加入依赖**和** bundle 层条目:
99
+
100
+ ```json
101
+ {
102
+ "dependencies": {
103
+ "dsh-retrace": "^0.2.0"
104
+ },
105
+ "dsh": {
106
+ "profile": {
107
+ "bundles": [
108
+ "@deepseek-ai/dsh-base",
109
+ "@deepseek-ai/dsh-web-app",
110
+ "dsh-retrace"
111
+ ]
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ (保留 profile 原有条目,只需新增 `dsh-retrace` 这两处。)
118
+
119
+ 2. 在 profile 目录里安装:
120
+
121
+ ```sh
122
+ cd ~/.dsh/profiles/<name> && pnpm install
123
+ ```
124
+
125
+ 3. 重启 DSH Desktop / `dsh` 进程(见上文)。
126
+
127
+ 本地开发时,可以把依赖指向本地检出目录而不是注册表:
128
+ `"dsh-retrace": "file:/路径/to/dsh-retrace"` —— 或者交给 `dsh`:
129
+ `dsh plugin --profile <name> add /路径/to/dsh-retrace`。
130
+
131
+ ### 3. npm 包 + 组合文件(经典方式)
132
+
133
+ ```sh
134
+ npm i dsh-retrace
135
+ ```
136
+
137
+ 在所使用的应用/部署的 `cordis.yml` 组合文件中加入一行普通插件条目:
138
+
139
+ ```yaml
140
+ - name: 'dsh-retrace'
141
+ ```
142
+
143
+ Client 半区会依据包内 `dsh.client` 元数据被自动打包进 Web 客户端(组合变化时会自动
144
+ 重建客户端模块);Host 半区为浏览器 UI 注册同源 HTTP 路由 `/api/plugins/retrace/*`。
145
+
146
+ ### 4. 动态插件(当前会话,免安装、免重建)
147
+
148
+ 包内提供了两个自包含的动态入口:
149
+
150
+ 1. 打开插件编辑界面,用 `lib/dynamic-host.js`(Host 半区)和
151
+ `lib/dynamic-client.js`(Client 半区)新建插件;
152
+ 2. 批准并运行 Client 半区;
153
+ 3. 完成 —— 悬停任意助手回复或用户消息,即可使用 ↩ / ✎ / ↻。
154
+
155
+ 动态 Host 通过 `harness.handle` 注册同一组操作
156
+ (`retrace.recall` / `retrace.editAndResend` / `retrace.regenerate`)。
157
+
158
+ ---
159
+
160
+ ## ⚙️ 设置 → 通用
161
+
162
+ | 设置项 | 默认 | 说明 |
163
+ | --- | --- | --- |
164
+ | **编辑后显示原提问对照** | 开 | 重发消息下方的折叠「原输入」引用,显示**最近一次**被替换的原文(仅作对照,不会进入模型上下文)。 |
165
+ | **编辑后从新对话开始** | 开 | 编辑后连此前的消息也一并隐藏,让对话看起来像从新消息重新开始(重发前回退整个表面)。 |
166
+
167
+ ---
168
+
169
+ ## 🧠 工作原理
170
+
171
+ ```
172
+ 持久化日志(只追加) 模型上下文与视图
173
+ ┌────────────────────────────────┐ ┌──────────────────┐
174
+ │ ... 目标消息 │ │ … 目标消息 │
175
+ │ ↓ 阴影区间 │ │ ↓ 回退 │
176
+ │ [目标 … 最后一个表面节点] │ ───▶│ (空 replace │
177
+ │ ↳ 追加一条替换型 │ │ = 上下文截断) │
178
+ │ assistant/message(空) │ └──────────────────┘
179
+ │ ↳ 可选「原提问」对照 │ agent.followup(新提示)
180
+ └────────────────────────────────┘ → 下一轮基于回退后的历史重建请求
181
+ ```
182
+
183
+ 1. **Host 核心**(`lib/host-core.js`,零运行时依赖):在会话的活跃表面中定位目标
184
+ 消息,计算阴影区间 `[消息 … 最后一个表面节点]`,追加一条**空内容**的替换型
185
+ `assistant/message` —— 空助手消息是合法表面节点,但派生不出任何模型消息,
186
+ 因此 LLM 上下文直接回退。
187
+ 2. **编辑 / 重新生成**:额外调用 `agent.followup(...)` 发送(新的)提示文本,
188
+ 智能体的下一轮请求基于回退后的 `session.deriveMessages()` 构建。
189
+ 3. **Client**(`lib/client.js`)注册:
190
+ - 每条用户消息下的 `user-actions` 对话节点(编辑/撤回行 + 内联编辑器);
191
+ 撤回后把原文回显到输入框,
192
+ - `recall-marker` 节点渲染器:提示行 + 注入 CSS 把被阴影化的消息行从对话流中
193
+ 隐藏(视图与模型上下文保持同步),并可显示「原提问」对照块,
194
+ - `conversation.chat.assistant-actions` 中的 `retrace` 入口
195
+ (撤回/重新生成),
196
+ - 设置 → 通用 中的两个偏好开关。
197
+
198
+ > 这里有两个不同层面:**持久化日志**(只追加;旧事件从不被改写或删除)与
199
+ > **模型可见表面**(由追加的替换事件回退)。因此旧事件作为审计痕迹留在记录中——
200
+ > 但它们会被**同步地从模型上下文和可见对话中清除**,界面始终反映智能体真正看到的内容。
201
+ > 因为插件只追加合法、带类型的会话事件,持久化、投影与记录保持一致。
202
+
203
+ ---
204
+
205
+ ## ⚠️ 要求与限制
206
+
207
+ - 只有**用户消息**可以编辑;撤回同时适用于用户与助手消息。工具结果会随区间一并
208
+ 被阴影化,但不能单独作为撤回目标。
209
+ - 智能体必须**空闲**:回复流式输出时需先点击 ⏹ 停止,再撤回或编辑;否则 Host
210
+ 返回 `agent-busy`。
211
+ - 撤回/编辑作用于**活跃模型表面**:已被压缩或此前已撤回的消息会被拒绝
212
+ (`target-shadowed`)。
213
+ - 重新生成只重发原提示的**文本**部分;携带图片的提示会退化为仅文本重发。
214
+
215
+ ---
216
+
217
+ ## 🗺️ 路线图
218
+
219
+ 按 [PLAN.md](./PLAN.md) 推进:
220
+
221
+ - **P1 — 时间线与产物回退**:单会话内的版本时间线(消息、思考、触碰文件),产物快照
222
+ (git 优先 + 快照兜底,可开关),带干跑预览的回退,以及跳转到对话位置。
223
+ - **P2 — 分叉图**:对话回合的流程分叉图,每次回退都是分叉点,逐回合思考流,
224
+ 分支意图卡,版本对比。
225
+ - 支持更多语言(当前:简体中文 / English)。
226
+
227
+ ---
228
+
229
+ ## 🛠️ 开发
230
+
231
+ ```sh
232
+ # 目录结构
233
+ lib/host-core.js # 传输无关的 Host 逻辑(无 import)
234
+ lib/index.js # 发布版 Host:harness RPC + HTTP 路由
235
+ lib/client.js # Client 源码(import React;传输层可插拔)
236
+ lib/client.bundle.js # 构建产物 —— 自注册 loader entry
237
+ # (`window.__ModuleLoader__.load`),由 client-modules 提供
238
+ lib/dynamic-host.js # 生成的动态 Host 半区(源自 lib/host-core.js)
239
+ lib/dynamic-client.js # 生成的动态 Client 半区(源自 lib/client.js)
240
+ scripts/build-client.mjs # 打包 lib/client.js → lib/client.bundle.js
241
+ scripts/generate-dynamic.mjs # 从权威源生成两个动态入口
242
+ scripts/check-dynamic.mjs # 语法检查动态入口(函数体形态)
243
+ test/ # vitest 套件:host-core 操作 + 生成产物冒烟测试
244
+ .github/workflows/ # CI(语法 + 构建同步 + 测试)与 npm 发布(v* tag)
245
+ cordis.patch.yml # dsh.bundle profile patch 层
246
+ ```
247
+
248
+ ```sh
249
+ pnpm install # 安装开发依赖(vitest、esbuild)
250
+ pnpm check # 语法检查源码与生成的动态入口
251
+ pnpm build # 重新生成 lib/dynamic-*.js 与 lib/client.bundle.js
252
+ pnpm test # 运行 host-core 单元测试
253
+ npm pack --dry-run # 校验发布文件清单
254
+ ```
255
+
256
+ > ⚠️ **生成文件。** `lib/dynamic-host.js`、`lib/dynamic-client.js` 与
257
+ > `lib/client.bundle.js` 是由 `lib/host-core.js` 和 `lib/client.js` 生成的构建
258
+ > 产物 —— **请勿手改**。CI 会在构建产物与源码不同步时失败
259
+ > (`git diff --exit-code`),因此提交前记得执行 `pnpm build`。动态 Client 与
260
+ > 发布版共用同一份 client 源码,仅通过 `__setMessageEditorWire` 切换传输层
261
+ > (`host.call` vs HTTP 路由)。
262
+
263
+ 欢迎提交 PR 与 issue —— 见 [CONTRIBUTING](./CONTRIBUTING.md)(筹备中)与
264
+ [问题追踪](https://github.com/azmavethy/dsh-retrace/issues)。
265
+
266
+ ---
267
+
268
+ ## 📚 生态
269
+
270
+ 收录于 [dsh-plugin topic](https://github.com/topics/dsh-plugin),可在
271
+ [dsh-market](https://github.com/dsh-market/dsh-market) 一键安装。DeepSeek Harness
272
+ 插件生态的精选总览见 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)。
273
+
274
+ ---
275
+
276
+ ## 👥 团队
277
+
278
+ 由 [OfferKuai](https://www.offerkuai.com) 团队开发——一款 AI 求职助手,使命是
279
+ 「用户要的是结果,而不是反复的对话」。创始人:Zhaofeng(Yaming)。本插件以开源
280
+ 形式发布,回馈 DeepSeek Harness 社区。
281
+
282
+ ## 📄 License
283
+
284
+ MIT
@@ -0,0 +1,17 @@
1
+ # dsh-retrace — profile bundle patch layer.
2
+ #
3
+ # Mounts the plugin as one normal Cordis row in the target profile (the
4
+ # package's `main` → lib/index.js Host half runs as that row). The package's
5
+ # `dsh.client` metadata is scanned by the modules node half, and its client
6
+ # half (exports["./client"] → lib/client.js) is bundled into the Web client
7
+ # automatically while this row is enabled.
8
+ #
9
+ # Install into any profile with:
10
+ # dsh plugin --profile <name> add dsh-retrace
11
+ # (manual: add "dsh-retrace" to the profile's dependencies and
12
+ # dsh.profile.bundles, then run `pnpm install` in the profile directory)
13
+ # Restart DSH Desktop / the dsh process after installing or removing.
14
+
15
+ - insert:
16
+ - id: dsh-retrace
17
+ name: dsh-retrace