dsh-retrace 0.4.7 → 0.4.8

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.
@@ -187,13 +187,34 @@ return {
187
187
  return session
188
188
  }
189
189
 
190
- function requireIdle(agent) {
191
- if (agent && typeof agent.status === 'string' && agent.status === 'running') {
192
- throw editorError(
193
- 'agent-busy',
194
- 'The agent is still responding. Stop the current reply before recalling or editing.',
195
- )
190
+ /**
191
+ * 确保 agent 空闲(2026-08-30 事故闭环,用户方案落地):agent 正在响应时,
192
+ * **不拒绝**——自动请求停止(`agent.cancel`)并等待其干净收尾(`whenIdle`)。
193
+ *
194
+ * 为什么必须等停止:编辑/重发发生在 agent 还开着 step 时,DSH resend 机制
195
+ * 会把旧 step 的 assistant/chunk 全部引用进新 assistant/message 的
196
+ * `sourceEventSeqs`(526f1835 seq 936047:引用跨 turn 54 的 step 7/8/9),
197
+ * token-meter 对跨 step 引用抛 `belongs to another step`(dsh-token-meter
198
+ * lib/index.js:645)→ 同样刷屏压垮 host。先停止 → step 干净关闭 → 编辑在
199
+ * 轮次边界执行,不再触发跨 step 引用。
200
+ *
201
+ * agent 无 cancel/whenIdle(headless/测试桩)时回退为抛 agent-busy(原行为)。
202
+ */
203
+ async function ensureIdle(agent) {
204
+ if (!agent || typeof agent.status !== 'string' || agent.status !== 'running') return
205
+ if (typeof agent.cancel === 'function' && typeof agent.whenIdle === 'function') {
206
+ try {
207
+ agent.cancel({ kind: 'plugin-retrace-edit' })
208
+ await agent.whenIdle()
209
+ return
210
+ } catch (error) {
211
+ throw editorError('agent-stop-failed', `Failed to stop the running reply before editing: ${String(error)}`)
212
+ }
196
213
  }
214
+ throw editorError(
215
+ 'agent-busy',
216
+ 'The agent is still responding. Stop the current reply before recalling or editing.',
217
+ )
197
218
  }
198
219
 
199
220
  /** Locate the durable seq of a user/assistant message by its stable message id. */
@@ -328,7 +349,7 @@ return {
328
349
  const sessionId = String(args?.sessionId ?? '')
329
350
  const messageId = String(args?.messageId ?? '')
330
351
  const session = requireSession(sessionId)
331
- requireIdle(agents.get(sessionId))
352
+ await ensureIdle(agents.get(sessionId))
332
353
  const seq = findMessageSeq(session, messageId)
333
354
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
334
355
  const span = roundSpanFrom(session, seq)
@@ -358,7 +379,7 @@ return {
358
379
  const fromScratch = args?.fromScratch === true
359
380
  const session = requireSession(sessionId)
360
381
  const agent = agents.get(sessionId)
361
- requireIdle(agent)
382
+ await ensureIdle(agent)
362
383
  const seq = findMessageSeq(session, messageId)
363
384
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
364
385
  const event = session.events[seq]
@@ -399,7 +420,7 @@ return {
399
420
  const messageId = String(args?.messageId ?? '')
400
421
  const session = requireSession(sessionId)
401
422
  const agent = agents.get(sessionId)
402
- requireIdle(agent)
423
+ await ensureIdle(agent)
403
424
  const seq = findMessageSeq(session, messageId)
404
425
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
405
426
  const event = session.events[seq]
package/lib/host-core.js CHANGED
@@ -178,13 +178,34 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
178
178
  return session
179
179
  }
180
180
 
181
- function requireIdle(agent) {
182
- if (agent && typeof agent.status === 'string' && agent.status === 'running') {
183
- throw editorError(
184
- 'agent-busy',
185
- 'The agent is still responding. Stop the current reply before recalling or editing.',
186
- )
181
+ /**
182
+ * 确保 agent 空闲(2026-08-30 事故闭环,用户方案落地):agent 正在响应时,
183
+ * **不拒绝**——自动请求停止(`agent.cancel`)并等待其干净收尾(`whenIdle`)。
184
+ *
185
+ * 为什么必须等停止:编辑/重发发生在 agent 还开着 step 时,DSH resend 机制
186
+ * 会把旧 step 的 assistant/chunk 全部引用进新 assistant/message 的
187
+ * `sourceEventSeqs`(526f1835 seq 936047:引用跨 turn 54 的 step 7/8/9),
188
+ * token-meter 对跨 step 引用抛 `belongs to another step`(dsh-token-meter
189
+ * lib/index.js:645)→ 同样刷屏压垮 host。先停止 → step 干净关闭 → 编辑在
190
+ * 轮次边界执行,不再触发跨 step 引用。
191
+ *
192
+ * agent 无 cancel/whenIdle(headless/测试桩)时回退为抛 agent-busy(原行为)。
193
+ */
194
+ async function ensureIdle(agent) {
195
+ if (!agent || typeof agent.status !== 'string' || agent.status !== 'running') return
196
+ if (typeof agent.cancel === 'function' && typeof agent.whenIdle === 'function') {
197
+ try {
198
+ agent.cancel({ kind: 'plugin-retrace-edit' })
199
+ await agent.whenIdle()
200
+ return
201
+ } catch (error) {
202
+ throw editorError('agent-stop-failed', `Failed to stop the running reply before editing: ${String(error)}`)
203
+ }
187
204
  }
205
+ throw editorError(
206
+ 'agent-busy',
207
+ 'The agent is still responding. Stop the current reply before recalling or editing.',
208
+ )
188
209
  }
189
210
 
190
211
  /** Locate the durable seq of a user/assistant message by its stable message id. */
@@ -319,7 +340,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
319
340
  const sessionId = String(args?.sessionId ?? '')
320
341
  const messageId = String(args?.messageId ?? '')
321
342
  const session = requireSession(sessionId)
322
- requireIdle(agents.get(sessionId))
343
+ await ensureIdle(agents.get(sessionId))
323
344
  const seq = findMessageSeq(session, messageId)
324
345
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
325
346
  const span = roundSpanFrom(session, seq)
@@ -349,7 +370,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
349
370
  const fromScratch = args?.fromScratch === true
350
371
  const session = requireSession(sessionId)
351
372
  const agent = agents.get(sessionId)
352
- requireIdle(agent)
373
+ await ensureIdle(agent)
353
374
  const seq = findMessageSeq(session, messageId)
354
375
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
355
376
  const event = session.events[seq]
@@ -390,7 +411,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
390
411
  const messageId = String(args?.messageId ?? '')
391
412
  const session = requireSession(sessionId)
392
413
  const agent = agents.get(sessionId)
393
- requireIdle(agent)
414
+ await ensureIdle(agent)
394
415
  const seq = findMessageSeq(session, messageId)
395
416
  if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
396
417
  const event = session.events[seq]
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.7",
4
+ "version": "0.4.8",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",