@scotthuang/agent-knock-knock 0.12.13 → 0.12.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -5
  3. package/dist/src/callback-cli-adapter.d.ts +9 -6
  4. package/dist/src/callback-cli-adapter.js +119 -30
  5. package/dist/src/callback-cli-adapter.js.map +1 -1
  6. package/dist/src/callback-outbox-policy.d.ts +39 -0
  7. package/dist/src/callback-outbox-policy.js +163 -6
  8. package/dist/src/callback-outbox-policy.js.map +1 -1
  9. package/dist/src/callback-outbox-service.d.ts +28 -1
  10. package/dist/src/callback-outbox-service.js +413 -82
  11. package/dist/src/callback-outbox-service.js.map +1 -1
  12. package/dist/src/callback-outbox-settlement.d.ts +14 -1
  13. package/dist/src/callback-outbox-settlement.js +151 -30
  14. package/dist/src/callback-outbox-settlement.js.map +1 -1
  15. package/dist/src/callback-route-authority.d.ts +59 -0
  16. package/dist/src/callback-route-authority.js +123 -0
  17. package/dist/src/callback-route-authority.js.map +1 -0
  18. package/dist/src/callback-transport.d.ts +134 -0
  19. package/dist/src/callback-transport.js +256 -0
  20. package/dist/src/callback-transport.js.map +1 -0
  21. package/dist/src/cli-core.js +17 -4
  22. package/dist/src/cli-core.js.map +1 -1
  23. package/dist/src/codex-lifecycle-compatibility.js +2 -1
  24. package/dist/src/codex-lifecycle-compatibility.js.map +1 -1
  25. package/dist/src/deferred-foreground-application-service.d.ts +18 -0
  26. package/dist/src/deferred-foreground-application-service.js +72 -0
  27. package/dist/src/deferred-foreground-application-service.js.map +1 -1
  28. package/dist/src/deferred-foreground-authority-cli-adapter.js +2 -1
  29. package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
  30. package/dist/src/deferred-foreground-recovery-cli-adapter.d.ts +1 -1
  31. package/dist/src/deferred-foreground-recovery-cli-adapter.js +108 -37
  32. package/dist/src/deferred-foreground-recovery-cli-adapter.js.map +1 -1
  33. package/dist/src/deferred-foreground-recovery-service.d.ts +1 -1
  34. package/dist/src/deferred-foreground-recovery-service.js +10 -3
  35. package/dist/src/deferred-foreground-recovery-service.js.map +1 -1
  36. package/dist/src/deferred-foreground-transfer-policy.d.ts +1 -1
  37. package/dist/src/deferred-foreground-transfer-policy.js +3 -2
  38. package/dist/src/deferred-foreground-transfer-policy.js.map +1 -1
  39. package/dist/src/deferred-foreground-transfer.d.ts +17 -1
  40. package/dist/src/deferred-foreground-transfer.js +244 -35
  41. package/dist/src/deferred-foreground-transfer.js.map +1 -1
  42. package/dist/src/deferred-foreground-user-close.d.ts +18 -0
  43. package/dist/src/deferred-foreground-user-close.js +180 -0
  44. package/dist/src/deferred-foreground-user-close.js.map +1 -0
  45. package/dist/src/openclaw-callback-transport.d.ts +4 -1
  46. package/dist/src/openclaw-callback-transport.js +403 -10
  47. package/dist/src/openclaw-callback-transport.js.map +1 -1
  48. package/dist/src/openclaw-managed-callback-cli-adapter.d.ts +23 -0
  49. package/dist/src/openclaw-managed-callback-cli-adapter.js +77 -0
  50. package/dist/src/openclaw-managed-callback-cli-adapter.js.map +1 -0
  51. package/dist/src/openclaw-plugin-command-adapter.js +28 -31
  52. package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
  53. package/dist/src/openclaw-plugin-helpers.js +8 -0
  54. package/dist/src/openclaw-plugin-helpers.js.map +1 -1
  55. package/dist/src/openclaw-plugin-schemas.d.ts +20 -1
  56. package/dist/src/openclaw-plugin-schemas.js +26 -2
  57. package/dist/src/openclaw-plugin-schemas.js.map +1 -1
  58. package/dist/src/protocol.d.ts +3 -0
  59. package/dist/src/protocol.js +4 -0
  60. package/dist/src/protocol.js.map +1 -1
  61. package/dist/src/store.d.ts +9 -0
  62. package/dist/src/store.js +79 -1
  63. package/dist/src/store.js.map +1 -1
  64. package/dist/src/terminal-acceptance-cli-adapter.d.ts +2 -0
  65. package/dist/src/terminal-acceptance-cli-adapter.js +62 -12
  66. package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
  67. package/dist/src/terminal-action-projection.d.ts +1 -2
  68. package/dist/src/terminal-action-projection.js +4 -5
  69. package/dist/src/terminal-action-projection.js.map +1 -1
  70. package/dist/src/terminal-agent-bridge.d.ts +82 -0
  71. package/dist/src/terminal-agent-bridge.js +539 -111
  72. package/dist/src/terminal-agent-bridge.js.map +1 -1
  73. package/dist/src/terminal-command-cli-adapter.d.ts +9 -0
  74. package/dist/src/terminal-command-cli-adapter.js +2130 -5
  75. package/dist/src/terminal-command-cli-adapter.js.map +1 -1
  76. package/dist/src/terminal-dispatch-application.d.ts +4 -1
  77. package/dist/src/terminal-dispatch-application.js +11 -2
  78. package/dist/src/terminal-dispatch-application.js.map +1 -1
  79. package/dist/src/terminal-dispatch-execution.js +3 -1
  80. package/dist/src/terminal-dispatch-execution.js.map +1 -1
  81. package/dist/src/terminal-dispatch-ledger-codec.d.ts +3 -0
  82. package/dist/src/terminal-dispatch-ledger-codec.js +46 -19
  83. package/dist/src/terminal-dispatch-ledger-codec.js.map +1 -1
  84. package/dist/src/terminal-dispatch-presenter.js +5 -3
  85. package/dist/src/terminal-dispatch-presenter.js.map +1 -1
  86. package/dist/src/terminal-dispatch-receipt.d.ts +3 -0
  87. package/dist/src/terminal-dispatch-receipt.js +15 -1
  88. package/dist/src/terminal-dispatch-receipt.js.map +1 -1
  89. package/dist/src/terminal-dispatch-recovery-cli-adapter.js +68 -7
  90. package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
  91. package/dist/src/terminal-dispatch-recovery-service.d.ts +2 -0
  92. package/dist/src/terminal-dispatch-recovery-service.js +9 -0
  93. package/dist/src/terminal-dispatch-recovery-service.js.map +1 -1
  94. package/dist/src/terminal-identity-authority-cli-adapter.d.ts +2 -0
  95. package/dist/src/terminal-identity-authority-cli-adapter.js +30 -27
  96. package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
  97. package/dist/src/terminal-list-cli-adapter.d.ts +1 -0
  98. package/dist/src/terminal-list-cli-adapter.js +50 -31
  99. package/dist/src/terminal-list-cli-adapter.js.map +1 -1
  100. package/dist/src/terminal-list-renderer.d.ts +9 -1
  101. package/dist/src/terminal-list-renderer.js +219 -98
  102. package/dist/src/terminal-list-renderer.js.map +1 -1
  103. package/dist/src/terminal-maintenance-cli-adapter.js +253 -326
  104. package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
  105. package/dist/src/terminal-monitor-application-service.d.ts +5 -0
  106. package/dist/src/terminal-monitor-application-service.js +5 -0
  107. package/dist/src/terminal-monitor-application-service.js.map +1 -1
  108. package/dist/src/terminal-monitor-cli-adapter.d.ts +25 -1
  109. package/dist/src/terminal-monitor-cli-adapter.js +170 -27
  110. package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
  111. package/dist/src/terminal-monitor-reconciliation-eligibility.d.ts +1 -1
  112. package/dist/src/terminal-monitor-reconciliation-eligibility.js +220 -32
  113. package/dist/src/terminal-monitor-reconciliation-eligibility.js.map +1 -1
  114. package/dist/src/terminal-monitor-state-cli-adapter.d.ts +1 -1
  115. package/dist/src/terminal-monitor-state-cli-adapter.js +808 -147
  116. package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
  117. package/dist/src/terminal-monitor-state-reconciliation-service.d.ts +2 -1
  118. package/dist/src/terminal-monitor-state-reconciliation-service.js +12 -0
  119. package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -1
  120. package/dist/src/terminal-monitor-supervision-cli-adapter.d.ts +2 -0
  121. package/dist/src/terminal-monitor-supervision-cli-adapter.js +31 -11
  122. package/dist/src/terminal-monitor-supervision-cli-adapter.js.map +1 -1
  123. package/dist/src/terminal-status-cli-adapter.d.ts +1 -0
  124. package/dist/src/terminal-status-cli-adapter.js +30 -28
  125. package/dist/src/terminal-status-cli-adapter.js.map +1 -1
  126. package/dist/src/terminal-submission-acceptance.js +78 -25
  127. package/dist/src/terminal-submission-acceptance.js.map +1 -1
  128. package/dist/src/terminal-submission-retry-service.d.ts +114 -0
  129. package/dist/src/terminal-submission-retry-service.js +589 -0
  130. package/dist/src/terminal-submission-retry-service.js.map +1 -0
  131. package/dist/src/terminal-watch-callback-cli-adapter.d.ts +10 -2
  132. package/dist/src/terminal-watch-callback-cli-adapter.js +76 -37
  133. package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
  134. package/dist/src/terminal-watch-cli-adapter.js +41 -17
  135. package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
  136. package/dist/src/terminal-watch-service.d.ts +11 -6
  137. package/dist/src/terminal-watch-service.js +210 -41
  138. package/dist/src/terminal-watch-service.js.map +1 -1
  139. package/dist/src/terminal-watch-store.d.ts +23 -0
  140. package/dist/src/terminal-watch-store.js +162 -4
  141. package/dist/src/terminal-watch-store.js.map +1 -1
  142. package/docs/quickstart-tmux.md +1 -1
  143. package/package.json +1 -1
  144. package/templates/openclaw-skills/agent-knock-knock/SKILL.md +8 -5
