@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.
- 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 +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 +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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { activeLiveRoleAgentSession, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed,
|
|
4
|
-
import {
|
|
3
|
+
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, recordRoleAgentSession, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
|
+
import { createProviderRuntimeBinding, endProviderActivation, startProviderActivation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
5
|
+
import { hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
5
6
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
6
7
|
import { answerInputRequest } from "../input/inputRequest.js";
|
|
7
8
|
import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
|
|
@@ -18,10 +19,12 @@ import { createLeaderRecoveryNotification, createLeaderStallNotification } from
|
|
|
18
19
|
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
19
20
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
20
21
|
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
22
|
+
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
23
|
+
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
21
24
|
import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
|
|
22
25
|
import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
23
26
|
import { formatAgentRunReceiptId, formatTaskRecordReference } from "../task/taskRecordReference.js";
|
|
24
|
-
import { bindExecution, claimPending, completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
|
|
27
|
+
import { bindExecution, claimPending, claimInputDelivery as claimMailboxInputDelivery, completeInputDelivery as completeMailboxInputDelivery, completeProcessing, mailboxHasPending, mailboxHasWork, pendingLane, markInputDeliveryPushed as markMailboxInputDeliveryPushed, markInputDeliveryUnknown as markMailboxInputDeliveryUnknown, releaseInputDelivery as releaseMailboxInputDelivery, resolveInputDeliveryNotAccepted as resolveMailboxInputDeliveryNotAccepted, releaseProcessing } from "../coordination/workMailbox.js";
|
|
25
28
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
26
29
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
27
30
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
@@ -57,6 +60,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
57
60
|
if (adapterId === null) {
|
|
58
61
|
return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
|
|
59
62
|
}
|
|
63
|
+
if (this.store.listEvents(taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
64
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
65
|
+
return "applied";
|
|
60
66
|
let outcome;
|
|
61
67
|
switch (input.kind) {
|
|
62
68
|
case "session.started":
|
|
@@ -64,6 +70,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
64
70
|
outcome = this.observeRuntimeSession(input, adapterId, now);
|
|
65
71
|
break;
|
|
66
72
|
case "turn.accepted":
|
|
73
|
+
case "input.accepted":
|
|
67
74
|
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
68
75
|
break;
|
|
69
76
|
case "turn.completed": {
|
|
@@ -94,6 +101,33 @@ export class FileSchedulerStoreAdapter {
|
|
|
94
101
|
}
|
|
95
102
|
case "turn.failed": {
|
|
96
103
|
const failureEvidence = input.payload.failure;
|
|
104
|
+
if (failureEvidence.runTerminal !== true) {
|
|
105
|
+
const classification = this.classifyRuntimeTurnCompleted({
|
|
106
|
+
taskId,
|
|
107
|
+
roleName: input.fence.roleName,
|
|
108
|
+
agentId: input.fence.agentId,
|
|
109
|
+
adapterId,
|
|
110
|
+
launchId: input.fence.launchId,
|
|
111
|
+
nativeSessionId: input.fence.nativeSessionId,
|
|
112
|
+
turnId: input.fence.nativeTurnId,
|
|
113
|
+
runId: input.fence.runId
|
|
114
|
+
});
|
|
115
|
+
if (classification !== "apply")
|
|
116
|
+
return classification;
|
|
117
|
+
const boundary = this.observeRuntimeTurnCompleted({
|
|
118
|
+
taskId,
|
|
119
|
+
roleName: input.fence.roleName,
|
|
120
|
+
agentId: input.fence.agentId,
|
|
121
|
+
adapterId,
|
|
122
|
+
launchId: input.fence.launchId,
|
|
123
|
+
nativeSessionId: input.fence.nativeSessionId,
|
|
124
|
+
turnId: input.fence.nativeTurnId,
|
|
125
|
+
runId: input.fence.runId,
|
|
126
|
+
summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`
|
|
127
|
+
}, now);
|
|
128
|
+
outcome = boundary.disposition === "obsolete" ? "obsolete" : "applied";
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
97
131
|
const failure = {
|
|
98
132
|
eventId: input.eventId,
|
|
99
133
|
eventType: input.kind,
|
|
@@ -126,8 +160,19 @@ export class FileSchedulerStoreAdapter {
|
|
|
126
160
|
case "turn.cancelled":
|
|
127
161
|
case "activity.observed":
|
|
128
162
|
case "observer.health":
|
|
163
|
+
case "native-work.snapshot":
|
|
164
|
+
case "continuation.started":
|
|
165
|
+
case "continuation.reported":
|
|
166
|
+
case "continuation.settled":
|
|
167
|
+
case "input.delivery-unknown":
|
|
129
168
|
outcome = this.validateCanonicalRunObservation(input, now);
|
|
130
169
|
break;
|
|
170
|
+
case "conversation.observed":
|
|
171
|
+
case "activation.started":
|
|
172
|
+
case "activation.ended":
|
|
173
|
+
case "activation.failed":
|
|
174
|
+
outcome = this.observeProviderRuntimeIdentity(input, now);
|
|
175
|
+
break;
|
|
131
176
|
case "session.ended":
|
|
132
177
|
case "session.failed":
|
|
133
178
|
outcome = this.validateCanonicalSessionObservation(input, now);
|
|
@@ -164,22 +209,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
164
209
|
validateCanonicalRunObservation(input, now) {
|
|
165
210
|
return this.store.transaction((store) => {
|
|
166
211
|
if (store.listEvents(input.fence.taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
167
|
-
&& event.payload.
|
|
212
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
168
213
|
return "applied";
|
|
169
214
|
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
170
215
|
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
171
216
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
172
217
|
const session = sessions?.sessions[input.fence.agentId];
|
|
173
|
-
const
|
|
218
|
+
const knownContinuation = input.kind.startsWith("continuation.")
|
|
219
|
+
&& projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.runId === input.fence.runId
|
|
220
|
+
&& entry.identity.providerNamespace === input.fence.driverId
|
|
221
|
+
&& entry.identity.accountScope === input.fence.agentId
|
|
222
|
+
&& entry.identity.conversationId === input.fence.conversationId
|
|
223
|
+
&& entry.identity.activationId === input.fence.activationId
|
|
224
|
+
&& entry.identity.continuationId === input.fence.continuationId
|
|
225
|
+
&& entry.identity.generation === input.fence.continuationGeneration));
|
|
226
|
+
const requiresCurrentRuntime = input.kind !== "turn.cancelled" && !knownContinuation;
|
|
174
227
|
const valid = run !== null
|
|
175
|
-
&& (!
|
|
228
|
+
&& (!requiresCurrentRuntime || (run.status === "active" && active?.id === run.id))
|
|
176
229
|
&& run.roleName === input.fence.roleName
|
|
177
230
|
&& run.effective.agentId === input.fence.agentId
|
|
178
231
|
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
179
|
-
&& session?.launchId === input.fence.launchId
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
&&
|
|
232
|
+
&& (knownContinuation || (session?.launchId === input.fence.launchId
|
|
233
|
+
&& session.nativeSessionId === input.fence.nativeSessionId
|
|
234
|
+
&& input.fence.sessionGenerationId === input.fence.launchId))
|
|
235
|
+
&& runtimeReceiptBelongsToRun(store, input)
|
|
183
236
|
&& (run.deliveredAt !== undefined || input.kind === "turn.cancelled");
|
|
184
237
|
if (!valid) {
|
|
185
238
|
recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
|
|
@@ -205,7 +258,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
205
258
|
|| run.roleName !== input.fence.roleName
|
|
206
259
|
|| run.effective.agentId !== input.fence.agentId
|
|
207
260
|
|| this.drivers.requireByAdapterId(run.effective.adapterId).id !== input.fence.driverId
|
|
208
|
-
|| input.fence.receiptId !== formatAgentRunReceiptId(input.fence.taskId, run.id)
|
|
209
261
|
|| session?.launchId !== input.fence.launchId
|
|
210
262
|
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
211
263
|
|| input.fence.sessionGenerationId !== input.fence.launchId) {
|
|
@@ -213,7 +265,101 @@ export class FileSchedulerStoreAdapter {
|
|
|
213
265
|
return "obsolete";
|
|
214
266
|
}
|
|
215
267
|
const status = input.kind === "session.failed" ? "broken" : "stopped";
|
|
216
|
-
|
|
268
|
+
let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now);
|
|
269
|
+
if (updatedSessions.providerBinding !== null) {
|
|
270
|
+
const activationId = input.fence.activationId ?? input.fence.launchId;
|
|
271
|
+
updatedSessions = updateTaskRoleProviderRuntime(updatedSessions, endProviderActivation(updatedSessions.providerBinding, activationId, {
|
|
272
|
+
status: input.kind === "session.failed" ? "failed" : "ended",
|
|
273
|
+
endedAt: input.observedAt ?? input.receivedAt,
|
|
274
|
+
reason: input.kind
|
|
275
|
+
}), now);
|
|
276
|
+
}
|
|
277
|
+
store.saveTaskRoleSessionSet(updatedSessions);
|
|
278
|
+
for (const continuation of projectProviderContinuations(store.listEvents(input.fence.taskId))) {
|
|
279
|
+
if (continuation.runId !== input.fence.runId
|
|
280
|
+
|| continuation.identity.conversationId
|
|
281
|
+
!== (input.fence.conversationId ?? input.fence.nativeSessionId)
|
|
282
|
+
|| continuation.identity.activationId
|
|
283
|
+
!== (input.fence.activationId ?? input.fence.launchId)
|
|
284
|
+
|| continuation.execution === "quiescent"
|
|
285
|
+
|| continuation.attachment === "detached")
|
|
286
|
+
continue;
|
|
287
|
+
const key = providerContinuationKey(continuation.identity);
|
|
288
|
+
const identityDigest = createHash("sha256").update(key).digest("hex");
|
|
289
|
+
const detached = createRuntimeObservation({
|
|
290
|
+
schemaVersion: 2,
|
|
291
|
+
eventId: `derived-continuation-detached:${identityDigest}`,
|
|
292
|
+
semanticKey: `continuation-detached:${identityDigest}`,
|
|
293
|
+
kind: "continuation.started",
|
|
294
|
+
authority: "controller",
|
|
295
|
+
receivedAt: input.receivedAt,
|
|
296
|
+
observedAt: input.observedAt ?? input.receivedAt,
|
|
297
|
+
fence: {
|
|
298
|
+
...input.fence,
|
|
299
|
+
conversationId: continuation.identity.conversationId,
|
|
300
|
+
activationId: continuation.identity.activationId,
|
|
301
|
+
continuationId: continuation.identity.continuationId,
|
|
302
|
+
continuationGeneration: continuation.identity.generation,
|
|
303
|
+
...(continuation.parentContinuationId === undefined
|
|
304
|
+
? {}
|
|
305
|
+
: { parentContinuationId: continuation.parentContinuationId })
|
|
306
|
+
},
|
|
307
|
+
payload: {
|
|
308
|
+
execution: continuation.execution,
|
|
309
|
+
outcome: continuation.outcome,
|
|
310
|
+
attachment: "detached",
|
|
311
|
+
observationQuality: continuation.observation,
|
|
312
|
+
mayWriteWorkspace: continuation.mayWriteWorkspace,
|
|
313
|
+
...(continuation.resultRef === undefined
|
|
314
|
+
? {}
|
|
315
|
+
: { resultRef: continuation.resultRef })
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
const eventId = store.nextEventId(input.fence.taskId);
|
|
319
|
+
store.saveEvent(input.fence.taskId, createTaskEvent(eventId, input.fence.taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(detached), now));
|
|
320
|
+
enqueueWork(store, {
|
|
321
|
+
kind: "role",
|
|
322
|
+
taskId: input.fence.taskId,
|
|
323
|
+
roleName: "leader"
|
|
324
|
+
}, "provider-continuation-detached", now, [
|
|
325
|
+
{ type: "event", taskId: input.fence.taskId, id: eventId }
|
|
326
|
+
], {
|
|
327
|
+
source: input.fence.driverId,
|
|
328
|
+
dedupeKey: detached.semanticKey,
|
|
329
|
+
deliveryMode: "followup",
|
|
330
|
+
lane: "normal"
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
334
|
+
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
335
|
+
if ((input.kind === "session.ended" || input.kind === "session.failed")
|
|
336
|
+
&& active !== null
|
|
337
|
+
&& active.id === input.fence.runId
|
|
338
|
+
&& active.status === "active"
|
|
339
|
+
&& active.deliveredAt !== undefined
|
|
340
|
+
&& role !== null) {
|
|
341
|
+
store.saveRole(input.fence.taskId, updateRoleStatus(role, "detached", now));
|
|
342
|
+
if (role.name === "leader") {
|
|
343
|
+
const message = [
|
|
344
|
+
`Leader native Session became unavailable while Run ${active.id} remains active.`,
|
|
345
|
+
"Yui preserved the Run because Session/host termination is not a workflow outcome.",
|
|
346
|
+
"Inspect native-child and mailbox facts, then explicitly continue, reset, or request user input."
|
|
347
|
+
].join(" ");
|
|
348
|
+
store.saveOperatorNotification(createLeaderRecoveryNotification(input.fence.taskId, message, now, store.getOperatorNotification(input.fence.taskId)));
|
|
349
|
+
enqueueWork(store, { kind: "operator" }, "leader-runtime-detached", now, [
|
|
350
|
+
{ type: "run", taskId: input.fence.taskId, id: active.id }
|
|
351
|
+
]);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
enqueueWork(store, {
|
|
355
|
+
kind: "role",
|
|
356
|
+
taskId: input.fence.taskId,
|
|
357
|
+
roleName: "leader"
|
|
358
|
+
}, "role-runtime-detached", now, [
|
|
359
|
+
{ type: "run", taskId: input.fence.taskId, id: active.id }
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
217
363
|
return "applied";
|
|
218
364
|
});
|
|
219
365
|
}
|
|
@@ -222,7 +368,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
222
368
|
const taskId = input.fence.taskId;
|
|
223
369
|
const events = store.listEvents(taskId);
|
|
224
370
|
if (events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
225
|
-
&& event.payload.
|
|
371
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
226
372
|
return;
|
|
227
373
|
if (usageSnapshotIsSuperseded(events, input))
|
|
228
374
|
return;
|
|
@@ -235,7 +381,90 @@ export class FileSchedulerStoreAdapter {
|
|
|
235
381
|
];
|
|
236
382
|
if (removable.length > 0)
|
|
237
383
|
store.removeEvents(taskId, removable);
|
|
238
|
-
|
|
384
|
+
const observationEventId = store.nextEventId(taskId);
|
|
385
|
+
store.saveEvent(taskId, createTaskEvent(observationEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(input), now));
|
|
386
|
+
if (input.kind === "native-work.snapshot"
|
|
387
|
+
&& input.payload.snapshotComplete === true
|
|
388
|
+
&& input.payload.observationQuality === "exact") {
|
|
389
|
+
for (const continuation of projectProviderContinuations(events)) {
|
|
390
|
+
if (continuation.runId !== input.fence.runId
|
|
391
|
+
|| continuation.identity.conversationId !== input.fence.conversationId
|
|
392
|
+
|| continuation.identity.activationId !== input.fence.activationId
|
|
393
|
+
|| continuation.execution === "quiescent")
|
|
394
|
+
continue;
|
|
395
|
+
const identityKey = providerContinuationKey(continuation.identity);
|
|
396
|
+
const digest = createHash("sha256")
|
|
397
|
+
.update(`${input.semanticKey}\u0000${identityKey}`)
|
|
398
|
+
.digest("hex");
|
|
399
|
+
const settled = createRuntimeObservation({
|
|
400
|
+
schemaVersion: 2,
|
|
401
|
+
eventId: `native-snapshot-settled:${digest}`,
|
|
402
|
+
semanticKey: `native-snapshot-settled:${digest}`,
|
|
403
|
+
kind: "continuation.settled",
|
|
404
|
+
authority: "controller",
|
|
405
|
+
receivedAt: input.receivedAt,
|
|
406
|
+
observedAt: input.observedAt ?? input.receivedAt,
|
|
407
|
+
fence: {
|
|
408
|
+
...input.fence,
|
|
409
|
+
continuationId: continuation.identity.continuationId,
|
|
410
|
+
continuationGeneration: continuation.identity.generation,
|
|
411
|
+
...(continuation.parentContinuationId === undefined
|
|
412
|
+
? {}
|
|
413
|
+
: { parentContinuationId: continuation.parentContinuationId })
|
|
414
|
+
},
|
|
415
|
+
payload: {
|
|
416
|
+
execution: "quiescent",
|
|
417
|
+
outcome: "unknown",
|
|
418
|
+
attachment: continuation.attachment,
|
|
419
|
+
observationQuality: "exact",
|
|
420
|
+
mayWriteWorkspace: false,
|
|
421
|
+
...(continuation.resultRef === undefined
|
|
422
|
+
? {}
|
|
423
|
+
: { resultRef: continuation.resultRef })
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
const settledEventId = store.nextEventId(taskId);
|
|
427
|
+
store.saveEvent(taskId, createTaskEvent(settledEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(settled), now));
|
|
428
|
+
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, "provider-continuation-settled", now, [{ type: "event", taskId, id: settledEventId }], {
|
|
429
|
+
source: input.fence.driverId,
|
|
430
|
+
dedupeKey: settled.semanticKey,
|
|
431
|
+
deliveryMode: "followup",
|
|
432
|
+
lane: "normal"
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (input.kind === "continuation.reported"
|
|
437
|
+
|| input.kind === "continuation.settled") {
|
|
438
|
+
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, input.kind === "continuation.reported"
|
|
439
|
+
? "provider-continuation-report"
|
|
440
|
+
: "provider-continuation-settled", now, [{ type: "event", taskId, id: observationEventId }], {
|
|
441
|
+
source: input.fence.driverId,
|
|
442
|
+
dedupeKey: input.semanticKey,
|
|
443
|
+
deliveryMode: "followup",
|
|
444
|
+
lane: "normal"
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (input.kind === "turn.failed" && input.payload.failure?.runTerminal !== true) {
|
|
448
|
+
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, "provider-turn-failed", now, [{ type: "event", taskId, id: observationEventId }], {
|
|
449
|
+
source: input.fence.driverId,
|
|
450
|
+
dedupeKey: input.semanticKey,
|
|
451
|
+
deliveryMode: "followup",
|
|
452
|
+
lane: "normal"
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if ((input.kind === "operation.completed" || input.kind === "operation.failed")
|
|
456
|
+
&& input.payload.operation === "subagent") {
|
|
457
|
+
const role = store.getRole(taskId, input.fence.roleName);
|
|
458
|
+
const session = store.getRoleSession(taskId, input.fence.roleName);
|
|
459
|
+
// A live provider Session owns delivery of its native child result.
|
|
460
|
+
// Route a Yui wake only when that parent is gone and another observer
|
|
461
|
+
// supplied the child's terminal fact.
|
|
462
|
+
if (role !== null && (session?.status === "stopped" || session?.status === "broken")) {
|
|
463
|
+
enqueueWork(store, role.name === "leader"
|
|
464
|
+
? { kind: "operator" }
|
|
465
|
+
: { kind: "role", taskId, roleName: "leader" }, "detached-native-subagent-terminal", now, [{ type: "run", taskId, id: input.fence.runId }]);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
239
468
|
if (confirmedActivity !== null) {
|
|
240
469
|
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(confirmedActivity), now));
|
|
241
470
|
}
|
|
@@ -513,6 +742,42 @@ export class FileSchedulerStoreAdapter {
|
|
|
513
742
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
514
743
|
return sessions !== null && sessions.inFlight !== null;
|
|
515
744
|
}
|
|
745
|
+
getActiveProviderTurnFence(input) {
|
|
746
|
+
const observations = this.store.listEvents(input.taskId)
|
|
747
|
+
.map(runtimeObservationFromTaskEvent)
|
|
748
|
+
.filter((entry) => entry !== null
|
|
749
|
+
&& entry.fence.runId === input.runId
|
|
750
|
+
&& entry.fence.roleName === input.roleName
|
|
751
|
+
&& entry.fence.agentId === input.agentId
|
|
752
|
+
&& entry.fence.launchId === input.launchId
|
|
753
|
+
&& entry.fence.nativeSessionId === input.nativeSessionId
|
|
754
|
+
&& entry.fence.nativeTurnId !== undefined)
|
|
755
|
+
.sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
|
|
756
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
757
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
758
|
+
|| left.semanticKey.localeCompare(right.semanticKey)));
|
|
759
|
+
const state = new Map();
|
|
760
|
+
const fences = new Map();
|
|
761
|
+
for (const observation of observations) {
|
|
762
|
+
const turnId = observation.fence.nativeTurnId;
|
|
763
|
+
if (observation.kind === "turn.accepted" || observation.kind === "input.accepted") {
|
|
764
|
+
state.set(turnId, "active");
|
|
765
|
+
fences.set(turnId, observation);
|
|
766
|
+
}
|
|
767
|
+
else if (["turn.completed", "turn.failed", "turn.cancelled"].includes(observation.kind)) {
|
|
768
|
+
state.set(turnId, "terminal");
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const active = [...state.entries()].filter(([, status]) => status === "active");
|
|
772
|
+
if (active.length !== 1)
|
|
773
|
+
return null;
|
|
774
|
+
const observation = fences.get(active[0][0]);
|
|
775
|
+
return {
|
|
776
|
+
conversationId: observation.fence.conversationId ?? input.nativeSessionId,
|
|
777
|
+
activationId: observation.fence.activationId ?? input.launchId,
|
|
778
|
+
nativeTurnId: active[0][0]
|
|
779
|
+
};
|
|
780
|
+
}
|
|
516
781
|
isRoleGenerationProviderReady(input) {
|
|
517
782
|
// A session.ready observation for this exact generation is the durable
|
|
518
783
|
// pre-input fence. This reads folded Driver truth
|
|
@@ -584,7 +849,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
584
849
|
claimWorkMailbox(input) {
|
|
585
850
|
return this.store.transaction((store) => {
|
|
586
851
|
const mailbox = store.getWorkMailbox(input.target);
|
|
587
|
-
if (mailbox === null || (mailbox
|
|
852
|
+
if (mailbox === null || !mailboxHasWork(mailbox)) {
|
|
588
853
|
return { status: "empty" };
|
|
589
854
|
}
|
|
590
855
|
if (mailbox.processing !== null) {
|
|
@@ -602,6 +867,104 @@ export class FileSchedulerStoreAdapter {
|
|
|
602
867
|
return { status: "claimed", processing: claimed.processing };
|
|
603
868
|
});
|
|
604
869
|
}
|
|
870
|
+
claimInputDelivery(input) {
|
|
871
|
+
return this.store.transaction((store) => {
|
|
872
|
+
const mailbox = store.getWorkMailbox(input.target);
|
|
873
|
+
if (mailbox === null)
|
|
874
|
+
return { status: "empty" };
|
|
875
|
+
if (mailbox.inputDelivery !== null) {
|
|
876
|
+
return { status: "delivery", delivery: mailbox.inputDelivery };
|
|
877
|
+
}
|
|
878
|
+
const pending = pendingLane(mailbox, input.lane);
|
|
879
|
+
if (pending === null)
|
|
880
|
+
return { status: "empty" };
|
|
881
|
+
const claimed = claimMailboxInputDelivery(mailbox, {
|
|
882
|
+
attemptId: input.attemptId,
|
|
883
|
+
lane: input.lane,
|
|
884
|
+
mode: input.mode,
|
|
885
|
+
owner: input.owner,
|
|
886
|
+
startedAt: input.now.toISOString(),
|
|
887
|
+
executionRef: input.executionRef,
|
|
888
|
+
providerFence: input.providerFence
|
|
889
|
+
});
|
|
890
|
+
store.saveWorkMailbox(claimed);
|
|
891
|
+
return { status: "claimed", delivery: claimed.inputDelivery };
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
markInputDeliveryPushed(target, attemptId, now) {
|
|
895
|
+
return this.store.transaction((store) => {
|
|
896
|
+
const mailbox = store.getWorkMailbox(target);
|
|
897
|
+
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
898
|
+
return false;
|
|
899
|
+
store.saveWorkMailbox(markMailboxInputDeliveryPushed(mailbox, attemptId, now));
|
|
900
|
+
return true;
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
completeInputDelivery(target, attemptId, now) {
|
|
904
|
+
return this.store.transaction((store) => {
|
|
905
|
+
const mailbox = store.getWorkMailbox(target);
|
|
906
|
+
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
907
|
+
return false;
|
|
908
|
+
const delivery = mailbox.inputDelivery;
|
|
909
|
+
if (target.kind === "role"
|
|
910
|
+
&& delivery.executionRef.type === "run"
|
|
911
|
+
&& delivery.executionRef.taskId === target.taskId) {
|
|
912
|
+
store.saveEvent(target.taskId, createTaskEvent(store.nextEventId(target.taskId), target.taskId, "run.input-delivered", {
|
|
913
|
+
attemptId: delivery.attemptId,
|
|
914
|
+
runId: delivery.executionRef.id,
|
|
915
|
+
...(delivery.providerFence === undefined ? {} : {
|
|
916
|
+
conversationId: delivery.providerFence.conversationId,
|
|
917
|
+
activationId: delivery.providerFence.activationId,
|
|
918
|
+
...(delivery.providerFence.nativeTurnId === undefined ? {} : {
|
|
919
|
+
nativeTurnId: delivery.providerFence.nativeTurnId
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
}, now));
|
|
923
|
+
}
|
|
924
|
+
store.saveWorkMailbox(completeMailboxInputDelivery(mailbox, attemptId, now));
|
|
925
|
+
return true;
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
releaseInputDelivery(target, attemptId) {
|
|
929
|
+
return this.store.transaction((store) => {
|
|
930
|
+
const mailbox = store.getWorkMailbox(target);
|
|
931
|
+
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
932
|
+
return false;
|
|
933
|
+
store.saveWorkMailbox(releaseMailboxInputDelivery(mailbox, attemptId));
|
|
934
|
+
return true;
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
resolveInputDeliveryNotAccepted(target, attemptId) {
|
|
938
|
+
return this.store.transaction((store) => {
|
|
939
|
+
const mailbox = store.getWorkMailbox(target);
|
|
940
|
+
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
941
|
+
return false;
|
|
942
|
+
store.saveWorkMailbox(resolveMailboxInputDeliveryNotAccepted(mailbox, attemptId));
|
|
943
|
+
return true;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
markInputDeliveryUnknown(target, attemptId, reason, now) {
|
|
947
|
+
return this.store.transaction((store) => {
|
|
948
|
+
const mailbox = store.getWorkMailbox(target);
|
|
949
|
+
if (mailbox?.inputDelivery?.attemptId !== attemptId)
|
|
950
|
+
return false;
|
|
951
|
+
const unknown = markMailboxInputDeliveryUnknown(mailbox, attemptId, reason, now);
|
|
952
|
+
store.saveWorkMailbox(unknown);
|
|
953
|
+
const taskId = target.kind === "role" ? target.taskId : undefined;
|
|
954
|
+
if (taskId !== undefined) {
|
|
955
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.input-delivery-unknown", { attemptId, reason }, now));
|
|
956
|
+
enqueueWork(store, { kind: "operator" }, "run-input-delivery-unknown", now, [
|
|
957
|
+
unknown.inputDelivery.executionRef
|
|
958
|
+
], {
|
|
959
|
+
source: "input-router",
|
|
960
|
+
dedupeKey: `input-delivery-unknown:${attemptId}`,
|
|
961
|
+
deliveryMode: "followup",
|
|
962
|
+
lane: "normal"
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
return true;
|
|
966
|
+
});
|
|
967
|
+
}
|
|
605
968
|
completeWorkMailbox(target, batchId) {
|
|
606
969
|
return this.store.transaction((store) => {
|
|
607
970
|
const mailbox = store.getWorkMailbox(target);
|
|
@@ -639,12 +1002,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
639
1002
|
}
|
|
640
1003
|
mailbox = completeProcessing(mailbox, mailbox.processing.batchId);
|
|
641
1004
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1005
|
+
const pending = pendingLane(mailbox, "normal");
|
|
1006
|
+
if (pending !== null) {
|
|
1007
|
+
if (pending.reasons.length !== 1
|
|
1008
|
+
|| pending.reasons[0] !== RUNTIME_CLEANUP_REQUIRED_REASON) {
|
|
645
1009
|
return false;
|
|
646
1010
|
}
|
|
647
|
-
const batchId = `runtime-cleanup-complete:${
|
|
1011
|
+
const batchId = `runtime-cleanup-complete:${pending.fromSequence}-${pending.toSequence}`;
|
|
648
1012
|
mailbox = completeProcessing(claimPending(mailbox, {
|
|
649
1013
|
batchId,
|
|
650
1014
|
owner: RUNTIME_LIFECYCLE_OWNER,
|
|
@@ -745,7 +1109,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
745
1109
|
}
|
|
746
1110
|
const target = { kind: "role", taskId: input.task.id, roleName: input.role.name };
|
|
747
1111
|
const mailbox = store.getWorkMailbox(target);
|
|
748
|
-
if (mailbox === null || mailbox
|
|
1112
|
+
if (mailbox === null || !mailboxHasPending(mailbox))
|
|
749
1113
|
return "state-changed";
|
|
750
1114
|
if (mailbox.processing !== null)
|
|
751
1115
|
return "busy";
|
|
@@ -1266,8 +1630,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1266
1630
|
}
|
|
1267
1631
|
/**
|
|
1268
1632
|
* Fast hook path: validates the native Turn boundary before it is either
|
|
1269
|
-
* retained as an intermediate child wait or
|
|
1270
|
-
*
|
|
1633
|
+
* retained as an intermediate child wait or recorded as a ready boundary
|
|
1634
|
+
* for later mailbox input. It never performs tmux, workspace, or Controller I/O.
|
|
1271
1635
|
*/
|
|
1272
1636
|
classifyRuntimeTurnCompleted(input) {
|
|
1273
1637
|
const task = this.store.getTask(input.taskId);
|
|
@@ -1294,11 +1658,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1294
1658
|
catch {
|
|
1295
1659
|
return "obsolete";
|
|
1296
1660
|
}
|
|
1297
|
-
if (sessions?.pendingTurnCompletion !== null && sessions !== null) {
|
|
1298
|
-
return sessions.pendingTurnCompletion.turnId === input.turnId
|
|
1299
|
-
? "apply"
|
|
1300
|
-
: "deferred";
|
|
1301
|
-
}
|
|
1302
1661
|
// A normal explicit CLI yield may precede its native Hook; that Hook still
|
|
1303
1662
|
// owns the turn fact. A forced cleanup boundary or stopped process instead
|
|
1304
1663
|
// makes the old generation obsolete.
|
|
@@ -1350,19 +1709,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1350
1709
|
&& hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, input.turnId)) {
|
|
1351
1710
|
return { session: effectiveExisting, duplicate: true };
|
|
1352
1711
|
}
|
|
1353
|
-
const pending = sessions.pendingTurnCompletion;
|
|
1354
|
-
if (effectiveExisting !== undefined
|
|
1355
|
-
&& pending !== null
|
|
1356
|
-
&& pending.agentId === input.agentId
|
|
1357
|
-
&& pending.nativeSessionId === input.nativeSessionId
|
|
1358
|
-
&& pending.turnId === input.turnId
|
|
1359
|
-
&& pending.runId === input.runId) {
|
|
1360
|
-
return {
|
|
1361
|
-
session: effectiveExisting,
|
|
1362
|
-
duplicate: true,
|
|
1363
|
-
pendingRunId: pending.runId
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
1712
|
if (sessions.inFlight === null
|
|
1367
1713
|
&& existing !== undefined
|
|
1368
1714
|
&& (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))
|
|
@@ -1403,205 +1749,23 @@ export class FileSchedulerStoreAdapter {
|
|
|
1403
1749
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1404
1750
|
return { session: sessions.sessions[input.agentId], duplicate: false };
|
|
1405
1751
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
const completion = createPendingTurnCompletion({
|
|
1412
|
-
taskId: task.id,
|
|
1413
|
-
roleName: role.name,
|
|
1752
|
+
// A provider Turn is an activation boundary, not a Yui Run outcome.
|
|
1753
|
+
// Preserve the Run fence and make the fixed native Session available for
|
|
1754
|
+
// a later mailbox batch. Provider-owned background subagents may finish
|
|
1755
|
+
// after this point and Yui must not invent a failure while waiting.
|
|
1756
|
+
sessions = recordTaskRoleTurnBoundary(sessions, {
|
|
1414
1757
|
agentId: input.agentId,
|
|
1415
1758
|
nativeSessionId: input.nativeSessionId,
|
|
1416
|
-
turnId: input.turnId
|
|
1417
|
-
|
|
1418
|
-
summary: input.summary,
|
|
1419
|
-
observedAt: now,
|
|
1420
|
-
dueAt: new Date(now.getTime() + 2_000)
|
|
1421
|
-
});
|
|
1422
|
-
// Runtime completion is not workflow progress. The exact Run remains
|
|
1423
|
-
// open for the short outcome grace period so a concurrently committed
|
|
1424
|
-
// yui yield/block/complete action can win; otherwise the contract fails.
|
|
1425
|
-
sessions = recordObservedTaskRoleCompletion(sessions, completion);
|
|
1426
|
-
const active = store.getActiveAgentRun(task.id, role.name);
|
|
1427
|
-
if (active === null || active.id !== fence.runId || active.status !== "active") {
|
|
1428
|
-
sessions = settleTaskRoleCompletion(sessions, {
|
|
1429
|
-
agentId: input.agentId,
|
|
1430
|
-
runId: fence.runId,
|
|
1431
|
-
turnId: input.turnId
|
|
1432
|
-
}, now);
|
|
1433
|
-
const settledStatus = sessions.sessions[input.agentId]?.status;
|
|
1434
|
-
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1435
|
-
sessions = updateRoleAgentSessionStatus(sessions, input.agentId, "ready", now);
|
|
1436
|
-
}
|
|
1437
|
-
store.saveTaskRoleSessionSet(sessions);
|
|
1438
|
-
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1439
|
-
return { session: sessions.sessions[input.agentId], duplicate: false };
|
|
1440
|
-
}
|
|
1759
|
+
turnId: input.turnId
|
|
1760
|
+
}, now);
|
|
1441
1761
|
store.saveTaskRoleSessionSet(sessions);
|
|
1442
1762
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1443
1763
|
return {
|
|
1444
1764
|
session: sessions.sessions[input.agentId],
|
|
1445
|
-
duplicate: false
|
|
1446
|
-
pendingRunId: fence.runId
|
|
1765
|
+
duplicate: false
|
|
1447
1766
|
};
|
|
1448
1767
|
});
|
|
1449
1768
|
}
|
|
1450
|
-
listPendingRuntimeTurnCompletions(taskIds) {
|
|
1451
|
-
return this.store.listPendingRuntimeTurnCompletions(taskIds);
|
|
1452
|
-
}
|
|
1453
|
-
/** Resolves only completions whose grace deadline has elapsed. */
|
|
1454
|
-
resolveDueRuntimeTurnCompletions(now, taskIds) {
|
|
1455
|
-
const selectedTaskIds = taskIds === undefined
|
|
1456
|
-
? undefined
|
|
1457
|
-
: [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
1458
|
-
const due = this.listPendingRuntimeTurnCompletions(selectedTaskIds).filter((completion) => Date.parse(completion.dueAt) <= now.getTime());
|
|
1459
|
-
const finalized = [];
|
|
1460
|
-
for (const completion of due) {
|
|
1461
|
-
this.store.transaction((store) => {
|
|
1462
|
-
const current = store.getTaskRoleSessionSet(completion.taskId, completion.roleName);
|
|
1463
|
-
if (current === null)
|
|
1464
|
-
return;
|
|
1465
|
-
const pending = current.pendingTurnCompletion;
|
|
1466
|
-
if (pending === null
|
|
1467
|
-
|| pending.turnId !== completion.turnId
|
|
1468
|
-
|| pending.runId !== completion.runId)
|
|
1469
|
-
return;
|
|
1470
|
-
const active = store.getActiveAgentRun(completion.taskId, completion.roleName);
|
|
1471
|
-
if (active?.id === completion.runId) {
|
|
1472
|
-
const result = this.recordRuntimeTurnCompleted({
|
|
1473
|
-
taskId: completion.taskId,
|
|
1474
|
-
roleName: completion.roleName,
|
|
1475
|
-
agentId: completion.agentId,
|
|
1476
|
-
adapterId: current.sessions[completion.agentId].adapterId,
|
|
1477
|
-
nativeSessionId: completion.nativeSessionId,
|
|
1478
|
-
turnId: completion.turnId,
|
|
1479
|
-
expectedRunId: completion.runId,
|
|
1480
|
-
summary: completion.summary
|
|
1481
|
-
}, now);
|
|
1482
|
-
if (result.finalizedRunId !== undefined) {
|
|
1483
|
-
finalized.push(formatTaskRecordReference(completion.taskId, result.finalizedRunId, "agentRun"));
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
let settled = store.getTaskRoleSessionSet(completion.taskId, completion.roleName);
|
|
1487
|
-
if (settled?.pendingTurnCompletion?.turnId !== completion.turnId)
|
|
1488
|
-
return;
|
|
1489
|
-
settled = settleTaskRoleCompletion(settled, {
|
|
1490
|
-
agentId: completion.agentId,
|
|
1491
|
-
runId: completion.runId,
|
|
1492
|
-
turnId: completion.turnId
|
|
1493
|
-
}, now);
|
|
1494
|
-
const settledStatus = settled.sessions[completion.agentId]?.status;
|
|
1495
|
-
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1496
|
-
settled = updateRoleAgentSessionStatus(settled, completion.agentId, "ready", now);
|
|
1497
|
-
}
|
|
1498
|
-
store.saveTaskRoleSessionSet(settled);
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
return finalized;
|
|
1502
|
-
}
|
|
1503
|
-
/** A structured native Turn-completion event terminalizes its exact Run fence. */
|
|
1504
|
-
recordRuntimeTurnCompleted(input, now = new Date()) {
|
|
1505
|
-
return this.store.transaction((store) => {
|
|
1506
|
-
const task = store.getTask(input.taskId);
|
|
1507
|
-
if (task === null)
|
|
1508
|
-
throw new Error(`Task not found: ${input.taskId}.`);
|
|
1509
|
-
if (task.status !== "active") {
|
|
1510
|
-
throw new Error(`Cannot complete a runtime turn for inactive Task: ${input.taskId}.`);
|
|
1511
|
-
}
|
|
1512
|
-
const role = requireRole(store, input.taskId, input.roleName);
|
|
1513
|
-
const current = store.getRoleSessionSet(input.taskId, input.roleName)
|
|
1514
|
-
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
1515
|
-
const existing = current.sessions[input.agentId];
|
|
1516
|
-
const owner = {
|
|
1517
|
-
scope: "task",
|
|
1518
|
-
taskId: input.taskId,
|
|
1519
|
-
roleName: input.roleName
|
|
1520
|
-
};
|
|
1521
|
-
if (existing === undefined
|
|
1522
|
-
&& !runtimeHookMatchesReservation(store, owner, input.launchId)) {
|
|
1523
|
-
throw new Error("Runtime turn completion does not match the launch reservation.");
|
|
1524
|
-
}
|
|
1525
|
-
const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed Role session.");
|
|
1526
|
-
const effective = taskSessionEffective(store, task.id, role.name, input.agentId, effectiveExisting);
|
|
1527
|
-
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
1528
|
-
throw new Error("Runtime turn completion does not match the effective launch identity.");
|
|
1529
|
-
}
|
|
1530
|
-
const sessions = effectiveExisting?.status === "ready"
|
|
1531
|
-
|| effectiveExisting?.status === "stopped"
|
|
1532
|
-
|| effectiveExisting?.status === "broken"
|
|
1533
|
-
? current
|
|
1534
|
-
: recordRoleAgentSession(current, {
|
|
1535
|
-
agentId: input.agentId,
|
|
1536
|
-
adapterId: input.adapterId,
|
|
1537
|
-
nativeSessionId: input.nativeSessionId,
|
|
1538
|
-
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1539
|
-
policy: "fixed",
|
|
1540
|
-
status: "ready",
|
|
1541
|
-
effective
|
|
1542
|
-
}, now);
|
|
1543
|
-
if (input.expectedRunId !== undefined
|
|
1544
|
-
&& store.getActiveAgentRun(task.id, role.name)?.id !== input.expectedRunId) {
|
|
1545
|
-
return { session: sessions.sessions[input.agentId] };
|
|
1546
|
-
}
|
|
1547
|
-
if (sessions !== current)
|
|
1548
|
-
store.saveRoleSessionSet(sessions);
|
|
1549
|
-
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1550
|
-
const session = sessions.sessions[input.agentId];
|
|
1551
|
-
const active = store.getActiveAgentRun(task.id, role.name);
|
|
1552
|
-
if (task.status !== "active"
|
|
1553
|
-
|| active === null
|
|
1554
|
-
|| active.pushedAt === undefined
|
|
1555
|
-
|| (input.expectedRunId !== undefined && active.id !== input.expectedRunId)) {
|
|
1556
|
-
return { session };
|
|
1557
|
-
}
|
|
1558
|
-
const summary = `Agent turn completed without a Yui workflow outcome. Last assistant message: ${input.summary}`;
|
|
1559
|
-
const result = terminalizeExactTaskRun(store, {
|
|
1560
|
-
taskId: task.id,
|
|
1561
|
-
roleName: role.name,
|
|
1562
|
-
agentId: active.effective.agentId,
|
|
1563
|
-
runId: active.id,
|
|
1564
|
-
receiptId: formatAgentRunReceiptId(task.id, active.id),
|
|
1565
|
-
nativeSessionId: input.nativeSessionId,
|
|
1566
|
-
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1567
|
-
outcome: { status: "failed", summary }
|
|
1568
|
-
}, now);
|
|
1569
|
-
if (result.disposition !== "applied" || result.run === null) {
|
|
1570
|
-
throw new Error(`Role Run changed during native Turn completion: ${active.id}/${result.reason}.`);
|
|
1571
|
-
}
|
|
1572
|
-
const terminal = result.run;
|
|
1573
|
-
const leaderWorkflowRun = role.name === "leader" && active.workItemId === undefined;
|
|
1574
|
-
if (active.purpose === "execution" && active.workItemId !== undefined) {
|
|
1575
|
-
const item = store.getWorkItem(task.id, active.workItemId);
|
|
1576
|
-
if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
|
|
1577
|
-
store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
store.saveRole(task.id, updateRoleStatus(role, leaderWorkflowRun ? "failed" : "idle", now));
|
|
1581
|
-
clearMatchingLeaderStallAttention(store, task.id, terminal.id);
|
|
1582
|
-
if (!leaderWorkflowRun) {
|
|
1583
|
-
enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
|
|
1584
|
-
{ type: "run", taskId: task.id, id: terminal.id },
|
|
1585
|
-
...(terminal.workItemId === undefined
|
|
1586
|
-
? []
|
|
1587
|
-
: [{
|
|
1588
|
-
type: "work-item",
|
|
1589
|
-
taskId: task.id,
|
|
1590
|
-
id: terminal.workItemId
|
|
1591
|
-
}])
|
|
1592
|
-
]);
|
|
1593
|
-
}
|
|
1594
|
-
else {
|
|
1595
|
-
store.saveLeaderFailure(recordLeaderFailure(task.id, input.nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
|
|
1596
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
|
|
1597
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
|
|
1598
|
-
{ type: "task", id: task.id },
|
|
1599
|
-
{ type: "run", taskId: task.id, id: terminal.id }
|
|
1600
|
-
]);
|
|
1601
|
-
}
|
|
1602
|
-
return { session, finalizedRunId: terminal.id };
|
|
1603
|
-
});
|
|
1604
|
-
}
|
|
1605
1769
|
classifyRuntimeTurnFailed(input) {
|
|
1606
1770
|
const task = this.store.getTask(input.taskId);
|
|
1607
1771
|
if (task?.status !== "active")
|
|
@@ -1736,24 +1900,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1736
1900
|
}
|
|
1737
1901
|
return null;
|
|
1738
1902
|
}
|
|
1739
|
-
// transport-uncertain: the Provider may have accepted the turn. If a
|
|
1740
|
-
// native completion is already durable, the completion path owns the
|
|
1741
|
-
// yield; do not resend.
|
|
1742
|
-
if (classification.errorClass === "transport-uncertain") {
|
|
1743
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1744
|
-
const pending = sessions?.pendingTurnCompletion;
|
|
1745
|
-
if (pending !== null
|
|
1746
|
-
&& pending !== undefined
|
|
1747
|
-
&& pending.runId === input.runId
|
|
1748
|
-
&& pending.agentId === input.agentId) {
|
|
1749
|
-
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
1750
|
-
wouldRetry: "false",
|
|
1751
|
-
shadow: "false",
|
|
1752
|
-
note: "native-turn-completion-durable"
|
|
1753
|
-
}, now);
|
|
1754
|
-
return { disposition: "applied", runId: input.runId };
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
1903
|
const run = store.getAgentRun(input.taskId, input.runId);
|
|
1758
1904
|
if (run === null || run.status !== "active")
|
|
1759
1905
|
return null;
|
|
@@ -1919,6 +2065,51 @@ export class FileSchedulerStoreAdapter {
|
|
|
1919
2065
|
* Applies already-normalized Session observations. Driver-specific startup
|
|
1920
2066
|
* names and source variants have terminated before this boundary.
|
|
1921
2067
|
*/
|
|
2068
|
+
observeProviderRuntimeIdentity(input, now) {
|
|
2069
|
+
return this.store.transaction((store) => {
|
|
2070
|
+
const taskId = input.fence.taskId;
|
|
2071
|
+
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
2072
|
+
const run = store.getAgentRun(taskId, input.fence.runId);
|
|
2073
|
+
if (sessions === null || sessions.providerBinding === null || run === null
|
|
2074
|
+
|| run.id !== sessions.inFlight?.runId
|
|
2075
|
+
|| input.fence.conversationId === undefined) {
|
|
2076
|
+
recordCanonicalObservationObsolete(store, input, "provider-binding-missing", now);
|
|
2077
|
+
return "obsolete";
|
|
2078
|
+
}
|
|
2079
|
+
let binding = sessions.providerBinding;
|
|
2080
|
+
if (input.fence.conversationId !== binding.conversations.find((entry) => (entry.epoch === binding.currentConversationEpoch))?.conversationId) {
|
|
2081
|
+
recordCanonicalObservationObsolete(store, input, "provider-conversation-mismatch", now);
|
|
2082
|
+
return "obsolete";
|
|
2083
|
+
}
|
|
2084
|
+
try {
|
|
2085
|
+
if (input.kind === "conversation.observed") {
|
|
2086
|
+
binding = updateProviderConversationRecoverability(binding, input.payload.recoverability);
|
|
2087
|
+
}
|
|
2088
|
+
else if (input.kind === "activation.started") {
|
|
2089
|
+
const active = binding.activations.find((entry) => entry.status === "active");
|
|
2090
|
+
if (active?.activationId !== input.fence.activationId) {
|
|
2091
|
+
binding = startProviderActivation(binding, {
|
|
2092
|
+
activationId: input.fence.activationId,
|
|
2093
|
+
startedAt: input.observedAt ?? input.receivedAt
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
else {
|
|
2098
|
+
binding = endProviderActivation(binding, input.fence.activationId, {
|
|
2099
|
+
status: input.kind === "activation.failed" ? "failed" : "ended",
|
|
2100
|
+
endedAt: input.observedAt ?? input.receivedAt,
|
|
2101
|
+
reason: input.kind
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
catch {
|
|
2106
|
+
recordCanonicalObservationObsolete(store, input, "provider-identity-conflict", now);
|
|
2107
|
+
return "obsolete";
|
|
2108
|
+
}
|
|
2109
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, binding, now));
|
|
2110
|
+
return "applied";
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
1922
2113
|
observeRuntimeSession(input, adapterId, now) {
|
|
1923
2114
|
return this.store.transaction((store) => {
|
|
1924
2115
|
const event = createCanonicalLifecycleEvent({
|
|
@@ -1968,7 +2159,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
1968
2159
|
status: "running",
|
|
1969
2160
|
effective: run.effective
|
|
1970
2161
|
}, now);
|
|
1971
|
-
|
|
2162
|
+
const withProvider = bound.providerBinding !== null || bound.inFlight === null
|
|
2163
|
+
? bound
|
|
2164
|
+
: bindTaskRoleProviderRuntime(bound, createProviderRuntimeBinding({
|
|
2165
|
+
providerNamespace: input.fence.driverId,
|
|
2166
|
+
accountScope: input.fence.agentId,
|
|
2167
|
+
runId: bound.inFlight.runId,
|
|
2168
|
+
conversationId: input.fence.conversationId ?? decision.outcome.nativeSessionId,
|
|
2169
|
+
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2170
|
+
startedAt: bound.inFlight.preparedAt
|
|
2171
|
+
}), now);
|
|
2172
|
+
store.saveTaskRoleSessionSet(withProvider);
|
|
1972
2173
|
const driver = this.drivers.require(input.fence.driverId);
|
|
1973
2174
|
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
1974
2175
|
&& run.pushedAt === undefined) {
|
|
@@ -1979,6 +2180,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
1979
2180
|
}
|
|
1980
2181
|
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.launchId);
|
|
1981
2182
|
}
|
|
2183
|
+
const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2184
|
+
const currentSession = current?.sessions[input.fence.agentId];
|
|
2185
|
+
if (current !== null && current !== undefined
|
|
2186
|
+
&& current.providerBinding === null
|
|
2187
|
+
&& current.inFlight !== null
|
|
2188
|
+
&& currentSession?.nativeSessionId === input.fence.nativeSessionId) {
|
|
2189
|
+
store.saveTaskRoleSessionSet(bindTaskRoleProviderRuntime(current, createProviderRuntimeBinding({
|
|
2190
|
+
providerNamespace: input.fence.driverId,
|
|
2191
|
+
accountScope: input.fence.agentId,
|
|
2192
|
+
runId: current.inFlight.runId,
|
|
2193
|
+
conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
|
|
2194
|
+
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2195
|
+
startedAt: current.inFlight.preparedAt
|
|
2196
|
+
}), now));
|
|
2197
|
+
}
|
|
1982
2198
|
return "applied";
|
|
1983
2199
|
}
|
|
1984
2200
|
}
|
|
@@ -1987,6 +2203,52 @@ export class FileSchedulerStoreAdapter {
|
|
|
1987
2203
|
/** Provider acceptance is canonical before storage and remains receipt-fenced. */
|
|
1988
2204
|
observeRuntimePromptAccepted(input, adapterId, now) {
|
|
1989
2205
|
return this.store.transaction((store) => {
|
|
2206
|
+
const roleTarget = {
|
|
2207
|
+
kind: "role",
|
|
2208
|
+
taskId: input.fence.taskId,
|
|
2209
|
+
roleName: input.fence.roleName
|
|
2210
|
+
};
|
|
2211
|
+
const mailbox = store.getWorkMailbox(roleTarget);
|
|
2212
|
+
const processing = mailbox?.processing;
|
|
2213
|
+
const inputDelivery = mailbox?.inputDelivery;
|
|
2214
|
+
const continuation = inputDelivery !== null
|
|
2215
|
+
&& inputDelivery !== undefined
|
|
2216
|
+
&& inputDelivery.attemptId === input.fence.receiptId
|
|
2217
|
+
&& inputDelivery.executionRef.type === "run"
|
|
2218
|
+
&& inputDelivery.executionRef.taskId === input.fence.taskId
|
|
2219
|
+
&& inputDelivery.executionRef.id === input.fence.runId
|
|
2220
|
+
&& inputDelivery.attemptId !== formatAgentRunReceiptId(input.fence.taskId, input.fence.runId);
|
|
2221
|
+
if (continuation) {
|
|
2222
|
+
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
2223
|
+
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2224
|
+
const session = sessions?.sessions[input.fence.agentId];
|
|
2225
|
+
if (active === null
|
|
2226
|
+
|| active.id !== input.fence.runId
|
|
2227
|
+
|| active.status !== "active"
|
|
2228
|
+
|| active.pushedAt === undefined
|
|
2229
|
+
|| sessions?.inFlight?.runId !== active.id
|
|
2230
|
+
|| session?.launchId !== input.fence.launchId
|
|
2231
|
+
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2232
|
+
recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
|
|
2233
|
+
return "obsolete";
|
|
2234
|
+
}
|
|
2235
|
+
store.saveTaskRoleSessionSet(updateRoleAgentSessionStatus(sessions, input.fence.agentId, "running", now));
|
|
2236
|
+
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
2237
|
+
if (role !== null && role.status !== "running") {
|
|
2238
|
+
store.saveRole(input.fence.taskId, updateRoleStatus(role, "running", now));
|
|
2239
|
+
}
|
|
2240
|
+
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.input-delivered", {
|
|
2241
|
+
attemptId: inputDelivery.attemptId,
|
|
2242
|
+
runId: active.id,
|
|
2243
|
+
conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
|
|
2244
|
+
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2245
|
+
...(input.fence.nativeTurnId === undefined
|
|
2246
|
+
? {}
|
|
2247
|
+
: { nativeTurnId: input.fence.nativeTurnId })
|
|
2248
|
+
}, now));
|
|
2249
|
+
store.saveWorkMailbox(completeMailboxInputDelivery(mailbox, inputDelivery.attemptId, now));
|
|
2250
|
+
return "applied";
|
|
2251
|
+
}
|
|
1990
2252
|
const event = createCanonicalLifecycleEvent({
|
|
1991
2253
|
phase: "provider-accepted",
|
|
1992
2254
|
source: "provider-native",
|
|
@@ -2020,6 +2282,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
2020
2282
|
markTaskRoleRunDeliveredInFlight(store, role, delivered, now);
|
|
2021
2283
|
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
|
|
2022
2284
|
}
|
|
2285
|
+
if (processing !== null && processing !== undefined
|
|
2286
|
+
&& processing.batchId === input.fence.receiptId
|
|
2287
|
+
&& processing.executionRef?.type === "run"
|
|
2288
|
+
&& processing.executionRef.taskId === input.fence.taskId
|
|
2289
|
+
&& processing.executionRef.id === input.fence.runId) {
|
|
2290
|
+
store.saveWorkMailbox(completeProcessing(mailbox, processing.batchId));
|
|
2291
|
+
}
|
|
2023
2292
|
return "applied";
|
|
2024
2293
|
});
|
|
2025
2294
|
}
|
|
@@ -2214,12 +2483,33 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2214
2483
|
...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
|
|
2215
2484
|
};
|
|
2216
2485
|
}
|
|
2486
|
+
/** Accepts the original Run receipt or a later mailbox activation receipt. */
|
|
2487
|
+
function runtimeReceiptBelongsToRun(store, input) {
|
|
2488
|
+
const taskId = input.fence.taskId;
|
|
2489
|
+
const runId = input.fence.runId;
|
|
2490
|
+
const receiptId = input.fence.receiptId;
|
|
2491
|
+
if (receiptId === formatAgentRunReceiptId(taskId, runId))
|
|
2492
|
+
return true;
|
|
2493
|
+
if (receiptId === undefined)
|
|
2494
|
+
return false;
|
|
2495
|
+
return store.listEvents(taskId).some((event) => {
|
|
2496
|
+
const accepted = runtimeObservationFromTaskEvent(event);
|
|
2497
|
+
return accepted?.kind === "turn.accepted"
|
|
2498
|
+
&& accepted.fence.runId === runId
|
|
2499
|
+
&& accepted.fence.roleName === input.fence.roleName
|
|
2500
|
+
&& accepted.fence.agentId === input.fence.agentId
|
|
2501
|
+
&& accepted.fence.launchId === input.fence.launchId
|
|
2502
|
+
&& accepted.fence.nativeSessionId === input.fence.nativeSessionId
|
|
2503
|
+
&& accepted.fence.receiptId === receiptId;
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2217
2506
|
function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
2218
2507
|
const taskId = input.fence.taskId;
|
|
2219
2508
|
if (taskId === undefined)
|
|
2220
2509
|
return;
|
|
2221
2510
|
recordObsoleteRuntimeEvent(store, {
|
|
2222
2511
|
eventId: input.eventId,
|
|
2512
|
+
dedupeKey: input.semanticKey,
|
|
2223
2513
|
eventType: input.kind,
|
|
2224
2514
|
taskId,
|
|
2225
2515
|
roleName: input.fence.roleName,
|
|
@@ -2231,10 +2521,11 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
|
2231
2521
|
}
|
|
2232
2522
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
2233
2523
|
if (store.listEvents(input.taskId).some((event) => (event.type === "runtime.event-obsolete"
|
|
2234
|
-
&& event.payload.eventId === input.eventId)))
|
|
2524
|
+
&& (event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
|
|
2235
2525
|
return;
|
|
2236
2526
|
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.event-obsolete", {
|
|
2237
2527
|
eventId: input.eventId,
|
|
2528
|
+
...(input.dedupeKey === undefined ? {} : { dedupeKey: input.dedupeKey }),
|
|
2238
2529
|
eventType: input.eventType ?? input.type ?? "unknown",
|
|
2239
2530
|
roleName: input.roleName,
|
|
2240
2531
|
agentId: input.agentId,
|
|
@@ -2341,7 +2632,7 @@ function completeRuntimeHookReservation(store, owner, launchId) {
|
|
|
2341
2632
|
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, launchId));
|
|
2342
2633
|
}
|
|
2343
2634
|
function saveRuntimeLifecycleMailbox(store, mailbox) {
|
|
2344
|
-
if (mailbox
|
|
2635
|
+
if (!mailboxHasWork(mailbox)) {
|
|
2345
2636
|
store.removeWorkMailbox(mailbox.target);
|
|
2346
2637
|
return;
|
|
2347
2638
|
}
|
|
@@ -2439,8 +2730,7 @@ function preparedDeliveryFailureSessionFenceMatches(sessions, session, input) {
|
|
|
2439
2730
|
return false;
|
|
2440
2731
|
if (sessions.inFlight === null) {
|
|
2441
2732
|
return input.nativeSessionId === undefined
|
|
2442
|
-
&& session === undefined
|
|
2443
|
-
&& sessions.pendingTurnCompletion === null;
|
|
2733
|
+
&& session === undefined;
|
|
2444
2734
|
}
|
|
2445
2735
|
return sessions.inFlight.agentId === input.agentId
|
|
2446
2736
|
&& sessions.inFlight.runId === input.runId
|
|
@@ -2675,7 +2965,7 @@ function bindTaskRoleRunInFlight(store, role, run, now) {
|
|
|
2675
2965
|
let current = store.getRoleSessionSet(role.taskId, role.name)
|
|
2676
2966
|
?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, agentId, now);
|
|
2677
2967
|
if (current.activeAgentId !== agentId) {
|
|
2678
|
-
if (current.inFlight !== null || current.
|
|
2968
|
+
if (current.inFlight !== null || current.providerBinding !== null) {
|
|
2679
2969
|
throw new Error("Task Role Session identity cannot change with an unsettled Run fence.");
|
|
2680
2970
|
}
|
|
2681
2971
|
const liveConflict = Object.values(current.sessions).find((session) => (session.agentId !== agentId
|
|
@@ -2895,8 +3185,9 @@ function confirmedUsageActivityObservation(events, incoming) {
|
|
|
2895
3185
|
.update(`confirmed-runtime-activity\0${incoming.eventId}`)
|
|
2896
3186
|
.digest("hex");
|
|
2897
3187
|
return createRuntimeObservation({
|
|
2898
|
-
schemaVersion:
|
|
3188
|
+
schemaVersion: 2,
|
|
2899
3189
|
eventId: `runtime-activity-${digest}`,
|
|
3190
|
+
semanticKey: `runtime-activity-${digest}`,
|
|
2900
3191
|
kind: "activity.observed",
|
|
2901
3192
|
authority: "controller",
|
|
2902
3193
|
receivedAt: incoming.receivedAt,
|