@zq-silk/yui 0.6.0 → 0.6.2

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 (150) hide show
  1. package/README.md +5 -5
  2. package/dist/agent/managedRuntimeEnvironment.js +2 -1
  3. package/dist/cli/commandCatalog.js +251 -13
  4. package/dist/cli/updateOrchestrator.js +8 -0
  5. package/dist/cli/updatePorts.js +76 -22
  6. package/dist/cli.js +264 -20
  7. package/dist/commands/configCommands.js +83 -9
  8. package/dist/commands/controllerCommands.js +103 -0
  9. package/dist/commands/deliveryGuardPreflight.js +30 -0
  10. package/dist/commands/durableJobCommands.js +231 -0
  11. package/dist/commands/executionAuditCommands.js +193 -0
  12. package/dist/commands/grantCommands.js +374 -0
  13. package/dist/commands/projectCommands.js +119 -81
  14. package/dist/commands/releaseCommands.js +444 -0
  15. package/dist/commands/resourcesCommands.js +274 -0
  16. package/dist/commands/sessionCommands.js +104 -0
  17. package/dist/commands/taskActor.js +117 -0
  18. package/dist/commands/taskChangeSetCommands.js +60 -0
  19. package/dist/commands/taskCommands.js +618 -202
  20. package/dist/commands/taskCompletionGate.js +78 -1
  21. package/dist/commands/taskContextCommand.js +33 -6
  22. package/dist/commands/taskInputCommands.js +1 -1
  23. package/dist/commands/taskIntegrationCommands.js +136 -33
  24. package/dist/commands/taskIntegrationQueueCommands.js +228 -0
  25. package/dist/commands/taskNextActionCommand.js +100 -0
  26. package/dist/commands/taskOverlapCommands.js +120 -0
  27. package/dist/commands/taskOverviewCommand.js +36 -8
  28. package/dist/commands/telemetryCommands.js +330 -0
  29. package/dist/commands/workflowCommands.js +415 -0
  30. package/dist/config/yuiConfig.js +62 -0
  31. package/dist/controller/clientRuntime.js +42 -1
  32. package/dist/controller/controller.js +402 -56
  33. package/dist/controller/controllerMain.js +25 -2
  34. package/dist/controller/domainIdentity.js +16 -8
  35. package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
  36. package/dist/controller/handoverCandidate.js +168 -0
  37. package/dist/controller/jobClient.js +102 -0
  38. package/dist/controller/jobControl.js +613 -0
  39. package/dist/controller/jobSupervisor.js +498 -0
  40. package/dist/controller/providerHookRunFence.js +34 -5
  41. package/dist/controller/resourceCleanupLinux.js +18 -9
  42. package/dist/controller/resourceInventoryLinux.js +90 -39
  43. package/dist/controller/runtime.js +165 -15
  44. package/dist/controller/runtimeEventInbox.js +234 -57
  45. package/dist/controller/runtimeEventProcessor.js +297 -58
  46. package/dist/controller/sessionOwnerReconciliation.js +321 -0
  47. package/dist/core/controllerServer.js +416 -27
  48. package/dist/core/controllerTelemetry.js +167 -0
  49. package/dist/doctor/doctor.js +113 -16
  50. package/dist/domain/validation.js +9 -0
  51. package/dist/execution/executionGroup.js +40 -3
  52. package/dist/executor/agentExecutor.js +6 -3
  53. package/dist/executor/effectiveLaunch.js +52 -0
  54. package/dist/executor/executorRegistry.js +50 -0
  55. package/dist/executor/fileRoleLaunchPlanner.js +61 -6
  56. package/dist/grant/capabilityGrant.js +282 -0
  57. package/dist/integration/changeSet.js +16 -3
  58. package/dist/integration/changeSetManifest.js +46 -0
  59. package/dist/integration/gitIntegrationService.js +528 -147
  60. package/dist/integration/integrationAttempt.js +54 -5
  61. package/dist/integration/integrationQueueEntry.js +221 -0
  62. package/dist/integration/integrationQueueService.js +955 -0
  63. package/dist/integration/manifestTags.js +99 -0
  64. package/dist/integration/overlapDiagnostics.js +211 -0
  65. package/dist/job/durableJob.js +449 -0
  66. package/dist/job/jobRunner.js +350 -0
  67. package/dist/lifecycle/exactRunTerminalization.js +24 -2
  68. package/dist/lifecycle/providerErrorClass.js +126 -0
  69. package/dist/message/message.js +16 -3
  70. package/dist/observability/executionAudit.js +545 -0
  71. package/dist/observability/faultClassification.js +160 -0
  72. package/dist/observability/runtimeIdentity.js +367 -0
  73. package/dist/release/fakeReleasePorts.js +55 -0
  74. package/dist/release/releaseHandover.js +475 -0
  75. package/dist/release/releaseIdempotencyStore.js +165 -0
  76. package/dist/release/releaseWorkflow.js +459 -0
  77. package/dist/release/releaseWorkflowEngine.js +688 -0
  78. package/dist/release/releaseWorkflowPorts.js +1720 -0
  79. package/dist/release/runtimeRelease.js +495 -0
  80. package/dist/release/workflowFileLock.js +218 -0
  81. package/dist/repository/gitWorkspace.js +177 -1
  82. package/dist/repository/projectMaintenanceLock.js +315 -0
  83. package/dist/repository/taskWorkspaceCoordinator.js +87 -17
  84. package/dist/repository/taskWorkspacePreparer.js +1091 -517
  85. package/dist/resources/autoResourceGc.js +116 -0
  86. package/dist/resources/liveReferences.js +574 -0
  87. package/dist/resources/resourceDiscovery.js +477 -0
  88. package/dist/resources/resourceGc.js +645 -0
  89. package/dist/resources/resourceRegistrar.js +256 -0
  90. package/dist/resources/resourceRegistry.js +150 -0
  91. package/dist/resources/resourceRegistryStore.js +41 -0
  92. package/dist/resources/resourceTypes.js +42 -0
  93. package/dist/resources/sqliteResourceRegistry.js +111 -0
  94. package/dist/review/reviewConfig.js +10 -0
  95. package/dist/review/reviewFinding.js +240 -0
  96. package/dist/review/reviewFindingLedger.js +545 -0
  97. package/dist/review/reviewOutcomeClassifier.js +61 -0
  98. package/dist/review/reviewRound.js +56 -4
  99. package/dist/run/agentRun.js +80 -4
  100. package/dist/run/providerRetry.js +84 -0
  101. package/dist/run/providerRetryConfig.js +63 -0
  102. package/dist/run/yieldReceipt.js +65 -0
  103. package/dist/runtime/exactControlPlane.js +79 -2
  104. package/dist/runtime/index.js +4 -0
  105. package/dist/runtime/sessionOwnerIdentity.js +269 -0
  106. package/dist/runtime/sessionOwnerRegistry.js +132 -0
  107. package/dist/runtime/sessionReconciliation.js +93 -0
  108. package/dist/runtime/sessionTerminationGuard.js +211 -0
  109. package/dist/runtime/taskRuntimeIsolation.js +13 -0
  110. package/dist/runtime/tmuxAdapters.js +34 -1
  111. package/dist/scheduler/actionability.js +155 -0
  112. package/dist/scheduler/activeRoleRunDelivery.js +14 -5
  113. package/dist/scheduler/activeTaskProgress.js +60 -0
  114. package/dist/scheduler/leaderWakeupProcessor.js +22 -11
  115. package/dist/scheduler/roleRunStall.js +135 -29
  116. package/dist/scheduler/taskExecutionProjection.js +11 -0
  117. package/dist/storage/compatibleTaskStore.js +112 -5
  118. package/dist/storage/migration/productionRegistry.js +736 -1
  119. package/dist/storage/sqliteSchema.js +264 -3
  120. package/dist/storage/sqliteStore.js +487 -13
  121. package/dist/storage/storeRpc.js +21 -0
  122. package/dist/storage/taskStore.js +974 -21
  123. package/dist/storage/upgrade/homeClassification.js +120 -2
  124. package/dist/storage/upgrade/migrationReceipt.js +67 -0
  125. package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
  126. package/dist/storage/upgrade/recordVersions.js +10 -1
  127. package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
  128. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
  129. package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
  130. package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
  131. package/dist/task/deliveryGuard.js +226 -0
  132. package/dist/task/nextAction.js +738 -0
  133. package/dist/task/repairWave.js +137 -0
  134. package/dist/task/taskRecordReference.js +6 -1
  135. package/dist/telemetry/sqliteTelemetryStore.js +387 -0
  136. package/dist/telemetry/telemetryCompaction.js +251 -0
  137. package/dist/telemetry/telemetryConfig.js +64 -0
  138. package/dist/telemetry/telemetryRouter.js +32 -0
  139. package/dist/telemetry/telemetryStore.js +19 -0
  140. package/dist/telemetry/telemetryWiring.js +33 -0
  141. package/dist/tmux/tmuxManager.js +20 -1
  142. package/dist/tmux/tmuxSocketEndpoint.js +20 -0
  143. package/dist/verification/gateArtifact.js +216 -0
  144. package/dist/verification/gateArtifactStore.js +87 -0
  145. package/dist/verification/verificationGateService.js +414 -0
  146. package/dist/verification/verificationPlan.js +308 -0
  147. package/dist/workspace/gitChangeSetCapture.js +12 -2
  148. package/dist/workspace/workItemChangeSetManager.js +60 -3
  149. package/package.json +1 -1
  150. package/skills/yui-leader/SKILL.md +8 -0
