dsh-retrace 0.4.8 → 0.4.10

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 CHANGED
@@ -18,6 +18,44 @@ Desktop app (both share the same Web frontend).
18
18
 
19
19
  </div>
20
20
 
21
+ > ## ⚡ Install in one minute
22
+ >
23
+ > **Option A — one command (recommended):** with DeepSeek Harness's `dsh` CLI:
24
+ >
25
+ > ```sh
26
+ > dsh plugin --profile desktop add dsh-retrace # DSH Desktop
27
+ > # or: dsh plugin --profile web add dsh-retrace # standalone Web
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
+
21
59
  DeepSeek Harness stores every conversation as an **append-only event log**, so there is
22
60
  no built-in "undo". `dsh-retrace` brings back the three moves every chat deserves —
23
61
  **撤回 (recall)**, **编辑重发 (edit-and-resend)**, **重新生成 (regenerate)** — and then
@@ -68,6 +106,9 @@ in the conversation — all **inside the same session**, no session-switching.
68
106
 
69
107
  ## 🚀 Quick start
70
108
 
109
+ The one-line install is at the top of this page (**⚡ Install in one minute**).
110
+ This section covers the same ground with more detail.
111
+
71
112
  > Requires DeepSeek Harness with the `dsh` CLI. Installs the plugin as a profile
72
113
  > bundle and automatically rebuilds the Web client:
73
114
 
@@ -83,8 +124,7 @@ dsh plugin --profile web add dsh-retrace
83
124
  > in memory, so **quit and reopen DSH Desktop** (or restart the `dsh` process for
84
125
  > a standalone Web deployment) before the plugin activates.
85
126
 
86
- That's it — after the restart, hover any assistant reply, or any user message,
87
- and use ↩ / ✎ / ↻.
127
+ After the restart, hover any assistant reply, or any user message, and use ↩ / ✎ / ↻.
88
128
 
89
129
  ---
90
130
 
@@ -105,14 +145,17 @@ dsh plugin --profile <name> add dsh-retrace
105
145
  > deployment) to load the plugin. To uninstall:
106
146
  > `dsh plugin --profile <name> remove dsh-retrace` (then restart again).
107
147
 
108
- It also shows up in [dsh-market](https://github.com/dsh-market/dsh-market) for
109
- one-click install from inside Settings (same restart applies).
110
-
111
148
  ### 2. Manual install (no `dsh` CLI)
112
149
 
113
150
  The same result with plain file edits and `pnpm` — exactly the steps
114
151
  `dsh plugin add` performs for you:
115
152
 
153
+ > **Downloaded this repo as a ZIP?** Unpack it somewhere stable (e.g.
154
+ > `~/plugins/dsh-retrace`), then either
155
+ > `dsh plugin --profile desktop add ~/plugins/dsh-retrace`, or follow the
156
+ > steps below with the dependency line pointing at the folder:
157
+ > `"dsh-retrace": "file:~/plugins/dsh-retrace"`.
158
+
116
159
  1. Open the profile manifest (defaults: `~/.dsh/profiles/desktop` on DSH
117
160
  Desktop, `~/.dsh/profiles/web` for standalone Web) and add **both** the
118
161
  dependency and the bundle-layer entry:
@@ -148,6 +191,9 @@ The same result with plain file edits and `pnpm` — exactly the steps
148
191
  For local development, point the dependency at a checkout instead of the
149
192
  registry: `"dsh-retrace": "file:/path/to/dsh-retrace"` — or let
150
193
  `dsh` do it: `dsh plugin --profile <name> add /path/to/dsh-retrace`.
194
+ For the latest GitHub commit without a release: use
195
+ `"dsh-retrace": "github:yamingmou/dsh-retrace"` (standard pnpm git
196
+ dependency syntax) in the same `dependencies` block, then `pnpm install`.
151
197
 
152
198
  ### 3. npm package + composition (classic)
153
199
 
@@ -311,10 +357,23 @@ and the [issue tracker](https://github.com/yamingmou/dsh-retrace/issues).
311
357
 
312
358
  ## 📚 Ecosystem
313
359
 
314
- Listed on the [dsh-plugin topic](https://github.com/topics/dsh-plugin) and
315
- installable from [dsh-market](https://github.com/dsh-market/dsh-market). For a
316
- curated overview of the DeepSeek Harness plugin ecosystem, see
317
- [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin).
360
+ Listed on the [dsh-plugin topic](https://github.com/topics/dsh-plugin).
361
+
362
+ > **Install straight from GitHub** (no npm registry needed — handy when you
363
+ > hand this repo's link to an AI or want the latest commit):
364
+ >
365
+ > ```sh
366
+ > dsh plugin --profile desktop add github:yamingmou/dsh-retrace
367
+ > # or with pnpm directly into a profile:
368
+ > cd ~/.dsh/profiles/desktop && pnpm add github:yamingmou/dsh-retrace
369
+ > ```
370
+ >
371
+ > Then restart DSH Desktop as usual. The `dsh-log-contract` dependency is
372
+ > pulled in automatically.
373
+
374
+ A curated overview of the DeepSeek Harness plugin ecosystem lives at
375
+ [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
376
+ (third-party listing — verify availability before relying on it).
318
377
 
319
378
  ---
320
379
 
package/README.zh.md CHANGED
@@ -17,6 +17,41 @@
17
17
 
18
18
  </div>
19
19
 
20
+ > ## ⚡ 一分钟安装
21
+ >
22
+ > **方式 A —— 一条命令(推荐):** 需要带 `dsh` CLI 的 DeepSeek Harness:
23
+ >
24
+ > ```sh
25
+ > dsh plugin --profile desktop add dsh-retrace # DSH 桌面版
26
+ > # 或:dsh plugin --profile web add dsh-retrace # 独立 Web 部署
27
+ > ```
28
+ >
29
+ > **方式 B —— 从 GitHub 下载了 ZIP(或把本仓库链接直接丢给 AI)?** 解压到固定位置
30
+ > (如 `~/plugins/dsh-retrace`)后执行 `dsh plugin --profile desktop add ~/plugins/dsh-retrace`;
31
+ > 或**直接从 GitHub 安装,无需解压**:
32
+ >
33
+ > ```sh
34
+ > dsh plugin --profile desktop add github:yamingmou/dsh-retrace
35
+ > ```
36
+ >
37
+ > 更详细的文件级步骤见 [📦 安装](#-安装) → *手动安装*。
38
+ >
39
+ > **方式 C —— 完全没有命令行?** 先装一次社区插件市场,再从市场 UI 一键安装
40
+ > dsh-retrace:
41
+ >
42
+ > ```sh
43
+ > dsh plugin --profile desktop add dshmarket # 只需一次
44
+ > ```
45
+ >
46
+ > 重启后进入 **设置 → Plugin Market** → 搜索 **dsh-retrace** → **安装**(一键)。
47
+ > 市场收录自 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
48
+ > 精选目录,dsh-retrace 已在其中。
49
+ >
50
+ > > ⚠️ **安装后必须重启。** 运行中的应用不会热加载 bundle,请**退出并重新打开
51
+ > > DSH Desktop**(独立 Web 部署则重启 `dsh` 进程)后插件才会生效。
52
+ >
53
+ > 重启后悬停任意助手回复 → **↩ / ↻**;任意用户消息 → **✎**,即可使用。
54
+
20
55
  DeepSeek Harness 的对话是「只追加(append-only)」的事件日志,本身没有撤销能力。
21
56
  `dsh-retrace` 先为对话补上聊天本该有的三个操作 —— **撤回**、**编辑重发**、
22
57
  **重新生成**;再往前一步:撤回只回退了**上下文**,而智能体已经改过的**产物文件**
@@ -62,6 +97,8 @@ DeepSeek Harness 的对话是「只追加(append-only)」的事件日志,
62
97
 
63
98
  ## 🚀 快速开始
64
99
 
100
+ 页首的 **⚡ 一分钟安装** 是最短路径;本节给出同样内容的更详细说明。
101
+
65
102
  > 需要带 `dsh` CLI 的 DeepSeek Harness。以 profile bundle 方式安装,并自动重建 Web 客户端:
66
103
 
67
104
  ```sh
