@zq-silk/yui 0.6.9 → 0.6.11
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.
- package/README.md +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +62 -27
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +546 -255
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +102 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +198 -9
- package/dist/runtime/runtimeProjection.js +162 -7
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +1 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- 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 !==
|
|
56
|
-
throw new Error("Runtime observation schemaVersion must be
|
|
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:
|
|
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
|
|
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, {
|
|
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
|
-
|
|
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:
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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
|