@@ -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,48 +18,128 @@ 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);
21
22
  }
22
- for (const event of events) {
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) {
23
28
  try {
24
- if (event.type === "native-turn-completed") {
25
- const outcome = this.applyCodex(event, now);
26
- if (outcome === "deferred") {
27
- deferred.push(event);
28
- continue;
29
- }
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);
30
33
  }
31
- else if (event.type === "claude-stop-failure") {
32
- this.applyClaudeStopFailure(event, now);
33
- }
34
- else if (event.type === "native-session-lifecycle") {
35
- const outcome = this.applySessionLifecycle(event, now);
36
- if (outcome === "deferred") {
37
- deferred.push(event);
38
- continue;
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);
39
43
  }
40
- }
41
- else if (event.type === "native-turn-progress") {
42
- const outcome = this.applyProviderTurnProgress(event, now);
43
- if (outcome === "deferred") {
44
- deferred.push(event);
45
- continue;
44
+ catch (candidateError) {
45
+ failed.push({ eventId: candidate.event.id, error: candidateError });
46
46
  }
47
47
  }
48
- else {
49
- const outcome = this.applyPromptAccepted(event, now);
50
- if (outcome === "deferred") {
51
- deferred.push(event);
52
- continue;
53
- }
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 });
54
58
  }
