@zq-silk/yui 0.5.3 → 0.6.1

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 (157) hide show
  1. package/README.md +5 -5
  2. package/dist/agent/managedRuntimeEnvironment.js +2 -1
  3. package/dist/cli/agentConfigurationPicker.js +1 -1
  4. package/dist/cli/commandCatalog.js +251 -13
  5. package/dist/cli/updateOrchestrator.js +8 -0
  6. package/dist/cli/updatePorts.js +76 -22
  7. package/dist/cli.js +264 -20
  8. package/dist/commands/configCommands.js +83 -9
  9. package/dist/commands/controllerCommands.js +103 -0
  10. package/dist/commands/deliveryGuardPreflight.js +35 -0
  11. package/dist/commands/durableJobCommands.js +231 -0
  12. package/dist/commands/executionAuditCommands.js +193 -0
  13. package/dist/commands/grantCommands.js +374 -0
  14. package/dist/commands/projectCommands.js +119 -81
  15. package/dist/commands/releaseCommands.js +444 -0
  16. package/dist/commands/resourcesCommands.js +274 -0
  17. package/dist/commands/sessionCommands.js +104 -0
  18. package/dist/commands/taskActor.js +117 -0
  19. package/dist/commands/taskChangeSetCommands.js +60 -0
  20. package/dist/commands/taskCommands.js +610 -201
  21. package/dist/commands/taskCompletionGate.js +78 -1
  22. package/dist/commands/taskContextCommand.js +24 -6
  23. package/dist/commands/taskInputCommands.js +1 -1
  24. package/dist/commands/taskIntegrationCommands.js +136 -33
  25. package/dist/commands/taskIntegrationQueueCommands.js +228 -0
  26. package/dist/commands/taskNextActionCommand.js +85 -0
  27. package/dist/commands/taskOverlapCommands.js +120 -0
  28. package/dist/commands/taskOverviewCommand.js +36 -8
  29. package/dist/commands/telemetryCommands.js +330 -0
  30. package/dist/commands/workflowCommands.js +415 -0
  31. package/dist/config/yuiConfig.js +60 -0
  32. package/dist/controller/clientRuntime.js +42 -1
  33. package/dist/controller/controller.js +413 -61
  34. package/dist/controller/controllerMain.js +25 -2
  35. package/dist/controller/domainIdentity.js +16 -8
  36. package/dist/controller/ephemeralResourceReaper.js +2 -1
  37. package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
  38. package/dist/controller/handoverCandidate.js +168 -0
  39. package/dist/controller/jobClient.js +102 -0
  40. package/dist/controller/jobControl.js +613 -0
  41. package/dist/controller/jobSupervisor.js +498 -0
  42. package/dist/controller/providerHookRunFence.js +34 -5
  43. package/dist/controller/resourceCleanupLinux.js +18 -9
  44. package/dist/controller/resourceInventoryLinux.js +90 -39
  45. package/dist/controller/resourceInventoryRpc.js +85 -0
  46. package/dist/controller/resourceInventoryWorker.js +50 -0
  47. package/dist/controller/runtime.js +238 -22
  48. package/dist/controller/runtimeEventInbox.js +234 -57
  49. package/dist/controller/runtimeEventProcessor.js +549 -42
  50. package/dist/controller/sessionOwnerReconciliation.js +321 -0
  51. package/dist/core/boundedRpc.js +475 -0
  52. package/dist/core/controllerServer.js +416 -27
  53. package/dist/core/controllerTelemetry.js +167 -0
  54. package/dist/doctor/doctor.js +113 -16
  55. package/dist/domain/validation.js +9 -0
  56. package/dist/execution/executionGroup.js +40 -3
  57. package/dist/executor/agentExecutor.js +6 -3
  58. package/dist/executor/effectiveLaunch.js +52 -0
  59. package/dist/executor/executorRegistry.js +50 -0
  60. package/dist/executor/fileRoleLaunchPlanner.js +61 -6
  61. package/dist/grant/capabilityGrant.js +282 -0
  62. package/dist/integration/changeSet.js +16 -3
  63. package/dist/integration/changeSetManifest.js +46 -0
  64. package/dist/integration/gitIntegrationService.js +528 -147
  65. package/dist/integration/integrationAttempt.js +54 -5
  66. package/dist/integration/integrationQueueEntry.js +221 -0
  67. package/dist/integration/integrationQueueService.js +955 -0
  68. package/dist/integration/manifestTags.js +99 -0
  69. package/dist/integration/overlapDiagnostics.js +211 -0
  70. package/dist/job/durableJob.js +449 -0
  71. package/dist/job/jobRunner.js +350 -0
  72. package/dist/lifecycle/exactRunTerminalization.js +24 -2
  73. package/dist/lifecycle/providerErrorClass.js +126 -0
  74. package/dist/message/message.js +16 -3
  75. package/dist/observability/executionAudit.js +545 -0
  76. package/dist/observability/faultClassification.js +160 -0
  77. package/dist/observability/runtimeIdentity.js +367 -0
  78. package/dist/release/fakeReleasePorts.js +55 -0
  79. package/dist/release/releaseHandover.js +475 -0
  80. package/dist/release/releaseIdempotencyStore.js +165 -0
  81. package/dist/release/releaseWorkflow.js +459 -0
  82. package/dist/release/releaseWorkflowEngine.js +688 -0
  83. package/dist/release/releaseWorkflowPorts.js +1720 -0
  84. package/dist/release/runtimeRelease.js +495 -0
  85. package/dist/release/workflowFileLock.js +218 -0
  86. package/dist/repository/gitWorkspace.js +177 -1
  87. package/dist/repository/projectMaintenanceLock.js +315 -0
  88. package/dist/repository/taskWorkspaceCoordinator.js +87 -17
  89. package/dist/repository/taskWorkspacePreparer.js +1091 -517
  90. package/dist/resources/autoResourceGc.js +116 -0
  91. package/dist/resources/liveReferences.js +574 -0
  92. package/dist/resources/resourceDiscovery.js +477 -0
  93. package/dist/resources/resourceGc.js +645 -0
  94. package/dist/resources/resourceRegistrar.js +256 -0
  95. package/dist/resources/resourceRegistry.js +150 -0
  96. package/dist/resources/resourceRegistryStore.js +41 -0
  97. package/dist/resources/resourceTypes.js +42 -0
  98. package/dist/resources/sqliteResourceRegistry.js +111 -0
  99. package/dist/review/reviewConfig.js +10 -0
  100. package/dist/review/reviewFinding.js +240 -0
  101. package/dist/review/reviewFindingLedger.js +545 -0
  102. package/dist/review/reviewOutcomeClassifier.js +61 -0
  103. package/dist/review/reviewRound.js +56 -4
  104. package/dist/run/agentRun.js +80 -4
  105. package/dist/run/providerRetry.js +84 -0
  106. package/dist/run/providerRetryConfig.js +63 -0
  107. package/dist/run/yieldReceipt.js +65 -0
  108. package/dist/runtime/exactControlPlane.js +79 -2
  109. package/dist/runtime/index.js +4 -0
  110. package/dist/runtime/sessionOwnerIdentity.js +269 -0
  111. package/dist/runtime/sessionOwnerRegistry.js +132 -0
  112. package/dist/runtime/sessionReconciliation.js +93 -0
  113. package/dist/runtime/sessionTerminationGuard.js +211 -0
  114. package/dist/runtime/taskRuntimeIsolation.js +13 -0
  115. package/dist/runtime/tmuxAdapters.js +34 -1
  116. package/dist/scheduler/actionability.js +155 -0
  117. package/dist/scheduler/activeRoleRunDelivery.js +14 -5
  118. package/dist/scheduler/activeTaskProgress.js +60 -0
  119. package/dist/scheduler/leaderWakeupProcessor.js +22 -11
  120. package/dist/scheduler/roleRunStall.js +135 -29
  121. package/dist/scheduler/taskExecutionProjection.js +11 -0
  122. package/dist/setup/setupCommand.js +27 -4
  123. package/dist/storage/compatibleTaskStore.js +112 -5
  124. package/dist/storage/migration/productionRegistry.js +769 -1
  125. package/dist/storage/persistenceWorker.js +194 -0
  126. package/dist/storage/sqliteSchema.js +705 -0
  127. package/dist/storage/sqliteStore.js +1695 -0
  128. package/dist/storage/storageVersions.js +9 -2
  129. package/dist/storage/storeRpc.js +298 -0
  130. package/dist/storage/taskStore.js +982 -21
  131. package/dist/storage/upgrade/homeClassification.js +157 -12
  132. package/dist/storage/upgrade/migrationReceipt.js +67 -0
  133. package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
  134. package/dist/storage/upgrade/recordVersions.js +10 -1
  135. package/dist/storage/upgrade/sqliteMigrationTarget.js +351 -0
  136. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
  137. package/dist/storage/upgrade/sqliteStateMigration.js +713 -0
  138. package/dist/storage/upgrade/upgradeOrchestrator.js +510 -18
  139. package/dist/task/deliveryGuard.js +226 -0
  140. package/dist/task/nextAction.js +343 -0
  141. package/dist/task/repairWave.js +137 -0
  142. package/dist/task/taskRecordReference.js +6 -1
  143. package/dist/telemetry/sqliteTelemetryStore.js +387 -0
  144. package/dist/telemetry/telemetryCompaction.js +251 -0
  145. package/dist/telemetry/telemetryConfig.js +64 -0
  146. package/dist/telemetry/telemetryRouter.js +32 -0
  147. package/dist/telemetry/telemetryStore.js +19 -0
  148. package/dist/telemetry/telemetryWiring.js +33 -0
  149. package/dist/tmux/tmuxManager.js +20 -1
  150. package/dist/tmux/tmuxSocketEndpoint.js +20 -0
  151. package/dist/verification/gateArtifact.js +216 -0
  152. package/dist/verification/gateArtifactStore.js +87 -0
  153. package/dist/verification/verificationGateService.js +414 -0
  154. package/dist/verification/verificationPlan.js +308 -0
  155. package/dist/workspace/gitChangeSetCapture.js +12 -2
  156. package/dist/workspace/workItemChangeSetManager.js +60 -3
  157. package/package.json +2 -1
