@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
|
@@ -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,15 +19,18 @@ 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";
|
|
28
31
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
29
|
-
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
32
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
33
|
+
import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
30
34
|
/** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
|
|
31
35
|
export class FileSchedulerStoreAdapter {
|
|
32
36
|
store;
|
|
@@ -56,6 +60,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
56
60
|
if (adapterId === null) {
|
|
57
61
|
return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
|
|
58
62
|
}
|
|
63
|
+
if (this.store.listEvents(taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
64
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
65
|
+
return "applied";
|
|
59
66
|
let outcome;
|
|
60
67
|
switch (input.kind) {
|
|
61
68
|
case "session.started":
|
|
@@ -63,6 +70,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
63
70
|
outcome = this.observeRuntimeSession(input, adapterId, now);
|
|
64
71
|
break;
|
|
65
72
|
case "turn.accepted":
|
|
73
|
+
case "input.accepted":
|
|
66
74
|
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
67
75
|
break;
|
|
68
76
|
case "turn.completed": {
|
|
@@ -80,12 +88,46 @@ export class FileSchedulerStoreAdapter {
|
|
|
80
88
|
const classification = this.classifyRuntimeTurnCompleted(completed);
|
|
81
89
|
if (classification !== "apply")
|
|
82
90
|
return classification;
|
|
91
|
+
if (this.runtimeTurnHasActiveNativeSubagents(input)) {
|
|
92
|
+
// This is an intermediate provider Turn boundary, not the end of the
|
|
93
|
+
// durable Yui Run. The provider will deliver child completion
|
|
94
|
+
// notifications as later native Turns in the same Run generation.
|
|
95
|
+
outcome = this.validateCanonicalRunObservation(input, now);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
83
98
|
const result = this.observeRuntimeTurnCompleted(completed, now);
|
|
84
99
|
outcome = result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
85
100
|
break;
|
|
86
101
|
}
|
|
87
102
|
case "turn.failed": {
|
|
88
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
|
+
}
|
|
89
131
|
const failure = {
|
|
90
132
|
eventId: input.eventId,
|
|
91
133
|
eventType: input.kind,
|
|
@@ -118,8 +160,19 @@ export class FileSchedulerStoreAdapter {
|
|
|
118
160
|
case "turn.cancelled":
|
|
119
161
|
case "activity.observed":
|
|
120
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":
|
|
121
168
|
outcome = this.validateCanonicalRunObservation(input, now);
|
|
122
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;
|
|
123
176
|
case "session.ended":
|
|
124
177
|
case "session.failed":
|
|
125
178
|
outcome = this.validateCanonicalSessionObservation(input, now);
|
|
@@ -156,22 +209,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
156
209
|
validateCanonicalRunObservation(input, now) {
|
|
157
210
|
return this.store.transaction((store) => {
|
|
158
211
|
if (store.listEvents(input.fence.taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
159
|
-
&& event.payload.
|
|
212
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
160
213
|
return "applied";
|
|
161
214
|
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
162
215
|
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
163
216
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
164
217
|
const session = sessions?.sessions[input.fence.agentId];
|
|
165
|
-
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;
|
|
166
227
|
const valid = run !== null
|
|
167
|
-
&& (!
|
|
228
|
+
&& (!requiresCurrentRuntime || (run.status === "active" && active?.id === run.id))
|
|
168
229
|
&& run.roleName === input.fence.roleName
|
|
169
230
|
&& run.effective.agentId === input.fence.agentId
|
|
170
231
|
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
171
|
-
&& session?.launchId === input.fence.launchId
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
&&
|
|
232
|
+
&& (knownContinuation || (session?.launchId === input.fence.launchId
|
|
233
|
+
&& session.nativeSessionId === input.fence.nativeSessionId
|
|
234
|
+
&& input.fence.sessionGenerationId === input.fence.launchId))
|
|
235
|
+
&& runtimeReceiptBelongsToRun(store, input)
|
|
175
236
|
&& (run.deliveredAt !== undefined || input.kind === "turn.cancelled");
|
|
176
237
|
if (!valid) {
|
|
177
238
|
recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
|
|
@@ -180,6 +241,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
180
241
|
return "applied";
|
|
181
242
|
});
|
|
182
243
|
}
|
|
244
|
+
runtimeTurnHasActiveNativeSubagents(input) {
|
|
245
|
+
const taskId = input.fence.taskId;
|
|
246
|
+
const run = this.store.getAgentRun(taskId, input.fence.runId);
|
|
247
|
+
if (run === null)
|
|
248
|
+
return false;
|
|
249
|
+
const projection = projectRuntimeTaskEvents(input.fence, run.createdAt, this.store.listEvents(taskId));
|
|
250
|
+
return Object.values(projection.operations).some(({ kind }) => kind === "subagent");
|
|
251
|
+
}
|
|
183
252
|
validateCanonicalSessionObservation(input, now) {
|
|
184
253
|
return this.store.transaction((store) => {
|
|
185
254
|
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
@@ -189,7 +258,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
189
258
|
|| run.roleName !== input.fence.roleName
|
|
190
259
|
|| run.effective.agentId !== input.fence.agentId
|
|
191
260
|
|| this.drivers.requireByAdapterId(run.effective.adapterId).id !== input.fence.driverId
|
|
192
|
-
|| input.fence.receiptId !== formatAgentRunReceiptId(input.fence.taskId, run.id)
|
|
193
261
|
|| session?.launchId !== input.fence.launchId
|
|
194
262
|
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
195
263
|
|| input.fence.sessionGenerationId !== input.fence.launchId) {
|
|
@@ -197,7 +265,101 @@ export class FileSchedulerStoreAdapter {
|
|
|
197
265
|
return "obsolete";
|
|
198
266
|
}
|
|
199
267
|
const status = input.kind === "session.failed" ? "broken" : "stopped";
|
|
200
|
-
|
|
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
|
+
}
|
|
201
363
|
return "applied";
|
|
202
364
|
});
|
|
203
365
|
}
|
|
@@ -206,7 +368,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
206
368
|
const taskId = input.fence.taskId;
|
|
207
369
|
const events = store.listEvents(taskId);
|
|
208
370
|
if (events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
209
|
-
&& event.payload.
|
|
371
|
+
&& event.payload.semanticKey === input.semanticKey)))
|
|
210
372
|
return;
|
|
211
373
|
if (usageSnapshotIsSuperseded(events, input))
|
|
212
374
|
return;
|
|
@@ -219,7 +381,90 @@ export class FileSchedulerStoreAdapter {
|
|
|
219
381
|
];
|
|
220
382
|
if (removable.length > 0)
|
|
221
383
|
store.removeEvents(taskId, removable);
|
|
222
|
-
|
|
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
|
+
}
|
|
223
468
|
if (confirmedActivity !== null) {
|
|
224
469
|
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(confirmedActivity), now));
|
|
225
470
|
}
|
|
@@ -497,6 +742,42 @@ export class FileSchedulerStoreAdapter {
|
|
|
497
742
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
498
743
|
return sessions !== null && sessions.inFlight !== null;
|
|
499
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
|
+
}
|
|
500
781
|
isRoleGenerationProviderReady(input) {
|
|
501
782
|
// A session.ready observation for this exact generation is the durable
|
|
502
783
|
// pre-input fence. This reads folded Driver truth
|
|
@@ -568,7 +849,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
568
849
|
claimWorkMailbox(input) {
|
|
569
850
|
return this.store.transaction((store) => {
|
|
570
851
|
const mailbox = store.getWorkMailbox(input.target);
|
|
571
|
-
if (mailbox === null || (mailbox
|
|
852
|
+
if (mailbox === null || !mailboxHasWork(mailbox)) {
|
|
572
853
|
return { status: "empty" };
|
|
573
854
|
}
|
|
574
855
|
if (mailbox.processing !== null) {
|
|
@@ -586,6 +867,104 @@ export class FileSchedulerStoreAdapter {
|
|
|
586
867
|
return { status: "claimed", processing: claimed.processing };
|
|
587
868
|
});
|
|
588
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
|
+
}
|
|
589
968
|
completeWorkMailbox(target, batchId) {
|
|
590
969
|
return this.store.transaction((store) => {
|
|
591
970
|
const mailbox = store.getWorkMailbox(target);
|
|
@@ -623,12 +1002,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
623
1002
|
}
|
|
624
1003
|
mailbox = completeProcessing(mailbox, mailbox.processing.batchId);
|
|
625
1004
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
1005
|
+
const pending = pendingLane(mailbox, "normal");
|
|
1006
|
+
if (pending !== null) {
|
|
1007
|
+
if (pending.reasons.length !== 1
|
|
1008
|
+
|| pending.reasons[0] !== RUNTIME_CLEANUP_REQUIRED_REASON) {
|
|
629
1009
|
return false;
|
|
630
1010
|
}
|
|
631
|
-
const batchId = `runtime-cleanup-complete:${
|
|
1011
|
+
const batchId = `runtime-cleanup-complete:${pending.fromSequence}-${pending.toSequence}`;
|
|
632
1012
|
mailbox = completeProcessing(claimPending(mailbox, {
|
|
633
1013
|
batchId,
|
|
634
1014
|
owner: RUNTIME_LIFECYCLE_OWNER,
|
|
@@ -729,7 +1109,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
729
1109
|
}
|
|
730
1110
|
const target = { kind: "role", taskId: input.task.id, roleName: input.role.name };
|
|
731
1111
|
const mailbox = store.getWorkMailbox(target);
|
|
732
|
-
if (mailbox === null || mailbox
|
|
1112
|
+
if (mailbox === null || !mailboxHasPending(mailbox))
|
|
733
1113
|
return "state-changed";
|
|
734
1114
|
if (mailbox.processing !== null)
|
|
735
1115
|
return "busy";
|
|
@@ -1249,8 +1629,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1249
1629
|
});
|
|
1250
1630
|
}
|
|
1251
1631
|
/**
|
|
1252
|
-
* Fast hook path:
|
|
1253
|
-
*
|
|
1632
|
+
* Fast hook path: validates the native Turn boundary before it is either
|
|
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.
|
|
1254
1635
|
*/
|
|
1255
1636
|
classifyRuntimeTurnCompleted(input) {
|
|
1256
1637
|
const task = this.store.getTask(input.taskId);
|
|
@@ -1277,11 +1658,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1277
1658
|
catch {
|
|
1278
1659
|
return "obsolete";
|
|
1279
1660
|
}
|
|
1280
|
-
if (sessions?.pendingTurnCompletion !== null && sessions !== null) {
|
|
1281
|
-
return sessions.pendingTurnCompletion.turnId === input.turnId
|
|
1282
|
-
? "apply"
|
|
1283
|
-
: "deferred";
|
|
1284
|
-
}
|
|
1285
1661
|
// A normal explicit CLI yield may precede its native Hook; that Hook still
|
|
1286
1662
|
// owns the turn fact. A forced cleanup boundary or stopped process instead
|
|
1287
1663
|
// makes the old generation obsolete.
|
|
@@ -1333,19 +1709,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1333
1709
|
&& hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, input.turnId)) {
|
|
1334
1710
|
return { session: effectiveExisting, duplicate: true };
|
|
1335
1711
|
}
|
|
1336
|
-
const pending = sessions.pendingTurnCompletion;
|
|
1337
|
-
if (effectiveExisting !== undefined
|
|
1338
|
-
&& pending !== null
|
|
1339
|
-
&& pending.agentId === input.agentId
|
|
1340
|
-
&& pending.nativeSessionId === input.nativeSessionId
|
|
1341
|
-
&& pending.turnId === input.turnId
|
|
1342
|
-
&& pending.runId === input.runId) {
|
|
1343
|
-
return {
|
|
1344
|
-
session: effectiveExisting,
|
|
1345
|
-
duplicate: true,
|
|
1346
|
-
pendingRunId: pending.runId
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
1712
|
if (sessions.inFlight === null
|
|
1350
1713
|
&& existing !== undefined
|
|
1351
1714
|
&& (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))
|
|
@@ -1386,205 +1749,23 @@ export class FileSchedulerStoreAdapter {
|
|
|
1386
1749
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1387
1750
|
return { session: sessions.sessions[input.agentId], duplicate: false };
|
|
1388
1751
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
const completion = createPendingTurnCompletion({
|
|
1395
|
-
taskId: task.id,
|
|
1396
|
-
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, {
|
|
1397
1757
|
agentId: input.agentId,
|
|
1398
1758
|
nativeSessionId: input.nativeSessionId,
|
|
1399
|
-
turnId: input.turnId
|
|
1400
|
-
|
|
1401
|
-
summary: input.summary,
|
|
1402
|
-
observedAt: now,
|
|
1403
|
-
dueAt: new Date(now.getTime() + 2_000)
|
|
1404
|
-
});
|
|
1405
|
-
// Runtime completion is not workflow progress. The exact Run remains
|
|
1406
|
-
// open for the short outcome grace period so a concurrently committed
|
|
1407
|
-
// yui yield/block/complete action can win; otherwise the contract fails.
|
|
1408
|
-
sessions = recordObservedTaskRoleCompletion(sessions, completion);
|
|
1409
|
-
const active = store.getActiveAgentRun(task.id, role.name);
|
|
1410
|
-
if (active === null || active.id !== fence.runId || active.status !== "active") {
|
|
1411
|
-
sessions = settleTaskRoleCompletion(sessions, {
|
|
1412
|
-
agentId: input.agentId,
|
|
1413
|
-
runId: fence.runId,
|
|
1414
|
-
turnId: input.turnId
|
|
1415
|
-
}, now);
|
|
1416
|
-
const settledStatus = sessions.sessions[input.agentId]?.status;
|
|
1417
|
-
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1418
|
-
sessions = updateRoleAgentSessionStatus(sessions, input.agentId, "ready", now);
|
|
1419
|
-
}
|
|
1420
|
-
store.saveTaskRoleSessionSet(sessions);
|
|
1421
|
-
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1422
|
-
return { session: sessions.sessions[input.agentId], duplicate: false };
|
|
1423
|
-
}
|
|
1759
|
+
turnId: input.turnId
|
|
1760
|
+
}, now);
|
|
1424
1761
|
store.saveTaskRoleSessionSet(sessions);
|
|
1425
1762
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1426
1763
|
return {
|
|
1427
1764
|
session: sessions.sessions[input.agentId],
|
|
1428
|
-
duplicate: false
|
|
1429
|
-
pendingRunId: fence.runId
|
|
1765
|
+
duplicate: false
|
|
1430
1766
|
};
|
|
1431
1767
|
});
|
|
1432
1768
|
}
|
|
1433
|
-
listPendingRuntimeTurnCompletions(taskIds) {
|
|
1434
|
-
return this.store.listPendingRuntimeTurnCompletions(taskIds);
|
|
1435
|
-
}
|
|
1436
|
-
/** Resolves only completions whose grace deadline has elapsed. */
|
|
1437
|
-
resolveDueRuntimeTurnCompletions(now, taskIds) {
|
|
1438
|
-
const selectedTaskIds = taskIds === undefined
|
|
1439
|
-
? undefined
|
|
1440
|
-
: [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
1441
|
-
const due = this.listPendingRuntimeTurnCompletions(selectedTaskIds).filter((completion) => Date.parse(completion.dueAt) <= now.getTime());
|
|
1442
|
-
const finalized = [];
|
|
1443
|
-
for (const completion of due) {
|
|
1444
|
-
this.store.transaction((store) => {
|
|
1445
|
-
const current = store.getTaskRoleSessionSet(completion.taskId, completion.roleName);
|
|
1446
|
-
if (current === null)
|
|
1447
|
-
return;
|
|
1448
|
-
const pending = current.pendingTurnCompletion;
|
|
1449
|
-
if (pending === null
|
|
1450
|
-
|| pending.turnId !== completion.turnId
|
|
1451
|
-
|| pending.runId !== completion.runId)
|
|
1452
|
-
return;
|
|
1453
|
-
const active = store.getActiveAgentRun(completion.taskId, completion.roleName);
|
|
1454
|
-
if (active?.id === completion.runId) {
|
|
1455
|
-
const result = this.recordRuntimeTurnCompleted({
|
|
1456
|
-
taskId: completion.taskId,
|
|
1457
|
-
roleName: completion.roleName,
|
|
1458
|
-
agentId: completion.agentId,
|
|
1459
|
-
adapterId: current.sessions[completion.agentId].adapterId,
|
|
1460
|
-
nativeSessionId: completion.nativeSessionId,
|
|
1461
|
-
turnId: completion.turnId,
|
|
1462
|
-
expectedRunId: completion.runId,
|
|
1463
|
-
summary: completion.summary
|
|
1464
|
-
}, now);
|
|
1465
|
-
if (result.finalizedRunId !== undefined) {
|
|
1466
|
-
finalized.push(formatTaskRecordReference(completion.taskId, result.finalizedRunId, "agentRun"));
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
let settled = store.getTaskRoleSessionSet(completion.taskId, completion.roleName);
|
|
1470
|
-
if (settled?.pendingTurnCompletion?.turnId !== completion.turnId)
|
|
1471
|
-
return;
|
|
1472
|
-
settled = settleTaskRoleCompletion(settled, {
|
|
1473
|
-
agentId: completion.agentId,
|
|
1474
|
-
runId: completion.runId,
|
|
1475
|
-
turnId: completion.turnId
|
|
1476
|
-
}, now);
|
|
1477
|
-
const settledStatus = settled.sessions[completion.agentId]?.status;
|
|
1478
|
-
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1479
|
-
settled = updateRoleAgentSessionStatus(settled, completion.agentId, "ready", now);
|
|
1480
|
-
}
|
|
1481
|
-
store.saveTaskRoleSessionSet(settled);
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
return finalized;
|
|
1485
|
-
}
|
|
1486
|
-
/** A structured native Turn-completion event terminalizes its exact Run fence. */
|
|
1487
|
-
recordRuntimeTurnCompleted(input, now = new Date()) {
|
|
1488
|
-
return this.store.transaction((store) => {
|
|
1489
|
-
const task = store.getTask(input.taskId);
|
|
1490
|
-
if (task === null)
|
|
1491
|
-
throw new Error(`Task not found: ${input.taskId}.`);
|
|
1492
|
-
if (task.status !== "active") {
|
|
1493
|
-
throw new Error(`Cannot complete a runtime turn for inactive Task: ${input.taskId}.`);
|
|
1494
|
-
}
|
|
1495
|
-
const role = requireRole(store, input.taskId, input.roleName);
|
|
1496
|
-
const current = store.getRoleSessionSet(input.taskId, input.roleName)
|
|
1497
|
-
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
1498
|
-
const existing = current.sessions[input.agentId];
|
|
1499
|
-
const owner = {
|
|
1500
|
-
scope: "task",
|
|
1501
|
-
taskId: input.taskId,
|
|
1502
|
-
roleName: input.roleName
|
|
1503
|
-
};
|
|
1504
|
-
if (existing === undefined
|
|
1505
|
-
&& !runtimeHookMatchesReservation(store, owner, input.launchId)) {
|
|
1506
|
-
throw new Error("Runtime turn completion does not match the launch reservation.");
|
|
1507
|
-
}
|
|
1508
|
-
const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed Role session.");
|
|
1509
|
-
const effective = taskSessionEffective(store, task.id, role.name, input.agentId, effectiveExisting);
|
|
1510
|
-
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
1511
|
-
throw new Error("Runtime turn completion does not match the effective launch identity.");
|
|
1512
|
-
}
|
|
1513
|
-
const sessions = effectiveExisting?.status === "ready"
|
|
1514
|
-
|| effectiveExisting?.status === "stopped"
|
|
1515
|
-
|| effectiveExisting?.status === "broken"
|
|
1516
|
-
? current
|
|
1517
|
-
: recordRoleAgentSession(current, {
|
|
1518
|
-
agentId: input.agentId,
|
|
1519
|
-
adapterId: input.adapterId,
|
|
1520
|
-
nativeSessionId: input.nativeSessionId,
|
|
1521
|
-
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1522
|
-
policy: "fixed",
|
|
1523
|
-
status: "ready",
|
|
1524
|
-
effective
|
|
1525
|
-
}, now);
|
|
1526
|
-
if (input.expectedRunId !== undefined
|
|
1527
|
-
&& store.getActiveAgentRun(task.id, role.name)?.id !== input.expectedRunId) {
|
|
1528
|
-
return { session: sessions.sessions[input.agentId] };
|
|
1529
|
-
}
|
|
1530
|
-
if (sessions !== current)
|
|
1531
|
-
store.saveRoleSessionSet(sessions);
|
|
1532
|
-
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1533
|
-
const session = sessions.sessions[input.agentId];
|
|
1534
|
-
const active = store.getActiveAgentRun(task.id, role.name);
|
|
1535
|
-
if (task.status !== "active"
|
|
1536
|
-
|| active === null
|
|
1537
|
-
|| active.pushedAt === undefined
|
|
1538
|
-
|| (input.expectedRunId !== undefined && active.id !== input.expectedRunId)) {
|
|
1539
|
-
return { session };
|
|
1540
|
-
}
|
|
1541
|
-
const summary = `Agent turn completed without a Yui workflow outcome. Last assistant message: ${input.summary}`;
|
|
1542
|
-
const result = terminalizeExactTaskRun(store, {
|
|
1543
|
-
taskId: task.id,
|
|
1544
|
-
roleName: role.name,
|
|
1545
|
-
agentId: active.effective.agentId,
|
|
1546
|
-
runId: active.id,
|
|
1547
|
-
receiptId: formatAgentRunReceiptId(task.id, active.id),
|
|
1548
|
-
nativeSessionId: input.nativeSessionId,
|
|
1549
|
-
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1550
|
-
outcome: { status: "failed", summary }
|
|
1551
|
-
}, now);
|
|
1552
|
-
if (result.disposition !== "applied" || result.run === null) {
|
|
1553
|
-
throw new Error(`Role Run changed during native Turn completion: ${active.id}/${result.reason}.`);
|
|
1554
|
-
}
|
|
1555
|
-
const terminal = result.run;
|
|
1556
|
-
const leaderWorkflowRun = role.name === "leader" && active.workItemId === undefined;
|
|
1557
|
-
if (active.purpose === "execution" && active.workItemId !== undefined) {
|
|
1558
|
-
const item = store.getWorkItem(task.id, active.workItemId);
|
|
1559
|
-
if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
|
|
1560
|
-
store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
store.saveRole(task.id, updateRoleStatus(role, leaderWorkflowRun ? "failed" : "idle", now));
|
|
1564
|
-
clearMatchingLeaderStallAttention(store, task.id, terminal.id);
|
|
1565
|
-
if (!leaderWorkflowRun) {
|
|
1566
|
-
enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
|
|
1567
|
-
{ type: "run", taskId: task.id, id: terminal.id },
|
|
1568
|
-
...(terminal.workItemId === undefined
|
|
1569
|
-
? []
|
|
1570
|
-
: [{
|
|
1571
|
-
type: "work-item",
|
|
1572
|
-
taskId: task.id,
|
|
1573
|
-
id: terminal.workItemId
|
|
1574
|
-
}])
|
|
1575
|
-
]);
|
|
1576
|
-
}
|
|
1577
|
-
else {
|
|
1578
|
-
store.saveLeaderFailure(recordLeaderFailure(task.id, input.nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
|
|
1579
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
|
|
1580
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
|
|
1581
|
-
{ type: "task", id: task.id },
|
|
1582
|
-
{ type: "run", taskId: task.id, id: terminal.id }
|
|
1583
|
-
]);
|
|
1584
|
-
}
|
|
1585
|
-
return { session, finalizedRunId: terminal.id };
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
1769
|
classifyRuntimeTurnFailed(input) {
|
|
1589
1770
|
const task = this.store.getTask(input.taskId);
|
|
1590
1771
|
if (task?.status !== "active")
|
|
@@ -1719,24 +1900,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1719
1900
|
}
|
|
1720
1901
|
return null;
|
|
1721
1902
|
}
|
|
1722
|
-
// transport-uncertain: the Provider may have accepted the turn. If a
|
|
1723
|
-
// native completion is already durable, the completion path owns the
|
|
1724
|
-
// yield; do not resend.
|
|
1725
|
-
if (classification.errorClass === "transport-uncertain") {
|
|
1726
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1727
|
-
const pending = sessions?.pendingTurnCompletion;
|
|
1728
|
-
if (pending !== null
|
|
1729
|
-
&& pending !== undefined
|
|
1730
|
-
&& pending.runId === input.runId
|
|
1731
|
-
&& pending.agentId === input.agentId) {
|
|
1732
|
-
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
1733
|
-
wouldRetry: "false",
|
|
1734
|
-
shadow: "false",
|
|
1735
|
-
note: "native-turn-completion-durable"
|
|
1736
|
-
}, now);
|
|
1737
|
-
return { disposition: "applied", runId: input.runId };
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
1903
|
const run = store.getAgentRun(input.taskId, input.runId);
|
|
1741
1904
|
if (run === null || run.status !== "active")
|
|
1742
1905
|
return null;
|
|
@@ -1902,6 +2065,51 @@ export class FileSchedulerStoreAdapter {
|
|
|
1902
2065
|
* Applies already-normalized Session observations. Driver-specific startup
|
|
1903
2066
|
* names and source variants have terminated before this boundary.
|
|
1904
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
|
+
}
|
|
1905
2113
|
observeRuntimeSession(input, adapterId, now) {
|
|
1906
2114
|
return this.store.transaction((store) => {
|
|
1907
2115
|
const event = createCanonicalLifecycleEvent({
|
|
@@ -1951,7 +2159,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
1951
2159
|
status: "running",
|
|
1952
2160
|
effective: run.effective
|
|
1953
2161
|
}, now);
|
|
1954
|
-
|
|
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);
|
|
1955
2173
|
const driver = this.drivers.require(input.fence.driverId);
|
|
1956
2174
|
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
1957
2175
|
&& run.pushedAt === undefined) {
|
|
@@ -1962,6 +2180,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
1962
2180
|
}
|
|
1963
2181
|
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.launchId);
|
|
1964
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
|
+
}
|
|
1965
2198
|
return "applied";
|
|
1966
2199
|
}
|
|
1967
2200
|
}
|
|
@@ -1970,6 +2203,52 @@ export class FileSchedulerStoreAdapter {
|
|
|
1970
2203
|
/** Provider acceptance is canonical before storage and remains receipt-fenced. */
|
|
1971
2204
|
observeRuntimePromptAccepted(input, adapterId, now) {
|
|
1972
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
|
+
}
|
|
1973
2252
|
const event = createCanonicalLifecycleEvent({
|
|
1974
2253
|
phase: "provider-accepted",
|
|
1975
2254
|
source: "provider-native",
|
|
@@ -2003,6 +2282,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
2003
2282
|
markTaskRoleRunDeliveredInFlight(store, role, delivered, now);
|
|
2004
2283
|
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
|
|
2005
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
|
+
}
|
|
2006
2292
|
return "applied";
|
|
2007
2293
|
});
|
|
2008
2294
|
}
|
|
@@ -2197,12 +2483,33 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2197
2483
|
...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
|
|
2198
2484
|
};
|
|
2199
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
|
+
}
|
|
2200
2506
|
function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
2201
2507
|
const taskId = input.fence.taskId;
|
|
2202
2508
|
if (taskId === undefined)
|
|
2203
2509
|
return;
|
|
2204
2510
|
recordObsoleteRuntimeEvent(store, {
|
|
2205
2511
|
eventId: input.eventId,
|
|
2512
|
+
dedupeKey: input.semanticKey,
|
|
2206
2513
|
eventType: input.kind,
|
|
2207
2514
|
taskId,
|
|
2208
2515
|
roleName: input.fence.roleName,
|
|
@@ -2214,10 +2521,11 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
|
2214
2521
|
}
|
|
2215
2522
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
2216
2523
|
if (store.listEvents(input.taskId).some((event) => (event.type === "runtime.event-obsolete"
|
|
2217
|
-
&& event.payload.eventId === input.eventId)))
|
|
2524
|
+
&& (event.payload.dedupeKey ?? event.payload.eventId) === (input.dedupeKey ?? input.eventId))))
|
|
2218
2525
|
return;
|
|
2219
2526
|
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.event-obsolete", {
|
|
2220
2527
|
eventId: input.eventId,
|
|
2528
|
+
...(input.dedupeKey === undefined ? {} : { dedupeKey: input.dedupeKey }),
|
|
2221
2529
|
eventType: input.eventType ?? input.type ?? "unknown",
|
|
2222
2530
|
roleName: input.roleName,
|
|
2223
2531
|
agentId: input.agentId,
|
|
@@ -2324,7 +2632,7 @@ function completeRuntimeHookReservation(store, owner, launchId) {
|
|
|
2324
2632
|
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, launchId));
|
|
2325
2633
|
}
|
|
2326
2634
|
function saveRuntimeLifecycleMailbox(store, mailbox) {
|
|
2327
|
-
if (mailbox
|
|
2635
|
+
if (!mailboxHasWork(mailbox)) {
|
|
2328
2636
|
store.removeWorkMailbox(mailbox.target);
|
|
2329
2637
|
return;
|
|
2330
2638
|
}
|
|
@@ -2422,8 +2730,7 @@ function preparedDeliveryFailureSessionFenceMatches(sessions, session, input) {
|
|
|
2422
2730
|
return false;
|
|
2423
2731
|
if (sessions.inFlight === null) {
|
|
2424
2732
|
return input.nativeSessionId === undefined
|
|
2425
|
-
&& session === undefined
|
|
2426
|
-
&& sessions.pendingTurnCompletion === null;
|
|
2733
|
+
&& session === undefined;
|
|
2427
2734
|
}
|
|
2428
2735
|
return sessions.inFlight.agentId === input.agentId
|
|
2429
2736
|
&& sessions.inFlight.runId === input.runId
|
|
@@ -2658,7 +2965,7 @@ function bindTaskRoleRunInFlight(store, role, run, now) {
|
|
|
2658
2965
|
let current = store.getRoleSessionSet(role.taskId, role.name)
|
|
2659
2966
|
?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, agentId, now);
|
|
2660
2967
|
if (current.activeAgentId !== agentId) {
|
|
2661
|
-
if (current.inFlight !== null || current.
|
|
2968
|
+
if (current.inFlight !== null || current.providerBinding !== null) {
|
|
2662
2969
|
throw new Error("Task Role Session identity cannot change with an unsettled Run fence.");
|
|
2663
2970
|
}
|
|
2664
2971
|
const liveConflict = Object.values(current.sessions).find((session) => (session.agentId !== agentId
|
|
@@ -2808,8 +3115,13 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
2808
3115
|
function compactedRuntimeObservationIds(events, incoming) {
|
|
2809
3116
|
const existing = events.flatMap((event) => {
|
|
2810
3117
|
const observation = runtimeObservationFromTaskEvent(event);
|
|
3118
|
+
const matches = incoming.kind.startsWith("operation.")
|
|
3119
|
+
? observation !== null
|
|
3120
|
+
&& runtimeObservationRunFenceMatches(observation.fence, incoming.fence)
|
|
3121
|
+
: observation !== null
|
|
3122
|
+
&& runtimeObservationFenceMatches(observation.fence, incoming.fence);
|
|
2811
3123
|
return observation !== null
|
|
2812
|
-
&&
|
|
3124
|
+
&& matches
|
|
2813
3125
|
? [{ event, observation }]
|
|
2814
3126
|
: [];
|
|
2815
3127
|
});
|
|
@@ -2838,7 +3150,8 @@ function compactedRuntimeObservationIds(events, incoming) {
|
|
|
2838
3150
|
&& observation.payload.sourceId === incoming.payload.sourceId;
|
|
2839
3151
|
}
|
|
2840
3152
|
if (["turn.completed", "turn.failed", "turn.cancelled"].includes(incoming.kind)) {
|
|
2841
|
-
return observation.kind.startsWith("operation.")
|
|
3153
|
+
return (observation.kind.startsWith("operation.")
|
|
3154
|
+
&& observation.payload.operation !== "subagent")
|
|
2842
3155
|
|| observation.kind === "turn.waiting"
|
|
2843
3156
|
|| observation.kind === "turn.completed"
|
|
2844
3157
|
|| observation.kind === "turn.failed"
|
|
@@ -2872,8 +3185,9 @@ function confirmedUsageActivityObservation(events, incoming) {
|
|
|
2872
3185
|
.update(`confirmed-runtime-activity\0${incoming.eventId}`)
|
|
2873
3186
|
.digest("hex");
|
|
2874
3187
|
return createRuntimeObservation({
|
|
2875
|
-
schemaVersion:
|
|
3188
|
+
schemaVersion: 2,
|
|
2876
3189
|
eventId: `runtime-activity-${digest}`,
|
|
3190
|
+
semanticKey: `runtime-activity-${digest}`,
|
|
2877
3191
|
kind: "activity.observed",
|
|
2878
3192
|
authority: "controller",
|
|
2879
3193
|
receivedAt: incoming.receivedAt,
|