@zq-silk/yui 0.6.2 → 0.6.3

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 (61) hide show
  1. package/ARCHITECTURE.md +28 -4
  2. package/README.md +62 -24
  3. package/dist/agent/argumentPolicy.js +1 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +1 -0
  5. package/dist/cli/commandCatalog.js +19 -9
  6. package/dist/cli/interactionPolicy.js +4 -2
  7. package/dist/cli.js +77 -32
  8. package/dist/commands/taskCommands.js +46 -11
  9. package/dist/commands/taskContextCommand.js +1 -1
  10. package/dist/commands/taskRoleRuntimeStatus.js +170 -10
  11. package/dist/controller/agentRuntimeObserver.js +210 -0
  12. package/dist/controller/clientRuntime.js +3 -21
  13. package/dist/controller/controller.js +47 -7
  14. package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
  15. package/dist/controller/runtime.js +9 -3
  16. package/dist/controller/runtimeEventInbox.js +49 -295
  17. package/dist/controller/runtimeEventProcessor.js +184 -321
  18. package/dist/controller/runtimeHookRunFence.js +226 -0
  19. package/dist/controller/runtimeLaunchCoordinator.js +91 -26
  20. package/dist/controller/runtimeObservationHook.js +112 -0
  21. package/dist/core/controllerServer.js +5 -0
  22. package/dist/executor/agentAdapter.js +18 -3
  23. package/dist/executor/fileRoleLaunchPlanner.js +64 -15
  24. package/dist/executor/managedClaudeRunner.js +121 -0
  25. package/dist/observability/executionAudit.js +6 -3
  26. package/dist/repository/taskWorkspacePreparer.js +1 -4
  27. package/dist/run/providerRetryConfig.js +8 -3
  28. package/dist/runtime/agentDriver.js +229 -0
  29. package/dist/runtime/agentDriverObservation.js +57 -0
  30. package/dist/runtime/builtinAgentDrivers.js +235 -0
  31. package/dist/runtime/builtinTranscriptObserver.js +290 -0
  32. package/dist/runtime/builtinTranscriptUsage.js +97 -0
  33. package/dist/runtime/exactControlPlane.js +2 -2
  34. package/dist/runtime/index.js +1 -1
  35. package/dist/runtime/ports.js +12 -1
  36. package/dist/runtime/runtimeObservation.js +297 -0
  37. package/dist/runtime/runtimeProjection.js +277 -0
  38. package/dist/runtime/sessionTerminationGuard.js +78 -22
  39. package/dist/runtime/tmuxAdapters.js +35 -0
  40. package/dist/scheduler/activeRoleRunDelivery.js +28 -13
  41. package/dist/scheduler/leaderWakeupProcessor.js +21 -2
  42. package/dist/scheduler/roleRunLiveness.js +2 -2
  43. package/dist/scheduler/roleRunStall.js +62 -114
  44. package/dist/storage/migration/productionRegistry.js +41 -0
  45. package/dist/storage/sqliteStore.js +3 -3
  46. package/dist/storage/storageVersions.js +1 -1
  47. package/dist/telemetry/sqliteTelemetryStore.js +0 -28
  48. package/dist/telemetry/telemetryCompaction.js +1 -0
  49. package/dist/telemetry/telemetryConfig.js +4 -5
  50. package/dist/tmux/tmuxManager.js +136 -22
  51. package/dist/web/assets/client/view.js +1 -1
  52. package/dist/web/tmuxWebTerminal.js +17 -12
  53. package/dist/web/webSnapshot.js +1 -1
  54. package/dist/worktree/managedWorkspace.js +14 -0
  55. package/i18n/README.zh-CN.md +7 -5
  56. package/package.json +1 -1
  57. package/dist/controller/claudeLifecycleHook.js +0 -203
  58. package/dist/controller/codexLifecycleHook.js +0 -108
  59. package/dist/controller/providerHookRunFence.js +0 -156
  60. package/dist/lifecycle/providerLifecycleMapping.js +0 -190
  61. package/dist/telemetry/telemetryRouter.js +0 -32
