dsh-retrace 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -121
- package/README.zh.md +86 -81
- package/lib/client.bundle.js +10 -9
- package/lib/client.js +16 -9
- package/lib/dynamic-client.js +10 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 🧭 dsh-retrace
|
|
4
4
|
|
|
5
|
-
**
|
|
6
|
-
versioning
|
|
7
|
-
|
|
8
|
-
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web GUI and
|
|
9
|
-
Desktop app (both share the same Web frontend).
|
|
5
|
+
**Recall · Edit-and-resend · Regenerate**, plus **write-safe** in-conversation
|
|
6
|
+
versioning — the **Agent business layer (production-grade guarantees)** for
|
|
7
|
+
DeepSeek Harness.
|
|
10
8
|
|
|
11
9
|
[](https://www.npmjs.com/package/dsh-retrace)
|
|
12
10
|
[](https://www.npmjs.com/package/dsh-retrace)
|
|
@@ -18,113 +16,73 @@ Desktop app (both share the same Web frontend).
|
|
|
18
16
|
|
|
19
17
|
</div>
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
>
|
|
29
|
-
>
|
|
30
|
-
> **Option B — downloaded this repo as ZIP (or handing this link to an AI):**
|
|
31
|
-
> unpack it and run `dsh plugin --profile desktop add <folder>` — or install
|
|
32
|
-
> straight from GitHub, no unpacking:
|
|
33
|
-
>
|
|
34
|
-
> ```sh
|
|
35
|
-
> dsh plugin --profile desktop add github:yamingmou/dsh-retrace
|
|
36
|
-
> ```
|
|
37
|
-
>
|
|
38
|
-
> Follow [📦 Installation](#-installation) → *Manual install* for the exact
|
|
39
|
-
> file-edit steps.
|
|
40
|
-
>
|
|
41
|
-
> **Option C — no command line at all:** install the community plugin market
|
|
42
|
-
> once, then install dsh-retrace from its UI:
|
|
43
|
-
>
|
|
44
|
-
> ```sh
|
|
45
|
-
> dsh plugin --profile desktop add dshmarket # one time
|
|
46
|
-
> ```
|
|
47
|
-
>
|
|
48
|
-
> Restart, then **Settings → Plugin Market** → search **dsh-retrace** →
|
|
49
|
-
> **Install** (one click). The market lists plugins from the curated
|
|
50
|
-
> [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
51
|
-
> registry; dsh-retrace is listed there.
|
|
52
|
-
>
|
|
53
|
-
> > ⚠️ **Restart required after install.** Quit and reopen **DSH Desktop**
|
|
54
|
-
> > (or restart the `dsh` process for a standalone Web deployment) — a running
|
|
55
|
-
> > app keeps the previous bundle in memory and will not hot-reload it.
|
|
56
|
-
>
|
|
57
|
-
> Hover any assistant reply → **↩ / ↻**; any user message → **✎** — that's it.
|
|
58
|
-
|
|
59
|
-
DeepSeek Harness stores every conversation as an **append-only event log**, so there is
|
|
60
|
-
no built-in "undo". `dsh-retrace` brings back the three moves every chat deserves —
|
|
61
|
-
**撤回 (recall)**, **编辑重发 (edit-and-resend)**, **重新生成 (regenerate)** — and then
|
|
62
|
-
goes further: because a recall only rewinds the **context**, while files the agent
|
|
63
|
-
already changed stay changed, retrace versions your conversation **and its artifacts**
|
|
64
|
-
in one place.
|
|
65
|
-
|
|
66
|
-
Recall / edit **remove the target messages from the conversation view and the model
|
|
67
|
-
context** — that is exactly the effect you see. What stays untouched is the underlying
|
|
68
|
-
**durable transcript**: it remains append-only, old events are never rewritten or deleted,
|
|
69
|
-
and the plugin merely appends one valid replacement event (the same `replace` primitive
|
|
70
|
-
the built-in compaction uses) to rewind the surface — so the log keeps a full audit trail
|
|
71
|
-
of every rewind. On top of that trail, retrace records version boundaries, touched files
|
|
72
|
-
and (optionally) git state, and lets you roll back artifacts or jump back to any point
|
|
73
|
-
in the conversation — all **inside the same session**, no session-switching.
|
|
74
|
-
|
|
75
|
-
> ✅ **Timeline + artifact rollback are live (0.4.x)** — recall / edit /
|
|
76
|
-
> regenerate, the version timeline, artifact rollback (git-first, snapshot
|
|
77
|
-
> fallback), jump-to-conversation and marker pre-write validation (three-layer
|
|
78
|
-
> contract guard) are all in. The fork map (P2) is in progress per [PLAN.md](./PLAN.md).
|
|
19
|
+
**Recall / edit-and-resend / regenerate** — the three moves every conversation
|
|
20
|
+
deserves. But rewinding is not just "delete a message": DeepSeek Harness stores
|
|
21
|
+
conversations in an append-only event log, so a recall only rewinds the context
|
|
22
|
+
while changed **artifact files stay changed**. dsh-retrace versions the
|
|
23
|
+
conversation **and its artifacts** together, and guarantees **every rewind is
|
|
24
|
+
legal — never dirtying the log, never breaking /compact**.
|
|
25
|
+
|
|
26
|
+
> 🛡️ **Write safety** · 🔍 **Deep offline checks** · 🔄 **Detect → repair → guard** — see below.
|
|
79
27
|
|
|
80
28
|
---
|
|
81
29
|
|
|
82
|
-
##
|
|
30
|
+
## ⚡ One-minute install
|
|
83
31
|
|
|
84
|
-
|
|
85
|
-
| --- | --- | --- |
|
|
86
|
-
| **↩ 撤回** (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. |
|
|
87
|
-
| **✎ 编辑重发** (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. |
|
|
88
|
-
| **↻ 重新生成** (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. |
|
|
32
|
+
> Requires DeepSeek Harness with the `dsh` CLI. **Restart DSH after install** (a running app does not hot-reload).
|
|
89
33
|
|
|
90
|
-
|
|
34
|
+
```sh
|
|
35
|
+
dsh plugin --profile desktop add dsh-retrace # DSH Desktop
|
|
36
|
+
# or Web: dsh plugin --profile web add dsh-retrace
|
|
37
|
+
# or GitHub: dsh plugin --profile desktop add github:yamingmou/dsh-retrace
|
|
38
|
+
# or ZIP: dsh plugin --profile desktop add ~/plugins/dsh-retrace
|
|
39
|
+
```
|
|
91
40
|
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
- 🧭 **Jump-to-conversation** — one click from a version to that point in the conversation (auto-loads earlier history, anchor highlight).
|
|
95
|
-
- 🧹 **Bounded storage** — file snapshots keep the most recent N versions (default 50); a throttled background sweep prunes snapshots of truncated versions, keeping long sessions bounded.
|
|
41
|
+
**No command line?** Install the community plugin market once, then find
|
|
42
|
+
**dsh-retrace** in **Settings → Plugin Market** and install it with one click:
|
|
96
43
|
|
|
97
|
-
|
|
44
|
+
```sh
|
|
45
|
+
dsh plugin --profile desktop add dshmarket # one time
|
|
46
|
+
```
|
|
98
47
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- 🔒 **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.
|
|
102
|
-
- 🧠 **View ⇄ context in sync** — the conversation view always reflects exactly what the agent sees.
|
|
103
|
-
- ⚡ **Try in 30 seconds** — the dynamic form installs in your current session with no rebuild.
|
|
48
|
+
After the restart, hover any assistant reply → **↩ / ↻**; any user message → **✎**.
|
|
49
|
+
Full steps in [📦 Installation](#-installation).
|
|
104
50
|
|
|
105
51
|
---
|
|
106
52
|
|
|
107
|
-
##
|
|
53
|
+
## 🛡️ Production-grade guarantees (all live in 0.4.x)
|
|
108
54
|
|
|
109
|
-
|
|
110
|
-
|
|
55
|
+
| | Capability | What it means |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| 🛡️ | **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 — **rewinds never dirty the log, /compact never breaks** |
|
|
58
|
+
| 🔍 | **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 |
|
|
59
|
+
| 🔄 | **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 |
|
|
111
60
|
|
|
112
|
-
|
|
113
|
-
> bundle and automatically rebuilds the Web client:
|
|
61
|
+
---
|
|
114
62
|
|
|
115
|
-
|
|
116
|
-
# DSH Desktop (desktop profile)
|
|
117
|
-
dsh plugin --profile desktop add dsh-retrace
|
|
63
|
+
## ✨ Features
|
|
118
64
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
65
|
+
| Action | Where | What happens |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| **↩ 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. |
|
|
68
|
+
| **✎ 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. |
|
|
69
|
+
| **↻ 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. |
|
|
70
|
+
|
|
71
|
+
**Versioning & rollback (live in 0.4.x)** — every rewind is also recorded as a **version**:
|
|
72
|
+
|
|
73
|
+
| | What | |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| 🕘 | **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 |
|
|
76
|
+
| ↩️ | **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`) |
|
|
77
|
+
| 🧭 | **Jump-to-conversation** | one click from a version to that point in the conversation (auto-loads history, anchor highlight) |
|
|
78
|
+
| 🧹 | **Bounded storage** | snapshots keep the most recent N versions (default 50); throttled background sweep prunes truncated ones |
|
|
122
79
|
|
|
123
|
-
|
|
124
|
-
> in memory, so **quit and reopen DSH Desktop** (or restart the `dsh` process for
|
|
125
|
-
> a standalone Web deployment) before the plugin activates.
|
|
80
|
+
**Why it's different** (the interaction layer — the guarantees above are the storage layer):
|
|
126
81
|
|
|
127
|
-
|
|
82
|
+
- 🎯 **Whole-round recall** — removes the input *and* its output (tool rows included), not just a single bubble.
|
|
83
|
+
- 🖥️ **Web + Desktop** — one plugin, both DeepSeek Harness surfaces.
|
|
84
|
+
- 🧠 **View ⇄ context in sync** — the conversation view always reflects exactly what the agent sees.
|
|
85
|
+
- ⚡ **Try in 30 seconds** — the dynamic form installs in your current session with no rebuild.
|
|
128
86
|
|
|
129
87
|
---
|
|
130
88
|
|
|
@@ -265,13 +223,13 @@ The dynamic host registers the same operations behind the package-private
|
|
|
265
223
|
rewound `session.deriveMessages()`.
|
|
266
224
|
3. **Client** (`lib/client.js`) registers:
|
|
267
225
|
- a `user-actions` conversation node under every user message
|
|
268
|
-
(
|
|
226
|
+
(an edit/recall row with an inline editor); recall echoes the text into the
|
|
269
227
|
composer,
|
|
270
228
|
- the `recall-marker` node renderer: a notice row that injects CSS hiding
|
|
271
229
|
every shadowed message row from the flow (view and model context stay in
|
|
272
230
|
sync), plus the optional original-input comparison block,
|
|
273
231
|
- the `retrace` entry in the `conversation.chat.assistant-actions`
|
|
274
|
-
strip (
|
|
232
|
+
strip (recall / regenerate),
|
|
275
233
|
- preference toggles and the retention limit under Settings → General.
|
|
276
234
|
|
|
277
235
|
> Two different layers are at play: the **durable transcript** (append-only; old
|
|
@@ -302,16 +260,21 @@ The dynamic host registers the same operations behind the package-private
|
|
|
302
260
|
|
|
303
261
|
## 🗺️ Roadmap
|
|
304
262
|
|
|
305
|
-
|
|
263
|
+
**What's in today (0.4.x):**
|
|
264
|
+
|
|
265
|
+
- Recall / edit-and-resend / regenerate, each written through a three-layer
|
|
266
|
+
**pre-write contract guard** and a safe-edit path (auto-stop the agent, temp-step
|
|
267
|
+
markers) — rewinds never corrupt the log or break `/compact`.
|
|
268
|
+
- In-session **version timeline** + **artifact rollback** (git-first, snapshot
|
|
269
|
+
fallback, dry-run preview, jump-to-conversation).
|
|
270
|
+
- **Fork map + session lineage** in the conversation view.
|
|
271
|
+
- **Real-time watchdog** — snapshots the log at the first sign of concurrent writes.
|
|
272
|
+
- Companion **`dsh-log-contract`**: 30+ offline contract rules + in-place repair
|
|
273
|
+
(`fix --neutralize` / `--clip-crossstep`) for sessions that would fail `/compact`.
|
|
306
274
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
navigation.
|
|
311
|
-
- **P2 — Fork map** 🔨 *in progress*: a flow graph of the conversation's turns with fork
|
|
312
|
-
points at every rewind, thinking flow per turn, branch-intent cards, and version
|
|
313
|
-
comparison.
|
|
314
|
-
- More locales beyond 简体中文 / English.
|
|
275
|
+
**What's next** — see the [public roadmap](./docs/ROADMAP.md) for the agent
|
|
276
|
+
business-layer plan (runtime guard, interruption governance, ecosystem-facing
|
|
277
|
+
interfaces). This README only describes what is already shipped.
|
|
315
278
|
|
|
316
279
|
---
|
|
317
280
|
|
|
@@ -359,6 +322,15 @@ and the [issue tracker](https://github.com/yamingmou/dsh-retrace/issues).
|
|
|
359
322
|
|
|
360
323
|
Listed on the [dsh-plugin topic](https://github.com/topics/dsh-plugin).
|
|
361
324
|
|
|
325
|
+
Part of the **Agent business layer (production-grade guarantees)** — see the
|
|
326
|
+
[public roadmap](./docs/ROADMAP.md) for the framework-agnostic layer and how
|
|
327
|
+
dsh-retrace is its DeepSeek Harness implementation. Companion components:
|
|
328
|
+
|
|
329
|
+
- [**dsh-log-contract**](https://github.com/yamingmou/dsh-log-contract) — the
|
|
330
|
+
business layer's "doctor": 30+ offline contract rules + in-place repair
|
|
331
|
+
(`fix --neutralize` / `--clip-crossstep`). Installed automatically as a
|
|
332
|
+
dependency; also published standalone for direct use.
|
|
333
|
+
|
|
362
334
|
> **Install straight from GitHub** (no npm registry needed — handy when you
|
|
363
335
|
> hand this repo's link to an AI or want the latest commit):
|
|
364
336
|
>
|
|
@@ -389,24 +361,27 @@ DeepSeek Harness community.
|
|
|
389
361
|
MIT
|
|
390
362
|
|
|
391
363
|
|
|
392
|
-
## 🧭
|
|
364
|
+
## 🧭 Session archaeology (`retrace` CLI)
|
|
393
365
|
|
|
394
|
-
|
|
395
|
-
`retrace` CLI
|
|
366
|
+
Every tool call's full input/output is persisted in the session log — a data and
|
|
367
|
+
audit asset. The `retrace` CLI provides read-only archaeology (reusing
|
|
368
|
+
dsh-log-contract's contracts and extraction):
|
|
396
369
|
|
|
397
370
|
```sh
|
|
398
|
-
retrace index <session> #
|
|
399
|
-
retrace query <session> --cmd "seed-scale" #
|
|
400
|
-
retrace extract <session> --pattern "seed-scale" --out ./found #
|
|
401
|
-
retrace file-history <session> <path> #
|
|
402
|
-
retrace file-diff <session> <path> 0 5 #
|
|
403
|
-
retrace lineage <session> #
|
|
371
|
+
retrace index <session> # tool-call index (A1)
|
|
372
|
+
retrace query <session> --cmd "seed-scale" # search outputs by command regex (A1)
|
|
373
|
+
retrace extract <session> --pattern "seed-scale" --out ./found # export outputs (A2)
|
|
374
|
+
retrace file-history <session> <path> # write/edit history of a file (A3)
|
|
375
|
+
retrace file-diff <session> <path> 0 5 # line diff between two versions (A3)
|
|
376
|
+
retrace lineage <session> # parent-chain lineage (A4)
|
|
404
377
|
```
|
|
405
378
|
|
|
406
|
-
|
|
379
|
+
`<session>` is a full log path or a sessionId (auto-looked-up under
|
|
380
|
+
`~/.dsh/sessions`). All read-only.
|
|
407
381
|
|
|
408
|
-
|
|
409
|
-
`parentSession`
|
|
410
|
-
`GET /api/plugins/retrace/lineage?sessionId
|
|
411
|
-
|
|
412
|
-
|
|
382
|
+
**Session lineage in the fork map (A4, UI)**: the Fork map view header shows the
|
|
383
|
+
current session's `parentSession` chain (session → parent → root, `←` direction).
|
|
384
|
+
Data comes from `GET /api/plugins/retrace/lineage?sessionId=` (read-only header
|
|
385
|
+
walk with cycle protection), the same semantics as the CLI `retrace lineage` —
|
|
386
|
+
so "which session did this one continue/fork from" is visible at a glance, and
|
|
387
|
+
serves as the fork-topology metadata source.
|
package/README.zh.md
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# 🧭 dsh-retrace
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**单会话内的版本化**——每一次回退的时间线、产物回退,以及对话走过的分叉路径图(路线图)。
|
|
8
|
-
同时支持 **Web 端** 与 **桌面客户端**(两者共用同一套 Web 前端)。
|
|
5
|
+
**撤回 · 编辑重发 · 重新生成**,加上**写安全**的会话版本化 —— DeepSeek Harness 的
|
|
6
|
+
**Agent 业务层(生产级保证)** 实现。
|
|
9
7
|
|
|
10
8
|
[](https://www.npmjs.com/package/dsh-retrace)
|
|
11
9
|
[](https://www.npmjs.com/package/dsh-retrace)
|
|
@@ -17,54 +15,46 @@
|
|
|
17
15
|
|
|
18
16
|
</div>
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
>
|
|
50
|
-
> > ⚠️ **安装后必须重启。** 运行中的应用不会热加载 bundle,请**退出并重新打开
|
|
51
|
-
> > DSH Desktop**(独立 Web 部署则重启 `dsh` 进程)后插件才会生效。
|
|
52
|
-
>
|
|
53
|
-
> 重启后悬停任意助手回复 → **↩ / ↻**;任意用户消息 → **✎**,即可使用。
|
|
18
|
+
**撤回 / 编辑重发 / 重新生成** —— 每个会话都该有的三个操作。但回退不只是「撤掉一条
|
|
19
|
+
消息」:DeepSeek Harness 把对话存在 append-only 事件日志里,撤回只回退上下文,改过的
|
|
20
|
+
**产物文件不会自动还原**。dsh-retrace 把对话**和它的产物**一起版本化,并且保证
|
|
21
|
+
**每次回退都合法、不弄脏日志、不破坏 /compact**。
|
|
22
|
+
|
|
23
|
+
> 🛡️ **写安全** · 🔍 **深层体检** · 🔄 **检测→修复→守护** —— 详见下方「生产级保证」。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## ⚡ 一分钟安装
|
|
28
|
+
|
|
29
|
+
> 需要带 `dsh` CLI 的 DeepSeek Harness;装完**重启 DSH** 生效(运行中的应用不会热加载)。
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
dsh plugin --profile desktop add dsh-retrace # DSH 桌面版
|
|
33
|
+
# 或 Web 部署:dsh plugin --profile web add dsh-retrace
|
|
34
|
+
# 或从 GitHub 直装:dsh plugin --profile desktop add github:yamingmou/dsh-retrace
|
|
35
|
+
# 或从 ZIP 解压后:dsh plugin --profile desktop add ~/plugins/dsh-retrace
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**没有命令行?** 先装一次社区插件市场,再在 **设置 → Plugin Market** 搜
|
|
39
|
+
**dsh-retrace** 一键安装:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
dsh plugin --profile desktop add dshmarket # 只需一次
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
重启后,悬停任意助手回复 → **↩ / ↻**;任意用户消息 → **✎**。详细步骤见
|
|
46
|
+
[📦 安装](#-安装)。
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
**重新生成**;再往前一步:撤回只回退了**上下文**,而智能体已经改过的**产物文件**
|
|
58
|
-
不会自动还原——retrace 把对话**和它的产物**放在一起做版本化。
|
|
48
|
+
---
|
|
49
|
+
---
|
|
59
50
|
|
|
60
|
-
|
|
61
|
-
效果。但底层的**持久化日志不会被改写或删除**:它始终保持只追加,旧事件原样保留,
|
|
62
|
-
插件只是在日志末尾追加一条合法的替换事件(与内置压缩使用的 `replace` 原语一致)来
|
|
63
|
-
回退对话表面,因此日志保留每一次回退的完整审计痕迹。在这条痕迹之上,retrace 记录
|
|
64
|
-
版本边界、触碰文件与(可选的)git 状态,支持产物回退与跳转到对话任意位置——全部
|
|
65
|
-
发生在**同一会话内**,不换会话。
|
|
51
|
+
## 🛡️ 生产级保证(0.4.x 全部已上线)
|
|
66
52
|
|
|
67
|
-
|
|
53
|
+
| | 能力 | 说明 |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| 🛡️ | **写安全** | 每次回退过三层写前契约校验;运行中的 agent 自动停止(官方 `cancel`/`whenIdle`);轮次间 marker 用临时 step 包裹 —— **回退永不弄脏日志,/compact 永不失效** |
|
|
56
|
+
| 🔍 | **深层体检** | 配套 `dsh-log-contract` 30+ 条契约规则(token-meter 配对 / 跨 step 引用 / 物理序 / inbox 重放),用真实损坏会话当测试集 —— 能找出让 /compact 永久失效的那类问题 |
|
|
57
|
+
| 🔄 | **检测→修复→守护** | 看门狗在并发写入第一时间快照日志;离线 `fix` 原地中和问题 marker、裁剪跨 step 引用;写前校验在坏事件落盘前拦住 |
|
|
68
58
|
|
|
69
59
|
---
|
|
70
60
|
|
|
@@ -78,44 +68,24 @@ DeepSeek Harness 的对话是「只追加(append-only)」的事件日志,
|
|
|
78
68
|
|
|
79
69
|
**版本化与回退(0.4.x 已上线)** —— 每次回退都会被记录为一个**版本**:
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
| | 能力 | 说明 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| 🕘 | **时间线** | 「版本」Tab(与官方「对话/轨迹」平级):每个版本的类型/时间/消息数/文件变更徽标,经 `session/projection` 推送帧实时更新(零轮询),大列表窗口化 |
|
|
74
|
+
| ↩️ | **产物回退** | 仅对话 / 仅产物 / 两者,先干跑预览再执行;git 优先 + 内容寻址快照兜底;回退本身是新版本(`restore`),可以再回退 |
|
|
75
|
+
| 🧭 | **跳转对话** | 从时间线一键跳转到对应位置(自动翻页加载更早历史 + 锚点高亮) |
|
|
76
|
+
| 🧹 | **存储有界** | 快照只保留最近 N 个版本(默认 50);节流后台扫掠回收被截断版本 |
|
|
77
|
+
|
|
78
|
+
**为什么与众不同**(交互层差异——上面的保证是存储层):
|
|
85
79
|
|
|
86
80
|
**为什么与众不同**
|
|
87
81
|
|
|
88
82
|
- 🎯 **整轮撤回** —— 一键移除输入 *和* 它的输出(含工具行),而不只是单条气泡。
|
|
89
83
|
- 🖥️ **Web + Desktop 双端** —— 同一插件覆盖 DeepSeek Harness 两种界面。
|
|
90
|
-
- 🔒 **删除的是视图与上下文,不是日志** —— 被撤回/编辑的消息从对话视图和模型上下文中
|
|
91
|
-
消失,但持久化日志从不被改写或删除;插件只追加合法、带类型的会话事件(与内置压缩
|
|
92
|
-
使用的 `replace` 原语一致),日志保留完整审计痕迹。
|
|
93
84
|
- 🧠 **视图 ⇄ 上下文同步** —— 对话视图永远反映智能体真正看到的内容。
|
|
94
85
|
- ⚡ **30 秒上手** —— 动态插件形式无需重建即可在当前会话试用。
|
|
95
86
|
|
|
96
87
|
---
|
|
97
88
|
|
|
98
|
-
## 🚀 快速开始
|
|
99
|
-
|
|
100
|
-
页首的 **⚡ 一分钟安装** 是最短路径;本节给出同样内容的更详细说明。
|
|
101
|
-
|
|
102
|
-
> 需要带 `dsh` CLI 的 DeepSeek Harness。以 profile bundle 方式安装,并自动重建 Web 客户端:
|
|
103
|
-
|
|
104
|
-
```sh
|
|
105
|
-
# DSH Desktop(desktop profile)
|
|
106
|
-
dsh plugin --profile desktop add dsh-retrace
|
|
107
|
-
|
|
108
|
-
# 独立 Web 部署(`dsh web` / web profile)
|
|
109
|
-
dsh plugin --profile web add dsh-retrace
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
> ⚠️ **安装后需要重启。** 运行中的应用仍在内存中保留之前加载的 bundle,请**退出并
|
|
113
|
-
> 重新打开 DSH Desktop**(独立 Web 部署则重启 `dsh` 进程)后插件才会生效。
|
|
114
|
-
|
|
115
|
-
重启后,悬停任意助手回复或用户消息,即可使用 ↩ / ✎ / ↻。
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
89
|
## 📦 安装
|
|
120
90
|
|
|
121
91
|
### 1. Profile bundle(推荐)
|
|
@@ -265,11 +235,15 @@ Client 半区会依据包内 `dsh.client` 元数据被自动打包进 Web 客户
|
|
|
265
235
|
|
|
266
236
|
## 🗺️ 路线图
|
|
267
237
|
|
|
268
|
-
|
|
238
|
+
**当前已具备(0.4.x):**
|
|
269
239
|
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
240
|
+
- 撤回 / 编辑重发 / 重新生成——每次回退都过**三层写前校验**与安全编辑路径(自动停 agent、临时 step 包裹 marker),**不会损坏日志、不会破坏 /compact**。
|
|
241
|
+
- 单会话**版本时间线** + **产物回退**(git 优先 + 快照兜底、干跑预览、跳转对话)。
|
|
242
|
+
- 对话视图内的**分叉图** + **会话谱系**。
|
|
243
|
+
- **实时看门狗**——并发写入第一时间快照日志。
|
|
244
|
+
- 配套 **`dsh-log-contract`**:30+ 条离线契约规则 + 原地修复(`fix --neutralize` / `--clip-crossstep`),能处理会让 /compact 永久失败的会话。
|
|
245
|
+
|
|
246
|
+
**未来计划**——见 [公开路线图](./docs/ROADMAP.md)(agent 业务层规划:运行时守护、中断治理、生态开放接口)。本 README 只描述已上线的能力。
|
|
273
247
|
|
|
274
248
|
---
|
|
275
249
|
|
|
@@ -316,6 +290,13 @@ npm pack --dry-run # 校验发布文件清单
|
|
|
316
290
|
|
|
317
291
|
收录于 [dsh-plugin topic](https://github.com/topics/dsh-plugin)。
|
|
318
292
|
|
|
293
|
+
**Agent 业务层(生产级保证)** 的一部分——见 [公开路线图](./docs/ROADMAP.md)
|
|
294
|
+
(框架无关的业务层定义,dsh-retrace 是它在 DeepSeek Harness 上的实现)。配套组件:
|
|
295
|
+
|
|
296
|
+
- [**dsh-log-contract**](https://github.com/yamingmou/dsh-log-contract) —— 业务层的
|
|
297
|
+
「医生」:30+ 条离线契约规则 + 原地修复(`fix --neutralize` / `--clip-crossstep`)。
|
|
298
|
+
作为依赖自动安装,也独立发布供直接使用。
|
|
299
|
+
|
|
319
300
|
> **直接从 GitHub 安装**(无需 npm registry —— 适合把本仓库链接丢给 AI,或想装最新提交):
|
|
320
301
|
>
|
|
321
302
|
> ```sh
|
|
@@ -341,3 +322,27 @@ DeepSeek Harness 插件生态的精选总览见
|
|
|
341
322
|
## 📄 License
|
|
342
323
|
|
|
343
324
|
MIT
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 🧭 会话日志考古(retrace CLI)
|
|
329
|
+
|
|
330
|
+
DSH 会话日志持久化了每次工具调用的完整输入输出——数据资产与审计资产。
|
|
331
|
+
`retrace` CLI 提供只读考古能力(复用 dsh-log-contract 的契约与提取):
|
|
332
|
+
|
|
333
|
+
```sh
|
|
334
|
+
retrace index <session> # 工具调用索引(A1)
|
|
335
|
+
retrace query <session> --cmd "seed-scale" # 按命令正则查输出(A1)
|
|
336
|
+
retrace extract <session> --pattern "seed-scale" --out ./found # 导出输出(A2)
|
|
337
|
+
retrace file-history <session> <path> # 文件 write/edit 历史版本(A3)
|
|
338
|
+
retrace file-diff <session> <path> 0 5 # 两版本行级 diff(A3)
|
|
339
|
+
retrace lineage <session> # 会话 parent 链谱系(A4)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
<session> 为完整日志路径或 sessionId(自动在 ~/.dsh/sessions 查找)。全部只读。
|
|
343
|
+
|
|
344
|
+
**分叉图里的会话谱系(A4, UI)**:Fork map 视图头部展示当前会话的
|
|
345
|
+
`parentSession` 接续链(当前会话 → 父 → 根,`←` 方向)。数据来自
|
|
346
|
+
`GET /api/plugins/retrace/lineage?sessionId=`(只读 header 遍历,带环保护),
|
|
347
|
+
与 CLI `retrace lineage` 同一语义。这样「这个会话是从哪个会话接着干/分叉出来的」
|
|
348
|
+
在界面上一眼可见——也是分叉图拓扑的元数据源。
|
package/lib/client.bundle.js
CHANGED
|
@@ -1136,7 +1136,9 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1136
1136
|
};
|
|
1137
1137
|
const ROW_H = 64;
|
|
1138
1138
|
const list = versions ?? [];
|
|
1139
|
-
const
|
|
1139
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2);
|
|
1140
|
+
const visibleEnd = Math.min(list.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2);
|
|
1141
|
+
const visible = list.slice(visibleStart, visibleEnd);
|
|
1140
1142
|
(0, import_react.useEffect)(() => {
|
|
1141
1143
|
if (list.length === 0) return void 0;
|
|
1142
1144
|
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-timeline-list"));
|
|
@@ -1175,11 +1177,11 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1175
1177
|
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1176
1178
|
}, [
|
|
1177
1179
|
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${list.length * ROW_H}px`, position: "relative" } }, [
|
|
1178
|
-
visible.map((record) => (0, import_react.createElement)(VersionRow, {
|
|
1180
|
+
visible.map((record, i) => (0, import_react.createElement)(VersionRow, {
|
|
1179
1181
|
key: record.versionId,
|
|
1180
1182
|
record,
|
|
1181
1183
|
t,
|
|
1182
|
-
top:
|
|
1184
|
+
top: (visibleStart + i) * ROW_H,
|
|
1183
1185
|
onPreview: () => requestPreview(record),
|
|
1184
1186
|
onTrajectory: () => switchToViewTab("trajectory"),
|
|
1185
1187
|
onJump: () => jump(record.boundarySeq)
|
|
@@ -1301,10 +1303,9 @@ function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1301
1303
|
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-fork-list"));
|
|
1302
1304
|
}, [nodes.length]);
|
|
1303
1305
|
const ROW_H = 56;
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
);
|
|
1306
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2);
|
|
1307
|
+
const visibleEnd = Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2);
|
|
1308
|
+
const visible = nodes.slice(visibleStart, visibleEnd);
|
|
1308
1309
|
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
1309
1310
|
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
1310
1311
|
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("fork.title")),
|
|
@@ -1341,13 +1342,13 @@ function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1341
1342
|
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1342
1343
|
}, [
|
|
1343
1344
|
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${nodes.length * ROW_H}px`, position: "relative" } }, [
|
|
1344
|
-
visible.map((node) => (0, import_react.createElement)(ForkRow, {
|
|
1345
|
+
visible.map((node, i) => (0, import_react.createElement)(ForkRow, {
|
|
1345
1346
|
key: node.seq,
|
|
1346
1347
|
node,
|
|
1347
1348
|
boundary: boundaryBySeq.get(node.seq),
|
|
1348
1349
|
markerText: markerBySeq.get(node.seq),
|
|
1349
1350
|
t,
|
|
1350
|
-
top:
|
|
1351
|
+
top: (visibleStart + i) * ROW_H,
|
|
1351
1352
|
onJump: () => jumpToAnchor(store, node.seq)
|
|
1352
1353
|
}))
|
|
1353
1354
|
])
|
package/lib/client.js
CHANGED
|
@@ -1485,7 +1485,11 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1485
1485
|
// ---- windowed list (uniform rows, zero-dep) ----
|
|
1486
1486
|
const ROW_H = 64
|
|
1487
1487
|
const list = versions ?? []
|
|
1488
|
-
|
|
1488
|
+
// 2026-08-30 渲染卡死修复(与 ForkView 同):带起始索引切片,渲染用索引算 top——
|
|
1489
|
+
// 原 `list.indexOf(record)` 是 O(N²)(每可见行线性查找),大会话渲染风暴。
|
|
1490
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2)
|
|
1491
|
+
const visibleEnd = Math.min(list.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2)
|
|
1492
|
+
const visible = list.slice(visibleStart, visibleEnd)
|
|
1489
1493
|
|
|
1490
1494
|
// Same view-area height trap as the fork list (see bindListHeight).
|
|
1491
1495
|
useEffect(() => {
|
|
@@ -1534,11 +1538,11 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1534
1538
|
onScroll: (event) => setScrollTop(event.target.scrollTop),
|
|
1535
1539
|
}, [
|
|
1536
1540
|
createElement('div', { key: 'spacer', style: { height: `${list.length * ROW_H}px`, position: 'relative' } }, [
|
|
1537
|
-
visible.map((record) => createElement(VersionRow, {
|
|
1541
|
+
visible.map((record, i) => createElement(VersionRow, {
|
|
1538
1542
|
key: record.versionId,
|
|
1539
1543
|
record,
|
|
1540
1544
|
t,
|
|
1541
|
-
top:
|
|
1545
|
+
top: (visibleStart + i) * ROW_H,
|
|
1542
1546
|
onPreview: () => requestPreview(record),
|
|
1543
1547
|
onTrajectory: () => switchToViewTab('trajectory'),
|
|
1544
1548
|
onJump: () => jump(record.boundarySeq),
|
|
@@ -1702,11 +1706,14 @@ function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1702
1706
|
}, [nodes.length])
|
|
1703
1707
|
|
|
1704
1708
|
// ---- windowed list (uniform rows, zero-dep; same as the versions view) ----
|
|
1709
|
+
// 2026-08-30 渲染卡死修复:visible 改为带起始索引的切片,渲染时直接用索引算
|
|
1710
|
+
// top——原实现 `nodes.indexOf(node)` 在每次渲染对每个可见节点做 O(N) 线性查找
|
|
1711
|
+
// (2047 节点 × ~15 可见行 = 每次渲染 ~30K 次比较,React 重渲染风暴 → 转圈、
|
|
1712
|
+
// Renderer CPU 27.7%,526f1835 打开卡死)。其他会话节点少不触发,仅大会话暴露。
|
|
1705
1713
|
const ROW_H = 56
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
)
|
|
1714
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2)
|
|
1715
|
+
const visibleEnd = Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2)
|
|
1716
|
+
const visible = nodes.slice(visibleStart, visibleEnd)
|
|
1710
1717
|
|
|
1711
1718
|
return createElement('div', { className: 'dsh-rt-view' }, [
|
|
1712
1719
|
createElement('div', { key: 'head', className: 'dsh-rt-timeline-head' }, [
|
|
@@ -1745,13 +1752,13 @@ function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
|
1745
1752
|
onScroll: (event) => setScrollTop(event.target.scrollTop),
|
|
1746
1753
|
}, [
|
|
1747
1754
|
createElement('div', { key: 'spacer', style: { height: `${nodes.length * ROW_H}px`, position: 'relative' } }, [
|
|
1748
|
-
visible.map((node) => createElement(ForkRow, {
|
|
1755
|
+
visible.map((node, i) => createElement(ForkRow, {
|
|
1749
1756
|
key: node.seq,
|
|
1750
1757
|
node,
|
|
1751
1758
|
boundary: boundaryBySeq.get(node.seq),
|
|
1752
1759
|
markerText: markerBySeq.get(node.seq),
|
|
1753
1760
|
t,
|
|
1754
|
-
top:
|
|
1761
|
+
top: (visibleStart + i) * ROW_H,
|
|
1755
1762
|
onJump: () => jumpToAnchor(store, node.seq),
|
|
1756
1763
|
})),
|
|
1757
1764
|
]),
|
package/lib/dynamic-client.js
CHANGED
|
@@ -1142,7 +1142,9 @@ return {
|
|
|
1142
1142
|
};
|
|
1143
1143
|
const ROW_H = 64;
|
|
1144
1144
|
const list = versions ?? [];
|
|
1145
|
-
const
|
|
1145
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2);
|
|
1146
|
+
const visibleEnd = Math.min(list.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2);
|
|
1147
|
+
const visible = list.slice(visibleStart, visibleEnd);
|
|
1146
1148
|
(0, import_react.useEffect)(() => {
|
|
1147
1149
|
if (list.length === 0) return void 0;
|
|
1148
1150
|
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-timeline-list"));
|
|
@@ -1181,11 +1183,11 @@ return {
|
|
|
1181
1183
|
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1182
1184
|
}, [
|
|
1183
1185
|
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${list.length * ROW_H}px`, position: "relative" } }, [
|
|
1184
|
-
visible.map((record) => (0, import_react.createElement)(VersionRow, {
|
|
1186
|
+
visible.map((record, i) => (0, import_react.createElement)(VersionRow, {
|
|
1185
1187
|
key: record.versionId,
|
|
1186
1188
|
record,
|
|
1187
1189
|
t,
|
|
1188
|
-
top:
|
|
1190
|
+
top: (visibleStart + i) * ROW_H,
|
|
1189
1191
|
onPreview: () => requestPreview(record),
|
|
1190
1192
|
onTrajectory: () => switchToViewTab("trajectory"),
|
|
1191
1193
|
onJump: () => jump(record.boundarySeq)
|
|
@@ -1307,10 +1309,9 @@ return {
|
|
|
1307
1309
|
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-fork-list"));
|
|
1308
1310
|
}, [nodes.length]);
|
|
1309
1311
|
const ROW_H = 56;
|
|
1310
|
-
const
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
);
|
|
1312
|
+
const visibleStart = Math.max(0, Math.floor(scrollTop / ROW_H) - 2);
|
|
1313
|
+
const visibleEnd = Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2);
|
|
1314
|
+
const visible = nodes.slice(visibleStart, visibleEnd);
|
|
1314
1315
|
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
1315
1316
|
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
1316
1317
|
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("fork.title")),
|
|
@@ -1347,13 +1348,13 @@ return {
|
|
|
1347
1348
|
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1348
1349
|
}, [
|
|
1349
1350
|
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${nodes.length * ROW_H}px`, position: "relative" } }, [
|
|
1350
|
-
visible.map((node) => (0, import_react.createElement)(ForkRow, {
|
|
1351
|
+
visible.map((node, i) => (0, import_react.createElement)(ForkRow, {
|
|
1351
1352
|
key: node.seq,
|
|
1352
1353
|
node,
|
|
1353
1354
|
boundary: boundaryBySeq.get(node.seq),
|
|
1354
1355
|
markerText: markerBySeq.get(node.seq),
|
|
1355
1356
|
t,
|
|
1356
|
-
top:
|
|
1357
|
+
top: (visibleStart + i) * ROW_H,
|
|
1357
1358
|
onJump: () => jumpToAnchor(store, node.seq)
|
|
1358
1359
|
}))
|
|
1359
1360
|
])
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-retrace",
|
|
3
3
|
"description": "Retrace · 回溯 — Recall, edit-and-resend, regenerate, and conversation/artifact versioning (timeline, rollback, fork map) for DeepSeek Harness — Web and Desktop",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|