@@ -94,13 +131,14 @@ dsh plugin --profile <name> add dsh-retrace
94
131
  > `dsh` 进程)来加载插件。卸载:`dsh plugin --profile <name> remove
95
132
  > dsh-retrace`(卸载后同样需要重启)。
96
133
 
97
- 同时可在 [dsh-market](https://github.com/dsh-market/dsh-market) 里一键安装
98
- (安装后同样需要重启)。
99
-
100
134
  ### 2. 手动安装(不依赖 `dsh` CLI)
101
135
 
102
136
  用纯文件编辑 + `pnpm` 装进同一个 profile —— 也就是 `dsh plugin add` 帮你做的那些步骤:
103
137
 
138
+ > **从 GitHub 下载了 ZIP?** 解压到固定位置(如 `~/plugins/dsh-retrace`),
139
+ > 然后执行 `dsh plugin --profile desktop add ~/plugins/dsh-retrace`;或按下面步骤,
140
+ > 把依赖行指向该文件夹:`"dsh-retrace": "file:~/plugins/dsh-retrace"`。
141
+
104
142
  1. 打开 profile 清单(默认位置:DSH Desktop 为 `~/.dsh/profiles/desktop`,
105
143
  独立 Web 为 `~/.dsh/profiles/web`),同时加入依赖**和** bundle 层条目:
106
144
 
@@ -276,9 +314,21 @@ npm pack --dry-run # 校验发布文件清单
276
314
 
277
315
  ## 📚 生态
278
316
 
279
- 收录于 [dsh-plugin topic](https://github.com/topics/dsh-plugin),可在
280
- [dsh-market](https://github.com/dsh-market/dsh-market) 一键安装。DeepSeek Harness
281
- 插件生态的精选总览见 [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)。
317
+ 收录于 [dsh-plugin topic](https://github.com/topics/dsh-plugin)
318
+
319
+ > **直接从 GitHub 安装**(无需 npm registry —— 适合把本仓库链接丢给 AI,或想装最新提交):
320
+ >
321
+ > ```sh
322
+ > dsh plugin --profile desktop add github:yamingmou/dsh-retrace
323
+ > # 或直接用 pnpm 装进 profile:
324
+ > cd ~/.dsh/profiles/desktop && pnpm add github:yamingmou/dsh-retrace
325
+ > ```
326
+ >
327
+ > 然后照常重启 DSH Desktop。`dsh-log-contract` 依赖会自动带上。
328
+
329
+ DeepSeek Harness 插件生态的精选总览见
330
+ [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
331
+ (第三方收录,使用前请自行确认可用性)。
282
332
 
283
333
  ---
284
334
 
@@ -121,13 +121,19 @@ return {
121
121
  content: [],
122
122
  source: { kind: 'model', provider: model.provider, model: model.model },
123
123
  }
124
- // R2 路径一(2026-08-30 事故闭环):有打开的 step(回合中编辑)时,marker 携带
125
- // step 的 turn/step —— token-meter 配对通过,不产生 T1 违规、不刷屏。
126
- // 无打开 step(轮次间编辑,常态)才回退 turn:null(配合 markerT1Broken 标注)。
124
+ // R2 根治(2026-08-30 事故闭环 v2):
125
+ // - 有打开的 step(回合中编辑):marker 携带该 step 的 turn/step token-meter 配对通过;
126
+ // - 无打开 step(轮次间编辑,常态):**自动开一个临时 step 包裹 marker**
127
+ // (step/start → marker → step/end,turn 用 nextTurn,step 恒 1)。
128
+ // 官方 token-meter 要求 assistant/message 必须有打开的 step(lib/index.js:590,
129
+ // stepStart===void 0 即抛)——turn-null 或伪造 turn/step 都过不了;
130
+ // 临时 step 是唯一能让轮次间 marker 合法化的形态(foldSurface/token-meter/客户端
131
+ // Location boundary 三层验证全过)。不再产生 turn-null marker → 不再刷屏。
127
132
  const openStep = findOpenStep(session)
133
+ const nextTurn = nextTurnOf(session)
128
134
  const data = {
129
- turn: openStep ? openStep.turn : null,
130
- step: openStep ? openStep.step : null,
135
+ turn: openStep ? openStep.turn : nextTurn,
136
+ step: openStep ? openStep.step : 1,
131
137
  message: marker,
132
138
  editor: {
133
139
  targetSeq,
@@ -139,11 +145,20 @@ return {
139
145
  if (typeof validate === 'function') {
140
146
  const result = await validate(session, { type: 'assistant/message', data, surfaceOp, sourceEventSeqs })
141
147
  if (result && result.t1Ok === false) {
142
- // R2:标注该 marker 会破坏 /compact(不阻断编辑)。
148
+ // 理论上临时 step T1 恒通过;残留 fallback 标注(防御)。
143
149
  data.editor.markerT1Broken = true
144
150
  }
145
151
  }
146
- return session.append('assistant/message', data, { surfaceOp, sourceEventSeqs })
152
+ // 轮次间编辑:开临时 step 包裹(marker 前后成对,token-meter 需要打开的 step)
153
+ const openedStep = openStep === null
154
+ if (openedStep) {
155
+ session.append('step/start', { turn: nextTurn, step: 1 })
156
+ }
157
+ const markerEvent = session.append('assistant/message', data, { surfaceOp, sourceEventSeqs })
158
+ if (openedStep) {
159
+ session.append('step/end', { turn: nextTurn, step: 1 })
160
+ }
161
+ return markerEvent
147
162
  }
148
163
 
149
164
  /**
@@ -164,6 +179,20 @@ return {
164
179
  return open
165
180
  }
166
181
 
182
+ /**
183
+ * 下一个 turn 号:scan 最大 turn(turn/start、step/start、assistant/message、user/message
184
+ * 的 data.turn 中取最大)+1。无任何 turn 时从 1 起。
185
+ */
186
+ function nextTurnOf(session) {
187
+ const events = Array.isArray(session?.events) ? session.events : []
188
+ let max = 0
189
+ for (const event of events) {
190
+ const t = event?.data?.turn
191
+ if (typeof t === 'number' && Number.isSafeInteger(t) && t > max) max = t
192
+ }
193
+ return max + 1
194
+ }
195
+
167
196
  function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {}) {
168
197
  /** One in-flight op per session; later ops wait for the earlier one. */
169
198
  const locks = new Map()
@@ -204,7 +233,10 @@ return {
204
233
  if (!agent || typeof agent.status !== 'string' || agent.status !== 'running') return
205
234
  if (typeof agent.cancel === 'function' && typeof agent.whenIdle === 'function') {
206
235
  try {
207
- agent.cancel({ kind: 'plugin-retrace-edit' })
236
+ // 官方 AgentCancelCause 类型只有 user/parent/hook/disposed 四种
237
+ // (dsh-commands typert.host.js:166)——用 { kind: 'user' } 与 UI 停止按钮同义,
238
+ // 中断的 turn/end 会按官方语义记录 reason。
239
+ agent.cancel({ kind: 'user' })
208
240
  await agent.whenIdle()
209
241
  return
210
242
  } catch (error) {
package/lib/host-core.js CHANGED
@@ -112,13 +112,19 @@ export async function appendEditorMarker(session, span, op, targetSeq, originalT
112
112
  content: [],
113
113
  source: { kind: 'model', provider: model.provider, model: model.model },
114
114
  }
115
- // R2 路径一(2026-08-30 事故闭环):有打开的 step(回合中编辑)时,marker 携带
116
- // step 的 turn/step —— token-meter 配对通过,不产生 T1 违规、不刷屏。
117
- // 无打开 step(轮次间编辑,常态)才回退 turn:null(配合 markerT1Broken 标注)。
115
+ // R2 根治(2026-08-30 事故闭环 v2):
116
+ // - 有打开的 step(回合中编辑):marker 携带该 step 的 turn/step token-meter 配对通过;
117
+ // - 无打开 step(轮次间编辑,常态):**自动开一个临时 step 包裹 marker**
118
+ // (step/start → marker → step/end,turn 用 nextTurn,step 恒 1)。
119
+ // 官方 token-meter 要求 assistant/message 必须有打开的 step(lib/index.js:590,
120
+ // stepStart===void 0 即抛)——turn-null 或伪造 turn/step 都过不了;
121
+ // 临时 step 是唯一能让轮次间 marker 合法化的形态(foldSurface/token-meter/客户端
122
+ // Location boundary 三层验证全过)。不再产生 turn-null marker → 不再刷屏。
118
123
  const openStep = findOpenStep(session)
124
+ const nextTurn = nextTurnOf(session)
119
125
  const data = {
120
- turn: openStep ? openStep.turn : null,
121
- step: openStep ? openStep.step : null,
126
+ turn: openStep ? openStep.turn : nextTurn,
127
+ step: openStep ? openStep.step : 1,
122
128
  message: marker,
123
129
  editor: {
124
130
  targetSeq,
@@ -130,11 +136,20 @@ export async function appendEditorMarker(session, span, op, targetSeq, originalT
130
136
  if (typeof validate === 'function') {
131
137
  const result = await validate(session, { type: 'assistant/message', data, surfaceOp, sourceEventSeqs })
132
138
  if (result && result.t1Ok === false) {
133
- // R2:标注该 marker 会破坏 /compact(不阻断编辑)。
139
+ // 理论上临时 step T1 恒通过;残留 fallback 标注(防御)。
134
140
  data.editor.markerT1Broken = true
135
141
  }
136
142
  }
137
- return session.append('assistant/message', data, { surfaceOp, sourceEventSeqs })
143
+ // 轮次间编辑:开临时 step 包裹(marker 前后成对,token-meter 需要打开的 step)
144
+ const openedStep = openStep === null
145
+ if (openedStep) {
146
+ session.append('step/start', { turn: nextTurn, step: 1 })
147
+ }
148
+ const markerEvent = session.append('assistant/message', data, { surfaceOp, sourceEventSeqs })
149
+ if (openedStep) {
150
+ session.append('step/end', { turn: nextTurn, step: 1 })
151
+ }
152
+ return markerEvent
138
153
  }
139
154
 
140
155
  /**
@@ -155,6 +170,20 @@ export function findOpenStep(session) {
155
170
  return open
156
171
  }
157
172
 
173
+ /**
174
+ * 下一个 turn 号:scan 最大 turn(turn/start、step/start、assistant/message、user/message
175
+ * 的 data.turn 中取最大)+1。无任何 turn 时从 1 起。
176
+ */
177
+ export function nextTurnOf(session) {
178
+ const events = Array.isArray(session?.events) ? session.events : []
179
+ let max = 0
180
+ for (const event of events) {
181
+ const t = event?.data?.turn
182
+ if (typeof t === 'number' && Number.isSafeInteger(t) && t > max) max = t
183
+ }
184
+ return max + 1
185
+ }
186
+
158
187
  export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {}) {
159
188
  /** One in-flight op per session; later ops wait for the earlier one. */
160
189
  const locks = new Map()
@@ -195,7 +224,10 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
195
224
  if (!agent || typeof agent.status !== 'string' || agent.status !== 'running') return
196
225
  if (typeof agent.cancel === 'function' && typeof agent.whenIdle === 'function') {
197
226
  try {
198
- agent.cancel({ kind: 'plugin-retrace-edit' })
227
+ // 官方 AgentCancelCause 类型只有 user/parent/hook/disposed 四种
228
+ // (dsh-commands typert.host.js:166)——用 { kind: 'user' } 与 UI 停止按钮同义,
229
+ // 中断的 turn/end 会按官方语义记录 reason。
230
+ agent.cancel({ kind: 'user' })
199
231
  await agent.whenIdle()
200
232
  return
201
233
  } catch (error) {
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.8",
4
+ "version": "0.4.10",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",