dsh-retrace 0.4.32 โ†’ 0.4.131

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.
Files changed (43) hide show
  1. package/README.md +217 -217
  2. package/README.zh.md +185 -140
  3. package/bin/retrace.mjs +1 -1
  4. package/lib/adapter/contract.js +13 -12
  5. package/lib/adapter/dsh-writer.js +28 -11
  6. package/lib/adapter/dsh.js +188 -24
  7. package/lib/archaeology-cli.js +3 -3
  8. package/lib/artifact-store.js +1 -1
  9. package/lib/badge.js +8 -8
  10. package/lib/boot-pin.js +1 -1
  11. package/lib/boundary-derive.js +3 -3
  12. package/lib/boundary-now.js +1 -1
  13. package/lib/boundary-tree.js +1 -1
  14. package/lib/boundary-what.js +1 -1
  15. package/lib/client.bundle.js +1301 -101
  16. package/lib/client.js +1593 -175
  17. package/lib/close-guard-client.js +36 -20
  18. package/lib/close-guard.js +22 -22
  19. package/lib/dynamic-client.js +1301 -101
  20. package/lib/dynamic-host.js +1110 -75
  21. package/lib/forkmap.js +2 -2
  22. package/lib/git-adapter.js +1 -1
  23. package/lib/host-compat.js +2 -2
  24. package/lib/host-core.js +1044 -33
  25. package/lib/http.js +28 -18
  26. package/lib/identity/shortcode.js +35 -35
  27. package/lib/identity/sidebar-badge.js +244 -0
  28. package/lib/index.js +343 -48
  29. package/lib/interrupt-guard.js +2 -2
  30. package/lib/marker-carrier.js +24 -18
  31. package/lib/message-list.js +2 -2
  32. package/lib/migration-traces.js +4 -4
  33. package/lib/platform/session-paths.js +100 -49
  34. package/lib/prewrite-guard.js +173 -40
  35. package/lib/projection/forkmap.js +1 -1
  36. package/lib/projection/versions.js +1 -1
  37. package/lib/rollback.js +710 -10
  38. package/lib/summary-gate.js +2 -2
  39. package/lib/summary-store.js +1 -1
  40. package/lib/version-index.js +9 -4
  41. package/lib/versioning.js +4 -4
  42. package/lib/watchdog.js +12 -12
  43. package/package.json +11 -9
package/README.md CHANGED
@@ -2,21 +2,20 @@
2
2
 
3
3
  # ๐Ÿงญ dsh-retrace
4
4
 
5
- **Recall ยท Edit-and-resend ยท Regenerate**, plus **write-safe** in-conversation
5
+ **Recall ยท Resend (restart from here) ยท Regenerate**, plus **write-safe** in-conversation
6
6
  versioning โ€” the **Agent business layer (production-grade guarantees)** for
