@zq-silk/yui 0.6.8 → 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.
- package/README.md +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- 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 +572 -258
- 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 +101 -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 +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -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/task/nextAction.js +1 -1
- 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 +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
|
@@ -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"
|
|
@@ -118,11 +155,35 @@ export function runtimeObservationFenceMatches(expected, actual) {
|
|
|
118
155
|
}
|
|
119
156
|
return true;
|
|
120
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Matches observations that belong to one durable Run/session generation.
|
|
160
|
+
* A provider may advance its native Turn while background subagents from an
|
|
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.
|
|
164
|
+
*/
|
|
165
|
+
export function runtimeObservationRunFenceMatches(expected, actual) {
|
|
166
|
+
for (const field of [
|
|
167
|
+
"taskId",
|
|
168
|
+
"roleName",
|
|
169
|
+
"runId",
|
|
170
|
+
"agentId",
|
|
171
|
+
"driverId",
|
|
172
|
+
"launchId",
|
|
173
|
+
"sessionGenerationId",
|
|
174
|
+
"nativeSessionId"
|
|
175
|
+
]) {
|
|
176
|
+
if (expected[field] !== actual[field])
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
121
181
|
/** Compact TaskEvent payload used for durable state-boundary observations. */
|
|
122
182
|
export function runtimeObservationTaskEventPayload(input) {
|
|
123
183
|
const observation = createRuntimeObservation(input);
|
|
124
184
|
return Object.freeze({
|
|
125
185
|
eventId: observation.eventId,
|
|
186
|
+
semanticKey: observation.semanticKey,
|
|
126
187
|
roleName: observation.fence.roleName,
|
|
127
188
|
agentId: observation.fence.agentId,
|
|
128
189
|
driverId: observation.fence.driverId,
|
|
@@ -165,7 +226,26 @@ function normalizeFence(input) {
|
|
|
165
226
|
: { nativeTurnId: requireIdentity(input.nativeTurnId, "Native Turn id") }),
|
|
166
227
|
...(input.receiptId === undefined
|
|
167
228
|
? {}
|
|
168
|
-
: { 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
|
+
})
|
|
169
249
|
});
|
|
170
250
|
}
|
|
171
251
|
function normalizePayload(kind, input) {
|
|
@@ -208,6 +288,41 @@ function normalizePayload(kind, input) {
|
|
|
208
288
|
if (kind === "turn.failed" && input.failure === undefined) {
|
|
209
289
|
throw new Error("turn.failed requires normalized failure evidence.");
|
|
210
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
|
+
}
|
|
211
326
|
const failure = input.failure === undefined
|
|
212
327
|
? undefined
|
|
213
328
|
: normalizeFailure(input.failure);
|
|
@@ -233,9 +348,92 @@ function normalizePayload(kind, input) {
|
|
|
233
348
|
? {}
|
|
234
349
|
: { observerDetail: requireText(input.observerDetail, "Runtime observer detail") }),
|
|
235
350
|
...(failure === undefined ? {} : { failure }),
|
|
236
|
-
...(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 })
|
|
237
378
|
});
|
|
238
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
|
+
}
|
|
239
437
|
function normalizeObserverSource(input) {
|
|
240
438
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
241
439
|
throw new Error("Runtime observer source must be an object.");
|
|
@@ -261,9 +459,17 @@ function normalizeFailure(input) {
|
|
|
261
459
|
: { details: requireText(input.details, "Runtime failure details") }),
|
|
262
460
|
...(input.lastOutput === undefined
|
|
263
461
|
? {}
|
|
264
|
-
: { 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") })
|
|
265
466
|
});
|
|
266
467
|
}
|
|
468
|
+
function requireBoolean(value, label) {
|
|
469
|
+
if (typeof value !== "boolean")
|
|
470
|
+
throw new Error(`${label} must be boolean.`);
|
|
471
|
+
return value;
|
|
472
|
+
}
|
|
267
473
|
function validateUsage(input) {
|
|
268
474
|
for (const [name, value] of Object.entries(input)) {
|
|
269
475
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
@@ -280,6 +486,11 @@ function requireTimestamp(value, label) {
|
|
|
280
486
|
throw new Error(`${label} is invalid.`);
|
|
281
487
|
return timestamp;
|
|
282
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
|
+
}
|
|
283
494
|
function requireIdentity(value, label) {
|
|
284
495
|
const identity = requireText(value, label);
|
|
285
496
|
if (identity.includes("/../") || identity === "__proto__")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRuntimeObservation,
|
|
1
|
+
import { createRuntimeObservation, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches } from "./runtimeObservation.js";
|
|
2
2
|
export function createRuntimeProjection(fence, createdAt) {
|
|
3
3
|
const timestamp = requireTimestamp(createdAt);
|
|
4
4
|
return Object.freeze({
|
|
@@ -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" }),
|
|
@@ -20,7 +28,7 @@ export function createRuntimeProjection(fence, createdAt) {
|
|
|
20
28
|
export function projectRuntimeTaskEvents(fence, createdAt, events) {
|
|
21
29
|
const observations = events
|
|
22
30
|
.map(runtimeObservationFromTaskEvent)
|
|
23
|
-
.filter((event) => (event !== null &&
|
|
31
|
+
.filter((event) => (event !== null && runtimeObservationRunFenceMatches(fence, event.fence)))
|
|
24
32
|
.sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
|
|
25
33
|
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
26
34
|
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
@@ -29,7 +37,7 @@ export function projectRuntimeTaskEvents(fence, createdAt, events) {
|
|
|
29
37
|
}
|
|
30
38
|
export function projectRuntimeObservation(current, raw) {
|
|
31
39
|
const event = createRuntimeObservation(raw);
|
|
32
|
-
if (!
|
|
40
|
+
if (!runtimeObservationRunFenceMatches(current.fence, event.fence)) {
|
|
33
41
|
throw new Error("Runtime observation fence does not match the projection.");
|
|
34
42
|
}
|
|
35
43
|
const at = event.receivedAt;
|
|
@@ -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",
|
|
@@ -79,7 +99,10 @@ export function projectRuntimeObservation(current, raw) {
|
|
|
79
99
|
turn: "completed",
|
|
80
100
|
waitingReason: undefined,
|
|
81
101
|
waitId: undefined,
|
|
82
|
-
operations
|
|
102
|
+
// Tool operations belong to the completed native Turn. Provider-owned
|
|
103
|
+
// background subagents may legitimately outlive it and wake later
|
|
104
|
+
// native Turns inside the same durable Yui Run.
|
|
105
|
+
operations: activeSubagentOperations(current.operations),
|
|
83
106
|
stateSince: at
|
|
84
107
|
}), "provider", at);
|
|
85
108
|
case "turn.failed":
|
|
@@ -104,7 +127,9 @@ export function projectRuntimeObservation(current, raw) {
|
|
|
104
127
|
const operationId = event.payload.operationId;
|
|
105
128
|
const operation = event.payload.operation;
|
|
106
129
|
return withActivity(next(current, {
|
|
107
|
-
session: "
|
|
130
|
+
session: current.session === "ended" || current.session === "failed"
|
|
131
|
+
? current.session
|
|
132
|
+
: "active",
|
|
108
133
|
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
109
134
|
waitingReason: undefined,
|
|
110
135
|
waitId: undefined,
|
|
@@ -120,7 +145,9 @@ export function projectRuntimeObservation(current, raw) {
|
|
|
120
145
|
const operations = { ...current.operations };
|
|
121
146
|
delete operations[event.payload.operationId];
|
|
122
147
|
return withActivity(next(current, {
|
|
123
|
-
session: "
|
|
148
|
+
session: current.session === "ended" || current.session === "failed"
|
|
149
|
+
? current.session
|
|
150
|
+
: "active",
|
|
124
151
|
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
125
152
|
waitingReason: undefined,
|
|
126
153
|
waitId: undefined,
|
|
@@ -151,6 +178,82 @@ export function projectRuntimeObservation(current, raw) {
|
|
|
151
178
|
: { detail: event.payload.observerDetail })
|
|
152
179
|
})
|
|
153
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, {});
|
|
154
257
|
default:
|
|
155
258
|
return current;
|
|
156
259
|
}
|
|
@@ -173,12 +276,27 @@ export function completeRuntimeWorkflow(current, completedAt) {
|
|
|
173
276
|
})
|
|
174
277
|
});
|
|
175
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
|
+
}
|
|
176
289
|
export function runtimeDisplayStatus(current) {
|
|
177
290
|
if (current.session === "failed")
|
|
178
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";
|
|
179
298
|
if (current.host === "exited" || current.session === "ended")
|
|
180
299
|
return "stopped";
|
|
181
|
-
const operation = dominantOperation(current.operations);
|
|
182
300
|
if (operation !== null)
|
|
183
301
|
return `${operation}-active`;
|
|
184
302
|
if (current.turn === "waiting")
|
|
@@ -236,7 +354,47 @@ function next(current, patch) {
|
|
|
236
354
|
}
|
|
237
355
|
if (patch.waitId === undefined && Object.hasOwn(patch, "waitId"))
|
|
238
356
|
delete copy.waitId;
|
|
239
|
-
|
|
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
|
+
});
|
|
240
398
|
}
|
|
241
399
|
function usageAdvanced(previous, current) {
|
|
242
400
|
// A first cumulative snapshot may contain history from a resumed native
|
|
@@ -261,6 +419,9 @@ function dominantOperation(operations) {
|
|
|
261
419
|
return "model";
|
|
262
420
|
return null;
|
|
263
421
|
}
|
|
422
|
+
function activeSubagentOperations(operations) {
|
|
423
|
+
return Object.freeze(Object.fromEntries(Object.entries(operations).filter(([, operation]) => operation.kind === "subagent")));
|
|
424
|
+
}
|
|
264
425
|
function terminalTurn(current, fallback = "cancelled") {
|
|
265
426
|
return current === "completed" || current === "failed" || current === "cancelled"
|
|
266
427
|
? current
|