@@ -1,13 +1,16 @@
1
+ import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
1
2
  const DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN = 64;
2
3
  /** Folds immutable Hook facts in one bounded transaction before acknowledging them. */
3
4
  export class FileRuntimeEventProcessor {
4
5
  inbox;
5
6
  observer;
6
7
  options;
8
+ drivers;
7
9
  constructor(inbox, observer, options = {}) {
8
10
  this.inbox = inbox;
9
11
  this.observer = observer;
10
12
  this.options = options;
13
+ this.drivers = options.drivers ?? builtinAgentDriverRegistry();
11
14
  }
12
15
  drain(now) {
13
16
  const acknowledgedEventIds = [];
@@ -58,11 +61,11 @@ export class FileRuntimeEventProcessor {
58
61
  }
59
62
  }
60
63
  }
61
- const progressEventsSelected = selected.filter(({ event }) => (event.type === "native-turn-progress")).length;
64
+ const progressEventsSelected = selected.filter(({ event }) => (isRuntimeActivityEvent(event))).length;
62
65
  const representedEventCount = selected.reduce((count, candidate) => (count + candidate.representedEventIds.length), 0);
63
66
  const acknowledged = new Set(acknowledgedEventIds);
64
67
  const remaining = events.filter(({ id }) => !acknowledged.has(id));