55
- this.acknowledge(event.id, acknowledgedEventIds);
56
59
  }
57
- catch (error) {
58
- failed.push({ eventId: event.id, error });
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
59
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 });
60
142
  }
61
- return { acknowledgedEventIds, deferred, failed };
62
143
  }
63
144
  applySessionLifecycle(event, now) {
64
145
  const task = this.observer.getTask(event.taskId);
@@ -77,12 +158,6 @@ export class FileRuntimeEventProcessor {
77
158
  ...(event.runId === undefined ? {} : { runId: event.runId }),
78
159
  ...(event.sessionSource === undefined ? {} : { sessionSource: event.sessionSource })
79
160
  }, now);
80
- if (outcome === "applied") {
81
- this.options.onTaskRuntimeApplied?.({
82
- taskId: event.taskId,
83
- roleName: event.roleName
84
- });
85
- }
86
161
  return outcome === "deferred" ? "deferred" : outcome;
87
162
  }
88
163
  applyPromptAccepted(event, now) {
@@ -102,14 +177,19 @@ export class FileRuntimeEventProcessor {
102
177
  runId: event.runId,
103
178
  receiptId: event.receiptId
104
179
  }, now);
105
- if (outcome === "applied") {
106
- this.options.onTaskRuntimeApplied?.({
107
- taskId: event.taskId,
108
- roleName: event.roleName
109
- });
110
- }
111
180
  return outcome === "deferred" ? "deferred" : outcome;
