@zq-silk/yui 0.6.9 → 0.6.10

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 (56) hide show
  1. package/README.md +10 -3
  2. package/dist/cli/commandCatalog.js +1 -1
  3. package/dist/commands/taskCommands.js +62 -27
  4. package/dist/commands/taskContextCommand.js +27 -1
  5. package/dist/commands/taskRoleRuntimeStatus.js +17 -6
  6. package/dist/controller/agentRuntimeObserver.js +6 -3
  7. package/dist/controller/controller.js +29 -47
  8. package/dist/controller/fileSchedulerStoreAdapter.js +546 -255
  9. package/dist/controller/runtime.js +8 -1
  10. package/dist/controller/runtimeEventInbox.js +16 -5
  11. package/dist/controller/runtimeHookRunFence.js +51 -5
  12. package/dist/controller/runtimeObservationHook.js +8 -2
  13. package/dist/coordination/workMailbox.js +408 -28
  14. package/dist/coordination/workMailboxQueue.js +12 -10
  15. package/dist/executor/agentExecutor.js +101 -94
  16. package/dist/executor/executorRegistry.js +47 -2
  17. package/dist/executor/fileRoleLaunchPlanner.js +4 -2
  18. package/dist/lifecycle/exactRunTerminalization.js +1 -7
  19. package/dist/repository/taskWorkspaceCoordinator.js +9 -4
  20. package/dist/runtime/agentDriver.js +83 -4
  21. package/dist/runtime/agentDriverObservation.js +25 -10
  22. package/dist/runtime/builtinAgentDrivers.js +168 -18
  23. package/dist/runtime/codexAppServerRuntime.js +355 -0
  24. package/dist/runtime/continuationManager.js +117 -0
  25. package/dist/runtime/index.js +2 -0
  26. package/dist/runtime/lifecycleReservation.js +4 -3
  27. package/dist/runtime/promptEnvelope.js +14 -3
  28. package/dist/runtime/providerContinuation.js +225 -0
  29. package/dist/runtime/providerContinuationReconciliationService.js +172 -0
  30. package/dist/runtime/providerRuntimeIdentity.js +232 -0
  31. package/dist/runtime/providerRuntimeReconciler.js +166 -0
  32. package/dist/runtime/runtimeContinuationProjection.js +34 -0
  33. package/dist/runtime/runtimeObservation.js +198 -9
  34. package/dist/runtime/runtimeProjection.js +162 -7
  35. package/dist/scheduler/activeRoleRunDelivery.js +314 -1
  36. package/dist/scheduler/leaderWakeupProcessor.js +1 -1
  37. package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +8 -7
  39. package/dist/scheduler/roleRunStall.js +4 -2
  40. package/dist/scheduler/taskExecutionProjection.js +2 -2
  41. package/dist/storage/migration/productionRegistry.js +474 -1
  42. package/dist/storage/sqliteSchema.js +102 -21
  43. package/dist/storage/sqliteStore.js +52 -110
  44. package/dist/storage/storageVersions.js +1 -1
  45. package/dist/storage/storeRpc.js +0 -1
  46. package/dist/storage/taskStore.js +40 -53
  47. package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
  48. package/dist/web/assets/client/app.js +1 -1
  49. package/dist/web/assets/client/components.js +233 -4
  50. package/dist/web/assets/client/i18n.js +166 -2
  51. package/dist/web/assets/client/view.js +30 -13
  52. package/dist/web/assets/styles/cards.js +62 -0
  53. package/dist/web/assets/styles/widgets.js +1 -0
  54. package/dist/web/webSnapshot.js +11 -2
  55. package/package.json +1 -1
  56. package/skills/yui-leader/SKILL.md +14 -9
