@zq-silk/yui 2.16.0 → 2.18.0

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 (118) hide show
  1. package/dist/cli/commandCatalog.js +19 -12
  2. package/dist/cli/interactionCandidates.js +20 -5
  3. package/dist/cli/interactionPolicy.js +5 -4
  4. package/dist/cli/roleWizard.js +7 -0
  5. package/dist/commands/controllerCommands.js +13 -4
  6. package/dist/commands/durableJobCommands.js +50 -3
  7. package/dist/commands/globalRoleCommands.js +31 -14
  8. package/dist/commands/inboxCommands.js +1 -0
  9. package/dist/commands/operatorCommands.js +12 -2
  10. package/dist/commands/profileCommands.js +19 -2
  11. package/dist/commands/roleConfiguration.js +3 -0
  12. package/dist/commands/taskCommands.js +16 -0
  13. package/dist/commands/taskRoleRuntimeStatus.js +3 -0
  14. package/dist/controlPlaneCli.js +21 -4
  15. package/dist/controller/agentJobControl.js +9 -1
  16. package/dist/controller/clientRuntime.js +34 -13
  17. package/dist/controller/controller.js +35 -22
  18. package/dist/controller/diagnosticJournal.js +214 -0
  19. package/dist/controller/fileSchedulerStoreAdapter.js +174 -105
  20. package/dist/controller/globalInputDelivery.js +19 -9
  21. package/dist/controller/globalRuntimeAttention.js +11 -3
  22. package/dist/controller/jobControl.js +1 -1
  23. package/dist/controller/jobDefinitionControl.js +24 -13
  24. package/dist/controller/jobSupervisor.js +4 -0
  25. package/dist/controller/providerRetryAdmission.js +9 -0
  26. package/dist/controller/providerRetryDelivery.js +16 -0
  27. package/dist/controller/runtime.js +80 -4
  28. package/dist/controller/runtimeLaunchCoordinator.js +3 -1
  29. package/dist/controller/workerSchedulerStore.js +24 -0
  30. package/dist/core/boundedRpc.js +4 -0
  31. package/dist/core/controllerServer.js +1 -1
  32. package/dist/core/controllerTelemetry.js +92 -19
  33. package/dist/executor/fileRoleLaunchPlanner.js +3 -0
  34. package/dist/job/globalShell.js +51 -0
  35. package/dist/job/jobBusinessResult.js +4 -3
  36. package/dist/job/jobDefinition.js +6 -1
  37. package/dist/job/jobRunner.js +9 -0
  38. package/dist/job/scheduleService.js +2 -0
  39. package/dist/message/globalInboxRecovery.js +30 -19
  40. package/dist/message/globalProviderRetry.js +7 -1
  41. package/dist/message/messageContinuation.js +4 -0
  42. package/dist/output/rolePresentation.js +3 -0
  43. package/dist/profile/agentProfile.js +8 -0
  44. package/dist/profile/agentProfileRuntime.js +16 -1
  45. package/dist/review/reviewDecision.js +6 -0
  46. package/dist/role/availability.js +28 -0
  47. package/dist/role/role.js +6 -0
  48. package/dist/runtime/builtinAgentErrorMappers.js +4 -0
  49. package/dist/runtime/codexNativeAccess.js +21 -4
  50. package/dist/runtime/globalSessionHealth.js +39 -0
  51. package/dist/runtime/tmuxAdapters.js +5 -0
  52. package/dist/scheduler/activeRoleRunDelivery.js +9 -0
  53. package/dist/scheduler/leaderWakeupProcessor.js +8 -1
  54. package/dist/scheduler/roleRunLiveness.js +3 -0
  55. package/dist/scheduler/roleRunStall.js +10 -10
  56. package/dist/storage/persistenceWorker.js +41 -19
  57. package/dist/storage/sqliteSchema.js +43 -9
  58. package/dist/storage/sqliteStore.js +86 -8
  59. package/dist/storage/storageVersions.js +1 -1
  60. package/dist/storage/storeRpc.js +25 -11
  61. package/dist/storage/taskReadRevisionSchema.js +39 -0
  62. package/dist/web/assets/client/app/catalog.js +0 -29
  63. package/dist/web/assets/client/app/jobs.js +115 -0
  64. package/dist/web/assets/client/app/main.js +32 -28
  65. package/dist/web/assets/client/app/manage.js +1 -0
  66. package/dist/web/assets/client/app/settings.js +1 -5
  67. package/dist/web/assets/client/app/taskView.js +7 -11
  68. package/dist/web/assets/client/i18n/en.js +265 -36
  69. package/dist/web/assets/client/i18n/zh-CN.js +264 -35
  70. package/dist/web/assets/client/index.js +4 -0
  71. package/dist/web/assets/client/layout/manageShell.js +61 -15
  72. package/dist/web/assets/client/layout/router.js +2 -1
  73. package/dist/web/assets/client/layout/shortcuts.js +2 -2
  74. package/dist/web/assets/client/layout/workspace.js +99 -112
  75. package/dist/web/assets/client/lib/prefs.js +1 -1
  76. package/dist/web/assets/client/ui/primitives.js +15 -2
  77. package/dist/web/assets/client/views/dock/conversation.js +421 -123
  78. package/dist/web/assets/client/views/dock/session.js +229 -0
  79. package/dist/web/assets/client/views/dock/terminal.js +48 -39
  80. package/dist/web/assets/client/views/jobs.js +1042 -160
  81. package/dist/web/assets/client/views/overview.js +2 -2
  82. package/dist/web/assets/client/views/sidebar.js +3 -0
  83. package/dist/web/assets/client/views/task/page.js +2 -12
  84. package/dist/web/assets/shared/manageNav.js +3 -1
  85. package/dist/web/assets/shell/appRail.js +2 -1
  86. package/dist/web/assets/shell/dock.js +9 -27
  87. package/dist/web/assets/shell/manage.js +7 -2
  88. package/dist/web/assets/shell/models.js +4 -3
  89. package/dist/web/assets/shell/settingsContent.js +0 -3
  90. package/dist/web/assets/shell/sidebar.js +0 -1
  91. package/dist/web/assets/styles/views/conversation.js +96 -17
  92. package/dist/web/assets/styles/views/dock.js +17 -5
  93. package/dist/web/assets/styles/views/jobs.js +301 -12
  94. package/dist/web/assets/styles/views/sidebar.js +1 -2
  95. package/dist/web/assets/styles/views/task.js +1 -2
  96. package/dist/web/assets/styles/views/terminal.js +12 -8
  97. package/dist/web/webConversation.js +1 -0
  98. package/dist/web/webServer.js +28 -1
  99. package/dist/web/webSettings.js +10 -2
  100. package/docs/global-shell-jobs.md +78 -0
  101. package/docs/job-runs.md +6 -3
  102. package/docs/managed-turn-and-session-runtime.md +84 -0
  103. package/docs/managed-turn-and-session-runtime.zh-CN.md +57 -0
  104. package/docs/release-workflow.md +67 -0
  105. package/docs/release-workflow.zh-CN.md +48 -0
  106. package/docs/roles-and-configuration.md +45 -1
  107. package/docs/roles-and-configuration.zh-CN.md +35 -0
  108. package/docs/storage-baseline.md +19 -1
  109. package/docs/storage-baseline.zh-CN.md +10 -1
  110. package/docs/testing/review-evaluation.md +35 -1
  111. package/docs/testing/review-evaluation.zh-CN.md +25 -1
  112. package/docs/testing/verification-levels.md +2 -2
  113. package/docs/testing/verification-levels.zh-CN.md +2 -2
  114. package/package.json +1 -1
  115. package/skills/yui-leader/VERSION +1 -1
  116. package/skills/yui-leader/references/execution.md +19 -8
  117. package/skills/yui-reviewer/SKILL.md +13 -2
  118. package/skills/yui-reviewer/VERSION +1 -1