@@ -1,4 +1,5 @@
1
- /** Folds immutable Hook facts one at a time before acknowledging them. */
1
+ const DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN = 64;
2
+ /** Folds immutable Hook facts in one bounded transaction before acknowledging them. */
2
3
  export class FileRuntimeEventProcessor {
3
4
  inbox;
4
5
  observer;
@@ -17,36 +18,513 @@ export class FileRuntimeEventProcessor {
17
18
  events = this.inbox.list();
18
19
  }
19
20
  catch (error) {
20
- return { acknowledgedEventIds, deferred, failed: [{ error }] };
21
+ return emptyDrainFailure(error);
22
+ }
23
+ const coalesced = coalesceRuntimeProgress(events);
24
+ const maximum = positiveInteger(this.options.maxEventsPerDrain, DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN);
25
+ const selected = selectDrainBatch(coalesced, maximum);
26
+ let stateTransactions = 0;
27
+ if (selected.length > 0 && this.observer.withRuntimeEventTransaction !== undefined) {
28
+ try {
29
+ stateTransactions += 1;
30
+ const folded = this.observer.withRuntimeEventTransaction(() => (selected.map((candidate) => this.foldOne(candidate, now))));
31
+ for (const result of folded) {
32
+ this.finalizeOne(result, acknowledgedEventIds, deferred, failed);
33
+ }
34
+ }
35
+ catch {
36
+ // A failed aggregate transaction commits nothing. Retry each candidate
37
+ // independently so one bad event cannot strand unrelated facts.
38
+ for (const candidate of selected) {
39
+ try {
40
+ stateTransactions += 1;
41
+ const folded = this.observer.withRuntimeEventTransaction(() => (this.foldOne(candidate, now)));
42
+ this.finalizeOne(folded, acknowledgedEventIds, deferred, failed);
43
+ }
44
+ catch (candidateError) {
45
+ failed.push({ eventId: candidate.event.id, error: candidateError });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ else {
51
+ for (const candidate of selected) {
52
+ try {
53
+ const folded = this.foldOne(candidate, now);
54
+ this.finalizeOne(folded, acknowledgedEventIds, deferred, failed);
55
+ }
56
+ catch (error) {
57
+ failed.push({ eventId: candidate.event.id, error });
58
+ }
59
+ }
60
+ }
61
+ const progressEventsSelected = selected.filter(({ event }) => (event.type === "native-turn-progress")).length;
62
+ const representedEventCount = selected.reduce((count, candidate) => (count + candidate.representedEventIds.length), 0);
63
+ const acknowledged = new Set(acknowledgedEventIds);
64
+ const remaining = events.filter(({ id }) => !acknowledged.has(id));
65
+ const remainingProgressEventCount = remaining.filter(({ type }) => (type === "native-turn-progress")).length;
66
+ return {
67
+ acknowledgedEventIds,
68
+ deferred,
69
+ failed,
70
+ remainingEventCount: Math.max(0, events.length - acknowledgedEventIds.length),
71
+ metrics: {
72
+ listedEventCount: events.length,
73
+ selectedEventCount: selected.length,
74
+ semanticEventsSelected: selected.length - progressEventsSelected,
75
+ progressEventsSelected,
76
+ progressEventsCoalesced: representedEventCount - selected.length,
77
+ stateTransactions,
78
+ remainingSemanticEventCount: remaining.length - remainingProgressEventCount,
79
+ remainingProgressEventCount
80
+ }
81
+ };
82
+ }
83
+ foldOne(candidate, now) {
84
+ const event = candidate.event;
85
+ let outcome = "applied";
86
+ if (event.type === "native-turn-completed") {
87
+ outcome = this.applyCodex(event, now);
88
+ }
89
+ else if (event.type === "claude-stop-failure") {
90
+ this.applyClaudeStopFailure(event, now);
91
+ }
92
+ else if (event.type === "native-session-lifecycle") {
93
+ outcome = this.applySessionLifecycle(event, now);
94
+ }
95
+ else if (event.type === "native-turn-progress") {
96
+ outcome = this.applyProviderTurnProgress(event, now);
97
+ }
98
+ else if (event.type === "durable-job-terminal") {
99
+ // f7/rr5: The supervisor already transitioned the job and
100
+ // enqueued the Leader wakeup. This event is the durable terminal
101
+ // channel: acknowledge it so the Controller's event pipeline
102
+ // converges without re-processing. No state change to apply.
103
+ this.applyDurableJobTerminal(event);
104
+ }
105
+ else {
106
+ outcome = this.applyPromptAccepted(event, now);
107
+ }
108
+ return {
109
+ candidate,
110
+ outcome,
111
+ notifyTaskRuntime: outcome === "applied" && (event.type === "native-session-lifecycle"
112
+ || event.type === "native-prompt-accepted"
113
+ || (event.type === "native-turn-completed" && event.scope === "task"))
114
+ };
115
+ }
116
+ finalizeOne(folded, acknowledged, deferred, failed) {
117
+ const { candidate, outcome } = folded;
118
+ try {
119
+ if (outcome === "deferred") {
120
+ deferred.push(candidate.event);
121
+ this.acknowledge(candidate.representedEventIds.filter((id) => id !== candidate.event.id), acknowledged);
122
+ return;
123
+ }
124
+ if (folded.notifyTaskRuntime) {
125
+ const event = candidate.event;
126
+ if (event.scope === "task" && event.taskId !== undefined && event.type !== "durable-job-terminal") {
127
+ this.options.onTaskRuntimeApplied?.({
128
+ taskId: event.taskId,
129
+ roleName: event.roleName,
130
+ agentId: event.agentId,
131
+ adapterId: event.adapterId,
132
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
133
+ nativeSessionId: event.nativeSessionId,
134
+ ...(event.runId === undefined ? {} : { runId: event.runId })
135
+ });
136
+ }
137
+ }
138
+ this.acknowledge(candidate.representedEventIds, acknowledged);
139
+ }
140
+ catch (error) {
141
+ failed.push({ eventId: candidate.event.id, error });
142
+ }
143
+ }
144
+ applySessionLifecycle(event, now) {
145
+ const task = this.observer.getTask(event.taskId);
146
+ if (task === null || task.status !== "active")
147
+ return "obsolete";
148
+ if (this.observer.observeProviderSessionLifecycle === undefined)
149
+ return "obsolete";
150
+ const outcome = this.observer.observeProviderSessionLifecycle({
151
+ eventId: event.id,
152
+ taskId: event.taskId,
153
+ roleName: event.roleName,
154
+ agentId: event.agentId,
155
+ adapterId: event.adapterId,
156
+ launchId: event.launchId,
157
+ nativeSessionId: event.nativeSessionId,
158
+ ...(event.runId === undefined ? {} : { runId: event.runId }),
159
+ ...(event.sessionSource === undefined ? {} : { sessionSource: event.sessionSource })
160
+ }, now);
161
+ return outcome === "deferred" ? "deferred" : outcome;
162
+ }
163
+ applyPromptAccepted(event, now) {
164
+ const task = this.observer.getTask(event.taskId);
165
+ if (task === null || task.status !== "active")
166
+ return "obsolete";
167
+ if (this.observer.observeProviderPromptAccepted === undefined)
168
+ return "obsolete";
169
+ const outcome = this.observer.observeProviderPromptAccepted({
170
+ eventId: event.id,
171
+ taskId: event.taskId,
172
+ roleName: event.roleName,
173
+ agentId: event.agentId,
174
+ adapterId: event.adapterId,
175
+ launchId: event.launchId,
176
+ nativeSessionId: event.nativeSessionId,
177
+ runId: event.runId,
178
+ receiptId: event.receiptId
179
+ }, now);
180
+ return outcome === "deferred" ? "deferred" : outcome;
181
+ }
182
+ /**
183
+ * f7/rr5: Acknowledge a durable-job-terminal event. The supervisor
184
+ * already committed the terminal transition and the Leader wakeup;
185
+ * this event only needs to be acknowledged so it leaves the inbox.
186
+ */
187
+ applyDurableJobTerminal(event) {
188
+ // The event is a notification of a committed state change. No observer
189
+ // call is needed — the Leader wakeup was enqueued in the same
190
+ // transaction as the terminal transition.
191
+ void event;
192
+ }
193
+ applyProviderTurnProgress(event, now) {
194
+ const task = this.observer.getTask(event.taskId);
195
+ if (task === null || task.status !== "active")
196
+ return "obsolete";
197
+ if (this.observer.observeProviderTurnProgress === undefined)
198
+ return "obsolete";
199
+ const outcome = this.observer.observeProviderTurnProgress({
200
+ eventId: event.id,
201
+ receivedAt: event.receivedAt,
202
+ taskId: event.taskId,
203
+ roleName: event.roleName,
204
+ agentId: event.agentId,
205
+ adapterId: event.adapterId,
206
+ launchId: event.launchId,
207
+ nativeSessionId: event.nativeSessionId,
208
+ runId: event.runId,
209
+ progressId: event.progressId,
210
+ ...(event.sequence === undefined ? {} : { sequence: event.sequence })
211
+ }, now);
212
+ return outcome === "deferred" ? "deferred" : "applied";
213
+ }
214
+ applyCodex(event, now) {
215
+ if (event.scope === "task") {
216
+ const task = this.observer.getTask(event.taskId);
217
+ const input = {
218
+ eventId: event.id,
219
+ taskId: event.taskId,
220
+ roleName: event.roleName,
221
+ agentId: event.agentId,
222
+ adapterId: event.adapterId,
223
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
224
+ nativeSessionId: event.nativeSessionId,
225
+ turnId: event.turnId,
226
+ ...(event.runId === undefined ? {} : { runId: event.runId }),
227
+ summary: event.summary
228
+ };
229
+ if (task === null)
230
+ return "obsolete";
231
+ if (task.status !== "active") {
232
+ this.recordObsolete(event, task.status === "archived" ? "task-archived" : "task-retired", now);
233
+ return "obsolete";
234
+ }
235
+ const disposition = this.observer.classifyRuntimeTurnCompleted?.(input) ?? "apply";
236
+ if (disposition === "deferred")
237
+ return disposition;
238
+ if (disposition === "obsolete") {
239
+ this.recordObsolete(event, "identity-mismatch-or-terminal", now);
240
+ return disposition;
241
+ }
242
+ const observed = this.observer.observeRuntimeTurnCompleted(input, now);
243
+ if (isObsoleteRuntimeTurnObservation(observed)) {
244
+ this.recordObsolete(event, "runtime-cleanup-or-stopped-session", now);
245
+ return "obsolete";
246
+ }
247
+ return "applied";
248
+ }
249
+ const input = {
250
+ eventId: event.id,
251
+ roleName: event.roleName,
252
+ agentId: event.agentId,
253
+ adapterId: event.adapterId,
254
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
255
+ nativeSessionId: event.nativeSessionId,
256
+ turnId: event.turnId,
257
+ ...(event.title === undefined ? {} : { title: event.title }),
258
+ summary: event.summary
259
+ };
260
+ if (this.observer.classifyGlobalRuntimeTurnCompleted?.(input) !== "obsolete") {
261
+ this.observer.observeGlobalRuntimeTurnCompleted(input, now);
262
+ return "applied";
263
+ }
264
+ return "obsolete";
265
+ }
266
+ applyClaudeStopFailure(event, now) {
267
+ const task = this.observer.getTask(event.taskId);
268
+ if (task === null)
269
+ return;
270
+ const input = {
271
+ eventId: event.id,
272
+ type: event.type,
273
+ taskId: event.taskId,
274
+ roleName: event.roleName,
275
+ agentId: event.agentId,
276
+ adapterId: event.adapterId,
277
+ launchId: event.launchId,
278
+ nativeSessionId: event.nativeSessionId,
279
+ runId: event.runId,
280
+ error: event.error,
281
+ ...(event.errorDetails === undefined ? {} : { errorDetails: event.errorDetails }),
282
+ ...(event.lastAssistantMessage === undefined
283
+ ? {}
284
+ : { lastAssistantMessage: event.lastAssistantMessage })
285
+ };
286
+ if (task.status !== "active"
287
+ || this.observer.classifyClaudeStopFailureEvent?.(input) === "obsolete") {
288
+ this.recordObsolete(event, task.status === "archived"
289
+ ? "task-archived"
290
+ : task.status !== "active"
291
+ ? "task-retired"
292
+ : "identity-mismatch-or-terminal", now);
293
+ return;
294
+ }
295
+ if (this.observer.observeClaudeStopFailureEvent === undefined) {
296
+ throw new Error("Claude StopFailure observer is unavailable.");
297
+ }
298
+ this.observer.observeClaudeStopFailureEvent(input, now);
299
+ }
300
+ recordObsolete(event, reason, now) {
301
+ if (event.scope !== "task" || event.taskId === undefined)
302
+ return;
303
+ // durable-job-terminal events have no provider identity; they are never
304
+ // recorded as obsolete.
305
+ if (!("roleName" in event) || !("nativeSessionId" in event))
306
+ return;
307
+ this.observer.observeObsoleteRuntimeEvent?.({
308
+ eventId: event.id,
309
+ eventType: event.type,
310
+ taskId: event.taskId,
311
+ roleName: event.roleName,
312
+ agentId: event.agentId,
313
+ ...(event.runId === undefined ? {} : { runId: event.runId }),
314
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
315
+ nativeSessionId: event.nativeSessionId,
316
+ reason
317
+ }, now);
318
+ }
319
+ acknowledge(ids, acknowledged) {
320
+ if (this.inbox.acknowledgeMany !== undefined) {
321
+ acknowledged.push(...this.inbox.acknowledgeMany(ids));
322
+ return;
323
+ }
324
+ for (const id of ids) {
325
+ if (this.inbox.acknowledge(id))
326
+ acknowledged.push(id);
327
+ }
328
+ }
329
+ }
330
+ export function coalesceRuntimeProgress(events, instrumentation = {}) {
331
+ const result = [];
332
+ let segment = [];
333
+ const flush = () => {
334
+ if (segment.length === 0)
335
+ return;
336
+ const frontiers = new Map();
337
+ for (let index = 0; index < segment.length; index += 1) {
338
+ instrumentation.onProgressVisit?.();
339
+ const event = segment[index];
340
+ const key = progressStreamKey(event);
341
+ const frontier = frontiers.get(key);
342
+ if (frontier === undefined || event.receivedAt > frontier.latestReceivedAt) {
343
+ frontiers.set(key, {
344
+ latestReceivedAt: event.receivedAt,
345
+ ...(event.sequence === undefined
346
+ ? {}
347
+ : {
348
+ greatestSequence: event.sequence,
349
+ firstGreatestSequenceIndex: index
350
+ }),
351
+ firstLatestIndex: index
352
+ });
353
+ }
354
+ else if (event.receivedAt === frontier.latestReceivedAt
355
+ && event.sequence !== undefined
356
+ && (frontier.greatestSequence === undefined
357
+ || event.sequence > frontier.greatestSequence)) {
358
+ frontiers.set(key, {
359
+ ...frontier,
360
+ greatestSequence: event.sequence,
361
+ firstGreatestSequenceIndex: index
362
+ });
363
+ }
364
+ }
365
+ const representedByIndex = new Map();
366
+ for (let index = 0; index < segment.length; index += 1) {
367
+ instrumentation.onProgressVisit?.();
368
+ const event = segment[index];
369
+ const frontier = frontiers.get(progressStreamKey(event));
370
+ const strictlyDominated = event.receivedAt < frontier.latestReceivedAt
371
+ || (event.receivedAt === frontier.latestReceivedAt
372
+ && event.sequence !== undefined
373
+ && frontier.greatestSequence !== undefined
374
+ && event.sequence < frontier.greatestSequence);
375
+ const representativeIndex = strictlyDominated
376
+ ? frontier.firstGreatestSequenceIndex ?? frontier.firstLatestIndex
377
+ : index;
378
+ const represented = representedByIndex.get(representativeIndex) ?? [];
379
+ represented.push(event.id);
380
+ representedByIndex.set(representativeIndex, represented);
381
+ }
382
+ for (let index = 0; index < segment.length; index += 1) {
383
+ const representedEventIds = representedByIndex.get(index);
384
+ if (representedEventIds === undefined)
385
+ continue;
386
+ result.push({ event: segment[index], representedEventIds });
387
+ }
388
+ segment = [];
389
+ };
390
+ for (const event of events) {
391
+ if (event.type !== "native-turn-progress") {
392
+ flush();
393
+ result.push({ event, representedEventIds: [event.id] });
394
+ continue;
395
+ }
396
+ segment.push(event);
397
+ }
398
+ flush();
399
+ return result;
400
+ }
401
+ function selectDrainBatch(events, maximum) {
402
+ // A batch is always an arrival-order prefix. Admission and drain coalescing
403
+ // keep a progress flood bounded without allowing a later semantic fact to
404
+ // overtake an earlier progress fence from another Run.
405
+ return events.slice(0, maximum);
406
+ }
407
+ function progressStreamKey(event) {
408
+ return JSON.stringify([
409
+ event.taskId,
410
+ event.roleName,
411
+ event.agentId,
412
+ event.adapterId,
413
+ event.launchId,
414
+ event.nativeSessionId,
415
+ event.runId
416
+ ]);
417
+ }
418
+ function emptyDrainFailure(error) {
419
+ return {
420
+ acknowledgedEventIds: [],
421
+ deferred: [],
422
+ failed: [{ error }],
423
+ remainingEventCount: 0,
424
+ metrics: {
425
+ listedEventCount: 0,
426
+ selectedEventCount: 0,
427
+ semanticEventsSelected: 0,
428
+ progressEventsSelected: 0,
429
+ progressEventsCoalesced: 0,
430
+ stateTransactions: 0,
431
+ remainingSemanticEventCount: 0,
432
+ remainingProgressEventCount: 0
433
+ }
434
+ };
435
+ }
436
+ function positiveInteger(value, fallback) {
437
+ const resolved = value ?? fallback;
438
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
439
+ throw new TypeError("Runtime event drain limit must be a positive integer.");
440
+ }
441
+ return resolved;
442
+ }
443
+ function isObsoleteRuntimeTurnObservation(value) {
444
+ return typeof value === "object"
445
+ && value !== null
446
+ && value.disposition === "obsolete";
447
+ }
448
+ /**
449
+ * Build an {@link AsyncRuntimeTurnEventObserver} that forwards every fold to the
450
+ * worker-hosted adapter via {@link AsyncObserverInvoker}. The adapter's fold
451
+ * logic (read-then-write transactions) runs in the worker, off the main event
452
+ * loop; the main thread only awaits the result.
453
+ */
454
+ export function createAsyncRuntimeObserver(invoke) {
455
+ const call = (method, args) => invoke(method, args);
456
+ const withNow = (method, input, now) => now === undefined ? call(method, [input]) : call(method, [input, now]);
457
+ return {
458
+ getTask: (taskId) => call("getTask", [taskId]),
459
+ observeRuntimeTurnCompleted: (input, now) => withNow("observeRuntimeTurnCompleted", input, now),
460
+ observeGlobalRuntimeTurnCompleted: (input, now) => withNow("observeGlobalRuntimeTurnCompleted", input, now),
461
+ observeClaudeStopFailureEvent: (input, now) => withNow("observeClaudeStopFailureEvent", input, now),
462
+ observeObsoleteRuntimeEvent: (input, now) => withNow("observeObsoleteRuntimeEvent", input, now),
463
+ observeProviderSessionLifecycle: (input, now) => withNow("observeProviderSessionLifecycle", input, now),
464
+ observeProviderPromptAccepted: (input, now) => withNow("observeProviderPromptAccepted", input, now),
465
+ observeProviderTurnProgress: (input, now) => withNow("observeProviderTurnProgress", input, now)
466
+ };
467
+ }
468
+ /**
469
+ * The async counterpart to {@link FileRuntimeEventProcessor}: folds immutable
470
+ * Hook facts one at a time (awaiting the worker-hosted observer) before
471
+ * acknowledging them. The inbox itself is file-based and stays on the main
472
+ * thread; only the db-touching folds are proxied to the worker.
473
+ */
474
+ export class AsyncRuntimeEventProcessor {
475
+ inbox;
476
+ observer;
477
+ options;
478
+ constructor(inbox, observer, options = {}) {
479
+ this.inbox = inbox;
480
+ this.observer = observer;
481
+ this.options = options;
482
+ }
483
+ async drainAsync(now) {
484
+ const acknowledgedEventIds = [];
485
+ const deferred = [];
486
+ const failed = [];
487
+ let events;
488
+ try {
489
+ events = this.inbox.list();
490
+ }
491
+ catch (error) {
492
+ return emptyDrainFailure(error);
21
493
  }
22
494
  for (const event of events) {
23
495
  try {
24
496
  if (event.type === "native-turn-completed") {
25
- const outcome = this.applyCodex(event, now);
497
+ const outcome = await this.applyCodex(event, now);
26
498
  if (outcome === "deferred") {
27
499
  deferred.push(event);
28
500
  continue;
29
501
  }
30
502
  }
31
503
  else if (event.type === "claude-stop-failure") {
32
- this.applyClaudeStopFailure(event, now);
504
+ await this.applyClaudeStopFailure(event, now);
33
505
  }
34
506
  else if (event.type === "native-session-lifecycle") {
35
- const outcome = this.applySessionLifecycle(event, now);
507
+ const outcome = await this.applySessionLifecycle(event, now);
36
508
  if (outcome === "deferred") {
37
509
  deferred.push(event);
38
510
  continue;
39
511
  }
40
512
  }
41
513
  else if (event.type === "native-turn-progress") {
42
- const outcome = this.applyProviderTurnProgress(event, now);
514
+ const outcome = await this.applyProviderTurnProgress(event, now);
43
515
  if (outcome === "deferred") {
44
516
  deferred.push(event);
45
517
  continue;
46
518
  }
47
519
  }
520
+ else if (event.type === "durable-job-terminal") {
521
+ // f7/rr5: The supervisor already transitioned the job and
522
+ // enqueued the Leader wakeup. This event is the durable terminal
523
+ // channel: acknowledge it so the pipeline converges without
524
+ // re-processing. No state change to apply.
525
+ }
48
526
  else {
49
- const outcome = this.applyPromptAccepted(event, now);
527
+ const outcome = await this.applyPromptAccepted(event, now);
50
528
  if (outcome === "deferred") {
51
529
  deferred.push(event);
52
530
  continue;
@@ -58,15 +536,34 @@ export class FileRuntimeEventProcessor {
58
536
  failed.push({ eventId: event.id, error });
59
537
  }
60
538
  }
61
- return { acknowledgedEventIds, deferred, failed };
539
+ const acknowledged = new Set(acknowledgedEventIds);
540
+ const remaining = events.filter(({ id }) => !acknowledged.has(id));
541
+ const progressEventsSelected = events.filter(({ type }) => (type === "native-turn-progress")).length;
542
+ const remainingProgressEventCount = remaining.filter(({ type }) => (type === "native-turn-progress")).length;
543
+ return {
544
+ acknowledgedEventIds,
545
+ deferred,
546
+ failed,
547
+ remainingEventCount: remaining.length,
548
+ metrics: {
549
+ listedEventCount: events.length,
550
+ selectedEventCount: events.length,
551
+ semanticEventsSelected: events.length - progressEventsSelected,
552
+ progressEventsSelected,
553
+ progressEventsCoalesced: 0,
554
+ stateTransactions: 0,
555
+ remainingSemanticEventCount: remaining.length - remainingProgressEventCount,
556
+ remainingProgressEventCount
557
+ }
558
+ };
62
559
  }
63
- applySessionLifecycle(event, now) {
64
- const task = this.observer.getTask(event.taskId);
560
+ async applySessionLifecycle(event, now) {
561
+ const task = await this.observer.getTask(event.taskId);
65
562
  if (task === null || task.status !== "active")
66
563
  return "obsolete";
67
564
  if (this.observer.observeProviderSessionLifecycle === undefined)
68
565
  return "obsolete";
69
- const outcome = this.observer.observeProviderSessionLifecycle({
566
+ const outcome = await this.observer.observeProviderSessionLifecycle({
70
567
  eventId: event.id,
71
568
  taskId: event.taskId,
72
569
  roleName: event.roleName,
@@ -80,18 +577,23 @@ export class FileRuntimeEventProcessor {
80
577
  if (outcome === "applied") {
81
578
  this.options.onTaskRuntimeApplied?.({
82
579
  taskId: event.taskId,
83
- roleName: event.roleName
580
+ roleName: event.roleName,
581
+ agentId: event.agentId,
582
+ adapterId: event.adapterId,
583
+ launchId: event.launchId,
584
+ nativeSessionId: event.nativeSessionId,
585
+ ...(event.runId === undefined ? {} : { runId: event.runId })
84
586
  });
85
587
  }
86
588
  return outcome === "deferred" ? "deferred" : outcome;
87
589
  }
88
- applyPromptAccepted(event, now) {
89
- const task = this.observer.getTask(event.taskId);
590
+ async applyPromptAccepted(event, now) {
591
+ const task = await this.observer.getTask(event.taskId);
90
592
  if (task === null || task.status !== "active")
91
593
  return "obsolete";
92
594
  if (this.observer.observeProviderPromptAccepted === undefined)
93
595
  return "obsolete";
94
- const outcome = this.observer.observeProviderPromptAccepted({
596
+ const outcome = await this.observer.observeProviderPromptAccepted({
95
597
  eventId: event.id,
96
598
  taskId: event.taskId,
97
599
  roleName: event.roleName,
@@ -105,18 +607,23 @@ export class FileRuntimeEventProcessor {
105
607
  if (outcome === "applied") {
106
608
  this.options.onTaskRuntimeApplied?.({
107
609
  taskId: event.taskId,
108
- roleName: event.roleName
610
+ roleName: event.roleName,
611
+ agentId: event.agentId,
612
+ adapterId: event.adapterId,
613
+ launchId: event.launchId,
614
+ nativeSessionId: event.nativeSessionId,
615
+ runId: event.runId
109
616
  });
110
617
  }
111
618
  return outcome === "deferred" ? "deferred" : outcome;
112
619
  }
113
- applyProviderTurnProgress(event, now) {
114
- const task = this.observer.getTask(event.taskId);
620
+ async applyProviderTurnProgress(event, now) {
621
+ const task = await this.observer.getTask(event.taskId);
115
622
  if (task === null || task.status !== "active")
116
623
  return "obsolete";
117
624
  if (this.observer.observeProviderTurnProgress === undefined)
118
625
  return "obsolete";
119
- const outcome = this.observer.observeProviderTurnProgress({
626
+ const outcome = await this.observer.observeProviderTurnProgress({
120
627
  eventId: event.id,
121
628
  receivedAt: event.receivedAt,
122
629
  taskId: event.taskId,
@@ -131,9 +638,9 @@ export class FileRuntimeEventProcessor {
131
638
  }, now);
132
639
  return outcome === "deferred" ? "deferred" : "applied";
133
640
  }
134
- applyCodex(event, now) {
641
+ async applyCodex(event, now) {
135
642
  if (event.scope === "task") {
136
- const task = this.observer.getTask(event.taskId);
643
+ const task = await this.observer.getTask(event.taskId);
137
644
  const input = {
138
645
  eventId: event.id,
139
646
  taskId: event.taskId,
@@ -149,24 +656,29 @@ export class FileRuntimeEventProcessor {
149
656
  if (task === null)
150
657
  return "obsolete";
151
658
  if (task.status !== "active") {
152
- this.recordObsolete(event, task.status === "archived" ? "task-archived" : "task-retired", now);
659
+ await this.recordObsolete(event, task.status === "archived" ? "task-archived" : "task-retired", now);
153
660
  return "obsolete";
154
661
  }
155
- const disposition = this.observer.classifyRuntimeTurnCompleted?.(input) ?? "apply";
662
+ const disposition = (await this.observer.classifyRuntimeTurnCompleted?.(input)) ?? "apply";
156
663
  if (disposition === "deferred")
157
664
  return disposition;
158
665
  if (disposition === "obsolete") {
159
- this.recordObsolete(event, "identity-mismatch-or-terminal", now);
666
+ await this.recordObsolete(event, "identity-mismatch-or-terminal", now);
160
667
  return disposition;
161
668
  }
162
- const observed = this.observer.observeRuntimeTurnCompleted(input, now);
669
+ const observed = await this.observer.observeRuntimeTurnCompleted(input, now);
163
670
  if (isObsoleteRuntimeTurnObservation(observed)) {
164
- this.recordObsolete(event, "runtime-cleanup-or-stopped-session", now);
671
+ await this.recordObsolete(event, "runtime-cleanup-or-stopped-session", now);
165
672
  return "obsolete";
166
673
  }
167
674
  this.options.onTaskRuntimeApplied?.({
168
675
  taskId: event.taskId,
169
- roleName: event.roleName
676
+ roleName: event.roleName,
677
+ agentId: event.agentId,
678
+ adapterId: event.adapterId,
679
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
680
+ nativeSessionId: event.nativeSessionId,
681
+ ...(event.runId === undefined ? {} : { runId: event.runId })
170
682
  });
171
683
  return "applied";
172
684
  }
@@ -181,14 +693,14 @@ export class FileRuntimeEventProcessor {
181
693
  ...(event.title === undefined ? {} : { title: event.title }),
182
694
  summary: event.summary
183
695
  };
184
- if (this.observer.classifyGlobalRuntimeTurnCompleted?.(input) !== "obsolete") {
185
- this.observer.observeGlobalRuntimeTurnCompleted(input, now);
696
+ if ((await this.observer.classifyGlobalRuntimeTurnCompleted?.(input)) !== "obsolete") {
697
+ await this.observer.observeGlobalRuntimeTurnCompleted(input, now);
186
698
  return "applied";
187
699
  }
188
700
  return "obsolete";
189
701
  }
190
- applyClaudeStopFailure(event, now) {
191
- const task = this.observer.getTask(event.taskId);
702
+ async applyClaudeStopFailure(event, now) {
703
+ const task = await this.observer.getTask(event.taskId);
192
704
  if (task === null)
193
705
  return;
194
706
  const input = {
@@ -208,8 +720,8 @@ export class FileRuntimeEventProcessor {
208
720
  : { lastAssistantMessage: event.lastAssistantMessage })
209
721
  };
210
722
  if (task.status !== "active"
211
- || this.observer.classifyClaudeStopFailureEvent?.(input) === "obsolete") {
212
- this.recordObsolete(event, task.status === "archived"
723
+ || (await this.observer.classifyClaudeStopFailureEvent?.(input)) === "obsolete") {
724
+ await this.recordObsolete(event, task.status === "archived"
213
725
  ? "task-archived"
214
726
  : task.status !== "active"
215
727
  ? "task-retired"
@@ -219,12 +731,12 @@ export class FileRuntimeEventProcessor {
219
731
  if (this.observer.observeClaudeStopFailureEvent === undefined) {
220
732
  throw new Error("Claude StopFailure observer is unavailable.");
221
733
  }
222
- this.observer.observeClaudeStopFailureEvent(input, now);
734
+ await this.observer.observeClaudeStopFailureEvent(input, now);
223
735
  }
224
- recordObsolete(event, reason, now) {
225
- if (event.scope !== "task" || event.taskId === undefined)
736
+ async recordObsolete(event, reason, now) {
737
+ if (event.scope !== "task" || event.taskId === undefined || event.type === "durable-job-terminal")
226
738
  return;
227
- this.observer.observeObsoleteRuntimeEvent?.({
739
+ await this.observer.observeObsoleteRuntimeEvent?.({
228
740
  eventId: event.id,
229
741
  eventType: event.type,
230
742
  taskId: event.taskId,
@@ -241,8 +753,3 @@ export class FileRuntimeEventProcessor {
241
753
  acknowledged.push(id);
242
754
  }
243
755
  }
244
- function isObsoleteRuntimeTurnObservation(value) {
245
- return typeof value === "object"
246
- && value !== null
247
- && value.disposition === "obsolete";
248
- }