dsh-retrace 0.4.7 → 0.4.9
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/lib/dynamic-host.js +33 -9
- package/lib/host-core.js +33 -9
- package/package.json +1 -1
package/lib/dynamic-host.js
CHANGED
|
@@ -187,13 +187,37 @@ return {
|
|
|
187
187
|
return session
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
// 官方 AgentCancelCause 类型只有 user/parent/hook/disposed 四种
|
|
208
|
+
// (dsh-commands typert.host.js:166)——用 { kind: 'user' } 与 UI 停止按钮同义,
|
|
209
|
+
// 中断的 turn/end 会按官方语义记录 reason。
|
|
210
|
+
agent.cancel({ kind: 'user' })
|
|
211
|
+
await agent.whenIdle()
|
|
212
|
+
return
|
|
213
|
+
} catch (error) {
|
|
214
|
+
throw editorError('agent-stop-failed', `Failed to stop the running reply before editing: ${String(error)}`)
|
|
215
|
+
}
|
|
196
216
|
}
|
|
217
|
+
throw editorError(
|
|
218
|
+
'agent-busy',
|
|
219
|
+
'The agent is still responding. Stop the current reply before recalling or editing.',
|
|
220
|
+
)
|
|
197
221
|
}
|
|
198
222
|
|
|
199
223
|
/** Locate the durable seq of a user/assistant message by its stable message id. */
|
|
@@ -328,7 +352,7 @@ return {
|
|
|
328
352
|
const sessionId = String(args?.sessionId ?? '')
|
|
329
353
|
const messageId = String(args?.messageId ?? '')
|
|
330
354
|
const session = requireSession(sessionId)
|
|
331
|
-
|
|
355
|
+
await ensureIdle(agents.get(sessionId))
|
|
332
356
|
const seq = findMessageSeq(session, messageId)
|
|
333
357
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
334
358
|
const span = roundSpanFrom(session, seq)
|
|
@@ -358,7 +382,7 @@ return {
|
|
|
358
382
|
const fromScratch = args?.fromScratch === true
|
|
359
383
|
const session = requireSession(sessionId)
|
|
360
384
|
const agent = agents.get(sessionId)
|
|
361
|
-
|
|
385
|
+
await ensureIdle(agent)
|
|
362
386
|
const seq = findMessageSeq(session, messageId)
|
|
363
387
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
364
388
|
const event = session.events[seq]
|
|
@@ -399,7 +423,7 @@ return {
|
|
|
399
423
|
const messageId = String(args?.messageId ?? '')
|
|
400
424
|
const session = requireSession(sessionId)
|
|
401
425
|
const agent = agents.get(sessionId)
|
|
402
|
-
|
|
426
|
+
await ensureIdle(agent)
|
|
403
427
|
const seq = findMessageSeq(session, messageId)
|
|
404
428
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
405
429
|
const event = session.events[seq]
|
package/lib/host-core.js
CHANGED
|
@@ -178,13 +178,37 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
|
|
|
178
178
|
return session
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
// 官方 AgentCancelCause 类型只有 user/parent/hook/disposed 四种
|
|
199
|
+
// (dsh-commands typert.host.js:166)——用 { kind: 'user' } 与 UI 停止按钮同义,
|
|
200
|
+
// 中断的 turn/end 会按官方语义记录 reason。
|
|
201
|
+
agent.cancel({ kind: 'user' })
|
|
202
|
+
await agent.whenIdle()
|
|
203
|
+
return
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw editorError('agent-stop-failed', `Failed to stop the running reply before editing: ${String(error)}`)
|
|
206
|
+
}
|
|
187
207
|
}
|
|
208
|
+
throw editorError(
|
|
209
|
+
'agent-busy',
|
|
210
|
+
'The agent is still responding. Stop the current reply before recalling or editing.',
|
|
211
|
+
)
|
|
188
212
|
}
|
|
189
213
|
|
|
190
214
|
/** Locate the durable seq of a user/assistant message by its stable message id. */
|
|
@@ -319,7 +343,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
|
|
|
319
343
|
const sessionId = String(args?.sessionId ?? '')
|
|
320
344
|
const messageId = String(args?.messageId ?? '')
|
|
321
345
|
const session = requireSession(sessionId)
|
|
322
|
-
|
|
346
|
+
await ensureIdle(agents.get(sessionId))
|
|
323
347
|
const seq = findMessageSeq(session, messageId)
|
|
324
348
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
325
349
|
const span = roundSpanFrom(session, seq)
|
|
@@ -349,7 +373,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
|
|
|
349
373
|
const fromScratch = args?.fromScratch === true
|
|
350
374
|
const session = requireSession(sessionId)
|
|
351
375
|
const agent = agents.get(sessionId)
|
|
352
|
-
|
|
376
|
+
await ensureIdle(agent)
|
|
353
377
|
const seq = findMessageSeq(session, messageId)
|
|
354
378
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
355
379
|
const event = session.events[seq]
|
|
@@ -390,7 +414,7 @@ export function createEditorApi(ctx, sessions, agents, log = () => {}, hooks = {
|
|
|
390
414
|
const messageId = String(args?.messageId ?? '')
|
|
391
415
|
const session = requireSession(sessionId)
|
|
392
416
|
const agent = agents.get(sessionId)
|
|
393
|
-
|
|
417
|
+
await ensureIdle(agent)
|
|
394
418
|
const seq = findMessageSeq(session, messageId)
|
|
395
419
|
if (seq === -1) throw editorError('message-not-found', 'Message not found in this session.')
|
|
396
420
|
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.
|
|
4
|
+
"version": "0.4.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|