@@ -6,6 +6,10 @@ const KINDS = [
6
6
  "session.ready",
7
7
  "session.ended",
8
8
  "session.failed",
9
+ "conversation.observed",
10
+ "activation.started",
11
+ "activation.ended",
12
+ "activation.failed",
9
13
  "turn.accepted",
10
14
  "turn.waiting",
11
15
  "turn.completed",
@@ -14,6 +18,12 @@ const KINDS = [
14
18
  "operation.started",
15
19
  "operation.completed",
16
20
  "operation.failed",
21
+ "native-work.snapshot",
22
+ "continuation.started",
23
+ "continuation.reported",
24
+ "continuation.settled",
25
+ "input.accepted",
26
+ "input.delivery-unknown",
17
27
  "activity.observed",
18
28
  "observer.health"
19
29
  ];
@@ -37,6 +47,11 @@ const RUN_SCOPED = new Set([
37
47
  "activity.observed",
38
48
  "observer.health"
39
49
  ]);
50
+ const CONTINUATION_SCOPED = new Set([
51
+ "continuation.started",
52
+ "continuation.reported",
53
+ "continuation.settled"
54
+ ]);
40
55
  const PROVIDER_STATE = new Set([
41
56
  "session.started",
42
57
  "session.ready",
@@ -52,8 +67,8 @@ const PROVIDER_STATE = new Set([
52
67
  "operation.failed"
53
68
  ]);
54
69
  export function createRuntimeObservation(input) {
55
- if (input.schemaVersion !== 1)
56
- throw new Error("Runtime observation schemaVersion must be 1.");
70
+ if (input.schemaVersion !== 2)
71
+ throw new Error("Runtime observation schemaVersion must be 2.");
57
72
  if (!KINDS.includes(input.kind))
58
73
  throw new Error("Runtime observation kind is invalid.");
59
74
  if (!AUTHORITIES.includes(input.authority))
@@ -76,6 +91,22 @@ export function createRuntimeObservation(input) {
76
91
  if (RUN_SCOPED.has(input.kind) && fence.nativeTurnId === undefined) {
77
92
  throw new Error(`${input.kind} requires nativeTurnId.`);
78
93
  }
94
+ if ((input.kind.startsWith("activation.") || CONTINUATION_SCOPED.has(input.kind)
95
+ || input.kind === "native-work.snapshot")
96
+ && fence.activationId === undefined) {
97
+ throw new Error(`${input.kind} requires activationId.`);
98
+ }
99
+ if ((input.kind === "conversation.observed" || input.kind.startsWith("activation.")
100
+ || CONTINUATION_SCOPED.has(input.kind) || input.kind === "native-work.snapshot")
101
+ && fence.conversationId === undefined) {
102
+ throw new Error(`${input.kind} requires conversationId.`);
103
+ }
104
+ if (CONTINUATION_SCOPED.has(input.kind) && fence.continuationId === undefined) {
105
+ throw new Error(`${input.kind} requires continuationId.`);
106
+ }
107
+ if (CONTINUATION_SCOPED.has(input.kind) && fence.continuationGeneration === undefined) {
108
+ throw new Error(`${input.kind} requires continuationGeneration.`);
109
+ }
79
110
  const payload = normalizePayload(input.kind, input.payload);
80
111
  const sequence = input.sequence;
81
112
  if (sequence !== undefined && (!Number.isSafeInteger(sequence) || sequence < 0)) {
@@ -86,8 +117,9 @@ export function createRuntimeObservation(input) {
86
117
  throw new Error("Runtime observation ordinal must be a non-negative safe integer.");
87
118
  }
88
119
  return Object.freeze({
89
- schemaVersion: 1,
120
+ schemaVersion: 2,
90
121
  eventId: requireIdentity(input.eventId, "Runtime observation event id"),
122
+ semanticKey: requireIdentity(input.semanticKey, "Runtime observation semantic key"),
91
123
  kind: input.kind,
92
124
  authority: input.authority,
93
125
  receivedAt: requireTimestamp(input.receivedAt, "Runtime observation receivedAt"),
@@ -109,6 +141,11 @@ export function runtimeObservationFenceMatches(expected, actual) {
109
141
  "driverId",
110
142
  "launchId",
111
143
  "sessionGenerationId",
144
+ "conversationId",
145
+ "activationId",
146
+ "continuationId",
147
+ "continuationGeneration",
148
+ "parentContinuationId",
112
149
  "nativeSessionId",
113
150
  "nativeTurnId",
114
151
  "receiptId"
@@ -121,7 +158,9 @@ export function runtimeObservationFenceMatches(expected, actual) {
121
158
  /**
122
159
  * Matches observations that belong to one durable Run/session generation.
123
160
  * A provider may advance its native Turn while background subagents from an
124
- * earlier Turn are still active, so nativeTurnId is intentionally excluded.
161
+ * earlier Turn are still active and later mailbox activations use their own
162
+ * exactly-once receipt, so nativeTurnId and receiptId are intentionally
163
+ * excluded. Exact acceptance still validates both fields before persistence.
125
164
  */
126
165
  export function runtimeObservationRunFenceMatches(expected, actual) {
127
166
  for (const field of [
@@ -132,8 +171,7 @@ export function runtimeObservationRunFenceMatches(expected, actual) {
132
171
  "driverId",
133
172
  "launchId",
134
173
  "sessionGenerationId",
135
- "nativeSessionId",
136
- "receiptId"
174
+ "nativeSessionId"
137
175
  ]) {
138
176
  if (expected[field] !== actual[field])
139
177
  return false;
@@ -145,6 +183,7 @@ export function runtimeObservationTaskEventPayload(input) {
145
183
  const observation = createRuntimeObservation(input);
146
184
  return Object.freeze({
147
185
  eventId: observation.eventId,
186
+ semanticKey: observation.semanticKey,
148
187
  roleName: observation.fence.roleName,
149
188
  agentId: observation.fence.agentId,
150
189
  driverId: observation.fence.driverId,
@@ -187,7 +226,26 @@ function normalizeFence(input) {
187
226
  : { nativeTurnId: requireIdentity(input.nativeTurnId, "Native Turn id") }),
188
227
  ...(input.receiptId === undefined
189
228
  ? {}
190
- : { receiptId: requireIdentity(input.receiptId, "Receipt id") })
229
+ : { receiptId: requireIdentity(input.receiptId, "Receipt id") }),
230
+ ...(input.conversationId === undefined
231
+ ? {}
232
+ : { conversationId: requireIdentity(input.conversationId, "Provider Conversation id") }),
233
+ ...(input.activationId === undefined
234
+ ? {}
235
+ : { activationId: requireIdentity(input.activationId, "Provider Activation id") }),
236
+ ...(input.continuationId === undefined
237
+ ? {}
238
+ : { continuationId: requireIdentity(input.continuationId, "Provider Continuation id") }),
239
+ ...(input.continuationGeneration === undefined
240
+ ? {}
241
+ : {
242
+ continuationGeneration: requireNonNegativeInteger(input.continuationGeneration, 1, "Provider Continuation generation")
243
+ }),
244
+ ...(input.parentContinuationId === undefined
245
+ ? {}
246
+ : {
247
+ parentContinuationId: requireIdentity(input.parentContinuationId, "Parent Provider Continuation id")
248
+ })
191
249
  });
192
250
  }
193
251
  function normalizePayload(kind, input) {
@@ -230,6 +288,41 @@ function normalizePayload(kind, input) {
230
288
  if (kind === "turn.failed" && input.failure === undefined) {
231
289
  throw new Error("turn.failed requires normalized failure evidence.");
232
290
  }
291
+ if (kind === "conversation.observed"
292
+ && !["unknown", "recoverable", "unrecoverable"].includes(input.recoverability ?? "")) {
293
+ throw new Error("conversation.observed requires recoverability.");
294
+ }
295
+ if (kind === "native-work.snapshot") {
296
+ if (typeof input.snapshotComplete !== "boolean"
297
+ || !["exact", "partial", "unavailable"].includes(input.observationQuality ?? "")) {
298
+ throw new Error("native-work.snapshot requires completeness and observation quality.");
299
+ }
300
+ if (input.snapshotComplete && input.observationQuality !== "exact") {
301
+ throw new Error("A complete native-work.snapshot requires exact observation quality.");
302
+ }
303
+ }
304
+ if (kind.startsWith("continuation.")) {
305
+ if (!["active", "quiescent", "unknown"].includes(input.execution ?? "")) {
306
+ throw new Error(`${kind} requires continuation execution.`);
307
+ }
308
+ if (!["pending", "succeeded", "failed", "cancelled", "unknown"].includes(input.outcome ?? "")) {
309
+ throw new Error(`${kind} requires continuation outcome.`);
310
+ }
311
+ if (input.attachment !== "attached" && input.attachment !== "detached") {
312
+ throw new Error(`${kind} requires continuation attachment.`);
313
+ }
314
+ if (!["exact", "partial", "unavailable"].includes(input.observationQuality ?? "")) {
315
+ throw new Error(`${kind} requires continuation observation quality.`);
316
+ }
317
+ if (typeof input.mayWriteWorkspace !== "boolean") {
318
+ throw new Error(`${kind} requires mayWriteWorkspace.`);
319
+ }
320
+ if (kind === "continuation.reported")
321
+ requireIdentity(input.reportId, "Provider report id");
322
+ if (kind === "continuation.settled" && input.observationQuality !== "exact") {
323
+ throw new Error("continuation.settled requires exact observation quality.");
324
+ }
325
+ }
233
326
  const failure = input.failure === undefined
234
327
  ? undefined
235
328
  : normalizeFailure(input.failure);
@@ -255,9 +348,92 @@ function normalizePayload(kind, input) {
255
348
  ? {}
256
349
  : { observerDetail: requireText(input.observerDetail, "Runtime observer detail") }),
257
350
  ...(failure === undefined ? {} : { failure }),
258
- ...(input.summary === undefined ? {} : { summary: requireText(input.summary, "Runtime summary") })
351
+ ...(input.summary === undefined ? {} : { summary: requireText(input.summary, "Runtime summary") }),
352
+ ...(input.execution === undefined ? {} : { execution: input.execution }),
353
+ ...(input.outcome === undefined ? {} : { outcome: input.outcome }),
354
+ ...(input.attachment === undefined ? {} : { attachment: input.attachment }),
355
+ ...(input.observationQuality === undefined
356
+ ? {}
357
+ : { observationQuality: input.observationQuality }),
358
+ ...(input.mayWriteWorkspace === undefined
359
+ ? {}
360
+ : { mayWriteWorkspace: input.mayWriteWorkspace }),
361
+ ...(input.resultRef === undefined
362
+ ? {}
363
+ : { resultRef: requireIdentity(input.resultRef, "Provider result ref") }),
364
+ ...(input.reportId === undefined
365
+ ? {}
366
+ : { reportId: requireIdentity(input.reportId, "Provider report id") }),
367
+ ...(input.providerDeliveryRef === undefined
368
+ ? {}
369
+ : {
370
+ providerDeliveryRef: requireIdentity(input.providerDeliveryRef, "Provider delivery ref")
371
+ }),
372
+ ...(input.snapshotComplete === undefined
373
+ ? {}
374
+ : { snapshotComplete: input.snapshotComplete }),
375
+ ...(input.recoverability === undefined
376
+ ? {}
377
+ : { recoverability: input.recoverability })
259
378
  });
260
379
  }
380
+ export function runtimeObservationSemanticKey(input) {
381
+ const fence = input.fence;
382
+ const continuationIdentity = [
383
+ fence.driverId,
384
+ fence.agentId,
385
+ fence.conversationId ?? fence.nativeSessionId ?? fence.launchId,
386
+ fence.activationId ?? fence.launchId,
387
+ fence.continuationId ?? "none",
388
+ fence.continuationGeneration ?? "none"
389
+ ];
390
+ // Terminal boundaries are semantic facts. Providers may replay them with a
391
+ // fresh transport sequence after reconnect, so terminal identity must win
392
+ // over occurrence ordering or one SessionEnd storm becomes many facts.
393
+ if (["session.ended", "session.failed", "activation.ended", "activation.failed",
394
+ "turn.completed", "turn.failed", "turn.cancelled", "continuation.settled"].includes(input.kind)) {
395
+ return [
396
+ "terminal",
397
+ ...continuationIdentity,
398
+ fence.continuationId ?? fence.nativeTurnId ?? "none",
399
+ input.kind,
400
+ input.payload?.outcome ?? input.payload?.failure?.code ?? "terminal",
401
+ input.kind === "continuation.settled" ? input.payload?.resultRef ?? "none" : "none",
402
+ input.kind === "turn.failed" && input.payload?.failure?.runTerminal === true
403
+ ? "run-terminal"
404
+ : "turn-terminal"
405
+ ].join(":");
406
+ }
407
+ if (input.kind === "continuation.reported") {
408
+ return ["continuation-report", ...continuationIdentity, input.payload?.reportId ?? "missing"]
409
+ .join(":");
410
+ }
411
+ if (input.kind === "continuation.started") {
412
+ return [
413
+ "continuation-state",
414
+ ...continuationIdentity,
415
+ input.payload?.execution ?? "unknown",
416
+ input.payload?.attachment ?? "unknown",
417
+ input.payload?.observationQuality ?? "unknown",
418
+ input.payload?.mayWriteWorkspace === true ? "writer" : "read-only",
419
+ input.payload?.outcome ?? "unknown",
420
+ input.payload?.resultRef ?? "none"
421
+ ].join(":");
422
+ }
423
+ if (input.sequence !== undefined) {
424
+ return [
425
+ "provider-sequence",
426
+ fence.driverId,
427
+ fence.conversationId ?? fence.nativeSessionId ?? fence.launchId,
428
+ fence.activationId ?? fence.launchId,
429
+ fence.continuationId ?? fence.nativeTurnId ?? "none",
430
+ fence.continuationGeneration ?? "none",
431
+ input.sequence,
432
+ input.kind
433
+ ].join(":");
434
+ }
435
+ return `provider-event:${requireIdentity(input.eventId, "Runtime observation event id")}`;
436
+ }
261
437
  function normalizeObserverSource(input) {
262
438
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
263
439
  throw new Error("Runtime observer source must be an object.");
@@ -283,9 +459,17 @@ function normalizeFailure(input) {
283
459
  : { details: requireText(input.details, "Runtime failure details") }),
284
460
  ...(input.lastOutput === undefined
285
461
  ? {}
286
- : { lastOutput: requireText(input.lastOutput, "Runtime failure last output") })
462
+ : { lastOutput: requireText(input.lastOutput, "Runtime failure last output") }),
463
+ ...(input.runTerminal === undefined
464
+ ? {}
465
+ : { runTerminal: requireBoolean(input.runTerminal, "Runtime failure runTerminal") })
287
466
  });
288
467
  }
468
+ function requireBoolean(value, label) {
469
+ if (typeof value !== "boolean")
470
+ throw new Error(`${label} must be boolean.`);
471
+ return value;
472
+ }
289
473
  function validateUsage(input) {
290
474
  for (const [name, value] of Object.entries(input)) {
291
475
  if (!Number.isSafeInteger(value) || value < 0) {
@@ -302,6 +486,11 @@ function requireTimestamp(value, label) {
302
486
  throw new Error(`${label} is invalid.`);
303
487
  return timestamp;
304
488
  }
489
+ function requireNonNegativeInteger(value, minimum, label) {
490
+ if (!Number.isSafeInteger(value) || value < minimum)
491
+ throw new Error(`${label} is invalid.`);
492
+ return value;
493
+ }
305
494
  function requireIdentity(value, label) {
306
495
  const identity = requireText(value, label);
307
496
  if (identity.includes("/../") || identity === "__proto__")
@@ -6,6 +6,14 @@ export function createRuntimeProjection(fence, createdAt) {
6
6
  host: "unknown",
7
7
  session: "unknown",
8
8
  turn: "none",
9
+ conversation: "unknown",
10
+ activation: "none",
11
+ continuations: Object.freeze({}),
12
+ inputDelivery: "none",
13
+ runActivity: "starting",
14
+ health: "reconciling",
15
+ waitingOn: Object.freeze([]),
16
+ attention: Object.freeze([]),
9
17
  operations: Object.freeze({}),
10
18
  activity: Object.freeze({ kind: "none" }),
11
19
  observer: Object.freeze({ status: "unknown" }),
@@ -40,24 +48,36 @@ export function projectRuntimeObservation(current, raw) {
40
48
  stateSince: at
41
49
  });
42
50
  case "session.started":
43
- return next(current, { session: "started", stateSince: at });
51
+ return next(current, {
52
+ session: "started",
53
+ conversation: "recoverable",
54
+ activation: "active",
55
+ stateSince: at
56
+ });
44
57
  case "session.ready":
45
58
  return next(current, { session: "ready", stateSince: at });
46
59
  case "session.ended":
47
60
  return next(current, {
48
61
  session: "ended",
62
+ activation: "ended",
49
63
  turn: terminalTurn(current.turn),
50
- operations: Object.freeze({}),
64
+ // A parent provider Session ending is independent from native child
65
+ // operations already observed under the Run. Keep those children
66
+ // visible until their own terminal facts arrive; host/session loss is
67
+ // health evidence, not proof that child work or the Yui Run ended.
68
+ operations: activeSubagentOperations(current.operations),
51
69
  stateSince: at
52
70
  });
53
71
  case "session.failed":
54
72
  return next(current, {
55
73
  session: "failed",
74
+ activation: "failed",
56
75
  turn: current.turn === "none" ? "failed" : terminalTurn(current.turn, "failed"),
57
- operations: Object.freeze({}),
76
+ operations: activeSubagentOperations(current.operations),
58
77
  stateSince: at
59
78
  });
60
79
  case "turn.accepted":
80
+ case "input.accepted":
61
81
  return withActivity(next(current, {
62
82
  session: "active",
63
83
  turn: "accepted",
@@ -107,7 +127,9 @@ export function projectRuntimeObservation(current, raw) {
107
127
  const operationId = event.payload.operationId;
108
128
  const operation = event.payload.operation;
109
129
  return withActivity(next(current, {
110
- session: "active",
130
+ session: current.session === "ended" || current.session === "failed"
131
+ ? current.session
132
+ : "active",
111
133
  turn: current.turn === "waiting" ? "accepted" : current.turn,
112
134
  waitingReason: undefined,
113
135
  waitId: undefined,
@@ -123,7 +145,9 @@ export function projectRuntimeObservation(current, raw) {
123
145
  const operations = { ...current.operations };
124
146
  delete operations[event.payload.operationId];
125
147
  return withActivity(next(current, {
126
- session: "active",
148
+ session: current.session === "ended" || current.session === "failed"
149
+ ? current.session
150
+ : "active",
127
151
  turn: current.turn === "waiting" ? "accepted" : current.turn,
128
152
  waitingReason: undefined,
129
153
  waitId: undefined,
@@ -154,6 +178,82 @@ export function projectRuntimeObservation(current, raw) {
154
178
  : { detail: event.payload.observerDetail })
155
179
  })
156
180
  });
181
+ case "conversation.observed":
182
+ return next(current, {
183
+ conversation: event.payload.recoverability === "unrecoverable"
184
+ ? "unrecoverable"
185
+ : event.payload.recoverability === "recoverable" ? "recoverable" : "unknown"
186
+ });
187
+ case "activation.started":
188
+ return next(current, { activation: "active", stateSince: at });
189
+ case "activation.ended":
190
+ return next(current, { activation: "ended", stateSince: at });
191
+ case "activation.failed":
192
+ return next(current, { activation: "failed", stateSince: at });
193
+ case "continuation.started":
194
+ case "continuation.reported":
195
+ case "continuation.settled": {
196
+ const id = [
197
+ event.fence.activationId,
198
+ event.fence.continuationId,
199
+ event.fence.continuationGeneration
200
+ ].join("/");
201
+ const existing = current.continuations[id];
202
+ if (event.kind === "continuation.reported") {
203
+ // A report is an attachment, not lifecycle evidence. When it races
204
+ // ahead of continuation.started, retain a conservative writer-owned
205
+ // stub until structured start/settlement metadata arrives.
206
+ const reported = existing ?? Object.freeze({
207
+ execution: "unknown",
208
+ outcome: "pending",
209
+ attachment: event.payload.attachment ?? "detached",
210
+ observation: "unavailable",
211
+ mayWriteWorkspace: true,
212
+ identityConflict: false
213
+ });
214
+ return next(current, {
215
+ continuations: Object.freeze({
216
+ ...current.continuations,
217
+ [id]: Object.freeze({
218
+ ...reported,
219
+ ...(event.payload.resultRef === undefined
220
+ ? {}
221
+ : { resultRef: event.payload.resultRef })
222
+ })
223
+ }),
224
+ stateSince: at
225
+ });
226
+ }
227
+ const settled = existing?.execution === "quiescent" && existing.observation === "exact";
228
+ const conflicts = settled && (event.payload.execution !== "quiescent"
229
+ || event.payload.outcome !== existing.outcome);
230
+ return next(current, {
231
+ continuations: Object.freeze({
232
+ ...current.continuations,
233
+ [id]: Object.freeze(conflicts
234
+ ? { ...existing, identityConflict: true }
235
+ : {
236
+ execution: event.payload.execution,
237
+ outcome: event.payload.outcome,
238
+ attachment: event.payload.attachment,
239
+ observation: event.payload.observationQuality,
240
+ mayWriteWorkspace: event.payload.observationQuality === "exact"
241
+ ? event.payload.mayWriteWorkspace
242
+ : (existing?.mayWriteWorkspace ?? false)
243
+ || event.payload.mayWriteWorkspace,
244
+ identityConflict: existing?.identityConflict ?? false,
245
+ ...(event.payload.resultRef === undefined
246
+ ? existing?.resultRef === undefined ? {} : { resultRef: existing.resultRef }
247
+ : { resultRef: event.payload.resultRef })
248
+ })
249
+ }),
250
+ stateSince: at
251
+ });
252
+ }
253
+ case "input.delivery-unknown":
254
+ return next(current, { inputDelivery: "delivery-unknown", stateSince: at });
255
+ case "native-work.snapshot":
256
+ return next(current, {});
157
257
  default:
158
258
  return current;
159
259
  }
@@ -176,12 +276,27 @@ export function completeRuntimeWorkflow(current, completedAt) {
176
276
  })
177
277
  });
178
278
  }
279
+ export function projectRuntimeMailbox(current, mailbox) {
280
+ if (mailbox?.inputDelivery === undefined || mailbox.inputDelivery === null) {
281
+ return next(current, { inputDelivery: "none" });
282
+ }
283
+ return next(current, {
284
+ inputDelivery: mailbox.inputDelivery.status === "delivery-unknown"
285
+ ? "delivery-unknown"
286
+ : "dispatching"
287
+ });
288
+ }
179
289
  export function runtimeDisplayStatus(current) {
180
290
  if (current.session === "failed")
181
291
  return "broken";
292
+ const operation = dominantOperation(current.operations);
293
+ // Provider-owned child work can outlive the parent Session. Surface the
294
+ // child as active while retaining host/session health orthogonally in the
295
+ // projection instead of collapsing both facts into "stopped".
296
+ if (operation === "subagent")
297
+ return "subagent-active";
182
298
  if (current.host === "exited" || current.session === "ended")
183
299
  return "stopped";
184
- const operation = dominantOperation(current.operations);
185
300
  if (operation !== null)
186
301
  return `${operation}-active`;
187
302
  if (current.turn === "waiting")
@@ -239,7 +354,47 @@ function next(current, patch) {
239
354
  }
240
355
  if (patch.waitId === undefined && Object.hasOwn(patch, "waitId"))
241
356
  delete copy.waitId;
242
- return Object.freeze(copy);
357
+ const continuations = Object.entries(copy.continuations);
358
+ const waitingNative = continuations.filter(([, continuation]) => (continuation.execution === "active" || continuation.execution === "unknown"));
359
+ const activeNativeOperations = Object.entries(copy.operations).filter(([, operation]) => (operation.kind === "subagent"));
360
+ const waitingOn = [
361
+ ...waitingNative.map(([id]) => `native:${id}`),
362
+ ...activeNativeOperations.map(([id]) => `native-operation:${id}`)
363
+ ];
364
+ const attention = [
365
+ ...(copy.inputDelivery === "delivery-unknown" ? ["delivery-unknown"] : []),
366
+ ...continuations.flatMap(([id, continuation]) => (continuation.identityConflict ? [`identity-conflict:${id}`]
367
+ : continuation.observation === "unavailable" ? [`continuation-unresolved:${id}`]
368
+ : [])),
369
+ ...(copy.conversation === "unrecoverable" ? ["leader-decision-needed"] : []),
370
+ ...(copy.host === "exited" || copy.session === "ended" || copy.session === "failed"
371
+ ? ["runtime-unobservable"]
372
+ : [])
373
+ ];
374
+ const health = copy.observer.status === "unavailable"
375
+ || waitingNative.some(([, continuation]) => continuation.observation === "unavailable")
376
+ || copy.host === "exited"
377
+ || copy.session === "ended"
378
+ || copy.session === "failed"
379
+ ? "unobservable"
380
+ : copy.inputDelivery === "dispatching"
381
+ || (copy.activation !== "active" && waitingNative.length > 0)
382
+ ? "reconciling"
383
+ : "healthy";
384
+ const runActivity = copy.inputDelivery === "dispatching"
385
+ || copy.turn === "accepted"
386
+ || copy.session === "active"
387
+ ? "running"
388
+ : waitingNative.length > 0 || activeNativeOperations.length > 0 ? "waiting"
389
+ : copy.session === "unknown" || copy.session === "started" ? "starting"
390
+ : "parked";
391
+ return Object.freeze({
392
+ ...copy,
393
+ waitingOn: Object.freeze(waitingOn),
394
+ attention: Object.freeze([...new Set(attention)]),
395
+ health,
396
+ runActivity
397
+ });
243
398
  }
244
399
  function usageAdvanced(previous, current) {
245
400
  // A first cumulative snapshot may contain history from a resumed native