65
- const remainingProgressEventCount = remaining.filter(({ type }) => (type === "native-turn-progress")).length;
68
+ const remainingProgressEventCount = remaining.filter(isRuntimeActivityEvent).length;
66
69
  return {
67
70
  acknowledgedEventIds,
68
71
  deferred,
@@ -86,33 +89,34 @@ export class FileRuntimeEventProcessor {
86
89
  if (event.type === "native-turn-completed") {
87
90
  outcome = this.applyCodex(event, now);
88
91
  }
89
- else if (event.type === "claude-stop-failure") {
90
- this.applyClaudeStopFailure(event, now);
92
+ else if (event.type === "runtime-observation") {
93
+ outcome = this.applyRuntimeObservation(event, now);
91
94
  }
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") {
95
+ else {
99
96
  // f7/rr5: The supervisor already transitioned the job and
100
97
  // enqueued the Leader wakeup. This event is the durable terminal
101
98
  // channel: acknowledge it so the Controller's event pipeline
102
99
  // converges without re-processing. No state change to apply.
103
100
  this.applyDurableJobTerminal(event);
104
101
  }
105
- else {
106
- outcome = this.applyPromptAccepted(event, now);
107
- }
108
102
  return {
109
103
  candidate,
110
104
  outcome,
111
- notifyTaskRuntime: outcome === "applied" && (event.type === "native-session-lifecycle"
112
- || event.type === "native-prompt-accepted"
105
+ notifyTaskRuntime: outcome === "applied" && ((event.type === "runtime-observation" && event.scope === "task"
106
+ && ["session.started", "session.ready", "turn.accepted", "turn.completed"]
107
+ .includes(event.observation.kind))
113
108
  || (event.type === "native-turn-completed" && event.scope === "task"))
114
109
  };
115
110
  }
111
+ applyRuntimeObservation(event, now) {
112
+ const taskId = event.observation.fence.taskId;
113
+ if (taskId !== undefined) {
114
+ const task = this.observer.getTask(taskId);
115
+ if (task === null || task.status !== "active")
116
+ return "obsolete";
117
+ }
118
+ return this.observer.observeRuntimeObservation?.(event.observation, now) ?? "obsolete";
119
+ }
116
120
  finalizeOne(folded, acknowledged, deferred, failed) {
117
121
  const { candidate, outcome } = folded;
118
122
  try {
@@ -123,16 +127,34 @@ export class FileRuntimeEventProcessor {
123
127
  }
124
128
  if (folded.notifyTaskRuntime) {
125
129
  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
- });
130
+ if (event.scope === "task"
131
+ && event.taskId !== undefined
132
+ && event.type !== "durable-job-terminal") {
133
+ if (event.type === "runtime-observation") {
134
+ const fence = event.observation.fence;
135
+ if (fence.taskId !== undefined && fence.nativeSessionId !== undefined) {
136
+ this.options.onTaskRuntimeApplied?.({
137
+ taskId: fence.taskId,
138
+ roleName: fence.roleName,
139
+ agentId: fence.agentId,
140
+ adapterId: this.drivers.require(fence.driverId).adapterId,
141
+ launchId: fence.launchId,
142
+ nativeSessionId: fence.nativeSessionId,
143
+ ...(fence.runId === undefined ? {} : { runId: fence.runId })
144
+ });
145
+ }
146
+ }
147
+ else if (event.type === "native-turn-completed") {
148
+ this.options.onTaskRuntimeApplied?.({
149
+ taskId: event.taskId,
150
+ roleName: event.roleName,
151
+ agentId: event.agentId,
152
+ adapterId: event.adapterId,
153
+ ...(event.launchId === undefined ? {} : { launchId: event.launchId }),
154
+ nativeSessionId: event.nativeSessionId,
155
+ ...(event.runId === undefined ? {} : { runId: event.runId })
156
+ });
157
+ }
136
158
  }
137
159
  }
138
160
  this.acknowledge(candidate.representedEventIds, acknowledged);
@@ -141,44 +163,6 @@ export class FileRuntimeEventProcessor {
141
163
  failed.push({ eventId: candidate.event.id, error });
142
164
  }
143
165
  }
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
166
  /**
183
167
  * f7/rr5: Acknowledge a durable-job-terminal event. The supervisor
184
168
  * already committed the terminal transition and the Leader wakeup;
@@ -190,27 +174,6 @@ export class FileRuntimeEventProcessor {
190
174
  // transaction as the terminal transition.
191
175
  void event;
192
176
  }
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
177
  applyCodex(event, now) {
215
178
  if (event.scope === "task") {
216
179
  const task = this.observer.getTask(event.taskId);
@@ -263,43 +226,26 @@ export class FileRuntimeEventProcessor {
263
226
  }
264
227
  return "obsolete";
265
228
  }
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
229
  recordObsolete(event, reason, now) {
301
230
  if (event.scope !== "task" || event.taskId === undefined)
302
231
  return;
232
+ if (event.type === "runtime-observation") {
233
+ const fence = event.observation.fence;
234
+ if (fence.nativeSessionId === undefined)
235
+ return;
236
+ this.observer.observeObsoleteRuntimeEvent?.({
237
+ eventId: event.id,
238
+ eventType: event.type,
239
+ taskId: event.taskId,
240
+ roleName: fence.roleName,
241
+ agentId: fence.agentId,
242
+ ...(fence.runId === undefined ? {} : { runId: fence.runId }),
243
+ launchId: fence.launchId,
244
+ nativeSessionId: fence.nativeSessionId,
245
+ reason
246
+ }, now);
247
+ return;
248
+ }
303
249
  // durable-job-terminal events have no provider identity; they are never
304
250
  // recorded as obsolete.
305
251
  if (!("roleName" in event) || !("nativeSessionId" in event))
@@ -333,51 +279,38 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
333
279
  const flush = () => {
334
280
  if (segment.length === 0)
335
281
  return;
336
- const frontiers = new Map();
282
+ const indicesByStream = new Map();
337
283
  for (let index = 0; index < segment.length; index += 1) {
338
284
  instrumentation.onProgressVisit?.();
339
285
  const event = segment[index];
340
286
  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
- }
287
+ const indices = indicesByStream.get(key) ?? [];
288
+ indices.push(index);
289
+ indicesByStream.set(key, indices);
364
290
  }
365
291
  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);
292
+ for (const indices of indicesByStream.values()) {
293
+ const hasUsage = segment[indices[0]].observation.payload.usage !== undefined;
294
+ const retainedPositions = hasUsage ? [0] : [];
295
+ if (hasUsage) {
296
+ for (let position = 1; position < indices.length; position += 1) {
297
+ const previous = segment[indices[position - 1]].observation.payload.usage;
298
+ const current = segment[indices[position]].observation.payload.usage;
299
+ if (runtimeUsageTotal(current) < runtimeUsageTotal(previous)) {
300
+ retainedPositions.push(position);
301
+ }
302
+ }
303
+ }
304
+ const lastPosition = indices.length - 1;
305
+ if (retainedPositions.at(-1) !== lastPosition)
306
+ retainedPositions.push(lastPosition);
307
+ let previousRetainedPosition = -1;
308
+ for (const retainedPosition of retainedPositions) {
309
+ const retainedIndex = indices[retainedPosition];
310
+ representedByIndex.set(retainedIndex, indices.slice(previousRetainedPosition + 1, retainedPosition + 1)
311
+ .map((index) => segment[index].id));
312
+ previousRetainedPosition = retainedPosition;
313
+ }
381
314
  }
382
315
  for (let index = 0; index < segment.length; index += 1) {
383
316
  const representedEventIds = representedByIndex.get(index);
@@ -388,7 +321,7 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
388
321
  segment = [];
389
322
  };
390
323
  for (const event of events) {
391
- if (event.type !== "native-turn-progress") {
324
+ if (!isRuntimeActivityEvent(event)) {
392
325
  flush();
393
326
  result.push({ event, representedEventIds: [event.id] });
394
327
  continue;
@@ -399,22 +332,32 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
399
332
  return result;
400
333
  }
401
334
  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
335
+ // A batch is always an arrival-order prefix. Drain-time coalescing bounds
336
+ // worker folds without allowing a later semantic fact to
404
337
  // overtake an earlier progress fence from another Run.
405
338
  return events.slice(0, maximum);
406
339
  }
407
340
  function progressStreamKey(event) {
341
+ const { fence, payload } = event.observation;
408
342
  return JSON.stringify([
409
- event.taskId,
410
- event.roleName,
411
- event.agentId,
412
- event.adapterId,
413
- event.launchId,
414
- event.nativeSessionId,
415
- event.runId
343
+ fence.taskId ?? null,
344
+ fence.roleName,
345
+ fence.agentId,
346
+ fence.driverId,
347
+ fence.launchId,
348
+ fence.nativeSessionId ?? null,
349
+ fence.runId ?? null,
350
+ payload.activity,
351
+ payload.usage === undefined ? "signal" : "usage"
416
352
  ]);
417
353
  }
354
+ function runtimeUsageTotal(usage) {
355
+ return usage.inputTokens + usage.outputTokens;
356
+ }
357
+ function isRuntimeActivityEvent(event) {
358
+ return event.type === "runtime-observation"
359
+ && event.observation.kind === "activity.observed";
360
+ }
418
361
  function emptyDrainFailure(error) {
419
362
  return {
420
363
  acknowledgedEventIds: [],
@@ -456,13 +399,10 @@ export function createAsyncRuntimeObserver(invoke) {
456
399
  const withNow = (method, input, now) => now === undefined ? call(method, [input]) : call(method, [input, now]);
457
400
  return {
458
401
  getTask: (taskId) => call("getTask", [taskId]),
402
+ observeRuntimeObservation: (input, now) => withNow("observeRuntimeObservation", input, now),
459
403
  observeRuntimeTurnCompleted: (input, now) => withNow("observeRuntimeTurnCompleted", input, now),
460
404
  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)
405
+ observeObsoleteRuntimeEvent: (input, now) => withNow("observeObsoleteRuntimeEvent", input, now)
466
406
  };
467
407
  }
468
408
  /**
@@ -475,10 +415,12 @@ export class AsyncRuntimeEventProcessor {
475
415
  inbox;
476
416
  observer;
477
417
  options;
418
+ drivers;
478
419
  constructor(inbox, observer, options = {}) {
479
420
  this.inbox = inbox;
480
421
  this.observer = observer;
481
422
  this.options = options;
423
+ this.drivers = options.drivers ?? builtinAgentDriverRegistry();
482
424
  }
483
425
  async drainAsync(now) {
484
426
  const acknowledgedEventIds = [];
@@ -491,46 +433,31 @@ export class AsyncRuntimeEventProcessor {
491
433
  catch (error) {
492
434
  return emptyDrainFailure(error);
493
435
  }
494
- for (const event of events) {
436
+ const coalesced = coalesceRuntimeProgress(events);
437
+ const maximum = positiveInteger(this.options.maxEventsPerDrain, DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN);
438
+ const selected = selectDrainBatch(coalesced, maximum);
439
+ for (const candidate of selected) {
440
+ const event = candidate.event;
495
441
  try {
442
+ let outcome = "applied";
496
443
  if (event.type === "native-turn-completed") {
497
- const outcome = await this.applyCodex(event, now);
498
- if (outcome === "deferred") {
499
- deferred.push(event);
500
- continue;
501
- }
444
+ outcome = await this.applyCodex(event, now);
502
445
  }
503
- else if (event.type === "claude-stop-failure") {
504
- await this.applyClaudeStopFailure(event, now);
505
- }
506
- else if (event.type === "native-session-lifecycle") {
507
- const outcome = await this.applySessionLifecycle(event, now);
508
- if (outcome === "deferred") {
509
- deferred.push(event);
510
- continue;
511
- }
512
- }
513
- else if (event.type === "native-turn-progress") {
514
- const outcome = await this.applyProviderTurnProgress(event, now);
515
- if (outcome === "deferred") {
516
- deferred.push(event);
517
- continue;
518
- }
446
+ else if (event.type === "runtime-observation") {
447
+ outcome = await this.applyRuntimeObservation(event, now);
519
448
  }
520
- else if (event.type === "durable-job-terminal") {
449
+ else {
521
450
  // f7/rr5: The supervisor already transitioned the job and
522
451
  // enqueued the Leader wakeup. This event is the durable terminal
523
452
  // channel: acknowledge it so the pipeline converges without
524
453
  // re-processing. No state change to apply.
525
454
  }
526
- else {
527
- const outcome = await this.applyPromptAccepted(event, now);
528
- if (outcome === "deferred") {
529
- deferred.push(event);
530
- continue;
531
- }
455
+ if (outcome === "deferred") {
456
+ deferred.push(event);
457
+ this.acknowledge(candidate.representedEventIds.filter((id) => id !== event.id), acknowledgedEventIds);
458
+ continue;
532
459
  }
533
- this.acknowledge(event.id, acknowledgedEventIds);
460
+ this.acknowledge(candidate.representedEventIds, acknowledgedEventIds);
534
461
  }
535
462
  catch (error) {
536
463
  failed.push({ eventId: event.id, error });
@@ -538,8 +465,9 @@ export class AsyncRuntimeEventProcessor {
538
465
  }
539
466
  const acknowledged = new Set(acknowledgedEventIds);
540
467
  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;
468
+ const progressEventsSelected = selected.filter(({ event }) => (isRuntimeActivityEvent(event))).length;
469
+ const representedEventCount = selected.reduce((count, candidate) => (count + candidate.representedEventIds.length), 0);
470
+ const remainingProgressEventCount = remaining.filter(isRuntimeActivityEvent).length;
543
471
  return {
544
472
  acknowledgedEventIds,
545
473
  deferred,
@@ -547,96 +475,42 @@ export class AsyncRuntimeEventProcessor {
547
475
  remainingEventCount: remaining.length,
548
476
  metrics: {
549
477
  listedEventCount: events.length,
550
- selectedEventCount: events.length,
551
- semanticEventsSelected: events.length - progressEventsSelected,
478
+ selectedEventCount: selected.length,
479
+ semanticEventsSelected: selected.length - progressEventsSelected,
552
480
  progressEventsSelected,
553
- progressEventsCoalesced: 0,
481
+ progressEventsCoalesced: representedEventCount - selected.length,
554
482
  stateTransactions: 0,
555
483
  remainingSemanticEventCount: remaining.length - remainingProgressEventCount,
556
484
  remainingProgressEventCount
557
485
  }
558
486
  };
559
487
  }
560
- async applySessionLifecycle(event, now) {
561
- const task = await this.observer.getTask(event.taskId);
562
- if (task === null || task.status !== "active")
563
- return "obsolete";
564
- if (this.observer.observeProviderSessionLifecycle === undefined)
565
- return "obsolete";
566
- const outcome = await this.observer.observeProviderSessionLifecycle({
567
- eventId: event.id,
568
- taskId: event.taskId,
569
- roleName: event.roleName,
570
- agentId: event.agentId,
571
- adapterId: event.adapterId,
572
- launchId: event.launchId,
573
- nativeSessionId: event.nativeSessionId,
574
- ...(event.runId === undefined ? {} : { runId: event.runId }),
575
- ...(event.sessionSource === undefined ? {} : { sessionSource: event.sessionSource })
576
- }, now);
577
- if (outcome === "applied") {
578
- this.options.onTaskRuntimeApplied?.({
579
- taskId: event.taskId,
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 })
586
- });
488
+ async applyRuntimeObservation(event, now) {
489
+ const taskId = event.observation.fence.taskId;
490
+ if (taskId !== undefined) {
491
+ const task = await this.observer.getTask(taskId);
492
+ if (task === null || task.status !== "active")
493
+ return "obsolete";
587
494
  }
588
- return outcome === "deferred" ? "deferred" : outcome;
589
- }
590
- async applyPromptAccepted(event, now) {
591
- const task = await this.observer.getTask(event.taskId);
592
- if (task === null || task.status !== "active")
593
- return "obsolete";
594
- if (this.observer.observeProviderPromptAccepted === undefined)
595
- return "obsolete";
596
- const outcome = await this.observer.observeProviderPromptAccepted({
597
- eventId: event.id,
598
- taskId: event.taskId,
599
- roleName: event.roleName,
600
- agentId: event.agentId,
601
- adapterId: event.adapterId,
602
- launchId: event.launchId,
603
- nativeSessionId: event.nativeSessionId,
604
- runId: event.runId,
605
- receiptId: event.receiptId
606
- }, now);
607
- if (outcome === "applied") {
495
+ const outcome = (await this.observer.observeRuntimeObservation?.(event.observation, now))
496
+ ?? "obsolete";
497
+ const fence = event.observation.fence;
498
+ if (outcome === "applied"
499
+ && fence.taskId !== undefined
500
+ && fence.nativeSessionId !== undefined
501
+ && ["session.started", "session.ready", "turn.accepted", "turn.completed"]
502
+ .includes(event.observation.kind)) {
608
503
  this.options.onTaskRuntimeApplied?.({
609
- taskId: event.taskId,
610
- roleName: event.roleName,
611
- agentId: event.agentId,
612
- adapterId: event.adapterId,
613
- launchId: event.launchId,
614
- nativeSessionId: event.nativeSessionId,
615
- runId: event.runId
504
+ taskId: fence.taskId,
505
+ roleName: fence.roleName,
506
+ agentId: fence.agentId,
507
+ adapterId: this.drivers.require(fence.driverId).adapterId,
508
+ launchId: fence.launchId,
509
+ nativeSessionId: fence.nativeSessionId,
510
+ ...(fence.runId === undefined ? {} : { runId: fence.runId })
616
511
  });
617
512
  }
618
- return outcome === "deferred" ? "deferred" : outcome;
619
- }
620
- async applyProviderTurnProgress(event, now) {
621
- const task = await this.observer.getTask(event.taskId);
622
- if (task === null || task.status !== "active")
623
- return "obsolete";
624
- if (this.observer.observeProviderTurnProgress === undefined)
625
- return "obsolete";
626
- const outcome = await this.observer.observeProviderTurnProgress({
627
- eventId: event.id,
628
- receivedAt: event.receivedAt,
629
- taskId: event.taskId,
630
- roleName: event.roleName,
631
- agentId: event.agentId,
632
- adapterId: event.adapterId,
633
- launchId: event.launchId,
634
- nativeSessionId: event.nativeSessionId,
635
- runId: event.runId,
636
- progressId: event.progressId,
637
- ...(event.sequence === undefined ? {} : { sequence: event.sequence })
638
- }, now);
639
- return outcome === "deferred" ? "deferred" : "applied";
513
+ return outcome;
640
514
  }
641
515
  async applyCodex(event, now) {
642
516
  if (event.scope === "task") {
@@ -699,43 +573,26 @@ export class AsyncRuntimeEventProcessor {
699
573
  }
700
574
  return "obsolete";
701
575
  }
702
- async applyClaudeStopFailure(event, now) {
703
- const task = await this.observer.getTask(event.taskId);
704
- if (task === null)
705
- return;
706
- const input = {
707
- eventId: event.id,
708
- type: event.type,
709
- taskId: event.taskId,
710
- roleName: event.roleName,
711
- agentId: event.agentId,
712
- adapterId: event.adapterId,
713
- launchId: event.launchId,
714
- nativeSessionId: event.nativeSessionId,
715
- runId: event.runId,
716
- error: event.error,
717
- ...(event.errorDetails === undefined ? {} : { errorDetails: event.errorDetails }),
718
- ...(event.lastAssistantMessage === undefined
719
- ? {}
720
- : { lastAssistantMessage: event.lastAssistantMessage })
721
- };
722
- if (task.status !== "active"
723
- || (await this.observer.classifyClaudeStopFailureEvent?.(input)) === "obsolete") {
724
- await this.recordObsolete(event, task.status === "archived"
725
- ? "task-archived"
726
- : task.status !== "active"
727
- ? "task-retired"
728
- : "identity-mismatch-or-terminal", now);
729
- return;
730
- }
731
- if (this.observer.observeClaudeStopFailureEvent === undefined) {
732
- throw new Error("Claude StopFailure observer is unavailable.");
733
- }
734
- await this.observer.observeClaudeStopFailureEvent(input, now);
735
- }
736
576
  async recordObsolete(event, reason, now) {
737
577
  if (event.scope !== "task" || event.taskId === undefined || event.type === "durable-job-terminal")
738
578
  return;
579
+ if (event.type === "runtime-observation") {
580
+ const fence = event.observation.fence;
581
+ if (fence.nativeSessionId === undefined)
582
+ return;
583
+ await this.observer.observeObsoleteRuntimeEvent?.({
584
+ eventId: event.id,
585
+ eventType: event.type,
586
+ taskId: event.taskId,
587
+ roleName: fence.roleName,
588
+ agentId: fence.agentId,
589
+ ...(fence.runId === undefined ? {} : { runId: fence.runId }),
590
+ launchId: fence.launchId,
591
+ nativeSessionId: fence.nativeSessionId,
592
+ reason
593
+ }, now);
594
+ return;
595
+ }
739
596
  await this.observer.observeObsoleteRuntimeEvent?.({
740
597
  eventId: event.id,
741
598
  eventType: event.type,
@@ -748,8 +605,14 @@ export class AsyncRuntimeEventProcessor {
748
605
  reason
749
606
  }, now);
750
607
  }
751
- acknowledge(id, acknowledged) {
752
- this.inbox.acknowledge(id);
753
- acknowledged.push(id);
608
+ acknowledge(ids, acknowledged) {
609
+ if (this.inbox.acknowledgeMany !== undefined) {
610
+ acknowledged.push(...this.inbox.acknowledgeMany(ids));
611
+ return;
612
+ }
613
+ for (const id of ids) {
614
+ if (this.inbox.acknowledge(id))
615
+ acknowledged.push(id);
616
+ }
754
617
  }
755
618
  }