@@ -3,6 +3,7 @@ import { assertJobRoleRunnable } from "../job/agentRoleScope.js";
3
3
  import { recordJobAgentObservation } from "../job/agentJobObservation.js";
4
4
  import { isDeepStrictEqual } from "node:util";
5
5
  import { nonReplayableMessageWake, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
6
+ import { ControllerWorkObserver, compactWorkObservation } from "../core/controllerTelemetry.js";
6
7
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
7
8
  import { freezeRunContextSnapshot } from "../context/runContextPack.js";
8
9
  import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
@@ -34,6 +35,8 @@ import { recordTaskAgentError } from "./taskAgentError.js";
34
35
  import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
35
36
  import { checkInboxTurn } from "./inboxTurnCheck.js";
36
37
  import { queueOperatorNotification } from "./operatorNotification.js";
38
+ import { reconcileProjectSync } from "../job/projectSync.js";
39
+ import { serializeError } from "../core/boundedRpc.js";
37
40
  import { claimOperatorAttention } from "../scheduler/operatorEvent.js";
38
41
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
39
42
  import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
@@ -65,6 +68,26 @@ export class FileSchedulerStoreAdapter {
65
68
  telemetry;
66
69
  drivers;
67
70
  snapshotExecutionLaneWorkspace;
71
+ #reads = new ControllerWorkObserver();
72
+ #projectionCache = { hits: 0, rebuilds: 0, invalidations: 0 };
73
+ readObservationMetrics() {
74
+ return { ...this.#reads.snapshot(), projectionCache: { ...this.#projectionCache } };
75
+ }
76
+ compactReadObservationMetrics() {
77
+ return compactWorkObservation(this.#reads);
78
+ }
79
+ #observeRead(phase, target, read) {
80
+ const finish = this.#reads.start(phase, target);
81
+ try {
82
+ const result = read();
83
+ finish();
84
+ return result;
85
+ }
86
+ catch (error) {
87
+ finish(true);
88
+ throw error;
89
+ }
90
+ }
68
91
  /** Diagnostic telemetry is optional and never participates in runtime truth. */
69
92
  constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry(), snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspaceSync) {
70
93
  this.store = store;
@@ -72,6 +95,12 @@ export class FileSchedulerStoreAdapter {
72
95
  this.drivers = drivers;
73
96
  this.snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspace;
74
97
  }
98
+ /** Registry maintenance only; this never executes a Job or starts a process. */
99
+ reconcileProjectSync(now) {
100
+ const errors = [];
101
+ reconcileProjectSync(this.store, now, error => errors.push(serializeError(error)));
102
+ return errors;
103
+ }
75
104
  observeAgentHostObservation(event, now = new Date()) {
76
105
  // Match canonical ingress: archive and Host fact settlement share the
77
106
  // same write boundary, including the original transport envelope.
@@ -168,7 +197,7 @@ export class FileSchedulerStoreAdapter {
168
197
  && sessions?.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId;
169
198
  if (!runFolded && !(isSteer && input.fence.runId === undefined))
170
199
  return "obsolete";
171
- if (runFolded && !hasPersistedRuntimeObservation(store.listEvents(taskId), input) && run.status === "active") {
200
+ if (runFolded && !hasPersistedRuntimeObservation(store, input) && run.status === "active") {
172
201
  const updated = appendRunInput(run, createRunInput({
173
202
  source: input.fence.receiptId.startsWith("native-input:")
174
203
  ? { type: "provider", channel: "visible-input" }
@@ -196,7 +225,7 @@ export class FileSchedulerStoreAdapter {
196
225
  if ((input.kind === "input.rejected" || input.kind === "input.delivery-unknown")
197
226
  && input.fence.receiptId?.startsWith("steer:") === true) {
198
227
  return this.store.transaction((store) => {
199
- if (hasPersistedRuntimeObservation(store.listEvents(taskId), input))
228
+ if (hasPersistedRuntimeObservation(store, input))
200
229
  return "applied";
201
230
  const recorded = this.recordSteerControlOutcome(store, input.fence.receiptId, input.kind === "input.rejected" ? "rejected" : "delivery-unknown", now);
202
231
  if (!recorded) {
@@ -217,7 +246,7 @@ export class FileSchedulerStoreAdapter {
217
246
  // together, including a retry after a previously interrupted fold.
218
247
  if (!isRunTerminalObservation(input)
219
248
  && input.kind !== "turn.accepted" && input.kind !== "input.accepted"
220
- && hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
249
+ && hasPersistedRuntimeObservation(this.store, input))
221
250
  return "applied";
222
251
  let outcome;
223
252
  switch (input.kind) {
@@ -330,7 +359,7 @@ export class FileSchedulerStoreAdapter {
330
359
  recordCanonicalObservationObsolete(store, input, "steer-receipt-fence-mismatch", now);
331
360
  return "obsolete";
332
361
  }
333
- if (hasPersistedRuntimeObservation(store.listEvents(taskId), input))
362
+ if (hasPersistedRuntimeObservation(store, input))
334
363
  return "applied";
335
364
  const target = { kind: "role", taskId, roleName: "leader" };
336
365
  const mailbox = store.getWorkMailbox(target);
@@ -408,7 +437,7 @@ export class FileSchedulerStoreAdapter {
408
437
  }
409
438
  if (input.kind === "turn.accepted") {
410
439
  if (input.authority === "transport" && matches) {
411
- const message = store.listGlobalRoleMessages(fence.roleName).find(entry => receipt === `global-input:${fence.roleName}/${entry.id}`);
440
+ const message = globalMessageForReceipt(store, fence.roleName, receipt);
412
441
  if (message !== undefined)
413
442
  store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at), "transport"));
414
443
  return "applied";
@@ -428,9 +457,8 @@ export class FileSchedulerStoreAdapter {
428
457
  }
429
458
  else if (binding.run?.status !== "accepted")
430
459
  return "applied";
431
- const message = store.listGlobalRoleMessages(fence.roleName).find(entry => receipt === `global-input:${fence.roleName}/${entry.id}`
432
- || binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
433
- && binding.retry.input.messageId === entry.id);
460
+ const message = globalMessageForReceipt(store, fence.roleName, receipt, "global-input", binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
461
+ ? binding.retry.input.messageId : undefined);
434
462
  if (message !== undefined)
435
463
  store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at)));
436
464
  }
@@ -443,9 +471,8 @@ export class FileSchedulerStoreAdapter {
443
471
  if (input.kind === "turn.completed" && binding.run != null
444
472
  && ["submitting", "delivery-unknown"].includes(binding.run.status)) {
445
473
  binding = acceptProviderTurn(binding, { attemptId: receipt, nativeTurnId: fence.nativeTurnId, acceptedAt: at });
446
- const message = store.listGlobalRoleMessages(fence.roleName).find(entry => receipt === `global-input:${fence.roleName}/${entry.id}`
447
- || binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
448
- && binding.retry.input.messageId === entry.id);
474
+ const message = globalMessageForReceipt(store, fence.roleName, receipt, "global-input", binding?.retry?.currentAttemptId === receipt && binding.retry.input.kind === "message"
475
+ ? binding.retry.input.messageId : undefined);
449
476
  if (message !== undefined)
450
477
  store.updateGlobalRoleMessage(markGlobalRoleMessageDelivered(message, new Date(at)));
451
478
  }
@@ -466,7 +493,7 @@ export class FileSchedulerStoreAdapter {
466
493
  if (input.authority !== "provider-structured" || binding.run?.nativeTurnId !== fence.nativeTurnId
467
494
  || binding.run?.status !== "accepted")
468
495
  return "obsolete";
469
- const message = store.listGlobalRoleMessages(fence.roleName).find(entry => receipt === `steer:${fence.roleName}/${entry.id}`);
496
+ const message = globalMessageForReceipt(store, fence.roleName, receipt, "steer");
470
497
  if (message !== undefined && message.inputControl !== undefined) {
471
498
  const outcome = input.kind === "input.accepted" ? "accepted"
472
499
  : input.kind === "input.rejected" ? "rejected" : "delivery-unknown";
@@ -548,7 +575,7 @@ export class FileSchedulerStoreAdapter {
548
575
  }
549
576
  validateCanonicalRunObservation(input, now) {
550
577
  return this.store.transaction((store) => {
551
- if (hasPersistedRuntimeObservation(store.listEvents(input.fence.taskId), input)) {
578
+ if (hasPersistedRuntimeObservation(store, input)) {
552
579
  return "applied";
553
580
  }
554
581
  const run = input.fence.runId === undefined ? null : store.getRun(input.fence.taskId, input.fence.runId);
@@ -565,7 +592,7 @@ export class FileSchedulerStoreAdapter {
565
592
  ? "applied" : "obsolete";
566
593
  }
567
594
  const knownContinuation = (input.kind.startsWith("continuation.") || input.kind === "observer.health")
568
- && projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.runId === input.fence.runId
595
+ && projectProviderContinuations(store.listEventsByType(input.fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT])).some((entry) => (entry.runId === input.fence.runId
569
596
  && entry.identity.providerNamespace === input.fence.driverId
570
597
  && entry.identity.accountScope === input.fence.agentId
571
598
  && entry.identity.conversationId === input.fence.conversationId
@@ -647,7 +674,7 @@ export class FileSchedulerStoreAdapter {
647
674
  const status = "ended";
648
675
  let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now, input.kind === "session.failed" ? "failed" : "stopped");
649
676
  store.saveTaskRoleSessionSet(updatedSessions);
650
- for (const continuation of projectProviderContinuations(store.listEvents(input.fence.taskId))) {
677
+ for (const continuation of projectProviderContinuations(store.listEventsByType(input.fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]))) {
651
678
  if (continuation.runId !== input.fence.runId || continuation.identity.conversationId
652
679
  !== (input.fence.conversationId ?? input.fence.nativeSessionId) || continuation.execution === "quiescent" || continuation.attachment === "detached")
653
680
  continue;
@@ -721,9 +748,9 @@ export class FileSchedulerStoreAdapter {
721
748
  persistRuntimeObservation(input, now, notifyGoal = true) {
722
749
  this.store.transaction((store) => {
723
750
  const taskId = input.fence.taskId;
724
- const events = store.listEvents(taskId);
725
- if (hasPersistedRuntimeObservation(events, input))
751
+ if (hasPersistedRuntimeObservation(store, input))
726
752
  return;
753
+ const events = store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]);
727
754
  if (usageSnapshotIsSuperseded(events, input))
728
755
  return;
729
756
  const removable = compactedRuntimeObservationIds(events, input);
@@ -851,43 +878,53 @@ export class FileSchedulerStoreAdapter {
851
878
  return "obsolete";
852
879
  }
853
880
  /**
854
- * Revision-scoped read projection. Keyed by the store's durable revision;
855
- * any committed mutation (ours or an external writer's) advances it and the
856
- * next read rebuilds. A store without getStateRevision disables caching.
881
+ * Task-scoped cache tokens cover the exact source tables across connections.
882
+ * Unrelated Tasks/Global traffic do not invalidate these projections. Retain
883
+ * only the 16 most recently read Tasks; eviction changes cost, never truth.
857
884
  */
858
- #readProjection = null;
859
- #taskReadProjection(taskId) {
860
- const revision = typeof this.store.getStateRevision === "function"
861
- ? this.store.getStateRevision()
862
- : Number.NaN;
863
- if (this.#readProjection === null
864
- || this.#readProjection.revision !== revision) {
865
- this.#readProjection = { revision, tasks: new Map() };
866
- }
867
- const tasks = this.#readProjection.tasks;
868
- let task = tasks.get(taskId);
869
- if (task === undefined) {
870
- const events = this.store.listEvents(taskId);
871
- task = {
872
- events,
873
- runFacts: foldRunProgressFacts(events),
874
- runs: this.store.listRuns(taskId),
875
- workItems: this.store.listWorkItems(taskId),
876
- reviewRounds: this.store.listReviewRounds(taskId),
877
- changeSets: this.store.listChangeSets(taskId),
878
- integrationAttempts: this.store.listIntegrationAttempts(taskId),
879
- inputRequests: this.store.listInputRequests(taskId),
880
- durableJobs: this.store.listDurableJobs(taskId),
881
- messages: this.store.listMessages(taskId)
882
- };
883
- tasks.set(taskId, task);
885
+ #readProjection = new Map();
886
+ #projected(taskId, family, read) {
887
+ const observed = this.store.getTaskReadRevision(taskId);
888
+ const previous = this.#readProjection.get(taskId);
889
+ const hit = previous?.revision === observed ? previous.value[family] : undefined;
890
+ if (hit !== undefined) {
891
+ // The token read is the read's snapshot point. A cache hit needs no new
892
+ // transaction: its value was built under that exact committed token.
893
+ this.#projectionCache.hits++;
894
+ this.#readProjection.delete(taskId);
895
+ this.#readProjection.set(taskId, previous);
896
+ return hit;
884
897
  }
885
- return task;
898
+ // Pin and recheck misses before reading any source rows: an external writer
899
+ // may have advanced the token after the optimistic lookup above.
900
+ return this.store.readTransaction(() => {
901
+ const revision = this.store.getTaskReadRevision(taskId);
902
+ let task = this.#readProjection.get(taskId);
903
+ if (task === undefined || task.revision !== revision) {
904
+ if (task !== undefined)
905
+ this.#projectionCache.invalidations++;
906
+ this.#projectionCache.rebuilds++;
907
+ task = { revision, value: {} };
908
+ }
909
+ this.#readProjection.delete(taskId);
910
+ this.#readProjection.set(taskId, task);
911
+ if (this.#readProjection.size > 16) {
912
+ this.#readProjection.delete(this.#readProjection.keys().next().value);
913
+ }
914
+ const cached = task.value[family];
915
+ if (cached !== undefined) {
916
+ this.#projectionCache.hits++;
917
+ return cached;
918
+ }
919
+ const value = this.#observeRead(`task-${family}-read`, taskId, read);
920
+ task.value[family] = value;
921
+ return value;
922
+ });
886
923
  }
887
924
  withRuntimeEventTransaction(execute) {
888
925
  return this.store.withRuntimeEventTransaction(execute);
889
926
  }
890
- listTasks() { return this.store.listTasks(); }
927
+ listTasks() { return this.#observeRead("list-tasks", "home", () => this.store.listTasks()); }
891
928
  listActiveTaskIds() {
892
929
  return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
893
930
  }
@@ -980,51 +1017,48 @@ export class FileSchedulerStoreAdapter {
980
1017
  return this.store.getTaskRoleSessionSet(taskId, roleName);
981
1018
  }
982
1019
  listEvents(taskId) {
983
- return this.#taskReadProjection(taskId).events;
1020
+ return this.#projected(taskId, "events", () => this.store.listEvents(taskId));
984
1021
  }
985
1022
  listRuns(taskId) {
986
- return this.#taskReadProjection(taskId).runs;
1023
+ return this.#projected(taskId, "runs", () => this.store.listRuns(taskId));
987
1024
  }
988
1025
  listWorkItems(taskId) {
989
- return this.#taskReadProjection(taskId).workItems;
1026
+ return this.#projected(taskId, "workItems", () => this.store.listWorkItems(taskId));
990
1027
  }
991
1028
  listReviewRounds(taskId) {
992
- return this.#taskReadProjection(taskId).reviewRounds;
1029
+ return this.#projected(taskId, "reviewRounds", () => this.store.listReviewRounds(taskId));
993
1030
  }
994
1031
  listIntegrationAttempts(taskId) {
995
- return this.#taskReadProjection(taskId).integrationAttempts;
1032
+ return this.#projected(taskId, "integrationAttempts", () => this.store.listIntegrationAttempts(taskId));
996
1033
  }
997
1034
  listDurableJobs(taskId) {
998
- return this.#taskReadProjection(taskId).durableJobs;
1035
+ return this.#projected(taskId, "durableJobs", () => this.store.listDurableJobs(taskId));
999
1036
  }
1000
1037
  listInputRequests(taskId) {
1001
- return this.#taskReadProjection(taskId).inputRequests;
1038
+ return this.#projected(taskId, "inputRequests", () => this.store.listInputRequests(taskId));
1002
1039
  }
1003
1040
  listMessages(taskId) {
1004
- return this.#taskReadProjection(taskId).messages;
1041
+ return this.#projected(taskId, "messages", () => this.store.listMessages(taskId));
1005
1042
  }
1006
1043
  getRunProgressFacts(taskId, runId) {
1007
- return this.#taskReadProjection(taskId).runFacts.get(runId);
1044
+ return this.#projected(taskId, "runFacts", () => foldRunProgressFacts(this.listEvents(taskId))).get(runId);
1008
1045
  }
1009
1046
  getRunDurableProgress(taskId, roleName, runId) {
1010
- const projected = this.#taskReadProjection(taskId);
1011
- // Serve the related-record fold from the same revision's projection: the
1012
- // event history and the WorkItem/Review/ChangeSet/Integration/Input lists
1013
- // are read once per Task per revision, and the per-AgentRun checkpoint/activity
1014
- // facts come from the one-pass fold instead of per-candidate scans.
1015
- const view = {
1016
- getRun: (id, runId) => this.store.getRun(id, runId),
1017
- listEvents: () => projected.events,
1018
- getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
1019
- listReviewRounds: () => projected.reviewRounds,
1020
- listChangeSets: () => projected.changeSets,
1021
- listIntegrationAttempts: () => projected.integrationAttempts,
1022
- listInputRequests: () => projected.inputRequests
1023
- };
1024
- // A missing fold entry is an authoritative empty fold, not a signal to
1025
- // re-scan the whole history. Pass {} so latestTurnDurableProgressAt treats
1026
- // the fold as present and skips the per-candidate fallback scans.
1027
- return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
1047
+ return this.store.readTransaction(() => {
1048
+ // Pin related reads to one snapshot while loading only required families.
1049
+ const view = {
1050
+ getRun: (id, runId) => this.store.getRun(id, runId),
1051
+ listEvents: () => this.listEvents(taskId),
1052
+ getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
1053
+ listReviewRounds: () => this.listReviewRounds(taskId),
1054
+ listChangeSets: () => this.#projected(taskId, "changeSets", () => this.store.listChangeSets(taskId)),
1055
+ listIntegrationAttempts: () => this.listIntegrationAttempts(taskId),
1056
+ listInputRequests: () => this.listInputRequests(taskId)
1057
+ };
1058
+ // A missing fold entry is authoritative empty evidence, not permission
1059
+ // to fall back to a per-candidate full-history scan.
1060
+ return latestRunDurableProgressAt(view, taskId, roleName, runId, this.getRunProgressFacts(taskId, runId) ?? {});
1061
+ });
1028
1062
  }
1029
1063
  recordRoleRunDiagnostic(input) {
1030
1064
  return this.store.transaction((store) => {
@@ -1160,12 +1194,11 @@ export class FileSchedulerStoreAdapter {
1160
1194
  if (turn != null && ["submitting", "accepted", "delivery-unknown"].includes(turn.status)) {
1161
1195
  throw new AgentHostProviderSessionBusyError("Global Conversation has an unsettled input.");
1162
1196
  }
1163
- const message = store.listGlobalRoleMessages(input.roleName).find(entry => input.attemptId === `global-input:${input.roleName}/${entry.id}`);
1164
- const reserved = store.listGlobalRoleMessages(input.roleName).find(entry => entry.delivery === undefined && entry.notDelivered === undefined
1165
- && entry.interruptThen?.targetNativeSessionId === input.nativeSessionId
1166
- && entry.interruptThen.targetAgentId === input.agentId
1167
- && entry.interruptThen.targetAuthorityEpoch === input.authorityEpoch
1168
- && entry.interruptThen.targetAuthorityHolderId === input.holderId);
1197
+ const message = globalMessageForReceipt(store, input.roleName, input.attemptId);
1198
+ const reserved = store.queryGlobalRoleMessages(input.roleName, {
1199
+ limit: 1, reservedThen: { nativeSessionId: input.nativeSessionId,
1200
+ agentId: input.agentId, authorityEpoch: input.authorityEpoch, holderId: input.holderId }
1201
+ }).messages[0];
1169
1202
  if (reserved !== undefined && reserved.id !== message?.id) {
1170
1203
  throw new AgentHostProviderSessionBusyError("The next Global input belongs to an explicit then handoff.");
1171
1204
  }
@@ -1285,7 +1318,7 @@ export class FileSchedulerStoreAdapter {
1285
1318
  // This shares the exact writer/session admission and its transaction.
1286
1319
  if ((input.authorityOwner === "human" || input.attemptId.startsWith(`human:${input.holderId}:`)) && input.roleName === "leader"
1287
1320
  && input.runId === undefined && input.boundedText !== undefined) {
1288
- const prior = store.listEvents(input.taskId).find(event => event.type === "message.native-user-input" && event.payload.attemptId === input.attemptId);
1321
+ const prior = store.listEventsByType(input.taskId, ["message.native-user-input"]).find(event => event.payload.attemptId === input.attemptId);
1289
1322
  if (prior !== undefined) {
1290
1323
  const message = store.listMessages(input.taskId).find(entry => entry.id === prior.payload.messageId);
1291
1324
  if (message?.body !== input.boundedText || prior.payload.nativeSessionId !== input.nativeSessionId) {
@@ -1344,7 +1377,7 @@ export class FileSchedulerStoreAdapter {
1344
1377
  unknown: "External effects are not established by submission settlement."
1345
1378
  }, input.now);
1346
1379
  }
1347
- const message = store.listGlobalRoleMessages(input.roleName).find(entry => input.attemptId === `global-input:${input.roleName}/${entry.id}`);
1380
+ const message = globalMessageForReceipt(store, input.roleName, input.attemptId);
1348
1381
  if (message !== undefined && message.delivery?.via !== "provider") {
1349
1382
  const outcome = input.status === "delivery-unknown" ? "delivery-unknown" : "rejected";
1350
1383
  const updated = { ...message, control: {
@@ -1890,9 +1923,9 @@ export class FileSchedulerStoreAdapter {
1890
1923
  return null;
1891
1924
  const wakeId = processing.owner.slice("leader-notification:".length);
1892
1925
  const attemptId = processing.batchId;
1893
- const previous = store.listEvents(taskId).filter((event) => event.type === "notification.delivery" && event.payload.attemptId === attemptId).at(-1);
1926
+ const previous = store.listEventsByType(taskId, ["notification.delivery"]).filter((event) => event.payload.attemptId === attemptId).at(-1);
1894
1927
  const native = provider?.attemptId === attemptId ? provider : undefined;
1895
- const accepted = store.listEvents(taskId).some((event) => {
1928
+ const accepted = store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).some((event) => {
1896
1929
  const observation = runtimeObservationFromTaskEvent(event);
1897
1930
  return observation?.fence.roleName === "leader"
1898
1931
  && observation.fence.receiptId === attemptId
@@ -2016,8 +2049,7 @@ export class FileSchedulerStoreAdapter {
2016
2049
  if (mailbox == null || processing == null || processing.batchId !== attemptId
2017
2050
  || !processing.owner.startsWith("leader-notification:"))
2018
2051
  return;
2019
- if (!store.listEvents(taskId).some((event) => event.type === "notification.delivery"
2020
- && event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
2052
+ if (!store.listEventsByType(taskId, ["notification.delivery"]).some((event) => event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
2021
2053
  // Native ingress may already have persisted the same rejection. Reuse
2022
2054
  // that original failure rather than reporting it twice from the caller.
2023
2055
  const existing = store.listEventsByType(taskId, ["runtime.agent-error"])
@@ -2083,7 +2115,7 @@ export class FileSchedulerStoreAdapter {
2083
2115
  nativeSessionId: input.session.nativeSessionId
2084
2116
  }, "active", input.now);
2085
2117
  }
2086
- if (!defersConversationReplacement && !store.listEvents(input.task.id).some((event) => event.type === "run.session-prepared" && event.payload.runId === input.run.id
2118
+ if (!defersConversationReplacement && !store.listEventsByType(input.task.id, ["run.session-prepared"]).some((event) => event.payload.runId === input.run.id
2087
2119
  && event.payload.nativeSessionId === input.session.nativeSessionId)) {
2088
2120
  store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "run.session-prepared", { runId: input.run.id, roleName: role.name,
2089
2121
  nativeSessionId: input.session.nativeSessionId }, input.now));
@@ -2235,7 +2267,7 @@ export class FileSchedulerStoreAdapter {
2235
2267
  && preparedRun.executionLaneId !== undefined
2236
2268
  && preparedRun.workspace !== undefined
2237
2269
  && input.outcome.status === "completed"
2238
- ? this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace)
2270
+ ? this.#observeRead("git-workspace-snapshot", preparedRun.taskId, () => this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace))
2239
2271
  : undefined;
2240
2272
  return this.store.transaction((store) => {
2241
2273
  const task = store.getTask(input.taskId);
@@ -2476,7 +2508,7 @@ export class FileSchedulerStoreAdapter {
2476
2508
  observeProviderGoal(input, now) {
2477
2509
  return this.store.transaction((store) => {
2478
2510
  const taskId = input.fence.taskId;
2479
- if (hasPersistedRuntimeObservation(store.listEvents(taskId), input))
2511
+ if (hasPersistedRuntimeObservation(store, input))
2480
2512
  return "applied";
2481
2513
  const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
2482
2514
  const binding = sessions?.providerBinding;
@@ -2798,7 +2830,7 @@ export class FileSchedulerStoreAdapter {
2798
2830
  ...(boundNativeSessionId === undefined ? {} : { nativeSessionId: boundNativeSessionId })
2799
2831
  };
2800
2832
  const driverId = this.drivers.requireByAdapterId(run.effective.adapterId).id;
2801
- const lifecycleEvents = store.listEvents(fence.taskId).flatMap((event) => {
2833
+ const lifecycleEvents = store.listEventsByType(fence.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).flatMap((event) => {
2802
2834
  const observation = runtimeObservationFromTaskEvent(event);
2803
2835
  return observation !== null && (observation.kind === "session.started" || observation.kind === "session.ready") && observation.fence.roleName === fence.roleName && observation.fence.agentId === run.effective.agentId && observation.fence.driverId === driverId && observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
2804
2836
  ? [observation]
@@ -2966,7 +2998,7 @@ function runtimeReceiptBelongsToRun(store, input) {
2966
2998
  && sessions?.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId) {
2967
2999
  return true;
2968
3000
  }
2969
- return store.listEvents(taskId).some((event) => {
3001
+ return store.listEventsByType(taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).some((event) => {
2970
3002
  const accepted = runtimeObservationFromTaskEvent(event);
2971
3003
  return accepted?.kind === "turn.accepted" && accepted.fence.runId === runId && accepted.fence.roleName === input.fence.roleName && accepted.fence.agentId === input.fence.agentId && accepted.fence.nativeSessionId === input.fence.nativeSessionId && accepted.fence.receiptId === receiptId;
2972
3004
  });
@@ -2988,8 +3020,7 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
2988
3020
  }, reason, now);
2989
3021
  }
2990
3022
  function recordObsoleteRuntimeEvent(store, input, reason, now) {
2991
- if (store.listEvents(input.taskId).some((event) => (event.type === "runtime.event-obsolete"
2992
- && (event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
3023
+ if (store.listEventsByType(input.taskId, ["runtime.event-obsolete"]).some((event) => ((event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
2993
3024
  return;
2994
3025
  store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.event-obsolete", {
2995
3026
  eventId: input.eventId,
@@ -3145,9 +3176,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
3145
3176
  if (task.status === "archived") {
3146
3177
  throw new Error(`Cannot register a native session for archived Task: ${input.taskId}.`);
3147
3178
  }
3148
- // A Draft registers a Session only for its planning Turn. Admission is decided
3149
- // by the active Turn's purpose through the one shared invariant, so a Draft
3150
- // still cannot open an execution Session before activation.
3179
+ // A Draft admits its first planning Run, restoration of that exact Session,
3180
+ // or the selected successor's initialization after verified replacement.
3181
+ // Ordinary Draft notifications and native hooks cannot create a Session.
3151
3182
  const admittingRun = store.getActiveRun(input.taskId, input.roleName);
3152
3183
  const existingPlanning = activeLiveRoleAgentSession(store.getTaskRoleSessionSet(input.taskId, input.roleName));
3153
3184
  if (!(admittingRun !== null
@@ -3156,6 +3187,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
3156
3187
  && !(task.status === "draft" && task.executionGate.state === "enabled" && input.roleName === "leader"
3157
3188
  && existingPlanning?.effective.executionAuthority === "planning"
3158
3189
  && existingPlanning.nativeSessionId === input.nativeSessionId)
3190
+ && !(task.status === "draft" && task.executionGate.state === "enabled" && input.roleName === "leader"
3191
+ && input.effective?.executionAuthority === "planning"
3192
+ && draftReplacementInitializationIsCurrent(store, input.taskId, input.agentId))
3159
3193
  && (task.status !== "active" || task.executionGate.state !== "enabled")) {
3160
3194
  throw new Error(`Cannot register a native session for a Task that is not active: ${input.taskId}.`);
3161
3195
  }
@@ -3191,6 +3225,32 @@ function recordTaskRuntimeNativeSession(store, input, now) {
3191
3225
  store.saveRoleSessionSet(updated);
3192
3226
  return updated.sessions[input.agentId];
3193
3227
  }
3228
+ /** The existing initialization wake is the launch authority, not historical
3229
+ * planning success or an arbitrary "session-replaced" reason. It is retained
3230
+ * on startup rejection for the normal explicit wake retry/replacement path. */
3231
+ function draftReplacementInitializationIsCurrent(store, taskId, agentId) {
3232
+ const sessions = store.getTaskRoleSessionSet(taskId, "leader");
3233
+ if (sessions === null || sessions.activeAgentId !== agentId
3234
+ || sessions.sessions[agentId] !== undefined || sessions.providerBinding !== null
3235
+ || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({
3236
+ scope: "task", taskId, roleName: "leader"
3237
+ }))))
3238
+ return false;
3239
+ const processing = store.getWorkMailbox({ kind: "role", taskId, roleName: "leader" })?.processing;
3240
+ if (!processing?.owner.startsWith("leader-notification:"))
3241
+ return false;
3242
+ const wake = store.getTaskWake(taskId, processing.owner.slice("leader-notification:".length));
3243
+ if (wake === null || !wake.reasons.includes("session-replaced"))
3244
+ return false;
3245
+ const events = store.listEventsByType(taskId, ["runtime.session-replaced", "notification.delivery"]);
3246
+ const replacement = events.filter(event => event.type === "runtime.session-replaced"
3247
+ && event.payload.roleName === "leader").at(-1);
3248
+ return replacement !== undefined
3249
+ && (wake.refs ?? []).some(ref => ref.type === "event" && ref.taskId === taskId && ref.id === replacement.id)
3250
+ && !events.some(event => event.type === "notification.delivery"
3251
+ && event.payload.attemptId === processing.batchId
3252
+ && event.payload.outcome !== "deferred");
3253
+ }
3194
3254
  function recordGlobalRuntimeNativeSession(store, input, now) {
3195
3255
  const role = store.getGlobalRole(input.roleName);
3196
3256
  if (role === null)
@@ -3271,6 +3331,7 @@ function mapRole(store, role) {
3271
3331
  return {
3272
3332
  taskId: role.taskId,
3273
3333
  name: role.name,
3334
+ enabled: role.enabled !== false,
3274
3335
  activeAgentId: role.activeAgentId,
3275
3336
  adapterId: binding.adapterId,
3276
3337
  ...(binding.config.model === undefined ? {} : { model: binding.config.model }),
@@ -3307,8 +3368,9 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
3307
3368
  ?? store.getTaskWorkspace(taskId)
3308
3369
  ?? undefined;
3309
3370
  const effective = resolveEffectiveLaunch({
3371
+ store,
3310
3372
  role,
3311
- purpose: "execution",
3373
+ purpose: store.getTask(taskId)?.status === "draft" && roleName === "leader" ? "planning" : "execution",
3312
3374
  ...(workspace === undefined ? {} : { workspace }),
3313
3375
  ...(item === null ? {} : { workItemWriteProjectIds: item.writeProjectIds })
3314
3376
  });
@@ -3482,7 +3544,7 @@ function resolveTerminalExecution(store, input) {
3482
3544
  const session = sessions?.sessions[input.agentId];
3483
3545
  const binding = sessions?.providerBinding;
3484
3546
  const pending = binding?.run;
3485
- const evidence = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
3547
+ const evidence = store.listEventsByType(input.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).map(runtimeObservationFromTaskEvent)
3486
3548
  .filter((event) => event !== null
3487
3549
  && (event.kind === "turn.accepted" || isRunTerminalObservation(event)));
3488
3550
  if (input.attemptId !== undefined && evidence.some((event) => (event.fence.roleName === input.roleName
@@ -3543,7 +3605,7 @@ function isRunTerminalObservation(input) {
3543
3605
  return ["turn.completed", "turn.failed", "turn.cancelled"].includes(input.kind);
3544
3606
  }
3545
3607
  function assertConsistentTerminal(store, input, run) {
3546
- const previous = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
3608
+ const previous = store.listEventsByType(input.taskId, [RUNTIME_OBSERVATION_TASK_EVENT]).map(runtimeObservationFromTaskEvent)
3547
3609
  .filter((event) => event !== null
3548
3610
  && isRunTerminalObservation(event)
3549
3611
  && event.fence.roleName === input.roleName
@@ -3669,14 +3731,21 @@ function usageSnapshotIsSuperseded(events, incoming) {
3669
3731
  && runtimeObservationFenceMatches(observation.fence, incoming.fence)
3670
3732
  && observationIsStrictlyNewer(observation, incoming));
3671
3733
  }
3672
- function hasPersistedRuntimeObservation(events, incoming) {
3734
+ function hasPersistedRuntimeObservation(store, incoming) {
3673
3735
  const usageIdentity = isRuntimeUsageEvidence(incoming)
3674
3736
  ? incoming.eventId
3675
3737
  : undefined;
3676
- return events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
3677
- && (usageIdentity === undefined
3678
- ? event.payload.semanticKey === incoming.semanticKey
3679
- : event.payload.eventId === usageIdentity)));
3738
+ return store.hasRuntimeObservation(incoming.fence.taskId, usageIdentity === undefined
3739
+ ? { semanticKey: incoming.semanticKey } : { eventId: usageIdentity });
3740
+ }
3741
+ function globalMessageForReceipt(store, roleName, receipt, kind = "global-input", retryMessageId) {
3742
+ const prefix = `${kind}:${roleName}/`;
3743
+ const id = receipt?.startsWith(prefix) ? receipt.slice(prefix.length) : undefined;
3744
+ // A retry references the original Message, not a second input. Preserve the
3745
+ // old numeric-id ordering if both explicit identities name different rows.
3746
+ return [...new Set([id, retryMessageId].filter((value) => value !== undefined))]
3747
+ .flatMap(id => store.queryGlobalRoleMessages(roleName, { id, limit: 1 }).messages)
3748
+ .sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))[0];
3680
3749
  }
3681
3750
  function observationIsStrictlyNewer(left, right) {
3682
3751
  return compareCanonicalObservationOrder(left, right) > 0;
@@ -1,9 +1,10 @@
1
+ import { isEnabled } from "../role/availability.js";
1
2
  import { sendAgentHostRunControl, reconcileAgentHostTurn, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
2
3
  import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
3
4
  import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
4
5
  import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
5
6
  import { providerRetryPending } from "../runtime/providerRetry.js";
6
- import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
7
+ import { globalRetryInputSettlement, settleGlobalRetryInput } from "../message/globalProviderRetry.js";
7
8
  import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
8
9
  import { recordGlobalRuntimeAttention, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
9
10
  import { globalInboxRecovery } from "../message/globalInboxRecovery.js";
@@ -25,14 +26,6 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
25
26
  if (sessions !== null && selected?.status !== "active"
26
27
  && !(role.jobOwner && selected === undefined))
27
28
  continue;
28
- if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
29
- store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
30
- pending = store.queryGlobalRoleMessages(role.name, { pending: true, unclaimed: true, limit: 64 }).messages;
31
- if (pending.length === 0)
32
- continue;
33
- }
34
- if (providerRetryPending(sessions?.providerBinding))
35
- continue;
36
29
  let current = sessions?.providerBinding?.run;
37
30
  const bindingBefore = sessions?.providerBinding;
38
31
  if (options.reconcile && current != null
@@ -95,6 +88,23 @@ export async function deliverGlobalInputs(home, store, ensure, onError, options
95
88
  if (pending.length === 0)
96
89
  continue;
97
90
  }
91
+ // Availability blocks new work, not observation of the original writer's
92
+ // exact terminal. Re-read after the asynchronous reconciliation above.
93
+ const currentRole = store.getGlobalRole(role.name);
94
+ if (currentRole === null || !isEnabled(currentRole))
95
+ continue;
96
+ if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
97
+ const now = options.now ?? new Date();
98
+ // Retained terminal retry history is not new write work each pass.
99
+ const settlement = store.readTransaction(tx => globalRetryInputSettlement(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, now));
100
+ if (settlement !== undefined)
101
+ store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, now));
102
+ pending = store.queryGlobalRoleMessages(role.name, { pending: true, unclaimed: true, limit: 64 }).messages;
103
+ if (pending.length === 0)
104
+ continue;
105
+ }
106
+ if (providerRetryPending(sessions?.providerBinding))
107
+ continue;
98
108
  const recovery = globalInboxRecovery(store, role.name, options.now ?? new Date());
99
109
  const recovering = recovery !== undefined && recovery.inbox === undefined && recovery.delivery === undefined
100
110
  && recovery.notDelivered === undefined && recovery.control === undefined;