dsh-done-sound 0.1.13 → 0.1.14
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/client.js +89 -53
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -370,6 +370,7 @@ window.__ModuleLoader__.load({
|
|
|
370
370
|
deadline: 0,
|
|
371
371
|
timer: null,
|
|
372
372
|
delayMs: 60000,
|
|
373
|
+
streamingSince: null, // when the loop first saw the session stuck streaming
|
|
373
374
|
};
|
|
374
375
|
// Latest session view, refreshed by the detector on every snapshot change,
|
|
375
376
|
// so this module-level timer can decide without reaching into React state.
|
|
@@ -380,13 +381,22 @@ window.__ModuleLoader__.load({
|
|
|
380
381
|
retryState.timer = null;
|
|
381
382
|
}
|
|
382
383
|
}
|
|
383
|
-
function cancelModuleRetry() {
|
|
384
|
+
function cancelModuleRetry(why) {
|
|
385
|
+
if (retryState.active || retryState.timer !== null) {
|
|
386
|
+
try {
|
|
387
|
+
console.info('[dsh-done-sound] retry: cancel', why || 'stop');
|
|
388
|
+
reportLog('info', ['retry: cancel', why || 'stop']);
|
|
389
|
+
} catch {
|
|
390
|
+
// ignore
|
|
391
|
+
}
|
|
392
|
+
}
|
|
384
393
|
clearRetryTimer();
|
|
385
394
|
retryState.active = false;
|
|
386
395
|
retryState.sessionId = null;
|
|
387
396
|
retryState.endTurnAtError = 0;
|
|
388
397
|
retryState.attempt = 0;
|
|
389
398
|
retryState.deadline = 0;
|
|
399
|
+
retryState.streamingSince = null;
|
|
390
400
|
}
|
|
391
401
|
/** Queue the next attempt and publish the countdown deadline. */
|
|
392
402
|
function scheduleModuleRetry(delayMs) {
|
|
@@ -410,70 +420,61 @@ window.__ModuleLoader__.load({
|
|
|
410
420
|
if (!retryState.active) return;
|
|
411
421
|
const sid = retryState.sessionId;
|
|
412
422
|
const delayMs = retryState.delayMs;
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
//
|
|
423
|
+
const now = Date.now();
|
|
424
|
+
|
|
425
|
+
// Stop the loop only when the session truly recovered: a newer turn
|
|
426
|
+
// closed normally (`completed`). Aborts / blocks / errors are FAILURES
|
|
427
|
+
// from the user's point of view (a dropped model often ends the retry
|
|
428
|
+
// turn as `aborted`) — those must keep retrying until the user stops it.
|
|
416
429
|
const recovered =
|
|
417
430
|
liveSnap.endReason !== null &&
|
|
418
431
|
liveSnap.endTurn > retryState.endTurnAtError &&
|
|
419
|
-
liveSnap.endReason
|
|
420
|
-
liveSnap.endReason !== 'max-tokens';
|
|
432
|
+
liveSnap.endReason === 'completed';
|
|
421
433
|
if (liveSnap.sessionId !== sid || liveSnap.pendingCount > 0 || recovered) {
|
|
422
|
-
|
|
434
|
+
retryLog('stop', 'liveSnap=' + String(liveSnap.sessionId) + '/' + (liveSnap.endReason || 'idle') + '/' + (liveSnap.pendingCount || 0));
|
|
435
|
+
cancelModuleRetry('recovered-or-stale');
|
|
423
436
|
setConfig({ retryCooldownUntil: null });
|
|
424
437
|
return;
|
|
425
438
|
}
|
|
426
|
-
|
|
427
|
-
//
|
|
439
|
+
|
|
440
|
+
// The agent is generating again. Wait for that turn to close instead of
|
|
441
|
+
// stacking another "继续" on top of it — UNLESS it has been "streaming"
|
|
442
|
+
// without ever closing for a suspiciously long time. A dropped model can
|
|
443
|
+
// leave the session stuck in running=true with no turn/end event, which
|
|
444
|
+
// would otherwise make the loop wait forever silently (the "only one
|
|
445
|
+
// retry" symptom). In that case treat it as hung and force another send.
|
|
428
446
|
if (liveSnap.streaming === true) {
|
|
429
|
-
|
|
447
|
+
if (retryState.streamingSince === null) retryState.streamingSince = now;
|
|
448
|
+
const stuckCap = Math.max(delayMs, 60000);
|
|
449
|
+
if (now - retryState.streamingSince < stuckCap) {
|
|
450
|
+
retryLog('wait', 'streaming ' + String(Math.round((now - retryState.streamingSince) / 1000)) + 's');
|
|
451
|
+
scheduleModuleRetry(delayMs);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
retryLog('force', 'streaming stuck ' + String(Math.round((now - retryState.streamingSince) / 1000)) + 's — sending anyway');
|
|
455
|
+
retryState.streamingSince = null;
|
|
456
|
+
sendModuleRetryContinue(sid, delayMs);
|
|
430
457
|
return;
|
|
431
458
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
// the old remote.sessions guess was silently skipped because no such
|
|
440
|
-
// namespace exists on the client context.
|
|
459
|
+
|
|
460
|
+
retryState.streamingSince = null;
|
|
461
|
+
sendModuleRetryContinue(sid, delayMs);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** One attempt: send "继续" and schedule the next window regardless of result. */
|
|
465
|
+
function sendModuleRetryContinue(sid, delayMs) {
|
|
441
466
|
const prompt = pluginCtx && pluginCtx.connection && pluginCtx.connection.api && pluginCtx.connection.api.sessions
|
|
442
467
|
? pluginCtx.connection.api.sessions.prompt
|
|
443
468
|
: null;
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
console.error('[dsh-done-sound] auto-retry could not send "继续":', why);
|
|
447
|
-
reportLog('error', ['auto-retry could not send "继续":', why]);
|
|
448
|
-
} catch {
|
|
449
|
-
// console may be gone during teardown
|
|
450
|
-
}
|
|
451
|
-
setConfig({
|
|
452
|
-
lastEvent: {
|
|
453
|
-
at: Date.now(),
|
|
454
|
-
kind: 'retry',
|
|
455
|
-
reason: 'retry-failed',
|
|
456
|
-
play: false,
|
|
457
|
-
retried: true,
|
|
458
|
-
retryFailed: true,
|
|
459
|
-
retryAttempt: retryState.attempt,
|
|
460
|
-
},
|
|
461
|
-
});
|
|
462
|
-
retryLater();
|
|
469
|
+
const retryLater = () => {
|
|
470
|
+
if (retryState.active) scheduleModuleRetry(delayMs);
|
|
463
471
|
};
|
|
464
472
|
if (typeof prompt !== 'function') {
|
|
465
473
|
// Nothing to retry with — stop instead of spinning a useless timer.
|
|
466
|
-
cancelModuleRetry();
|
|
474
|
+
cancelModuleRetry('prompt-api-unavailable');
|
|
467
475
|
setConfig({
|
|
468
476
|
retryCooldownUntil: null,
|
|
469
|
-
lastEvent: {
|
|
470
|
-
at: Date.now(),
|
|
471
|
-
kind: 'retry',
|
|
472
|
-
reason: 'retry-failed',
|
|
473
|
-
play: false,
|
|
474
|
-
retried: true,
|
|
475
|
-
retryFailed: true,
|
|
476
|
-
},
|
|
477
|
+
lastEvent: { at: Date.now(), kind: 'retry', reason: 'retry-failed', play: false, retried: true, retryFailed: true },
|
|
477
478
|
});
|
|
478
479
|
return;
|
|
479
480
|
}
|
|
@@ -483,13 +484,14 @@ window.__ModuleLoader__.load({
|
|
|
483
484
|
} catch {
|
|
484
485
|
// keep UTC
|
|
485
486
|
}
|
|
487
|
+
retryLog('send', '继续 attempt=' + String(retryState.attempt + 1));
|
|
486
488
|
Promise.resolve(prompt({ sessionId: sid, mode: 'queue', content: [{ type: 'text', text: '继续' }], clientTimeZone: tz }))
|
|
487
489
|
.then((res) => {
|
|
488
490
|
const result = res && res.result;
|
|
489
491
|
if (result && result.ok) {
|
|
490
492
|
try {
|
|
491
493
|
console.log('[dsh-done-sound] auto-retry sent "继续" to session', sid);
|
|
492
|
-
reportLog('info', ['auto-retry sent "继续" to session', sid]);
|
|
494
|
+
reportLog('info', ['auto-retry sent "继续" to session', sid, 'attempt', retryState.attempt + 1]);
|
|
493
495
|
} catch {
|
|
494
496
|
// ignore
|
|
495
497
|
}
|
|
@@ -505,6 +507,9 @@ window.__ModuleLoader__.load({
|
|
|
505
507
|
// never let a playback error break the retry flow
|
|
506
508
|
}
|
|
507
509
|
retryState.attempt += 1;
|
|
510
|
+
// A send that the host accepted but that revived nothing must still
|
|
511
|
+
// keep the loop alive — the next window sends "继续" again.
|
|
512
|
+
retryState.streamingSince = null;
|
|
508
513
|
setConfig({
|
|
509
514
|
lastEvent: {
|
|
510
515
|
at: Date.now(),
|
|
@@ -516,22 +521,52 @@ window.__ModuleLoader__.load({
|
|
|
516
521
|
retryAttempt: retryState.attempt,
|
|
517
522
|
},
|
|
518
523
|
});
|
|
519
|
-
// Keep the loop alive: if this attempt did not revive the session,
|
|
520
|
-
// the next tick sends "继续" again until the user stops it.
|
|
521
524
|
retryLater();
|
|
522
525
|
} else {
|
|
523
526
|
const err = result && result.error ? result.error.message : JSON.stringify(result);
|
|
524
|
-
|
|
527
|
+
sendRetryFail(err || 'prompt not accepted', delayMs);
|
|
525
528
|
}
|
|
526
529
|
})
|
|
527
|
-
.catch((err) =>
|
|
530
|
+
.catch((err) => sendRetryFail(err && err.message ? err.message : String(err), delayMs));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function sendRetryFail(why, delayMs) {
|
|
534
|
+
try {
|
|
535
|
+
console.error('[dsh-done-sound] auto-retry could not send "继续":', why);
|
|
536
|
+
reportLog('error', ['auto-retry could not send "继续":', why]);
|
|
537
|
+
} catch {
|
|
538
|
+
// console may be gone during teardown
|
|
539
|
+
}
|
|
540
|
+
setConfig({
|
|
541
|
+
lastEvent: {
|
|
542
|
+
at: Date.now(),
|
|
543
|
+
kind: 'retry',
|
|
544
|
+
reason: 'retry-failed',
|
|
545
|
+
play: false,
|
|
546
|
+
retried: true,
|
|
547
|
+
retryFailed: true,
|
|
548
|
+
retryAttempt: retryState.attempt,
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
if (retryState.active) scheduleModuleRetry(delayMs);
|
|
528
552
|
}
|
|
553
|
+
|
|
554
|
+
function retryLog(action, detail) {
|
|
555
|
+
try {
|
|
556
|
+
console.info('[dsh-done-sound] retry:', action, detail || '');
|
|
557
|
+
reportLog('info', ['retry:', action, detail || '']);
|
|
558
|
+
} catch {
|
|
559
|
+
// ignore — logging must never break the loop
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
529
563
|
function startModuleRetry(sessionId, endTurnAtError, delayMs) {
|
|
530
|
-
cancelModuleRetry();
|
|
564
|
+
cancelModuleRetry('re-arm');
|
|
531
565
|
retryState.active = true;
|
|
532
566
|
retryState.sessionId = sessionId;
|
|
533
567
|
retryState.endTurnAtError = endTurnAtError;
|
|
534
568
|
retryState.attempt = 0;
|
|
569
|
+
retryState.streamingSince = null;
|
|
535
570
|
scheduleModuleRetry(delayMs);
|
|
536
571
|
}
|
|
537
572
|
|
|
@@ -1566,3 +1601,4 @@ if (snap.pendingCount > 0) {
|
|
|
1566
1601
|
},
|
|
1567
1602
|
});
|
|
1568
1603
|
|
|
1604
|
+
|