112
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
+ }
113
193
  applyProviderTurnProgress(event, now) {
114
194
  const task = this.observer.getTask(event.taskId);
115
195
  if (task === null || task.status !== "active")
@@ -164,10 +244,6 @@ export class FileRuntimeEventProcessor {
164
244
  this.recordObsolete(event, "runtime-cleanup-or-stopped-session", now);
165
245
  return "obsolete";
166
246
  }
167
- this.options.onTaskRuntimeApplied?.({
168
- taskId: event.taskId,
169
- roleName: event.roleName
170
- });
171
247
  return "applied";
172
248
  }
173
249
  const input = {
@@ -224,6 +300,10 @@ export class FileRuntimeEventProcessor {
224
300
  recordObsolete(event, reason, now) {
225
301
  if (event.scope !== "task" || event.taskId === undefined)
226
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;
227
307
  this.observer.observeObsoleteRuntimeEvent?.({
228
308
  eventId: event.id,
229
309
  eventType: event.type,
@@ -236,10 +316,129 @@ export class FileRuntimeEventProcessor {
236
316
  reason
237
317
  }, now);
238
318
  }
239
- acknowledge(id, acknowledged) {
240
- this.inbox.acknowledge(id);
241
- acknowledged.push(id);
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);
242
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;
243
442
  }
244
443
  function isObsoleteRuntimeTurnObservation(value) {
245
444
  return typeof value === "object"
@@ -290,7 +489,7 @@ export class AsyncRuntimeEventProcessor {
290
489
  events = this.inbox.list();
291
490
  }
292
491
  catch (error) {
293
- return { acknowledgedEventIds, deferred, failed: [{ error }] };
492
+ return emptyDrainFailure(error);
294
493
  }
295
494
  for (const event of events) {
296
495
  try {
@@ -318,6 +517,12 @@ export class AsyncRuntimeEventProcessor {
318
517
  continue;
319
518
  }
320
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
+ }
321
526
  else {
322
527
  const outcome = await this.applyPromptAccepted(event, now);
323
528
  if (outcome === "deferred") {
@@ -331,7 +536,26 @@ export class AsyncRuntimeEventProcessor {
331
536
  failed.push({ eventId: event.id, error });
332
537
  }
333
538
  }
334
- 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
+ };
335
559
  }
336
560
  async applySessionLifecycle(event, now) {
337
561
  const task = await this.observer.getTask(event.taskId);
@@ -353,7 +577,12 @@ export class AsyncRuntimeEventProcessor {
353
577
  if (outcome === "applied") {
354
578
  this.options.onTaskRuntimeApplied?.({
355
579
  taskId: event.taskId,
356
- 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 })
357
586
  });
358
587
  }
359
588
  return outcome === "deferred" ? "deferred" : outcome;
@@ -378,7 +607,12 @@ export class AsyncRuntimeEventProcessor {
378
607
  if (outcome === "applied") {
379
608
  this.options.onTaskRuntimeApplied?.({
380
609
  taskId: event.taskId,
381
- 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
382
616
  });
383
617
  }
384
618
  return outcome === "deferred" ? "deferred" : outcome;
@@ -439,7 +673,12 @@ export class AsyncRuntimeEventProcessor {
439
673
  }
440
674
  this.options.onTaskRuntimeApplied?.({
441
675
  taskId: event.taskId,
442
- 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 })
443
682
  });
444
683
  return "applied";
445
684
  }
@@ -495,7 +734,7 @@ export class AsyncRuntimeEventProcessor {
495
734
  await this.observer.observeClaudeStopFailureEvent(input, now);
496
735
  }
497
736
  async recordObsolete(event, reason, now) {
498
- if (event.scope !== "task" || event.taskId === undefined)
737
+ if (event.scope !== "task" || event.taskId === undefined || event.type === "durable-job-terminal")
499
738
  return;
500
739
  await this.observer.observeObsoleteRuntimeEvent?.({
501
740
  eventId: event.id,