7
7
  DeepSeek Harness.
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/dsh-retrace)](https://www.npmjs.com/package/dsh-retrace)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/dsh-retrace)](https://www.npmjs.com/package/dsh-retrace)
11
- [![License: MIT](https://img.shields.io/npm/l/dsh-retrace)](https://github.com/yamingmou/dsh-retrace/blob/main/LICENSE)
11
+ [![License: MIT](https://img.shields.io/npm/l/dsh-retrace)](https://www.npmjs.com/package/dsh-retrace)
12
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/yamingmou/dsh-retrace/pulls)
14
13
 
15
14
  **English** ยท [็ฎ€ไฝ“ไธญๆ–‡](./README.zh.md)
16
15
 
17
16
  </div>
18
17
 
19
- **Recall / edit-and-resend / regenerate** โ€” the three moves every conversation
18
+ **Recall / resend / regenerate** โ€” the three moves every conversation
20
19
  deserves. But rewinding is not just "delete a message": DeepSeek Harness stores
21
20
  conversations in an append-only event log, so a recall only rewinds the context
22
21
  while changed **artifact files stay changed**. dsh-retrace versions the
@@ -27,8 +26,8 @@ it cannot dirty the log, and new markers create **no token-meter pairing debt**
27
26
  > โš ๏ธ **Honest scope (matches the companion contract's own note)**: sessions that
28
27
  > already contain **legacy single-segment markers** โ€” written by older versions โ€”
29
28
  > are **known design debt**. Before `/compact`, run the companion `check` and
30
- > clean them up (`fix --remove-markers`); otherwise the host's own T1 self-check
31
- > blocks compaction. New rewinds do not add to that debt.
29
+ > clean them up (`fix --remove-markers`); otherwise the host's own pre-compaction
30
+ > self-check blocks compaction. New rewinds do not add to that debt.
32
31
 
33
32
  > ๐Ÿ›ก๏ธ **Write safety** ยท ๐Ÿ” **Deep offline checks** ยท ๐Ÿ”„ **Detect โ†’ repair โ†’ guard** โ€” see below.
34
33
 
@@ -41,8 +40,8 @@ it cannot dirty the log, and new markers create **no token-meter pairing debt**
41
40
  ```sh
42
41
  dsh plugin --profile desktop add dsh-retrace # DSH Desktop
43
42
  # or Web: dsh plugin --profile web add dsh-retrace
44
- # or GitHub: dsh plugin --profile desktop add github:yamingmou/dsh-retrace
45
- # or ZIP: dsh plugin --profile desktop add ~/plugins/dsh-retrace
43
+ # or from an unpacked checkout / ZIP:
44
+ # dsh plugin --profile desktop add ~/plugins/dsh-retrace
46
45
  ```
47
46
 
48
47
  **No command line?** Install the community plugin market once, then find
@@ -61,7 +60,7 @@ Full steps in [๐Ÿ“ฆ Installation](#-installation).
61
60
 
62
61
  | | Capability | What it means |
63
62
  |---|---|---|
64
- | ๐Ÿ›ก๏ธ | **Write safety** | Every rewind passes a three-layer pre-write contract guard; running agents are auto-stopped (official `cancel`/`whenIdle`); turn-interval markers are wrapped in a temporary step โ€” **new rewinds cannot dirty the log and add no token-meter pairing debt**; **legacy single-segment markers are known debt** (run the companion `check` + `fix --remove-markers` before `/compact`) |
63
+ | ๐Ÿ›ก๏ธ | **Write safety** | Every rewind passes a three-layer pre-write contract guard; a running agent is stopped automatically (official `cancel`/`whenIdle`, with a bounded wait); turn-interval markers are wrapped in a temporary step โ€” **new rewinds cannot dirty the log and add no token-meter pairing debt**; **legacy single-segment markers are known debt** (run the companion `check` + `fix --remove-markers` before `/compact`) |
65
64
  | ๐Ÿ” | **Deep offline checks** | Companion `dsh-log-contract` ships 30+ contract rules (token-meter pairing / cross-step references / physical order / inbox replay), validated against real corrupted-session fixtures โ€” it finds the class of problem that makes /compact permanently fail |
66
65
  | ๐Ÿ”„ | **Detect โ†’ repair โ†’ guard** | A watchdog snapshots the log at the first sign of concurrent writes; offline `fix` neutralizes problem markers and clips cross-step references in place; pre-write validation stops bad events before they land |
67
66
 
@@ -71,18 +70,33 @@ Full steps in [๐Ÿ“ฆ Installation](#-installation).
71
70
 
72
71
  | Action | Where | What happens |
73
72
  | --- | --- | --- |
74
- | **โ†ฉ 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. |
75
- | **โœŽ Edit & re-send** | row under any user message | The edited round is rewound and the new text is re-sent. By default **only the edited round** is replaced โ€” earlier history stays visible; the optional "fresh conversation" setting rewinds the whole surface (earlier messages then leave the model context, and stay visible in the view as a marker notice by default). A collapsed **"original input"** reference sits right under the new message โ€” click to expand, configurable off. |
73
+ | **โ†ฉ Recall** | hover any assistant reply, or the row under any user message | Opens a **confirmation first** (destructive: this message and everything after it leaves the conversation). On confirm, if a turn is still running the plugin **pauses it first** through the official stop entry and waits (bounded); if the pause fails or times out the recall still continues, with a visible notice. The whole exchange round (the input **and** the agent's output, tool rows included) is then removed 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 transient notice marks the rewind and disappears once you keep typing. |
74
+ | **โœŽ Resend (restart from here)** | row under any user message | The primary meaning is **restart from this message**: this round and everything after it is rewound and the new text is re-sent, so the conversation continues on a new timeline branch. If that range is refused by the guard (too large to rewrite in place), the error offers a fallback action โ€” **Change this text only (keep what follows)**. A collapsed **"original input"** reference sits right under the new message โ€” click to expand, configurable off. |
76
75
  | **โ†ป 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. |
77
76
 
78
- **Versioning & rollback (live in 0.4.x)** โ€” every rewind is also recorded as a **version**:
77
+ ### Undoing a recall
78
+
79
+ | Action | Scope | What happens |
80
+ | --- | --- | --- |
81
+ | โ†บ **Restore display** | view only | Puts the rows hidden by that marker back into the conversation view. The log only grows, the model context is unchanged, and the recall trace stays. Available on the marker row. |
82
+ | โช **True restore** | log level | Neutralizes the two marker segments **in place** (sequence numbers are not renumbered), so the recalled range returns to the **model surface** and its edit / resend / recall actions work again. Requires an idle session that is not the one you are currently viewing; once it succeeds, switch away and reopen that session (or refresh the page) to see the restored content. Refusals are explained with a next step (session running / held by another process / already re-shadowed by a later marker / host does not provide the capability). |
83
+
84
+ **Checkpoints & rollback (live in 0.4.x)** โ€” every rewind is also recorded as a **checkpoint**:
79
85
 
80
86
  | | What | |
81
87
  |---|---|---|
82
- | ๐Ÿ•˜ | **Timeline** | a **Versions** tab in the conversation view: every version (type, time, message count, file-change badges), pushed live via `session/projection` (no polling), windowed for long histories |
83
- | โ†ฉ๏ธ | **Artifact rollback** | **context-only / artifacts-only / both** with dry-run preview; git-first + content-addressed snapshot fallback; the rollback is itself a new version (`restore`) |
84
- | ๐Ÿงญ | **Jump-to-conversation** | one click from a version to that point in the conversation (auto-loads history, anchor highlight) |
85
- | ๐Ÿงน | **Bounded storage** | snapshots keep the most recent N versions (default 50); throttled background sweep prunes truncated ones |
88
+ | ๐Ÿ•˜ | **Checkpoints** | a **Checkpoints** tab in the conversation view: every change (type, time, message count, file-change badges), pushed live via `session/projection` (no polling), windowed for long histories; nested history is browsable outline-style |
89
+ | โ†ฉ๏ธ | **Artifact rollback** | **context-only / artifacts-only / both** with dry-run preview; git-first + content-addressed snapshot fallback; a rollback is itself a new checkpoint (`restore`). Preview refuses while the agent is running (so "preview says yes, confirm says no" cannot happen), and preview/rollback failures are shown in the UI instead of failing silently |
90
+ | ๐Ÿงญ | **Jump-to-conversation** | one click from a checkpoint to that point in the conversation (auto-loads history, anchor highlight); when the point is out of the auto-load budget the reason is reported instead of nothing happening |
91
+ | ๐Ÿงน | **Bounded storage** | snapshots keep the most recent N checkpoints (default 50); throttled background sweep prunes truncated ones |
92
+
93
+ > **Rollback on a damaged log** is not silent: the host's strict replay is the
94
+ > authority, but a single malformed event (for example a `sourceEventSeqs` row
95
+ > written by a third party with range encoding) can poison the whole replay. In
96
+ > that case the rollback degrades to a mirror replay, logs both the degradation
97
+ > and the original error, and flags the result as `degraded` on the wire; if even
98
+ > the mirror cannot run you get a structured `replay-failed` error, not a generic
99
+ > `internal`.
86
100
 
87
101
  **Why it's different** (the interaction layer โ€” the guarantees above are the storage layer):
88
102
 
@@ -124,14 +138,15 @@ The same result with plain file edits and `pnpm` โ€” exactly the steps
124
138
  1. Open the profile manifest (defaults: `<plugin data home>/profiles/desktop`
125
139
  on DSH Desktop, `<plugin data home>/profiles/web` for standalone Web โ€” where
126
140
  `<plugin data home>` is `$DSH_HOME` when set, otherwise the **active session
127
- base**, e.g. a newer `DSH_HOME` directory; `~/.dsh/profiles` is only the
128
- pre-migration fallback) and add **both** the
141
+ base** (`$DSH_HOME` and any base configured through `$DSH_SESSION_BASES`,
142
+ falling back to `~/.dsh`); `~/.dsh/profiles` is only the pre-migration fallback)
143
+ and add **both** the
129
144
  dependency and the bundle-layer entry:
130
145
 
131
146
  ```json
132
147
  {
133
148
  "dependencies": {
134
- "dsh-retrace": "^0.4.0"
149
+ "dsh-retrace": "^0.4.131"
135
150
  },
136
151
  "dsh": {
137
152
  "profile": {
@@ -159,9 +174,10 @@ The same result with plain file edits and `pnpm` โ€” exactly the steps
159
174
  For local development, point the dependency at a checkout instead of the
160
175
  registry: `"dsh-retrace": "file:/path/to/dsh-retrace"` โ€” or let
161
176
  `dsh` do it: `dsh plugin --profile <name> add /path/to/dsh-retrace`.
162
- For the latest GitHub commit without a release: use
163
- `"dsh-retrace": "github:yamingmou/dsh-retrace"` (standard pnpm git
164
- dependency syntax) in the same `dependencies` block, then `pnpm install`.
177
+ For the latest commit without a release, add a git dependency using the repository
178
+ URL from this package's `repository` field (pnpm syntax
179
+ `"dsh-retrace": "github:<owner>/dsh-retrace"`) in the same `dependencies` block,
180
+ then `pnpm install`.
165
181
 
166
182
  ### 3. npm package + composition (classic)
167
183
 
@@ -194,6 +210,66 @@ The dynamic host registers the same operations behind the package-private
194
210
  `harness.handle` RPC (`retrace.recall` / `retrace.editAndResend` /
195
211
  `retrace.regenerate`).
196
212
 
213
+ ### 5. Requirements
214
+
215
+ - DeepSeek Harness providing the peer packages listed in `package.json`
216
+ (`@deepseek-ai/dsh-session`, `@deepseek-ai/dsh-client-*`, `cordis`, `react` โ€” the
217
+ current build targets the `0.1.7-rc.2` generation).
218
+ - `dsh-log-contract >= 0.3.12` (installed automatically as a dependency).
219
+ - The plugin declares the host capabilities it needs via `inject`
220
+ (`sessions`, `agents`, `webServer`, `fs`, `subprocess`, `sandboxPolicy`, `jobs`).
221
+ A missing capability degrades the corresponding feature (for example, no
222
+ `subprocess` โ‡’ rollback uses the built-in snapshots only); it does not block boot.
223
+
224
+ ---
225
+
226
+ ## ๐Ÿ”’ Close guard (don't lose work by accident)
227
+
228
+ Before you exit or reload, know what is still running:
229
+
230
+ | | What | |
231
+ |---|---|---|
232
+ | ๐Ÿ›ก๏ธ | **Running-work detection** | every session is scanned for live work: agent running, queued inbox items, background jobs, unclosed turns |
233
+ | ๐Ÿ“‹ | **Running banner** | sessions with live work show a persistent in-page banner (short session code + reasons), so you can see it before quitting |
234
+ | โš ๏ธ | **Exit prompt** | on plugin dispose (app exit / reload) a Chinese notice lists each running session and why it is considered busy โ€” it only warns, it never cancels your running agent |
235
+ | ๐Ÿ”’ | **Page-close interception** | The desktop never arms the host's native confirm dialog; it uses a **page-drawn confirm gate** instead: it draws the box and **verifies visibility synchronously** before blocking (`preventDefault`); if it cannot draw, is not visible, or the page is hidden โ‡’ **lets the close through immediately**; the dialog waits for your choice (**Esc = cancel**); a **Web-Worker watchdog** (immune to background throttling) is only the *last* resort. Turning off "Exit confirmation (close guard)" recovers immediately (**no restart**) |
236
+ | ๐Ÿ”Ž | **Query surface** | `retrace.runningState` (host RPC) + `GET\|POST /api/plugins/retrace/runningState` (HTTP) โ€” same shape on both transports; the all-sessions shape also carries the host-reported page surface (`surface` / `quitVeto`) |
237
+
238
+ > **How the close confirm behaves (user-visible)**: whether the host pops its own
239
+ > **native** quit confirmation is decided **jointly by the host and the page**. On a
240
+ > desktop shell the plugin does **not** use the host's native dialog โ€” it draws its
241
+ > **own in-page confirmation** (Esc cancels). If the page cannot draw that box, the
242
+ > box is not visible, or the page itself is hidden, the close is let through
243
+ > immediately. Turning off "Exit confirmation (close guard)" reverts to the host's
244
+ > default behaviour (no restart needed).
245
+ >
246
+ > **Known limitation (stated plainly)**: some desktop shells route quit **around
247
+ > the page entirely** (e.g. the tray item destroys the window directly, or the X
248
+ > button merely hides the window) โ‡’ on those shells a plugin **cannot** show a
249
+ > confirm dialog at quit time; it **needs a shell seam** (a `will-prevent-unload`
250
+ > handler, or a pre-quit prompt hook). If the app cannot quit, turning off "Exit
251
+ > confirmation (close guard)" recovers immediately (no restart).
252
+
253
+ ### Session badge & display name (**planned / off by default**)
254
+
255
+ Badges are for human collaboration; **identity is still decided by the session id**.
256
+ The transports (`sessionBadge` / `setBadgeTitle` / `initBadgeTitles` / `badgeMap`,
257
+ on both HTTP and harness), the resolver and the write guards are **in place**, but
258
+ the **automatic startup path that writes titles is off by default** (the client
259
+ gate is `globalThis.__DSH_RETRACE_BADGE_BOOTSTRAP = true`):
260
+
261
+ - A session title renders as `[badge] original title` โ€” the **stable identifier is
262
+ derived deterministically from the session id**, so a title change never changes it.
263
+ - Sessions with **no `session/title` event are left blank on purpose** (the title shows just
264
+ `[badge]`) โ€” **no project-name fallback, no fabricated name**.
265
+ - If the resolver is unavailable it falls back to the **raw session id as a placeholder**.
266
+ - The sidebar row, the running banner and the checkpoint view share **one source**: the same badge
267
+ map the host hands down (host op `badgeMap`).
268
+
269
+ > **Planned (not shipped)**: the agent business-layer plan (runtime guard,
270
+ > interruption governance, ecosystem-facing interfaces) is a **plan**, not a
271
+ > shipped capability.
272
+
197
273
  ---
198
274
 
199
275
  ## โš™๏ธ Settings โ†’ General
@@ -201,11 +277,18 @@ The dynamic host registers the same operations behind the package-private
201
277
  | Setting | Default | Description |
202
278
  | --- | --- | --- |
203
279
  | **Show the original input after editing** | on | A collapsed "original input" reference under the re-sent message showing the **most recent** replaced text (reference only โ€” never sent to the model). |
204
- | **Start a fresh conversation after editing** | off | Hide earlier messages too, so the conversation looks like a fresh start (the whole surface is rewound before re-sending). Default off: only the edited round's context is replaced. |
205
- | **Hide shadowed messages per marker** | on | On (default): recall/edit/regenerate hide the replaced round per their markers. Off: every message stays visible; markers only show the notice and reference (review the full history). A single marker that would hide more than 40% of the conversation degrades to notice-only (history never silently vanishes). |
206
- | **Version & artifact snapshots** | on | On: every recall/edit records a version (messages and touched files) powering the timeline and artifact rollback. Off: only rewinds context โ€” no version records, no artifact tracking (lightest). |
207
- | **Git integration** | on | On: use git to record and roll back when the workspace is a repository (never auto-commits, never touches your branches); non-repo workspaces can enable git from the timeline. Off: built-in snapshots under the plugin data home only โ€” the plugin never touches the workspace git state; features are equivalent. |
208
- | **Version retention limit** | 50 | File snapshots are kept for the most recent N versions; older ones are pruned automatically (timeline records and the audit trail are always kept). |
280
+ | **Checkpoints & artifact snapshots** | on | On: every recall/edit records a checkpoint (messages and touched files) powering the checkpoint list and artifact rollback. Off: only rewinds context โ€” no checkpoint records, no artifact tracking (lightest). |
281
+ | **Summarize old content with AI** | off | On: each recall/edit makes one small model call to summarize the discarded old content. Requires the host `llm` service (the official `@deepseek-ai/dsh-llm`); absent โ‡’ it degrades to **verbatim excerpts only**. Off: **zero LLM calls**; the verbatim excerpt (zero token cost) is **always recorded**. At most one small call per operation (input text capped at 500 characters, โ‰ค200 output tokens, 5 s timeout); model and credentials follow the session's own default selection, and the plugin adds **no configuration surface of its own**. |
282
+ | **Git integration** | on | On: use git to record and roll back when the workspace is a repository (never auto-commits, never touches your branches); non-repo workspaces can enable git from the checkpoint view. Off: built-in snapshots under the plugin data home only โ€” the plugin never touches the workspace git state; features are equivalent. |
283
+ | **Checkpoint retention limit** | 50 | File snapshots are kept for the most recent N checkpoints; older ones are pruned automatically (checkpoint records and the audit trail are always kept). |
284
+ | **Exit confirmation (close guard)** | off | On: confirm before the page closes when sessions have running or unfinished work (the plugin draws its own dialog; Esc cancels). Off by default because the gate can also intercept an in-app reload โ€” see [๐Ÿ”’ Close guard](#-close-guard-dont-lose-work-by-accident) for the desktop details and the self-recovery note. |
285
+
286
+ The two toggles "hide shadowed messages per marker" and "start a fresh
287
+ conversation after editing" no longer exist: recall/resend/regenerate always hide
288
+ what they replaced (a single marker that would hide more than 40% of the
289
+ conversation degrades to notice-only, so history never silently vanishes), and
290
+ resend always uses the "restart from here" semantics with the fallback action
291
+ described above.
209
292
 
210
293
  ---
211
294
 
@@ -228,19 +311,20 @@ The dynamic host registers the same operations behind the package-private
228
311
  `[message โ€ฆ last surface node]`, and appends one replacement
229
312
  `assistant/message` with an **empty** body โ€” a valid surface node that
230
313
  derives to *no* model message, so the LLM context simply rewinds.
231
- 2. **Edit / regenerate** additionally call `agent.followup(...)` with the
314
+ 2. **Resend / regenerate** additionally call `agent.followup(...)` with the
232
315
  (new) prompt text; the agent's next turn builds its request from the
233
316
  rewound `session.deriveMessages()`.
234
317
  3. **Client** (`lib/client.js`) registers:
235
318
  - a `user-actions` conversation node under every user message
236
- (an edit/recall row with an inline editor); recall echoes the text into the
319
+ (a resend/recall row with an inline editor); recall echoes the text into the
237
320
  composer,
238
321
  - the `recall-marker` node renderer: a notice row that injects CSS hiding
239
322
  every shadowed message row from the flow (view and model context stay in
240
- sync), plus the optional original-input comparison block,
323
+ sync), plus the optional original-input comparison block, the
324
+ **Restore display** button and the **True restore** action,
241
325
  - the `retrace` entry in the `conversation.chat.assistant-actions`
242
326
  strip (recall / regenerate),
243
- - preference toggles and the retention limit under Settings โ†’ General.
327
+ - the **Checkpoints** view and the preference toggles under Settings โ†’ General.
244
328
 
245
329
  > Two different layers are at play: the **durable transcript** (append-only; old
246
330
  > events are never rewritten or deleted) and the **model-visible surface** (rewound
@@ -248,7 +332,8 @@ The dynamic host registers the same operations behind the package-private
248
332
  > trail โ€” but they are **synchronized out of both the model context and the visible
249
333
  > conversation**, and the view always reflects what the agent actually sees.
250
334
  > Persistence, projections and the transcript remain consistent because the plugin
251
- > only appends valid, typed session events.
335
+ > only appends valid, typed session events. (**True restore** is the one operation
336
+ > that does edit the log, and it does so in place, without renumbering sequences.)
252
337
 
253
338
  ---
254
339
 
@@ -262,114 +347,93 @@ the symptom is usually a failed boot, not a wrong-looking feature.
262
347
  This section exists so you can tell **host-side breakage** from **plugin-side bugs**.
263
348
  Read it before filing an issue.
264
349
 
265
- ### Host-side breaking changes that `0.4.26` adapts to โ€” *not caused by this plugin*
350
+ ### Host-side breaking changes this plugin adapts to โ€” *not caused by this plugin*
266
351
 
267
- 1. **`@deepseek-ai/dsh-session` dropped `decodeStorageRecord` from its public export surface (in `0.1.5-rc.1`; the function still exists internally but is no longer exported from the package root and is unreachable via the exports map).**
352
+ 1. **`@deepseek-ai/dsh-session` removed the `Session.events` member** (in `0.1.5-rc.1`).
353
+ The supported readers are `snapshotEvents(fromSeq, toSeqExclusive)` (frozen,
354
+ sequence-indexed), `eventAt(seq)`, `ownEvents()` and `isOwnSeq(seq)`. Before the
355
+ fix, recall and edit did nothing and surfaced
356
+ `TypeError: Cannot read properties of undefined (reading 'length')`. The plugin
357
+ now reaches the log through a compatibility accessor that prefers the new API and
358
+ falls back to the old array, so both host generations work.
359
+ 2. **`@deepseek-ai/dsh-session` dropped `decodeStorageRecord` from its public export
360
+ surface** (in `0.1.5-rc.1`; the function still exists internally but is no longer
361
+ exported from the package root and is unreachable via the exports map).
268
362
  `dsh-retrace` itself never imported it, but its dependency `dsh-log-contract` did.
269
363
  With no such export the loader aborts with
270
364
  `plugin tree failed to load โ€ฆ does not provide an export named 'decodeStorageRecord'`
271
365
  and **the whole plugin tree fails to load โ€” not just this plugin**, so the app does
272
- not start. `0.4.26` requires a `dsh-log-contract` build that decodes through its own
273
- local compatibility layer instead of the removed host export.
366
+ not start.
274
367
  โ†’ **Dependency note:** needs `dsh-log-contract >= 0.3.12`.
275
- 2. **A client **service** disappeared: `conversationEvents`** โ€” it used to be provided by
276
- the legacy client runtime `@deepseek-ai/dsh-client-runtime`, which has been removed
277
- (the string `conversationEvents` no longer occurs anywhere in the host). A plugin whose
278
- client half still declares that service in `export const inject` never becomes ready:
279
- its fiber stays **pending**, which the host reports as
368
+ 3. **A client **service** disappeared: `conversationEvents`** โ€” it used to be provided by
369
+ the legacy client runtime `@deepseek-ai/dsh-client-runtime`, which has been removed.
370
+ A plugin whose client half still declares that service in `export const inject` never
371
+ becomes ready: its fiber stays **pending**, which the host reports as
280
372
  `renderer boot failed (plugins: โ€ฆ): The client Loader did not provide an error message.`
281
373
  โ€” no error text at all, the window does not finish starting, and the only way in is to
282
- disable the plugin.
283
- `0.4.26` drops the service from `inject` and resolves it **defensively** in `apply`
284
- (`uiConversation`, falling back to the legacy name), so it runs on hosts that provide
285
- the new service *and* on older hosts that still provide the old one.
374
+ disable the plugin. The plugin drops the service from `inject` and resolves it
375
+ **defensively** in `apply` (`uiConversation`, falling back to the legacy name).
286
376
  > Note: declaring a **package** that no longer exists in `dsh.client.inject` is *not*
287
377
  > what breaks the boot โ€” the client loader skips unknown entries silently. The
288
378
  > breakage comes from the **service name** the plugin waits for.
379
+ 4. **The client-side session store has no `keys()`** (`ctx.sessions`). A plugin that
380
+ enumerates sessions with `keys()` silently sees **zero** of them: no crash, no error,
381
+ just safety warnings that never fire. The plugin prefers the official `list()` and
382
+ falls back to `keys()`; it deliberately does **not** fall back to enumerating service
383
+ fields, because guessing produces a silent empty result as well.
289
384
 
290
- > Both items above are **host-side removals**, documented here on purpose: if you hit
291
- > either symptom right after a host upgrade, the first question is "does this plugin
292
- > build predate the removal?", not "what did the plugin break?".
385
+ > All four items are **host-side removals**, documented here on purpose: if you hit
386
+ > one of these symptoms right after a host upgrade, the first question is "does this
387
+ > plugin build predate the removal?", not "what did the plugin break?".
293
388
 
294
- ### Plugin-side fixes in `0.4.26` (these are ours)
389
+ ### Plugin-side fixes (0.4.26 โ€“ 0.4.28)
295
390
 
296
391
  - **Data home and session base are now one source.** The plugin previously resolved its
297
392
  own data directory through the host's home resolver (`$DSH_HOME` โ†’ `~/.dsh`), which
298
- does not know about a migrated base (for example a newer `DSH_HOME` directory). With
299
- `$DSH_HOME` unset,
300
- sessions were read from one base while snapshots and the artifact store were written
301
- to another. Snapshots, version stores and `verify-install` now follow the **active
302
- session base**. When `$DSH_HOME` is set, behaviour is unchanged.
393
+ does not know about extra bases configured through `$DSH_SESSION_BASES`. With
394
+ `$DSH_HOME` unset, sessions were read from one base while snapshots and the artifact
395
+ store were written to another. Snapshots,
396
+ version stores and `verify-install` now follow the **active session base**. When
397
+ `$DSH_HOME` is set, behaviour is unchanged.
303
398
  - **No user-visible string hard-codes `~/.dsh` any more** (the settings hint used to say
304
399
  snapshots live under `~/.dsh`).
305
- - Stale peer declarations with no remaining import site removed
306
- (`@deepseek-ai/dsh-home-paths`, `@deepseek-ai/dsh-client-runtime`).
307
-
308
- ### Host-side breaking changes that `0.4.28` adapts to โ€” *not caused by this plugin*
309
-
310
- 1. **`@deepseek-ai/dsh-session` removed the `Session.events` member** (in `0.1.5-rc.1`).
311
- The class has no `events` field and no `events` getter at all any more; the supported
312
- readers are `snapshotEvents(fromSeq, toSeqExclusive)` (frozen, sequence-indexed),
313
- `eventAt(seq)`, `ownEvents()` and `isOwnSeq(seq)`. `0.4.28` reaches the log through a
314
- compatibility accessor that prefers the new API and falls back to the old array, so it
315
- runs on both host generations.
316
- **Symptom before the fix:** recall and edit did nothing and surfaced the raw error
317
- `TypeError: Cannot read properties of undefined (reading 'length')` โ€” both operations
318
- start by locating the target message id, and that lookup read the removed member.
319
- 2. **The client-side session store has no `keys()`** (`ctx.sessions`). A plugin that
320
- enumerates sessions with `keys()` silently sees **zero** of them: no crash, no error,
321
- just safety warnings that never fire. `0.4.28` prefers the official `list()` and falls
322
- back to `keys()`; it deliberately does **not** fall back to enumerating service fields,
323
- because guessing produces a silent empty result as well.
324
- 3. **The client session controller has no title accessor** โ€” `getTitle` does not exist
325
- anywhere in `@deepseek-ai/dsh-api-session-controller`, and its `getSnapshot()` carries
326
- no `title`. See the plugin-side item below: this one used to *overwrite your titles*.
327
-
328
- ### Plugin-side fixes in `0.4.28` (these are ours)
329
-
330
400
  - **The edit / recall affordances never appeared at all.** The client half read chat
331
401
  nodes from `snapshot.chat.nodes`, a path this host build does not have โ€” nodes live in
332
402
  the `useChat` store (`snapshot.nodes`). Every message-level component threw while
333
403
  rendering and was swallowed by the error boundary, so the buttons were missing, while
334
- the settings entry (which reads no nodes) rendered fine. Fixed: the client half now
335
- takes `useChat` from the slot contract.
336
- - **"Jump to message" in the version and fork views did nothing.** It resolved the target
404
+ the settings entry (which reads no nodes) rendered fine.
405
+ - **"Jump to message" in the checkpoint view did nothing.** It resolved the target
337
406
  anchor through `store.getSnapshot()?.chat?.nodes`, which is permanently `undefined`
338
407
  here. It now resolves through the `useChat` snapshot injected by the view and pages
339
408
  with the official `store.loadThrough(seq)`; when the jump cannot complete it reports a
340
409
  **diagnosable reason** (renderer warning + host-log line) instead of failing silently.
341
- - **Assigning a short code could overwrite your session title.** The client composed
342
- `[CODE] <current title>` locally but had no way to read the current title, so the base
343
- degraded to the session-id prefix (`[XXXXXX] <session-id-prefix>`). Title tagging now goes
410
+ - **Assigning a lineage ID could overwrite your session title.** Title tagging now goes
344
411
  through the host route only (`setBadgeTitle`), which reads the current title from the
345
412
  session log. Manual renames are unaffected.
346
- - **Host-side operation failures are logged again** (code + message + stack). They used to
347
- return the message to the UI without a log line, which is why this whole class of bug
348
- was hard to diagnose from outside.
349
-
350
- - **The version and fork views now explain themselves.** They used to show a title plus a row of
351
- actions with **no sentence anywhere saying what a "version" or a "fork" is** (the only near-miss
352
- was an empty-state line that disappears as soon as data arrives), and fork rows printed raw node
353
- types. They now carry an always-visible concept sentence, a type legend, and a plain-language
354
- "why" line on every row; impact text reads `ๆ—ง่ทฏๅพ„็š„ N ๆกๆถˆๆฏ่ขซๆ›ฟๆข`, not `่ขซ้ฎ่”ฝ N ไธช่Š‚็‚น`.
355
- - **Client hide-lookup no longer rescans per row.** `useSeqHidden` re-scanned the node map for every
356
- row (measured **346 ms** at 2000 rows / 20 markers, **1568 ms** at 3000/30 โ€” and that path was
357
- *dead* on this host until this release made the rows render at all, so it is this fix own cost).
358
- It now reuses one per-snapshot hide plan: **8.3 ms** and **18.3 ms** respectively, with the
359
- predicate verified equivalent against the old one.
413
+ - **Host-side operation failures are logged again** (code + message + stack). They used
414
+ to return the message to the UI without a log line, which made this class of bug hard
415
+ to diagnose from outside.
416
+ - **The checkpoint view now explains itself.** It used to show a title plus a row of
417
+ actions with **no sentence anywhere saying what a "checkpoint" is**, and rows printed
418
+ raw node types. It now carries an always-visible concept sentence, a type legend, and
419
+ a plain-language "why" line on every row.
420
+ - **Client hide-lookup no longer rescans per row.** `useSeqHidden` re-scanned the node map
421
+ for every row (measured **346 ms** at 2000 rows / 20 markers, **1568 ms** at 3000/30).
422
+ It now reuses one per-snapshot hide plan: **8.3 ms** and **18.3 ms** respectively, with
423
+ the predicate verified equivalent against the old one.
424
+
360
425
  ### Upgrading
361
426
 
362
427
  ```bash
363
- dsh plugin --profile desktop add dsh-retrace@0.4.32
428
+ dsh plugin --profile desktop add dsh-retrace@0.4.131
364
429
  # then restart DSH โ€” plugins are not hot-reloaded
365
430
  ```
366
431
 
367
- **`0.4.27` was withdrawn.** It was briefly published and then recalled โ€” `latest` points at `0.4.26`
368
- again and `0.4.27` is marked deprecated. **`0.4.28` is its replacement**: it carries every fix
369
- `0.4.27` had, plus the two items below.
432
+ **`0.4.27` and `0.4.31` were withdrawn** (briefly published, then recalled and marked
433
+ deprecated on npm). Their replacements carry every fix they had.
370
434
 
371
- **`0.4.28` needs no data migration.** The session format is unchanged (v3), no session is
372
- re-written, and nothing has to be re-indexed: upgrade, restart, and the two symptoms above
435
+ **No data migration is required.** The session format is unchanged (v3), no session is
436
+ re-written, and nothing has to be re-indexed: upgrade, restart, and the symptoms above
373
437
  are gone. If you are on a host that still provides the old members, the compatibility
374
438
  accessors keep those paths working โ€” this build does not drop older hosts.
375
439
 
@@ -385,7 +449,7 @@ whole tree down, so recover first and diagnose second:
385
449
 
386
450
  ### Pinning
387
451
 
388
- Pin an exact plugin version (`dsh-retrace@0.4.32`) and let `dsh-log-contract` resolve to
452
+ Pin an exact plugin version (`dsh-retrace@0.4.131`) and let `dsh-log-contract` resolve to
389
453
  `>=0.3.12`. Do not rely on `^0.4` across a host upgrade: compatibility here is decided by
390
454
  the **host surface**, not by semver alone.
391
455
 
@@ -393,28 +457,23 @@ the **host surface**, not by semver alone.
393
457
 
394
458
  ## โš ๏ธ Requirements & limitations
395
459
 
396
- - **Optional dependency (deliberately NOT in `package.json`)**: AI summaries need
397
- an `llm` service from the host (the official `@deepseek-ai/dsh-llm`, bundled
398
- with DSH Desktop). The plugin takes it **dynamically** via `ctx.get('llm')`:
399
- present โ‡’ summaries available, absent โ‡’ it degrades to **verbatim excerpts
400
- only**. Install and startup are unaffected either way. Model and credentials
401
- follow the session's own default selection
402
- (`agentDefaultModel.currentSelection()`); the plugin adds **no configuration
403
- surface of its own**. Summaries sit behind a **default-off** switch (at most one
404
- small call per operation: โ‰ค6ร—400 chars in, โ‰ค200 tokens out, 5 s timeout); when
405
- it is off there are **zero LLM calls**, while the verbatim excerpt (zero token
406
- cost) is **always recorded**.
407
- - Only **user messages** can be edited; recall works on user and assistant
460
+ - Only **user messages** can be re-sent from; recall works on user and assistant
408
461
  messages. Tool results are shadowed along with the recalled range but are not
409
462
  themselves recall targets.
410
- - The agent must be **idle**: while a reply is streaming you must stop it
411
- (โน) before recalling or editing. The Host rejects with `agent-busy`
463
+ - A recall that targets a running turn **pauses it first** (official stop entry, bounded
464
+ wait). If the pause fails or times out, the recall continues and a notice tells you so.
465
+ True restore and rollback preview require an **idle** session; they are rejected
412
466
  otherwise.
413
- - Recall/edit operate on the **active model surface**: a message that was
467
+ - Recall/resend operate on the **active model surface**: a message that was
414
468
  already compacted away or previously recalled is rejected
415
- (`target-shadowed`).
469
+ (`target-shadowed`), and a message whose write has not flushed yet is reported as
470
+ `message-pending` ("still being generated") rather than being misreported as gone.
416
471
  - Regenerate re-sends only the **text** of the original prompt; prompts that
417
472
  carried images fall back to the text-only content.
473
+ - True restore edits the session log **in place** (it neutralizes the two marker
474
+ segments; sequence numbers stay as they are). It refuses to run while the session is
475
+ the one you are viewing, while it is running, or while another process holds the file,
476
+ and you must reopen the session (or refresh) to see the result.
418
477
 
419
478
  ---
420
479
 
@@ -422,82 +481,22 @@ the **host surface**, not by semver alone.
422
481
 
423
482
  **What's in today (0.4.x):**
424
483
 
425
- - Recall / edit-and-resend / regenerate, each written through a three-layer
484
+ - Recall / resend / regenerate, each written through a three-layer
426
485
  **pre-write contract guard** and a safe-edit path (auto-stop the agent, temp-step
427
486
  markers) โ€” new rewinds do not corrupt the log and add no `/compact` debt;
428
487
  **legacy single-segment markers** remain known debt (see the honest note above).
429
- - In-session **version timeline** + **artifact rollback** (git-first, snapshot
488
+ - **Undo**: Restore display (view) and True restore (log level, in place).
489
+ - In-session **checkpoint timeline** + **artifact rollback** (git-first, snapshot
430
490
  fallback, dry-run preview, jump-to-conversation).
431
- - **Fork map + session lineage** in the conversation view.
491
+ - **Close guard** (running-work banner, exit notice, page-drawn confirm gate).
432
492
  - **Real-time watchdog** โ€” snapshots the log at the first sign of concurrent writes.
433
493
  - Companion **`dsh-log-contract`**: 30+ offline contract rules + in-place repair
434
494
  (`fix --neutralize` / `--clip-crossstep`) for sessions that would fail `/compact`.
435
- **Close guard (don't lose work by accident)** โ€” before you exit or reload, know what is still running:
436
-
437
- | | What | |
438
- |---|---|---|
439
- | ๐Ÿ›ก๏ธ | **Running-work detection** | every session is scanned for live work: agent running, queued inbox items, background jobs, unclosed turns |
440
- | ๐Ÿ“‹ | **Running banner** | sessions with live work show a persistent in-page banner (short session code + reasons), so you can see it before quitting |
441
- | โš ๏ธ | **Exit prompt** | on plugin dispose (app exit / reload) a Chinese notice lists each running session and why it is considered busy โ€” it only warns, it never cancels your running agent |
442
- | ๐Ÿ”’ | **Page-close interception** | **Does not arm the host's native confirm dialog**; uses a **page-drawn confirm gate** instead: it draws the box and **verifies visibility synchronously** before blocking (`preventDefault`); if it cannot draw, is not visible, or the page is hidden โ‡’ **lets the close through immediately**; the dialog waits for your choice (**Esc = cancel**); a **Web-Worker watchdog** (immune to background throttling) is only the *last* resort; turning the setting off returns to the official behaviour (**no restart**) |
443
- | ๐Ÿ”Ž | **Query surface** | `retrace.runningState` (host RPC) + `GET|POST /api/plugins/retrace/runningState` (HTTP) โ€” same shape on both transports; the all-sessions shape also carries the host-reported page surface (`surface` / `quitVeto`) |
444
-
445
- > Desktop wording (corrected 2026-09-20): **desktop never arms the host's native confirm dialog** โ€”
446
- > quit entry points differ **by version/platform**, and the native dialog either does not exist on a
447
- > desktop shell or is swallowed: the 2.0.9 shell we inspected has no `will-prevent-unload` handler
448
- > (0 hits across the packaged `app.asar`) and routes the tray item through
449
- > `requestQuit(0) โ†’ window.destroy() โ†’ app.exit(0)`; on the external report's DSH Desktop 0.9.0 /
450
- > Windows the entry **does reach** the page `beforeunload` (0.9.0), but the veto is **swallowed
451
- > silently** (no dialog, no feedback โ€” the exit just looks stuck).
452
- >
453
- > What actually protects on desktop is the **page-drawn confirm gate** (finalised 2026-09-19):
454
- > it **does call `preventDefault`** โ€” but only after it has **drawn the box and verified it is
455
- > visible, synchronously**; a `visibilityState` pre-check lets the close through immediately when the
456
- > box cannot be drawn / is not visible / the page is hidden; the dialog waits for your choice
457
- > (**Esc = cancel**); a **Web-Worker watchdog** (immune to background throttling) is only the
458
- > **last** resort.
459
- >
460
- > **Both criteria must hold** (2026-09-18, second round): the host side is only a *non-objection*
461
- > (`quitVeto: true`), and the client keeps a **veto** โ€” if this page's `navigator.userAgent`
462
- > contains `Electron`, or the page URL contains `dsh-desktop-` (the external report's shell puts its
463
- > desktop marker in the query string), the native gate is **never** armed. The host-side criteria
464
- > were widened to four request-level facts: the `x-dsh-desktop-renderer` capability header, a
465
- > request `User-Agent` containing `Electron`, or a request URL **or `Referer`** containing
466
- > `dsh-desktop-` โ€” **any one** of them classifies the page as a desktop page. (Our own polling URL
467
- > carries no query string, so the page marker is in practice read from `Referer`, which a
468
- > same-origin `fetch` sends by default.) That covers the hosts that expose no desktop evidence at
469
- > all (the reporter's: a pure-Node harness plus an Electron renderer, where all three older criteria
470
- > were false, and the old code filed "no evidence at all" as a browser page โ‡’ the gate was still
471
- > armed โ‡’ the exit still hung).
472
- >
473
- > **Known limitation (stated plainly)**: some desktop shells route quit **around the page entirely**
474
- > (e.g. `requestQuit โ†’ destroy โ†’ app.exit`, or the X button merely hides the window) โ‡’ on those
475
- > shells a plugin **cannot** show a confirm dialog at quit time; it **needs a shell seam**
476
- > (a `will-prevent-unload` handler, or a pre-quit prompt hook). If Desktop cannot quit, turning off
477
- > "Exit confirmation (close guard)" in settings recovers immediately (no restart).
478
-
479
- **Session badge & display name** (**planned / off by default**) โ€” badges are for human
480
- collaboration; **identity is still decided by the session id**. The transports
481
- (`sessionBadge` / `setBadgeTitle` / `initBadgeTitles` / `badgeMap`, on both HTTP and harness),
482
- the resolver and the write guards are **in place**, but the **automatic startup path that writes
483
- titles is off by default** (after three distinct on-device failures it moves to the **scripting
484
- layer T3**, which is not implemented yet) โ€” set `__DSH_RETRACE_BADGE_BOOTSTRAP = true` to enable it:
485
-
486
- - A session title renders as `[badge] original title` (shape placeholder: `[opxxxopxxx] original title`); the badge
487
- shape is **`opxxxopxxx`** and comes from the **real table** `codes.json` (workspace + sequence +
488
- parent-chain semantics โ€” **not a hash**).
489
- - Sessions with **no `session/title` event are left blank on purpose** (the title shows just
490
- `[badge]`) โ€” **no project-name fallback, no fabricated name**.
491
- - If the table / resolver is unavailable it falls back to the **raw session id as a placeholder**,
492
- and **never** to FNV.
493
- - The sidebar row, the running banner and the checkpoint view share **one source**: the same badge
494
- map the host hands down (host op `badgeMap`).
495
-
496
- > **Planned (not shipped)**: the scripting-layer T3 is not implemented yet (the badge/name
497
- > **automatic title-writing startup path** is blocked on it); the agent business-layer plan (runtime guard,
498
- > interruption governance, ecosystem-facing interfaces) is a **plan**, not a shipped capability.
499
-
500
- > Command surface: `retrace.runningState` (host RPC) + `GET|POST /api/plugins/retrace/runningState` (HTTP).
495
+ - Read-only host surfaces for integration: `retrace/versions` and `retrace/forkmap`
496
+ projections, `GET /api/plugins/retrace/{versions,forkmap,lineage,event,surface,doctor,snapshot}`,
497
+ `POST /api/plugins/retrace/{rollback/preview,rollback,git/init}`, plus the badge
498
+ ops (`sessionBadge` / `setBadgeTitle` / `initBadgeTitles` / `badgeMap`) and the
499
+ `retrace` CLI (see below).
501
500
 
502
501
  **What's next** โ€” the agent business-layer plan (runtime guard, interruption
503
502
  governance, ecosystem-facing interfaces) is **not published yet**: it is a plan,
@@ -526,10 +525,10 @@ cordis.patch.yml # dsh.bundle profile patch layer
526
525
  ```
527
526
 
528
527
  ```sh
529
- pnpm install # install dev dependencies (vitest, esbuild)
528
+ pnpm install # install dev dependencies (vitest, esbuild); Node >= 22.13
530
529
  pnpm check # syntax-check sources AND the generated dynamic entries
531
530
  pnpm build # regenerate lib/dynamic-*.js + lib/client.bundle.js
532
- pnpm test # run the host-core unit tests
531
+ pnpm test # run the unit tests
533
532
  npm pack --dry-run # verify the published file list
534
533
  ```
535
534
 
@@ -541,8 +540,8 @@ npm pack --dry-run # verify the published file list
541
540
  > one and only swaps the transport (`host.call` vs the HTTP route) via
542
541
  > `__setMessageEditorWire`.
543
542
 
544
- PRs and issues are welcome โ€” a `CONTRIBUTING.md` is coming soon
545
- and the [issue tracker](https://github.com/yamingmou/dsh-retrace/issues).
543
+ PRs and issues are welcome โ€” a `CONTRIBUTING.md` is coming soon; use the issue
544
+ tracker linked from this package's `repository`/`bugs` metadata.
546
545
 
547
546
  ---
548
547
 
@@ -554,22 +553,22 @@ Part of the **Agent business layer (production-grade guarantees)** โ€” the
554
553
  framework-agnostic layer that dsh-retrace implements on DeepSeek Harness.
555
554
  Companion components:
556
555
 
557
- - [**dsh-log-contract**](https://github.com/yamingmou/dsh-log-contract) โ€” the
556
+ - [**dsh-log-contract**](https://www.npmjs.com/package/dsh-log-contract) โ€” the
558
557
  business layer's "doctor": 30+ offline contract rules + in-place repair
559
558
  (`fix --neutralize` / `--clip-crossstep`). Installed automatically as a
560
559
  dependency; also published standalone for direct use.
561
560
 
562
- > **Install straight from GitHub** (no npm registry needed โ€” handy when you
561
+ > **Install straight from git** (no npm registry needed โ€” handy when you
563
562
  > hand this repo's link to an AI or want the latest commit):
564
563
  >
565
564
  > ```sh
566
- > dsh plugin --profile desktop add github:yamingmou/dsh-retrace
565
+ > dsh plugin --profile desktop add github:<owner>/dsh-retrace
567
566
  > # or with pnpm directly into a profile:
568
- > cd "$DSH_HOME/profiles/desktop" && pnpm add github:yamingmou/dsh-retrace
567
+ > cd "$DSH_HOME/profiles/desktop" && pnpm add github:<owner>/dsh-retrace
569
568
  > ```
570
569
  >
571
- > Then restart DSH Desktop as usual. The `dsh-log-contract` dependency is
572
- > pulled in automatically.
570
+ > Substitute the owner from this package's `repository` field. Then restart DSH
571
+ > Desktop as usual โ€” the `dsh-log-contract` dependency is pulled in automatically.
573
572
 
574
573
  A curated overview of the DeepSeek Harness plugin ecosystem lives at
575
574
  [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
@@ -581,13 +580,13 @@ A curated overview of the DeepSeek Harness plugin ecosystem lives at
581
580
 
582
581
  Built by the [OfferKuai](https://www.offerkuai.com) team โ€” an AI job application
583
582
  assistant on a mission that "users need results, not repeated conversations".
584
- Founder: Zhaofeng (Yaming). This plugin is released as open source for the
585
- DeepSeek Harness community.
583
+ This plugin is released as open source for the DeepSeek Harness community.
586
584
 
587
585
  ## ๐Ÿ“„ License
588
586
 
589
587
  MIT
590
588
 
589
+ ---
591
590
 
592
591
  ## ๐Ÿงญ Session archaeology (`retrace` CLI)
593
592
 
@@ -596,20 +595,21 @@ audit asset. The `retrace` CLI provides read-only archaeology (reusing
596
595
  dsh-log-contract's contracts and extraction):
597
596
 
598
597
  ```sh
599
- retrace index <session> # tool-call index (A1)
600
- retrace query <session> --cmd "seed-scale" # search outputs by command regex (A1)
601
- retrace extract <session> --pattern "seed-scale" --out ./found # export outputs (A2)
602
- retrace file-history <session> <path> # write/edit history of a file (A3)
603
- retrace file-diff <session> <path> 0 5 # line diff between two versions (A3)
604
- retrace lineage <session> # parent-chain lineage (A4)
598
+ retrace index <session> # tool-call index
599
+ retrace query <session> --cmd "seed-scale" # search outputs by command regex
600
+ retrace extract <session> --pattern "seed-scale" --out ./found # export outputs
601
+ retrace file-history <session> <path> # write/edit history of a file
602
+ retrace file-diff <session> <path> 0 5 # line diff between two versions
603
+ retrace lineage <session> # parent-chain lineage
605
604
  ```
606
605
 
607
- `<session>` is a full log path or a sessionId (auto-looked-up under
608
- the active session base โ€” `$DSH_HOME/sessions`, else a newer base, else `~/.dsh/sessions`). All read-only.
606
+ `<session>` is a full log path or a sessionId (auto-looked-up under the active
607
+ session base โ€” `$DSH_HOME/sessions`, else each `$DSH_SESSION_BASES` base, else
608
+ `~/.dsh/sessions`).
609
+ All read-only.
609
610
 
610
- **Session lineage in the fork map (A4, UI)**: the Fork map view header shows the
611
- current session's `parentSession` chain (session โ†’ parent โ†’ root, `โ†` direction).
612
- Data comes from `GET /api/plugins/retrace/lineage?sessionId=` (read-only header
613
- walk with cycle protection), the same semantics as the CLI `retrace lineage` โ€”
614
- so "which session did this one continue/fork from" is visible at a glance, and
615
- serves as the fork-topology metadata source.
611
+ **Session lineage**: `GET /api/plugins/retrace/lineage?sessionId=` walks the
612
+ current session's `parentSession` chain (read-only, with cycle protection) โ€” the
613
+ same semantics as the CLI `retrace lineage` โ€” so "which session did this one
614
+ continue/fork from" is available to any client, and serves as the fork-topology
615
+ metadata source.