@@ -10,6 +10,39 @@ export const DEFERRED_FOREGROUND_TRANSFER_SCHEMA = "agent-knock-knock/deferred-f
10
10
  export const DEFERRED_FOREGROUND_TRANSFER_VERSION = 2;
11
11
  export const DEFERRED_FOREGROUND_TRANSFER_LEGACY_VERSION = 1;
12
12
  const TRANSFER_STATE_FILE = "state.json";
13
+ export function deferredForegroundActiveMessageId(transfer) {
14
+ return transfer.submission_retry_message_id ?? transfer.message_id;
15
+ }
16
+ export function deferredForegroundActivePreparedAt(transfer) {
17
+ return transfer.submission_retry_prepared_at ?? transfer.prepared_at;
18
+ }
19
+ export function isDeferredForegroundSubmissionRetryPending(transfer) {
20
+ const common = transfer.status === "uncertain" &&
21
+ transfer.input_stage === "enter_dispatched" &&
22
+ Boolean(transfer.submission_retry_attempt_id) &&
23
+ Boolean(transfer.submission_retry_mode) &&
24
+ Boolean(transfer.submission_retry_message_id) &&
25
+ Boolean(transfer.submission_retry_prepared_at) &&
26
+ Boolean(transfer.submission_retry_enter_reserved_at) &&
27
+ Boolean(transfer.submission_retry_enter_dispatched_at) &&
28
+ deferredForegroundActiveMessageId(transfer) ===
29
+ transfer.submission_retry_message_id;
30
+ return common && (transfer.submission_retry_mode === "exact_draft_enter"
31
+ ? transfer.submission_retry_message_id === transfer.message_id &&
32
+ transfer.submission_retry_text_reserved_at === undefined &&
33
+ transfer.submission_retry_text_injected_at === undefined &&
34
+ Boolean(transfer.text_injected_at)
35
+ : Boolean(transfer.submission_retry_text_reserved_at) &&
36
+ Boolean(transfer.submission_retry_text_injected_at));
37
+ }
38
+ export function deferredForegroundActiveTextInjectedAt(transfer) {
39
+ return transfer.submission_retry_text_injected_at ??
40
+ transfer.text_injected_at;
41
+ }
42
+ export function deferredForegroundActiveEnterDispatchedAt(transfer) {
43
+ return transfer.submission_retry_enter_dispatched_at ??
44
+ transfer.enter_dispatched_at;
45
+ }
13
46
  export class DeferredForegroundTransferConflictError extends Error {
14
47
  code = "AKK_DEFERRED_FOREGROUND_TRANSFER_CONFLICT";
15
48
  transferId;
@@ -54,6 +87,12 @@ export function assertDeferredForegroundTransfer(value, expectedTransferId, opti
54
87
  assertDeferredForegroundTransferResolution(transfer);
55
88
  assertDeferredForegroundTransferAbort(transfer, provedTerminalInputNotStarted);
56
89
  assertDeferredForegroundTransferFailure(transfer);
90
+ assertDeferredForegroundUserAbandonment(transfer);
91
+ }
92
+ function deferredForegroundEvidenceStatus(value) {
93
+ return value.status === "user_abandoned"
94
+ ? value.origin_status ?? value.status
95
+ : value.status;
57
96
  }
58
97
  function assertDeferredForegroundTransferHeader(value, expectedTransferId, options) {
59
98
  assertOnlyKeys(value, [
@@ -97,6 +136,14 @@ function assertDeferredForegroundTransferHeader(value, expectedTransferId, optio
97
136
  "enter_dispatched_at",
98
137
  "agent_accepted_at",
99
138
  "message_id",
139
+ "submission_retry_attempt_id",
140
+ "submission_retry_mode",
141
+ "submission_retry_message_id",
142
+ "submission_retry_prepared_at",
143
+ "submission_retry_text_reserved_at",
144
+ "submission_retry_text_injected_at",
145
+ "submission_retry_enter_reserved_at",
146
+ "submission_retry_enter_dispatched_at",
100
147
  "turn_id",
101
148
  "state_path",
102
149
  "target_native_thread_id",
@@ -132,7 +179,9 @@ function assertDeferredForegroundTransferHeader(value, expectedTransferId, optio
132
179
  "uncertain_at",
133
180
  "recovered_at",
134
181
  "error",
135
- "do_not_retry"
182
+ "do_not_retry",
183
+ "origin_status",
184
+ "user_abandoned_at"
136
185
  ], "deferred foreground transfer");
137
186
  if (value.schema !== DEFERRED_FOREGROUND_TRANSFER_SCHEMA ||
138
187
  ![
@@ -347,6 +396,21 @@ function assertDeferredForegroundTransferTarget(value) {
347
396
  assertOptionalTimestamp(value[field], `deferred transfer ${field}`);
348
397
  }
349
398
  assertOptionalNonEmptyString(value.message_id, "deferred transfer message_id");
399
+ assertOptionalNonEmptyString(value.submission_retry_attempt_id, "deferred transfer submission retry attempt id");
400
+ if (value.submission_retry_mode !== undefined &&
401
+ !["exact_draft_enter", "replacement_send"].includes(value.submission_retry_mode)) {
402
+ throw new Error("deferred transfer submission retry mode is invalid");
403
+ }
404
+ assertOptionalNonEmptyString(value.submission_retry_message_id, "deferred transfer submission retry message id");
405
+ for (const field of [
406
+ "submission_retry_prepared_at",
407
+ "submission_retry_text_reserved_at",
408
+ "submission_retry_text_injected_at",
409
+ "submission_retry_enter_reserved_at",
410
+ "submission_retry_enter_dispatched_at"
411
+ ]) {
412
+ assertOptionalTimestamp(value[field], `deferred transfer ${field}`);
413
+ }
350
414
  assertOptionalNonEmptyString(value.turn_id, "deferred transfer turn_id");
351
415
  if (value.state_path !== undefined) {
352
416
  assertAbsolutePath(value.state_path, "deferred transfer state_path");
@@ -357,13 +421,14 @@ function assertDeferredForegroundTransferTarget(value) {
357
421
  }
358
422
  }
359
423
  function assertDeferredForegroundTransferPreparation(value) {
424
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
360
425
  const requiresTargetPrepared = [
361
426
  "target_prepared",
362
427
  "dispatch_started",
363
428
  "committed",
364
429
  "resolved",
365
430
  "uncertain"
366
- ].includes(String(value.status));
431
+ ].includes(String(evidenceStatus));
367
432
  const targetPreparedFields = [
368
433
  value.target_prepared_at,
369
434
  value.target_prepared_revision,
@@ -379,7 +444,7 @@ function assertDeferredForegroundTransferPreparation(value) {
379
444
  const hasAllTargetPreparedFields = targetPreparedFields.every((field) => field !== undefined);
380
445
  if (hasAnyTargetPreparedField !== hasAllTargetPreparedFields ||
381
446
  (requiresTargetPrepared && !hasAllTargetPreparedFields)) {
382
- throw new Error(`${String(value.status)} deferred transfer requires its target/Turn identity`);
447
+ throw new Error(`${String(evidenceStatus)} deferred transfer requires its target/Turn identity`);
383
448
  }
384
449
  if (hasAllTargetPreparedFields) {
385
450
  if (!value.source_reserved_at ||
@@ -397,45 +462,89 @@ function assertDeferredForegroundTransferPreparation(value) {
397
462
  "resolved",
398
463
  "uncertain"
399
464
  ]
400
- .includes(String(value.status)) &&
465
+ .includes(String(evidenceStatus)) &&
401
466
  !value.source_reserved_at) {
402
- throw new Error(`${String(value.status)} deferred transfer requires source_reserved_at`);
467
+ throw new Error(`${String(evidenceStatus)} deferred transfer requires source_reserved_at`);
403
468
  }
404
- if (value.status === "prepared" &&
469
+ if (evidenceStatus === "prepared" &&
405
470
  (value.source_reserved_at !== undefined || hasAnyTargetPreparedField)) {
406
471
  throw new Error("prepared deferred transfer cannot carry reservation evidence");
407
472
  }
408
- if (value.status === "source_reserved" && hasAnyTargetPreparedField) {
473
+ if (evidenceStatus === "source_reserved" && hasAnyTargetPreparedField) {
409
474
  throw new Error("source_reserved deferred transfer cannot carry target evidence");
410
475
  }
411
476
  }
412
477
  function assertDeferredForegroundTransferDispatch(value) {
413
- if (["dispatch_started", "committed", "resolved", "uncertain"].includes(String(value.status)) &&
478
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
479
+ if (["dispatch_started", "committed", "resolved", "uncertain"].includes(String(evidenceStatus)) &&
414
480
  (!value.dispatch_started_at || value.input_stage === "none")) {
415
- throw new Error(`${String(value.status)} deferred transfer requires dispatch-start evidence`);
481
+ throw new Error(`${String(evidenceStatus)} deferred transfer requires dispatch-start evidence`);
416
482
  }
417
483
  assertInputStageEvidence(value);
418
484
  if ([
419
485
  "prepared",
420
486
  "source_reserved",
421
487
  "target_prepared"
422
- ].includes(String(value.status)) &&
488
+ ].includes(String(evidenceStatus)) &&
423
489
  value.input_stage !== "none") {
424
- throw new Error(`${String(value.status)} deferred transfer cannot carry input evidence`);
490
+ throw new Error(`${String(evidenceStatus)} deferred transfer cannot carry input evidence`);
425
491
  }
426
- const provedTerminalInputNotStarted = ["aborted", "abort_resolved"].includes(String(value.status)) &&
492
+ const provedTerminalInputNotStarted = ["aborted", "abort_resolved"].includes(String(evidenceStatus)) &&
427
493
  value.input_stage === "dispatch_started" &&
428
494
  value.terminal_input_not_started_at !== undefined;
429
- if (["aborted", "abort_resolved"].includes(String(value.status)) &&
495
+ if (["aborted", "abort_resolved"].includes(String(evidenceStatus)) &&
430
496
  value.input_stage !== "none" &&
431
497
  !provedTerminalInputNotStarted) {
432
- throw new Error(`${String(value.status)} deferred transfer requires exact no-input proof`);
498
+ throw new Error(`${String(evidenceStatus)} deferred transfer requires exact no-input proof`);
433
499
  }
500
+ assertDeferredForegroundSubmissionRetryEvidence(value);
434
501
  assertTimestampOrder(value);
435
502
  return provedTerminalInputNotStarted;
436
503
  }
504
+ function assertDeferredForegroundSubmissionRetryEvidence(value) {
505
+ const retryFields = [
506
+ value.submission_retry_attempt_id,
507
+ value.submission_retry_mode,
508
+ value.submission_retry_message_id,
509
+ value.submission_retry_prepared_at
510
+ ];
511
+ const hasAny = retryFields.some((field) => field !== undefined) || [
512
+ value.submission_retry_text_reserved_at,
513
+ value.submission_retry_text_injected_at,
514
+ value.submission_retry_enter_reserved_at,
515
+ value.submission_retry_enter_dispatched_at
516
+ ].some((field) => field !== undefined);
517
+ if (!hasAny)
518
+ return;
519
+ const hasCommon = retryFields.every((field) => field !== undefined);
520
+ const replacement = value.submission_retry_mode === "replacement_send";
521
+ const exactDraft = value.submission_retry_mode === "exact_draft_enter";
522
+ const replacementPrefixIsContinuous = value.submission_retry_text_injected_at === undefined ||
523
+ value.submission_retry_text_reserved_at !== undefined;
524
+ const enterPrefixIsContinuous = value.submission_retry_enter_reserved_at === undefined ||
525
+ exactDraft || value.submission_retry_text_injected_at !== undefined;
526
+ const dispatchPrefixIsContinuous = value.submission_retry_enter_dispatched_at === undefined ||
527
+ value.submission_retry_enter_reserved_at !== undefined;
528
+ if (!hasCommon ||
529
+ !value.uncertain_at ||
530
+ (!replacement && !exactDraft) ||
531
+ value.submission_retry_message_id !== value.message_id ||
532
+ value.submission_retry_prepared_at !== value.prepared_at ||
533
+ (exactDraft && (value.submission_retry_text_reserved_at !== undefined ||
534
+ value.submission_retry_text_injected_at !== undefined)) ||
535
+ !replacementPrefixIsContinuous ||
536
+ !enterPrefixIsContinuous ||
537
+ !dispatchPrefixIsContinuous ||
538
+ (value.input_stage === "enter_dispatched" &&
539
+ value.submission_retry_enter_dispatched_at === undefined) ||
540
+ (value.submission_retry_enter_dispatched_at !== undefined &&
541
+ !["enter_dispatched", "agent_accepted"].includes(value.input_stage))) {
542
+ throw new Error("deferred foreground submission retry stage evidence is inconsistent");
543
+ }
544
+ }
437
545
  function assertDeferredForegroundTransferCommit(value) {
438
- const committed = ["committed", "resolved"].includes(String(value.status));
546
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
547
+ const committed = ["committed", "resolved"].includes(String(evidenceStatus));
439
548
  const committedFields = [
440
549
  value.target_native_thread_id,
441
550
  value.target_accepted_revision,
@@ -510,6 +619,7 @@ function assertDeferredForegroundTransferCommit(value) {
510
619
  }
511
620
  }
512
621
  function assertDeferredForegroundTransferResolution(value) {
622
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
513
623
  const resolvedFields = [
514
624
  value.target_after_revision,
515
625
  value.target_after_status,
@@ -522,12 +632,12 @@ function assertDeferredForegroundTransferResolution(value) {
522
632
  ];
523
633
  const hasAnyResolvedField = resolvedFields.some((field) => field !== undefined);
524
634
  const hasAllResolvedFields = resolvedFields.every((field) => field !== undefined);
525
- if (value.status === "resolved"
635
+ if (evidenceStatus === "resolved"
526
636
  ? !hasAllResolvedFields
527
637
  : hasAnyResolvedField) {
528
638
  throw new Error("resolved deferred transfer requires exact final Session evidence");
529
639
  }
530
- if (value.status === "resolved") {
640
+ if (evidenceStatus === "resolved") {
531
641
  if (!isPositiveSafeInteger(value.target_after_revision) ||
532
642
  value.target_after_revision !==
533
643
  Number(value.target_accepted_revision) + 1 ||
@@ -558,7 +668,8 @@ function assertDeferredForegroundTransferResolution(value) {
558
668
  }
559
669
  }
560
670
  function assertDeferredForegroundTransferAbort(value, provedTerminalInputNotStarted) {
561
- if (["aborted", "abort_resolved"].includes(String(value.status))
671
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
672
+ if (["aborted", "abort_resolved"].includes(String(evidenceStatus))
562
673
  ? !value.aborted_at || !(value.input_stage === "none" || provedTerminalInputNotStarted)
563
674
  : value.aborted_at !== undefined) {
564
675
  throw new Error("only a proven zero-input deferred transfer can carry an aborted receipt");
@@ -569,7 +680,7 @@ function assertDeferredForegroundTransferAbort(value, provedTerminalInputNotStar
569
680
  value.enter_dispatched_at !== undefined ||
570
681
  value.agent_accepted_at !== undefined
571
682
  : value.input_stage === "dispatch_started" &&
572
- ["aborted", "abort_resolved"].includes(String(value.status))) {
683
+ ["aborted", "abort_resolved"].includes(String(evidenceStatus))) {
573
684
  throw new Error("terminal-input-not-started proof must fence only a dispatch intent");
574
685
  }
575
686
  const abortCleanupSourceFields = [
@@ -589,7 +700,7 @@ function assertDeferredForegroundTransferAbort(value, provedTerminalInputNotStar
589
700
  ...abortCleanupSourceFields,
590
701
  ...abortCleanupTargetFields
591
702
  ].some((field) => field !== undefined);
592
- if (value.status === "abort_resolved") {
703
+ if (evidenceStatus === "abort_resolved") {
593
704
  if (abortCleanupSourceFields.some((field) => field === undefined)) {
594
705
  throw new Error("resolved deferred abort requires exact source cleanup evidence");
595
706
  }
@@ -649,10 +760,11 @@ function assertDeferredForegroundTransferAbort(value, provedTerminalInputNotStar
649
760
  }
650
761
  }
651
762
  function assertDeferredForegroundTransferFailure(value) {
652
- if (value.status === "uncertain"
763
+ const evidenceStatus = deferredForegroundEvidenceStatus(value);
764
+ if (evidenceStatus === "uncertain"
653
765
  ? !value.uncertain_at || value.do_not_retry !== true ||
654
766
  value.input_stage === "none"
655
- : ["committed", "resolved"].includes(String(value.status)) &&
767
+ : ["committed", "resolved"].includes(String(evidenceStatus)) &&
656
768
  value.uncertain_at !== undefined
657
769
  ? !value.recovered_at || value.do_not_retry !== true
658
770
  : value.uncertain_at !== undefined ||
@@ -666,10 +778,25 @@ function assertDeferredForegroundTransferFailure(value) {
666
778
  "abort_resolved",
667
779
  "uncertain",
668
780
  ...(value.recovered_at ? ["committed", "resolved"] : [])
669
- ].includes(String(value.status))) {
781
+ ].includes(String(evidenceStatus))) {
670
782
  throw new Error("deferred transfer error is allowed only on failure/recovery receipts");
671
783
  }
672
784
  }
785
+ function assertDeferredForegroundUserAbandonment(value) {
786
+ const origin = value.origin_status;
787
+ const abandonedAt = value.user_abandoned_at;
788
+ if (value.status !== "user_abandoned") {
789
+ if (origin !== undefined || abandonedAt !== undefined) {
790
+ throw new Error("user abandonment evidence is allowed only on a user_abandoned receipt");
791
+ }
792
+ return;
793
+ }
794
+ if (!USER_ABANDONMENT_ORIGIN_STATUSES.has(origin) ||
795
+ abandonedAt === undefined) {
796
+ throw new Error("user_abandoned deferred transfer requires its origin status and timestamp");
797
+ }
798
+ assertTimestamp(abandonedAt, "deferred transfer user_abandoned_at");
799
+ }
673
800
  export function saveDeferredForegroundTransfer(storeDir, value, options) {
674
801
  assertDeferredForegroundTransfer(value, undefined, {
675
802
  allowMissingRevision: true
@@ -773,20 +900,24 @@ function assertTransferAdvance(current, candidate) {
773
900
  `immutable ${changed}`);
774
901
  }
775
902
  const allowed = {
776
- prepared: ["source_reserved", "aborted"],
777
- source_reserved: ["target_prepared", "aborted"],
778
- target_prepared: ["dispatch_started", "uncertain", "aborted"],
903
+ prepared: ["source_reserved", "aborted", "user_abandoned"],
904
+ source_reserved: ["target_prepared", "aborted", "user_abandoned"],
905
+ target_prepared: [
906
+ "dispatch_started", "uncertain", "aborted", "user_abandoned"
907
+ ],
779
908
  dispatch_started: [
780
909
  "dispatch_started",
781
910
  "committed",
782
911
  "uncertain",
783
- "aborted"
912
+ "aborted",
913
+ "user_abandoned"
784
914
  ],
785
- committed: ["resolved"],
915
+ committed: ["resolved", "user_abandoned"],
786
916
  resolved: [],
787
- aborted: ["abort_resolved"],
917
+ aborted: ["abort_resolved", "user_abandoned"],
788
918
  abort_resolved: [],
789
- uncertain: ["committed"]
919
+ uncertain: ["uncertain", "committed", "user_abandoned"],
920
+ user_abandoned: []
790
921
  };
791
922
  if (!allowed[current.status].includes(candidate.status)) {
792
923
  throw new Error(`deferred foreground transfer cannot move from ${current.status} to ` +
@@ -811,6 +942,21 @@ function assertTransferAdvance(current, candidate) {
811
942
  throw new Error(`deferred foreground transfer ${current.transfer_id} cannot change ${field}`);
812
943
  }
813
944
  }
945
+ for (const field of [
946
+ "submission_retry_attempt_id",
947
+ "submission_retry_mode",
948
+ "submission_retry_message_id",
949
+ "submission_retry_prepared_at",
950
+ "submission_retry_text_reserved_at",
951
+ "submission_retry_text_injected_at",
952
+ "submission_retry_enter_reserved_at",
953
+ "submission_retry_enter_dispatched_at"
954
+ ]) {
955
+ if (current[field] !== undefined &&
956
+ JSON.stringify(current[field]) !== JSON.stringify(candidate[field])) {
957
+ throw new Error(`deferred foreground transfer ${current.transfer_id} cannot change ${field}`);
958
+ }
959
+ }
814
960
  for (const field of [
815
961
  "source_reserved_at",
816
962
  "target_prepared_at",
@@ -851,13 +997,19 @@ function assertTransferAdvance(current, candidate) {
851
997
  "uncertain_at",
852
998
  "recovered_at",
853
999
  "error",
854
- "do_not_retry"
1000
+ "do_not_retry",
1001
+ "origin_status",
1002
+ "user_abandoned_at"
855
1003
  ]) {
856
1004
  if (current[field] !== undefined &&
857
1005
  JSON.stringify(current[field]) !== JSON.stringify(candidate[field])) {
858
1006
  throw new Error(`deferred foreground transfer ${current.transfer_id} cannot change ${field}`);
859
1007
  }
860
1008
  }
1009
+ if (candidate.status === "user_abandoned" &&
1010
+ candidate.origin_status !== current.status) {
1011
+ throw new Error("deferred foreground user abandonment must freeze its origin status");
1012
+ }
861
1013
  if (candidate.status === "aborted" &&
862
1014
  current.input_stage !== "none" &&
863
1015
  !(current.input_stage === "dispatch_started" &&
@@ -865,6 +1017,37 @@ function assertTransferAdvance(current, candidate) {
865
1017
  candidate.terminal_input_not_started_at !== undefined)) {
866
1018
  throw new Error("deferred foreground transfer cannot abort after input may start");
867
1019
  }
1020
+ if (current.submission_retry_attempt_id === undefined &&
1021
+ candidate.submission_retry_attempt_id !== undefined &&
1022
+ (current.status !== "uncertain" ||
1023
+ candidate.status !== "uncertain" ||
1024
+ current.input_stage !== "text_injected" ||
1025
+ !candidate.submission_retry_mode ||
1026
+ !candidate.submission_retry_message_id ||
1027
+ !candidate.submission_retry_prepared_at)) {
1028
+ throw new Error("deferred foreground submission retry requires uncertain/text_injected authority");
1029
+ }
1030
+ if (candidate.submission_retry_attempt_id !== undefined &&
1031
+ (!candidate.submission_retry_message_id ||
1032
+ !candidate.submission_retry_prepared_at ||
1033
+ !candidate.submission_retry_mode ||
1034
+ !candidate.uncertain_at ||
1035
+ candidate.submission_retry_message_id !== candidate.message_id ||
1036
+ candidate.submission_retry_prepared_at !== candidate.prepared_at ||
1037
+ (candidate.submission_retry_mode === "replacement_send"
1038
+ ? (candidate.submission_retry_text_injected_at !== undefined &&
1039
+ !candidate.submission_retry_text_reserved_at) ||
1040
+ (candidate.submission_retry_enter_reserved_at !== undefined &&
1041
+ !candidate.submission_retry_text_injected_at)
1042
+ : candidate.submission_retry_message_id !== candidate.message_id ||
1043
+ candidate.submission_retry_text_reserved_at !== undefined ||
1044
+ candidate.submission_retry_text_injected_at !== undefined) ||
1045
+ (candidate.submission_retry_enter_dispatched_at !== undefined &&
1046
+ (!candidate.submission_retry_enter_reserved_at ||
1047
+ (candidate.input_stage !== "enter_dispatched" &&
1048
+ candidate.input_stage !== "agent_accepted"))))) {
1049
+ throw new Error("deferred foreground submission retry stage evidence is inconsistent");
1050
+ }
868
1051
  }
869
1052
  function bindingIsMonotonicTargetRefinement(before, after) {
870
1053
  return before.binding_id === after.binding_id &&
@@ -921,6 +1104,15 @@ function assertTimestampOrder(value) {
921
1104
  value.agent_accepted_at
922
1105
  ].filter((candidate) => candidate !== undefined);
923
1106
  assertMonotonicTimestamps(inputTimeline);
1107
+ if (value.submission_retry_attempt_id) {
1108
+ assertMonotonicTimestamps([
1109
+ value.uncertain_at,
1110
+ value.submission_retry_text_reserved_at,
1111
+ value.submission_retry_text_injected_at,
1112
+ value.submission_retry_enter_reserved_at,
1113
+ value.submission_retry_enter_dispatched_at
1114
+ ].filter((candidate) => candidate !== undefined));
1115
+ }
924
1116
  const latestInputEvidence = inputTimeline[inputTimeline.length - 1];
925
1117
  if (value.aborted_at) {
926
1118
  assertTimestampNotBefore(value.aborted_at, latestInputEvidence);
@@ -936,11 +1128,13 @@ function assertTimestampOrder(value) {
936
1128
  // Recovery may prove acceptance after uncertainty was recorded, so the
937
1129
  // uncertainty timestamp is ordered after the evidence known at its own
938
1130
  // stage, not unconditionally after a later agent_accepted_at.
939
- const latestPossibleDispatch = value.enter_dispatched_at ??
940
- value.text_injected_at ?? value.dispatch_started_at ??
941
- value.target_prepared_at ?? value.source_reserved_at ?? value.prepared_at;
1131
+ const latestPossibleDispatch = value.submission_retry_attempt_id
1132
+ ? value.text_injected_at ?? value.dispatch_started_at
1133
+ : value.enter_dispatched_at ?? value.text_injected_at ?? value.dispatch_started_at ??
1134
+ value.target_prepared_at ?? value.source_reserved_at ?? value.prepared_at;
942
1135
  assertTimestampNotBefore(value.uncertain_at, latestPossibleDispatch);
943
- if (value.status === "uncertain" && value.input_stage === "agent_accepted") {
1136
+ if (deferredForegroundEvidenceStatus(value) === "uncertain" &&
1137
+ value.input_stage === "agent_accepted") {
944
1138
  assertTimestampNotBefore(value.uncertain_at, value.agent_accepted_at);
945
1139
  }
946
1140
  }
@@ -956,6 +1150,11 @@ function assertTimestampOrder(value) {
956
1150
  assertTimestampNotBefore(value.resolved_at, value.committed_at);
957
1151
  assertTimestampNotBefore(value.resolved_at, value.recovered_at);
958
1152
  }
1153
+ if (value.user_abandoned_at) {
1154
+ assertTimestampNotBefore(value.user_abandoned_at, value.abort_cleanup_completed_at ?? value.resolved_at ??
1155
+ value.recovered_at ?? value.committed_at ?? value.aborted_at ??
1156
+ value.uncertain_at ?? latestInputEvidence);
1157
+ }
959
1158
  }
960
1159
  function assertMonotonicTimestamps(values) {
961
1160
  for (let index = 1; index < values.length; index += 1) {
@@ -1086,6 +1285,16 @@ const TRANSFER_STATUSES = new Set([
1086
1285
  "resolved",
1087
1286
  "aborted",
1088
1287
  "abort_resolved",
1288
+ "uncertain",
1289
+ "user_abandoned"
1290
+ ]);
1291
+ const USER_ABANDONMENT_ORIGIN_STATUSES = new Set([
1292
+ "prepared",
1293
+ "source_reserved",
1294
+ "target_prepared",
1295
+ "dispatch_started",
1296
+ "committed",
1297
+ "aborted",
1089
1298
  "uncertain"
1090
1299
  ]);
1091
1300
  const INPUT_STAGES = new Set([