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 +18 -0
- package/LICENSE +21 -0
- package/README.md +317 -0
- package/README.zh.md +284 -0
- package/cordis.patch.yml +17 -0
- package/lib/artifact-store.js +239 -0
- package/lib/client.bundle.js +637 -0
- package/lib/client.js +752 -0
- package/lib/dynamic-client.js +647 -0
- package/lib/dynamic-host.js +399 -0
- package/lib/host-core.js +379 -0
- package/lib/http.js +186 -0
- package/lib/index.js +75 -0
- package/lib/projection/versions.js +69 -0
- package/lib/types/client.d.ts +14 -0
- package/lib/types/index.d.ts +65 -0
- package/lib/version-index.js +310 -0
- package/lib/versioning.js +193 -0
- package/package.json +100 -0
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
|
+
[](https://www.npmjs.com/package/dsh-retrace)
|
|
12
|
+
[](https://github.com/azmavethy/dsh-retrace/blob/main/LICENSE)
|
|
13
|
+
[](https://github.com/topics/dsh-plugin)
|
|
14
|
+
[](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
|
+
[](https://www.npmjs.com/package/dsh-retrace)
|
|
11
|
+
[](https://github.com/azmavethy/dsh-retrace/blob/main/LICENSE)
|
|
12
|
+
[](https://github.com/topics/dsh-plugin)
|
|
13
|
+
[](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
|
package/cordis.patch.yml
ADDED
|
@@ -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
|