@zq-silk/yui 0.11.3 → 0.12.1
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/ARCHITECTURE.md +23 -4
- package/README.md +44 -22
- package/dist/cli/commandCatalog.js +13 -10
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +156 -57
- package/dist/commands/configCommands.js +13 -46
- package/dist/commands/executionAuditCommands.js +3 -2
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +234 -200
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +61 -13
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +55 -7
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +3 -3
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +39 -3
- package/dist/controller/controller.js +31 -27
- package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
- package/dist/controller/runtime.js +58 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +52 -50
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +129 -45
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +69 -5
- package/dist/observability/executionAudit.js +5 -0
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +28 -0
- package/dist/runtime/runtimeProjection.js +22 -32
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +34 -199
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +42 -138
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +53 -31
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +66 -23
- package/dist/storage/sqliteStore.js +46 -23
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -58
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +7 -1
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +60 -20
- package/skills/yui-operator/SKILL.md +13 -4
- package/skills/yui-reviewer/SKILL.md +35 -11
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/lifecycle/taskRoleSessionReset.js +0 -126
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun,
|
|
4
|
-
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
3
|
+
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, prepareTaskRoleRunRedispatch, recordRoleAgentSession, replaceTaskRoleAgentSessionForConversationSwitch, recordTaskRoleTurnBoundary, rememberRoleAgentCompletedTurn, stopTaskRoleRuntimeAfterPhysicalExit, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
4
|
+
import { acceptProviderTurn, beginProviderTurn, createProviderRuntimeBinding, endProviderActivation, currentProviderActivation, currentProviderConversation, markProviderTurnDeliveryUnknown, rejectProviderTurn, settleProviderTurnSubmission, settleProviderTurn, startProviderActivation, supersedeProviderConversation, updateProviderConversationRecoverability } from "../runtime/providerRuntimeIdentity.js";
|
|
5
|
+
import { CONVERSATION_SWITCH_DETACHED_EVENT, CONVERSATION_SWITCH_RESOLVED_EVENT, conversationDetachmentBasis, conversationReplacementBasis, pendingConversationSwitch, roleSessionDispatchModeWithConversationSwitch } from "../runtime/conversationSwitch.js";
|
|
5
6
|
import { decideProviderRecovery } from "../runtime/providerRecoveryDecision.js";
|
|
6
7
|
import { hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
7
8
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
8
9
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
9
10
|
import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
|
|
10
11
|
import { createTaskWake, fallbackWakeCursor, latestTaskWake } from "../scheduler/taskWake.js";
|
|
11
|
-
import { rolloverTaskRoleSessionForContextBudget } from "../lifecycle/contextBudgetRollover.js";
|
|
12
|
-
import { resolveContextBudget } from "../config/yuiConfig.js";
|
|
13
12
|
import { answerInputRequest } from "../input/inputRequest.js";
|
|
14
13
|
import { activeRoleAgentBinding, updateRoleStatus } from "../role/role.js";
|
|
15
|
-
import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible,
|
|
14
|
+
import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
16
15
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
17
16
|
import { failAgentRun, markAgentRunDelivered, markAgentRunPushed, reopenRunForProviderRetry, clearProviderRetryOnProgress, agentRunDeliveryReceiptId, withProviderRetry } from "../run/agentRun.js";
|
|
18
17
|
import { markWorkflowOutcomeRequestDispatched } from "../run/runControlRequest.js";
|
|
@@ -23,11 +22,11 @@ import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } fro
|
|
|
23
22
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
24
23
|
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
25
24
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
26
|
-
import {
|
|
25
|
+
import { recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
|
|
27
26
|
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
28
27
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
29
28
|
import { wakeReason } from "../scheduler/wakeReason.js";
|
|
30
|
-
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey,
|
|
29
|
+
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
31
30
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
32
31
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
33
32
|
import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus, workItemOwnsUnresolvedExecutionLane } from "../workItem/workItem.js";
|
|
@@ -38,7 +37,7 @@ import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
|
38
37
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, RuntimeLifecycleBusyError, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
39
38
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
40
39
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
41
|
-
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
40
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload, runHasActiveRuntimeOperations } from "../runtime/runtimeObservation.js";
|
|
42
41
|
import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
43
42
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
44
43
|
import { contextSnapshotDeltaRefIds, freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
@@ -90,8 +89,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
90
89
|
if (adapterId === null) {
|
|
91
90
|
return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
|
|
92
91
|
}
|
|
93
|
-
if (this.store.listEvents(taskId)
|
|
94
|
-
&& event.payload.semanticKey === input.semanticKey)))
|
|
92
|
+
if (hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
|
|
95
93
|
return "applied";
|
|
96
94
|
let outcome;
|
|
97
95
|
switch (input.kind) {
|
|
@@ -112,6 +110,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
112
110
|
launchId: input.fence.launchId,
|
|
113
111
|
nativeSessionId: input.fence.nativeSessionId,
|
|
114
112
|
turnId: input.fence.nativeTurnId,
|
|
113
|
+
attemptId: input.fence.receiptId,
|
|
115
114
|
runId: input.fence.runId,
|
|
116
115
|
summary: input.payload.summary ?? "Agent turn completed without a workflow outcome."
|
|
117
116
|
};
|
|
@@ -152,6 +151,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
152
151
|
launchId: input.fence.launchId,
|
|
153
152
|
nativeSessionId: input.fence.nativeSessionId,
|
|
154
153
|
turnId: input.fence.nativeTurnId,
|
|
154
|
+
attemptId: input.fence.receiptId,
|
|
155
155
|
runId: input.fence.runId,
|
|
156
156
|
summary: input.payload.summary ?? `Provider Turn failed: ${failureEvidence.code}.`,
|
|
157
157
|
providerStatus: "failed"
|
|
@@ -223,7 +223,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
223
223
|
}
|
|
224
224
|
if (outcome === "applied"
|
|
225
225
|
&& input.kind === "activity.observed"
|
|
226
|
-
&& input.payload.activityId !== undefined
|
|
226
|
+
&& input.payload.activityId !== undefined
|
|
227
|
+
&& input.payload.usage === undefined) {
|
|
227
228
|
this.clearProviderRetryForProgress(input, "correlated-activity", now);
|
|
228
229
|
}
|
|
229
230
|
if (outcome === "applied")
|
|
@@ -251,9 +252,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
251
252
|
}
|
|
252
253
|
validateCanonicalRunObservation(input, now) {
|
|
253
254
|
return this.store.transaction((store) => {
|
|
254
|
-
if (store.listEvents(input.fence.taskId)
|
|
255
|
-
&& event.payload.semanticKey === input.semanticKey)))
|
|
255
|
+
if (hasPersistedRuntimeObservation(store.listEvents(input.fence.taskId), input)) {
|
|
256
256
|
return "applied";
|
|
257
|
+
}
|
|
257
258
|
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
258
259
|
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
259
260
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
@@ -386,12 +387,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
386
387
|
const message = [
|
|
387
388
|
`Leader native Session became unavailable while Run ${active.id} remains active.`,
|
|
388
389
|
"Yui preserved the Run because Session/host termination is not a workflow outcome.",
|
|
389
|
-
"Inspect native-child and mailbox facts, then explicitly
|
|
390
|
+
"Inspect native-child and mailbox facts, then explicitly diagnose, recover the Run, request a Conversation switch, or request user input."
|
|
390
391
|
].join(" ");
|
|
391
|
-
store
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
392
|
+
recordLeaderAttentionRequired(store, {
|
|
393
|
+
taskId: input.fence.taskId,
|
|
394
|
+
reason: "leader-runtime-detached",
|
|
395
|
+
payload: { message, runId: active.id },
|
|
396
|
+
now
|
|
397
|
+
});
|
|
395
398
|
}
|
|
396
399
|
else {
|
|
397
400
|
enqueueWork(store, {
|
|
@@ -410,22 +413,16 @@ export class FileSchedulerStoreAdapter {
|
|
|
410
413
|
this.store.transaction((store) => {
|
|
411
414
|
const taskId = input.fence.taskId;
|
|
412
415
|
const events = store.listEvents(taskId);
|
|
413
|
-
if (events
|
|
414
|
-
&& event.payload.semanticKey === input.semanticKey)))
|
|
416
|
+
if (hasPersistedRuntimeObservation(events, input))
|
|
415
417
|
return;
|
|
416
418
|
if (usageSnapshotIsSuperseded(events, input))
|
|
417
419
|
return;
|
|
418
|
-
const
|
|
419
|
-
const removable = [
|
|
420
|
-
...compactedRuntimeObservationIds(events, input),
|
|
421
|
-
...(confirmedActivity === null
|
|
422
|
-
? []
|
|
423
|
-
: compactedRuntimeObservationIds(events, confirmedActivity))
|
|
424
|
-
];
|
|
420
|
+
const removable = compactedRuntimeObservationIds(events, input);
|
|
425
421
|
if (removable.length > 0)
|
|
426
422
|
store.removeEvents(taskId, removable);
|
|
427
423
|
const observationEventId = store.nextEventId(taskId);
|
|
428
|
-
|
|
424
|
+
const observationEvent = createTaskEvent(observationEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(input), now);
|
|
425
|
+
store.saveEvent(taskId, observationEvent);
|
|
429
426
|
if (input.kind === "native-work.snapshot"
|
|
430
427
|
&& input.payload.snapshotComplete === true
|
|
431
428
|
&& input.payload.observationQuality === "exact") {
|
|
@@ -503,14 +500,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
503
500
|
// Route a Yui wake only when that parent is gone and another observer
|
|
504
501
|
// supplied the child's terminal fact.
|
|
505
502
|
if (role !== null && (session?.status === "stopped" || session?.status === "broken")) {
|
|
506
|
-
|
|
507
|
-
? { kind: "operator" }
|
|
508
|
-
: { kind: "role", taskId, roleName: "leader" }, "detached-native-subagent-terminal", now, [{ type: "run", taskId, id: input.fence.runId }]);
|
|
503
|
+
routeRoleEvent(store, observationEvent, role.name, "detached-native-subagent-terminal", now);
|
|
509
504
|
}
|
|
510
505
|
}
|
|
511
|
-
if (confirmedActivity !== null) {
|
|
512
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(confirmedActivity), now));
|
|
513
|
-
}
|
|
514
506
|
});
|
|
515
507
|
if (this.telemetry !== null && input.fence.runId !== undefined) {
|
|
516
508
|
try {
|
|
@@ -573,9 +565,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
573
565
|
withRuntimeEventTransaction(execute) {
|
|
574
566
|
return this.store.withRuntimeEventTransaction(execute);
|
|
575
567
|
}
|
|
576
|
-
getPresentationContext() {
|
|
577
|
-
return { timeZone: this.store.getConfig().timeZone };
|
|
578
|
-
}
|
|
579
568
|
listTasks() { return this.store.listTasks(); }
|
|
580
569
|
listActiveTaskIds() {
|
|
581
570
|
return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
@@ -608,12 +597,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
608
597
|
});
|
|
609
598
|
});
|
|
610
599
|
}
|
|
611
|
-
rolloverTaskRoleSessionForContextBudget(input) {
|
|
612
|
-
return this.store.transaction((reader) => rolloverTaskRoleSessionForContextBudget(reader, input.taskId, input.roleName, { peakTokens: input.peakTokens, hardTokens: input.hardTokens }, input.now));
|
|
613
|
-
}
|
|
614
|
-
getContextBudget() {
|
|
615
|
-
return resolveContextBudget(this.store.getConfig().contextBudget);
|
|
616
|
-
}
|
|
617
600
|
listRoles(taskId) {
|
|
618
601
|
return this.store.listRoles(taskId).map((role) => mapRole(this.store, role));
|
|
619
602
|
}
|
|
@@ -646,6 +629,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
646
629
|
adapterId: effectiveSession.effective.adapterId
|
|
647
630
|
};
|
|
648
631
|
}
|
|
632
|
+
markOperatorTurnStarted(now) {
|
|
633
|
+
this.store.transaction((store) => {
|
|
634
|
+
const sessions = store.getGlobalRoleSessionSet(SYSTEM_OPERATOR_ROLE);
|
|
635
|
+
if (sessions === null)
|
|
636
|
+
return;
|
|
637
|
+
const active = sessions.sessions[sessions.activeAgentId];
|
|
638
|
+
if (active === undefined || active.status !== "ready")
|
|
639
|
+
return;
|
|
640
|
+
store.saveGlobalRoleSessionSet(updateRoleAgentSessionStatus(sessions, sessions.activeAgentId, "running", now));
|
|
641
|
+
});
|
|
642
|
+
}
|
|
649
643
|
resolveExpiredInputRecommendations(now, taskIds) {
|
|
650
644
|
return this.store.transaction((store) => {
|
|
651
645
|
const selectedTaskIds = taskIds === undefined
|
|
@@ -725,6 +719,27 @@ export class FileSchedulerStoreAdapter {
|
|
|
725
719
|
// the fold as present and skips the per-candidate fallback scans.
|
|
726
720
|
return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
|
|
727
721
|
}
|
|
722
|
+
recordRoleRunDiagnostic(input) {
|
|
723
|
+
return this.store.transaction((store) => {
|
|
724
|
+
const task = store.getTask(input.taskId);
|
|
725
|
+
const run = store.getActiveAgentRun(input.taskId, input.roleName);
|
|
726
|
+
if (task === null || task.status !== "active"
|
|
727
|
+
|| run === null || run.id !== input.runId || run.status !== "active") {
|
|
728
|
+
return "state-changed";
|
|
729
|
+
}
|
|
730
|
+
const latest = latestRunEventTime(store.listEvents(input.taskId), RUN_DIAGNOSTIC_FINISHED_EVENT, input.runId);
|
|
731
|
+
if (latest !== undefined && Date.parse(latest) >= Date.parse(input.startedAt)) {
|
|
732
|
+
return "already-recorded";
|
|
733
|
+
}
|
|
734
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_DIAGNOSTIC_FINISHED_EVENT, {
|
|
735
|
+
runId: input.runId,
|
|
736
|
+
roleName: input.roleName,
|
|
737
|
+
outcome: input.outcome,
|
|
738
|
+
startedAt: input.startedAt
|
|
739
|
+
}, input.now));
|
|
740
|
+
return "recorded";
|
|
741
|
+
});
|
|
742
|
+
}
|
|
728
743
|
recordRoleRunStall(input) {
|
|
729
744
|
return this.store.transaction((store) => {
|
|
730
745
|
const task = store.getTask(input.taskId);
|
|
@@ -756,18 +771,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
756
771
|
progressAt: input.progressAt,
|
|
757
772
|
idleMs: String(Math.max(0, Math.floor(input.idleMs))),
|
|
758
773
|
evidenceKey: input.evidenceKey,
|
|
759
|
-
status: "
|
|
774
|
+
status: "diagnostic-only"
|
|
760
775
|
}, input.now);
|
|
761
776
|
store.saveEvent(task.id, event);
|
|
762
|
-
if (role.name === "leader") {
|
|
763
|
-
store.saveOperatorNotification(createLeaderStallNotification(task.id, run.id, input.progressAt, input.evidenceKey, input.now, store.getOperatorNotification(task.id)));
|
|
764
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-stalled", input.now, [
|
|
765
|
-
{ type: "task", id: task.id }
|
|
766
|
-
]);
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
queueLeaderWakeup(store, task.id, wakeReason("role-run-stalled"), input.now);
|
|
770
|
-
}
|
|
771
777
|
return "raised";
|
|
772
778
|
});
|
|
773
779
|
}
|
|
@@ -795,10 +801,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
795
801
|
|| (typeof event.payload.progressAt === "string"
|
|
796
802
|
&& Number.isFinite(Date.parse(event.payload.progressAt))
|
|
797
803
|
&& Date.parse(event.payload.progressAt) >= Date.parse(input.progressAt)))));
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const recovered =
|
|
801
|
-
&& (recoveredAt === undefined || Date.parse(recoveredAt) <= Date.parse(stalledAt));
|
|
804
|
+
// Advisory 30-minute diagnostics are intentionally not lifecycle
|
|
805
|
+
// episodes and therefore must never synthesize run.recovered.
|
|
806
|
+
const recovered = isRoleRunStalled(events, run.id);
|
|
802
807
|
if (!existing) {
|
|
803
808
|
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_PROGRESS_EVENT, {
|
|
804
809
|
runId: run.id,
|
|
@@ -815,7 +820,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
815
820
|
progressAt: input.progressAt,
|
|
816
821
|
kind: "durable-progress"
|
|
817
822
|
}, input.now));
|
|
818
|
-
clearMatchingLeaderStallAttention(store, task.id, run.id);
|
|
819
823
|
}
|
|
820
824
|
return existing && !recovered ? "already-recorded" : "recorded";
|
|
821
825
|
});
|
|
@@ -824,11 +828,92 @@ export class FileSchedulerStoreAdapter {
|
|
|
824
828
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
825
829
|
return sessions !== null && sessions.inFlight !== null;
|
|
826
830
|
}
|
|
831
|
+
detachAgentHostProviderForConversationSwitch(input) {
|
|
832
|
+
return this.store.transaction((store) => {
|
|
833
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
834
|
+
const run = store.getAgentRun(input.taskId, input.runId);
|
|
835
|
+
const mailbox = store.getWorkMailbox({
|
|
836
|
+
kind: "role",
|
|
837
|
+
taskId: input.taskId,
|
|
838
|
+
roleName: input.roleName
|
|
839
|
+
});
|
|
840
|
+
const binding = sessions?.providerBinding;
|
|
841
|
+
const session = sessions?.sessions[input.agentId];
|
|
842
|
+
const task = store.getTask(input.taskId);
|
|
843
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
844
|
+
const activeRun = store.getActiveAgentRun(input.taskId, input.roleName);
|
|
845
|
+
if (task === null || task.status !== "active"
|
|
846
|
+
|| role === null || role.activeAgentId !== input.agentId
|
|
847
|
+
|| sessions === null || sessions === undefined
|
|
848
|
+
|| run === null || run.status !== "active" || run.mode !== "new"
|
|
849
|
+
|| activeRun?.id !== run.id
|
|
850
|
+
|| run.effective.agentId !== input.agentId
|
|
851
|
+
|| session?.nativeSessionId !== input.previousNativeSessionId
|
|
852
|
+
|| binding === null || binding === undefined
|
|
853
|
+
|| currentProviderConversation(binding).conversationId !== input.previousConversationId
|
|
854
|
+
|| sessions.inFlight?.runId !== input.runId
|
|
855
|
+
|| mailbox?.processing?.batchId !== sessions.inFlight.receiptId
|
|
856
|
+
|| mailbox.processing.owner !== "controller"
|
|
857
|
+
|| mailbox.processing.executionRef?.type !== "run"
|
|
858
|
+
|| mailbox.processing.executionRef.taskId !== input.taskId
|
|
859
|
+
|| mailbox.processing.executionRef.id !== input.runId
|
|
860
|
+
|| mailbox.inputDelivery !== null
|
|
861
|
+
|| pendingConversationSwitch(store.listEvents(input.taskId), input.roleName, sessions) === null
|
|
862
|
+
|| !runtimeHookMatchesReservation(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, input.launchId)
|
|
863
|
+
|| input.nextAuthorityHolderId !== input.launchId) {
|
|
864
|
+
throw new Error("Provider Conversation detachment no longer matches the exact switch Run.");
|
|
865
|
+
}
|
|
866
|
+
const target = binding.activations.find((entry) => (entry.activationId === input.previousActivationId
|
|
867
|
+
&& entry.conversationId === input.previousConversationId));
|
|
868
|
+
if (target === undefined) {
|
|
869
|
+
throw new Error("Provider Conversation detachment targets an unknown Activation.");
|
|
870
|
+
}
|
|
871
|
+
if (target.status !== "active") {
|
|
872
|
+
if (binding.authority.owner !== "none"
|
|
873
|
+
|| input.nextAuthorityEpoch !== binding.authority.epoch + 1) {
|
|
874
|
+
throw new Error("Provider Conversation detachment replay has a stale authority fence.");
|
|
875
|
+
}
|
|
876
|
+
return "already-detached";
|
|
877
|
+
}
|
|
878
|
+
if (conversationDetachmentBasis({
|
|
879
|
+
sessions,
|
|
880
|
+
events: store.listEvents(input.taskId),
|
|
881
|
+
mailbox,
|
|
882
|
+
roleName: input.roleName,
|
|
883
|
+
runId: input.runId,
|
|
884
|
+
runMode: run.mode
|
|
885
|
+
}) !== "actor-request"
|
|
886
|
+
|| binding.authority.owner !== "controller"
|
|
887
|
+
|| binding.authority.holderId !== target.activationId
|
|
888
|
+
|| input.nextAuthorityEpoch !== binding.authority.epoch + 2) {
|
|
889
|
+
throw new Error("Provider Conversation detachment is not authorized at this boundary.");
|
|
890
|
+
}
|
|
891
|
+
const detached = endProviderActivation(binding, target.activationId, {
|
|
892
|
+
status: "ended",
|
|
893
|
+
endedAt: input.now.toISOString(),
|
|
894
|
+
reason: "conversation-switch"
|
|
895
|
+
});
|
|
896
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, detached, input.now));
|
|
897
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, CONVERSATION_SWITCH_DETACHED_EVENT, {
|
|
898
|
+
runId: input.runId,
|
|
899
|
+
roleName: input.roleName,
|
|
900
|
+
launchId: input.launchId,
|
|
901
|
+
conversationId: input.previousConversationId,
|
|
902
|
+
activationId: input.previousActivationId
|
|
903
|
+
}, input.now));
|
|
904
|
+
return "detached";
|
|
905
|
+
});
|
|
906
|
+
}
|
|
827
907
|
beginAgentHostProviderTurn(input) {
|
|
828
908
|
this.store.transaction((store) => {
|
|
829
909
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
830
910
|
const session = sessions?.sessions[input.agentId];
|
|
831
911
|
const binding = sessions?.providerBinding;
|
|
912
|
+
const mailbox = store.getWorkMailbox({
|
|
913
|
+
kind: "role",
|
|
914
|
+
taskId: input.taskId,
|
|
915
|
+
roleName: input.roleName
|
|
916
|
+
});
|
|
832
917
|
if (sessions === null || sessions === undefined
|
|
833
918
|
|| binding === null || binding === undefined
|
|
834
919
|
|| sessions.inFlight?.runId !== input.runId
|
|
@@ -838,7 +923,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
838
923
|
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
839
924
|
|| binding.authority.owner !== input.authorityOwner
|
|
840
925
|
|| binding.authority.epoch !== input.authorityEpoch
|
|
841
|
-
|| binding.authority.holderId !== input.holderId
|
|
926
|
+
|| binding.authority.holderId !== input.holderId
|
|
927
|
+
|| !processingOwnsExactInitialDelivery(mailbox, input.taskId, input.runId, input.attemptId)) {
|
|
842
928
|
throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
|
|
843
929
|
}
|
|
844
930
|
const exactReplay = binding.turn?.attemptId === input.attemptId
|
|
@@ -863,21 +949,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
863
949
|
if (sessions === null || sessions === undefined
|
|
864
950
|
|| binding === null || binding === undefined
|
|
865
951
|
|| binding.runId !== input.runId
|
|
866
|
-
|| binding.turn?.attemptId !== input.attemptId
|
|
867
|
-
|| binding.turn.status !== "submitting") {
|
|
952
|
+
|| binding.turn?.attemptId !== input.attemptId) {
|
|
868
953
|
throw new Error("Agent Host Provider Turn submission is no longer current.");
|
|
869
954
|
}
|
|
870
|
-
const updated =
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
: rejectProviderTurn(binding, {
|
|
877
|
-
attemptId: input.attemptId,
|
|
878
|
-
rejectedAt: input.now.toISOString(),
|
|
879
|
-
reason: input.reason
|
|
880
|
-
});
|
|
955
|
+
const updated = settleProviderTurnSubmission(binding, {
|
|
956
|
+
attemptId: input.attemptId,
|
|
957
|
+
status: input.status,
|
|
958
|
+
reason: input.reason,
|
|
959
|
+
resolvedAt: input.now.toISOString()
|
|
960
|
+
});
|
|
881
961
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
882
962
|
});
|
|
883
963
|
}
|
|
@@ -900,68 +980,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
900
980
|
...binding.authority
|
|
901
981
|
};
|
|
902
982
|
}
|
|
903
|
-
beginRoleRunProviderTurn(input) {
|
|
904
|
-
return this.store.transaction((store) => {
|
|
905
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
906
|
-
const session = sessions?.sessions[input.agentId];
|
|
907
|
-
const binding = sessions?.providerBinding;
|
|
908
|
-
if (sessions === null || sessions === undefined
|
|
909
|
-
|| binding === null || binding === undefined
|
|
910
|
-
|| sessions.inFlight?.runId !== input.runId
|
|
911
|
-
|| binding.runId !== input.runId
|
|
912
|
-
|| session?.launchId !== input.launchId
|
|
913
|
-
|| session.nativeSessionId !== input.nativeSessionId
|
|
914
|
-
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
915
|
-
|| binding.authority.owner !== "controller")
|
|
916
|
-
return false;
|
|
917
|
-
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
918
|
-
attemptId: input.attemptId,
|
|
919
|
-
authorityEpoch: binding.authority.epoch,
|
|
920
|
-
submittedAt: input.now.toISOString()
|
|
921
|
-
}), input.now));
|
|
922
|
-
return true;
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
resolveRoleRunProviderSubmission(input) {
|
|
926
|
-
return this.store.transaction((store) => {
|
|
927
|
-
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
928
|
-
const binding = sessions?.providerBinding;
|
|
929
|
-
if (sessions === null || sessions === undefined
|
|
930
|
-
|| binding === null || binding === undefined
|
|
931
|
-
|| binding.runId !== input.runId
|
|
932
|
-
|| binding.turn?.attemptId !== input.attemptId
|
|
933
|
-
|| binding.turn.status !== "submitting")
|
|
934
|
-
return false;
|
|
935
|
-
const updated = input.status === "delivery-unknown"
|
|
936
|
-
? markProviderTurnDeliveryUnknown(binding, {
|
|
937
|
-
attemptId: input.attemptId,
|
|
938
|
-
observedAt: input.now.toISOString(),
|
|
939
|
-
reason: input.reason
|
|
940
|
-
})
|
|
941
|
-
: rejectProviderTurn(binding, {
|
|
942
|
-
attemptId: input.attemptId,
|
|
943
|
-
rejectedAt: input.now.toISOString(),
|
|
944
|
-
reason: input.reason
|
|
945
|
-
});
|
|
946
|
-
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
947
|
-
return true;
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
isRoleGenerationProviderReady(input) {
|
|
951
|
-
// A session.ready observation for this exact generation is the durable
|
|
952
|
-
// pre-input fence. This reads folded Driver truth
|
|
953
|
-
// only — no liveness, screen, or pane/PID inference. Served from the
|
|
954
|
-
// revision projection: a stale miss only repeats an idempotent first push.
|
|
955
|
-
return this.listEvents(input.taskId).some((event) => {
|
|
956
|
-
const observation = runtimeObservationFromTaskEvent(event);
|
|
957
|
-
return observation?.kind === "session.ready"
|
|
958
|
-
&& observation.fence.roleName === input.roleName
|
|
959
|
-
&& observation.fence.agentId === input.agentId
|
|
960
|
-
&& (input.launchId === undefined || observation.fence.launchId === input.launchId)
|
|
961
|
-
&& (input.nativeSessionId === undefined
|
|
962
|
-
|| observation.fence.nativeSessionId === input.nativeSessionId);
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
983
|
peekNextAgentRunId(taskId) {
|
|
966
984
|
return this.store.peekNextAgentRunId(taskId);
|
|
967
985
|
}
|
|
@@ -1161,17 +1179,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1161
1179
|
const unknown = markMailboxInputDeliveryUnknown(mailbox, attemptId, reason, now);
|
|
1162
1180
|
markSubmittingProviderTurnUnknown(store, target, mailbox.inputDelivery, now, reason);
|
|
1163
1181
|
store.saveWorkMailbox(unknown);
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
], {
|
|
1170
|
-
source: "input-router",
|
|
1171
|
-
dedupeKey: `input-delivery-unknown:${attemptId}`,
|
|
1172
|
-
deliveryMode: "followup",
|
|
1173
|
-
lane: "normal"
|
|
1174
|
-
});
|
|
1182
|
+
if (target.kind === "role") {
|
|
1183
|
+
const taskId = target.taskId;
|
|
1184
|
+
const event = createTaskEvent(store.nextEventId(taskId), taskId, "run.input-delivery-unknown", { attemptId, reason }, now);
|
|
1185
|
+
store.saveEvent(taskId, event);
|
|
1186
|
+
routeRoleEvent(store, event, target.roleName, "run-input-delivery-unknown", now);
|
|
1175
1187
|
}
|
|
1176
1188
|
return true;
|
|
1177
1189
|
});
|
|
@@ -1228,12 +1240,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1228
1240
|
}
|
|
1229
1241
|
const owner = runtimeOwnerFromTarget(target);
|
|
1230
1242
|
markRuntimeOwnerSessionStopped(store, owner, now);
|
|
1231
|
-
if (owner.scope === "task") {
|
|
1232
|
-
const sessions = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
1233
|
-
if (sessions !== null && sessions.providerBinding !== null) {
|
|
1234
|
-
store.saveTaskRoleSessionSet(clearTaskRoleProviderRuntimeForCleanup(sessions, now));
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
1243
|
saveRuntimeLifecycleMailbox(store, mailbox);
|
|
1238
1244
|
return true;
|
|
1239
1245
|
});
|
|
@@ -1298,7 +1304,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1298
1304
|
}
|
|
1299
1305
|
clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
|
|
1300
1306
|
getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
|
|
1301
|
-
getOperatorNotification(taskId) { return this.store.getOperatorNotification(taskId); }
|
|
1302
1307
|
saveLeaderDispatch(input) {
|
|
1303
1308
|
return this.store.transaction((store) => {
|
|
1304
1309
|
const task = store.getTask(input.task.id);
|
|
@@ -1371,14 +1376,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
1371
1376
|
now: input.now
|
|
1372
1377
|
}));
|
|
1373
1378
|
}
|
|
1374
|
-
if (input.
|
|
1379
|
+
if (input.run.mode !== "new"
|
|
1380
|
+
&& input.session !== null
|
|
1381
|
+
&& input.session.nativeSessionId !== undefined) {
|
|
1375
1382
|
saveTaskSession(store, role, {
|
|
1376
1383
|
...input.session,
|
|
1377
1384
|
nativeSessionId: input.session.nativeSessionId
|
|
1378
1385
|
}, "running", input.now);
|
|
1379
1386
|
}
|
|
1380
1387
|
store.clearLeaderFailure(input.task.id);
|
|
1381
|
-
store.clearOperatorNotification(input.task.id);
|
|
1382
1388
|
return "claimed";
|
|
1383
1389
|
});
|
|
1384
1390
|
}
|
|
@@ -1469,9 +1475,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
1469
1475
|
if (input.session !== null && input.session.nativeSessionId !== undefined) {
|
|
1470
1476
|
const existing = store.getRoleSession(input.task.id, input.role.name);
|
|
1471
1477
|
matchingPreparedRuntimeReservation(store, input);
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1478
|
+
const defersConversationReplacement = active.mode === "new"
|
|
1479
|
+
&& existing !== null
|
|
1480
|
+
&& existing.nativeSessionId !== input.session.nativeSessionId;
|
|
1481
|
+
if (!defersConversationReplacement
|
|
1482
|
+
&& (existing?.nativeSessionId !== input.session.nativeSessionId
|
|
1483
|
+
|| (input.launchId !== undefined && existing.launchId !== input.launchId)
|
|
1484
|
+
|| existing.status !== "running")) {
|
|
1475
1485
|
saveTaskSession(store, role, {
|
|
1476
1486
|
...input.session,
|
|
1477
1487
|
nativeSessionId: input.session.nativeSessionId
|
|
@@ -1515,7 +1525,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1515
1525
|
|| !preparedReservationMatches(runtimeMailbox, input.taskId, input.runId, input.launchId)) {
|
|
1516
1526
|
return "state-changed";
|
|
1517
1527
|
}
|
|
1518
|
-
const summary =
|
|
1528
|
+
const summary = input.summary
|
|
1529
|
+
?? `Role delivery retry limit exhausted before exact Run input delivery: ${input.runId}.`;
|
|
1519
1530
|
const result = terminalizeExactTaskRun(store, {
|
|
1520
1531
|
taskId: input.taskId,
|
|
1521
1532
|
roleName: input.roleName,
|
|
@@ -1531,17 +1542,20 @@ export class FileSchedulerStoreAdapter {
|
|
|
1531
1542
|
if (result.disposition !== "applied" || result.run === null) {
|
|
1532
1543
|
return "state-changed";
|
|
1533
1544
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1545
|
+
if (input.cleanupRequired !== false) {
|
|
1546
|
+
enqueueWork(store, runtimeLifecycleTarget({
|
|
1547
|
+
scope: "task",
|
|
1548
|
+
taskId: input.taskId,
|
|
1549
|
+
roleName: input.roleName
|
|
1550
|
+
}), RUNTIME_CLEANUP_REQUIRED_REASON, input.now, [{ type: "task", id: input.taskId }]);
|
|
1551
|
+
}
|
|
1539
1552
|
const terminal = result.run;
|
|
1540
|
-
|
|
1553
|
+
const deliveryFailureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
|
|
1541
1554
|
runId: terminal.id,
|
|
1542
1555
|
roleName: input.roleName,
|
|
1543
1556
|
outcome: terminal.status
|
|
1544
|
-
}, input.now)
|
|
1557
|
+
}, input.now);
|
|
1558
|
+
store.saveEvent(input.taskId, deliveryFailureEvent);
|
|
1545
1559
|
if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
|
|
1546
1560
|
const item = store.getWorkItem(input.taskId, terminal.workItemId);
|
|
1547
1561
|
if (item !== null && ![
|
|
@@ -1553,12 +1567,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1553
1567
|
}
|
|
1554
1568
|
}
|
|
1555
1569
|
if (input.roleName !== "leader") {
|
|
1556
|
-
|
|
1557
|
-
{ type: "run", taskId: input.taskId, id: terminal.id },
|
|
1558
|
-
...(terminal.workItemId === undefined
|
|
1559
|
-
? []
|
|
1560
|
-
: [{ type: "work-item", taskId: input.taskId, id: terminal.workItemId }])
|
|
1561
|
-
]);
|
|
1570
|
+
routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-run-failed", input.now);
|
|
1562
1571
|
}
|
|
1563
1572
|
else {
|
|
1564
1573
|
const failedRole = store.getRole(input.taskId, input.roleName);
|
|
@@ -1566,11 +1575,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1566
1575
|
store.saveRole(input.taskId, updateRoleStatus(failedRole, "failed", input.now));
|
|
1567
1576
|
}
|
|
1568
1577
|
store.saveLeaderFailure(recordLeaderFailure(input.taskId, session?.nativeSessionId ?? "(unregistered)", summary, input.now, store.getLeaderFailure(input.taskId)));
|
|
1569
|
-
store
|
|
1570
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-failed", input.now, [
|
|
1571
|
-
{ type: "task", id: input.taskId },
|
|
1572
|
-
{ type: "run", taskId: input.taskId, id: terminal.id }
|
|
1573
|
-
]);
|
|
1578
|
+
routeRoleEvent(store, deliveryFailureEvent, input.roleName, "leader-run-failed", input.now);
|
|
1574
1579
|
}
|
|
1575
1580
|
return "failed";
|
|
1576
1581
|
});
|
|
@@ -1606,10 +1611,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
1606
1611
|
breakTaskSessionIfPresent(store, input.task.id, role.name, active.effective.agentId, input.now);
|
|
1607
1612
|
const failure = recordLeaderFailure(input.task.id, input.failure.nativeSessionId, input.failure.message, input.now, store.getLeaderFailure(input.task.id));
|
|
1608
1613
|
store.saveLeaderFailure(failure);
|
|
1609
|
-
store
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1614
|
+
recordLeaderAttentionRequired(store, {
|
|
1615
|
+
taskId: input.task.id,
|
|
1616
|
+
reason: "leader-recovery-failed",
|
|
1617
|
+
payload: {
|
|
1618
|
+
message: input.failure.message,
|
|
1619
|
+
runId: active.id
|
|
1620
|
+
},
|
|
1621
|
+
now: input.now
|
|
1622
|
+
});
|
|
1613
1623
|
return "failed";
|
|
1614
1624
|
});
|
|
1615
1625
|
}
|
|
@@ -1661,7 +1671,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1661
1671
|
}, input.now);
|
|
1662
1672
|
if (terminal.disposition !== "applied")
|
|
1663
1673
|
return "state-changed";
|
|
1664
|
-
clearMatchingLeaderStallAttention(store, task.id, currentRun.id);
|
|
1665
1674
|
store.saveRole(task.id, updateRoleStatus(role, "exited", input.now));
|
|
1666
1675
|
stopTaskSessionIfPresent(store, task.id, role.name, currentRun.effective.agentId, input.now);
|
|
1667
1676
|
queueLeaderWakeup(store, task.id, wakeReason("review-failed"), input.now);
|
|
@@ -1679,7 +1688,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1679
1688
|
}
|
|
1680
1689
|
}
|
|
1681
1690
|
store.saveAgentRun(failAgentRun(currentRun, input.summary, input.now));
|
|
1682
|
-
clearMatchingLeaderStallAttention(store, task.id, currentRun.id);
|
|
1683
1691
|
store.clearActiveAgentRun(task.id, role.name);
|
|
1684
1692
|
clearTaskRoleRunInFlight(store, role, currentRun, input.now);
|
|
1685
1693
|
if (mailbox !== null && processing !== null && processing !== undefined) {
|
|
@@ -1945,6 +1953,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1945
1953
|
const role = requireRole(store, input.taskId, input.roleName);
|
|
1946
1954
|
let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
|
|
1947
1955
|
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
1956
|
+
const canonicalTurnId = canonicalStructuredProviderTurnId(sessions, input.turnId, input.attemptId);
|
|
1948
1957
|
const existing = sessions.sessions[input.agentId];
|
|
1949
1958
|
const owner = {
|
|
1950
1959
|
scope: "task",
|
|
@@ -1961,7 +1970,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1961
1970
|
throw new Error("Runtime turn completion does not match the effective launch identity.");
|
|
1962
1971
|
}
|
|
1963
1972
|
if (effectiveExisting !== undefined
|
|
1964
|
-
&& hasRecentTurnId(effectiveExisting.recentCompletedTurnIds,
|
|
1973
|
+
&& hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
|
|
1965
1974
|
return { session: effectiveExisting, duplicate: true };
|
|
1966
1975
|
}
|
|
1967
1976
|
if (sessions.inFlight === null
|
|
@@ -1998,9 +2007,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1998
2007
|
: sessions.inFlight === null ? idleStatus : "running",
|
|
1999
2008
|
effective
|
|
2000
2009
|
}, now);
|
|
2001
|
-
sessions = settleStructuredProviderTurn(sessions,
|
|
2010
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus ?? "completed", now);
|
|
2002
2011
|
if (sessions.inFlight === null) {
|
|
2003
|
-
sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId,
|
|
2012
|
+
sessions = rememberRoleAgentCompletedTurn(sessions, input.agentId, input.nativeSessionId, canonicalTurnId, now);
|
|
2004
2013
|
store.saveTaskRoleSessionSet(sessions);
|
|
2005
2014
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
2006
2015
|
// The in-flight fence was cleared by an in-place provider retry
|
|
@@ -2024,7 +2033,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2024
2033
|
sessions = recordTaskRoleTurnBoundary(sessions, {
|
|
2025
2034
|
agentId: input.agentId,
|
|
2026
2035
|
nativeSessionId: input.nativeSessionId,
|
|
2027
|
-
turnId:
|
|
2036
|
+
turnId: canonicalTurnId
|
|
2028
2037
|
}, now);
|
|
2029
2038
|
store.saveTaskRoleSessionSet(sessions);
|
|
2030
2039
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
@@ -2109,27 +2118,16 @@ export class FileSchedulerStoreAdapter {
|
|
|
2109
2118
|
if (role !== null) {
|
|
2110
2119
|
store.saveRole(input.taskId, updateRoleStatus(role, input.roleName === "leader" ? "failed" : "idle", now));
|
|
2111
2120
|
}
|
|
2112
|
-
|
|
2121
|
+
const failureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.turn-failed", {
|
|
2113
2122
|
eventId: input.eventId,
|
|
2114
2123
|
runId: input.runId,
|
|
2115
2124
|
roleName: input.roleName,
|
|
2116
2125
|
outcome: terminal.status
|
|
2117
|
-
}, now)
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
? []
|
|
2123
|
-
: [{ type: "work-item", taskId: input.taskId, id: terminal.workItemId }])
|
|
2124
|
-
]);
|
|
2125
|
-
}
|
|
2126
|
-
else {
|
|
2127
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, summary, now, store.getOperatorNotification(input.taskId)));
|
|
2128
|
-
enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
|
|
2129
|
-
{ type: "task", id: input.taskId },
|
|
2130
|
-
{ type: "run", taskId: input.taskId, id: terminal.id }
|
|
2131
|
-
]);
|
|
2132
|
-
}
|
|
2126
|
+
}, now);
|
|
2127
|
+
store.saveEvent(input.taskId, failureEvent);
|
|
2128
|
+
routeRoleEvent(store, failureEvent, input.roleName, input.roleName !== "leader" && before.purpose === "review"
|
|
2129
|
+
? "review-failed"
|
|
2130
|
+
: input.roleName === "leader" ? "leader-run-failed" : "role-run-failed", now);
|
|
2133
2131
|
return { disposition: "applied", runId: input.runId };
|
|
2134
2132
|
});
|
|
2135
2133
|
}
|
|
@@ -2222,14 +2220,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
2222
2220
|
if (blocked.outcome === "exhausted")
|
|
2223
2221
|
return null;
|
|
2224
2222
|
store.saveAgentRun(withProviderRetry(run, blocked.retry));
|
|
2225
|
-
this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
2223
|
+
const classifiedEvent = this.recordProviderRetryClassified(store, input, classification.errorClass, {
|
|
2226
2224
|
wouldRetry: "false",
|
|
2227
2225
|
shadow: "false",
|
|
2228
2226
|
note: "delivery-deduplication-unproven"
|
|
2229
2227
|
}, now);
|
|
2230
|
-
|
|
2231
|
-
? { kind: "operator" }
|
|
2232
|
-
: { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-delivery-uncertain", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
2228
|
+
routeRoleEvent(store, classifiedEvent, input.roleName, "provider-delivery-uncertain", now);
|
|
2233
2229
|
return { disposition: "applied", runId: input.runId };
|
|
2234
2230
|
}
|
|
2235
2231
|
}
|
|
@@ -2314,22 +2310,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
2314
2310
|
if (retryDecision.outcome === "exhausted")
|
|
2315
2311
|
return null;
|
|
2316
2312
|
store.saveAgentRun(withProviderRetry(run, retryDecision.retry));
|
|
2317
|
-
this.recordProviderRetryClassified(store, input, "policy-denied", {
|
|
2313
|
+
const classifiedEvent = this.recordProviderRetryClassified(store, input, "policy-denied", {
|
|
2318
2314
|
wouldRetry: "false",
|
|
2319
2315
|
shadow: "false",
|
|
2320
2316
|
note: "policy-denied-blocker"
|
|
2321
2317
|
}, now);
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
else {
|
|
2326
|
-
const message = `Leader Run ${input.runId} is blocked by Provider policy: ${summary}`;
|
|
2327
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(input.taskId, message, now, store.getOperatorNotification(input.taskId)));
|
|
2328
|
-
enqueueWork(store, { kind: "operator" }, "leader-provider-policy-blocked", now, [
|
|
2329
|
-
{ type: "task", id: input.taskId },
|
|
2330
|
-
{ type: "run", taskId: input.taskId, id: input.runId }
|
|
2331
|
-
]);
|
|
2332
|
-
}
|
|
2318
|
+
routeRoleEvent(store, classifiedEvent, input.roleName, input.roleName === "leader"
|
|
2319
|
+
? "leader-provider-policy-blocked"
|
|
2320
|
+
: "provider-policy-blocked", now);
|
|
2333
2321
|
return { disposition: "applied", runId: input.runId };
|
|
2334
2322
|
}
|
|
2335
2323
|
/**
|
|
@@ -2355,7 +2343,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2355
2343
|
return null;
|
|
2356
2344
|
store.saveAgentRun(withProviderRetry(run, decision.retry));
|
|
2357
2345
|
const driver = this.drivers.requireByAdapterId(input.adapterId);
|
|
2358
|
-
|
|
2346
|
+
const capacityEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.context-capacity-failure", {
|
|
2359
2347
|
eventId: input.eventId,
|
|
2360
2348
|
runId: input.runId,
|
|
2361
2349
|
roleName: input.roleName,
|
|
@@ -2366,15 +2354,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
2366
2354
|
action: driver.capabilities.lifecycle.compaction === "native-explicit"
|
|
2367
2355
|
? "native-explicit-compaction-required"
|
|
2368
2356
|
: "await-provider-native-recovery"
|
|
2369
|
-
}, now)
|
|
2357
|
+
}, now);
|
|
2358
|
+
store.saveEvent(input.taskId, capacityEvent);
|
|
2370
2359
|
this.recordProviderRetryClassified(store, input, "context-capacity", {
|
|
2371
2360
|
wouldRetry: "false",
|
|
2372
2361
|
shadow: "false",
|
|
2373
2362
|
note: "provider-native-capacity-recovery-only"
|
|
2374
2363
|
}, now);
|
|
2375
|
-
|
|
2376
|
-
? { kind: "operator" }
|
|
2377
|
-
: { kind: "role", taskId: input.taskId, roleName: "leader" }, "provider-context-capacity-recovery-required", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
2364
|
+
routeRoleEvent(store, capacityEvent, input.roleName, "provider-context-capacity-recovery-required", now);
|
|
2378
2365
|
return { disposition: "applied", runId: input.runId };
|
|
2379
2366
|
}
|
|
2380
2367
|
applyNativeSessionRecoverabilityBlock(store, input, summary, now) {
|
|
@@ -2393,27 +2380,28 @@ export class FileSchedulerStoreAdapter {
|
|
|
2393
2380
|
if (decision.outcome === "exhausted")
|
|
2394
2381
|
return null;
|
|
2395
2382
|
store.saveAgentRun(withProviderRetry(run, decision.retry));
|
|
2396
|
-
|
|
2383
|
+
const recoverabilityEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.native-session-recoverability-unproven", {
|
|
2397
2384
|
eventId: input.eventId,
|
|
2398
2385
|
runId: input.runId,
|
|
2399
2386
|
roleName: input.roleName,
|
|
2400
2387
|
launchId: input.launchId,
|
|
2401
2388
|
nativeSessionId: input.nativeSessionId,
|
|
2402
2389
|
requiredEvidence: "exact-native-resume-outcome"
|
|
2403
|
-
}, now)
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
: { kind: "role", taskId: input.taskId, roleName: "leader" }, "native-session-recoverability-unproven", now, [{ type: "run", taskId: input.taskId, id: input.runId }]);
|
|
2390
|
+
}, now);
|
|
2391
|
+
store.saveEvent(input.taskId, recoverabilityEvent);
|
|
2392
|
+
routeRoleEvent(store, recoverabilityEvent, input.roleName, "native-session-recoverability-unproven", now);
|
|
2407
2393
|
return { disposition: "applied", runId: input.runId };
|
|
2408
2394
|
}
|
|
2409
2395
|
recordProviderRetryClassified(store, input, errorClass, extra, now) {
|
|
2410
|
-
|
|
2396
|
+
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-retry-classified", {
|
|
2411
2397
|
eventId: input.eventId,
|
|
2412
2398
|
runId: input.runId,
|
|
2413
2399
|
roleName: input.roleName,
|
|
2414
2400
|
errorClass,
|
|
2415
2401
|
...extra
|
|
2416
|
-
}, now)
|
|
2402
|
+
}, now);
|
|
2403
|
+
store.saveEvent(input.taskId, event);
|
|
2404
|
+
return event;
|
|
2417
2405
|
}
|
|
2418
2406
|
/**
|
|
2419
2407
|
* Issue 04 durable retry timer: lists active Runs whose in-place retry is
|
|
@@ -2474,7 +2462,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2474
2462
|
/**
|
|
2475
2463
|
* Issue 04: reopens each due retry on its original Native Session. A Run
|
|
2476
2464
|
* whose Session is proven dead terminalizes with an exact replacement
|
|
2477
|
-
* blocker; a live Session is
|
|
2465
|
+
* blocker; a live Session is reopened for the existing delivery path, which
|
|
2478
2466
|
* re-pushes the exact same input in the same pass.
|
|
2479
2467
|
*/
|
|
2480
2468
|
resolveDueProviderRetries(now, taskIds) {
|
|
@@ -2507,9 +2495,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
2507
2495
|
|| !providerRetryIsDue(run.providerRetry, now)) {
|
|
2508
2496
|
return;
|
|
2509
2497
|
}
|
|
2510
|
-
|
|
2498
|
+
const sessions = store.getTaskRoleSessionSet(entry.taskId, entry.roleName);
|
|
2511
2499
|
const providerBinding = sessions?.providerBinding;
|
|
2512
2500
|
let recoveryMode = "resume";
|
|
2501
|
+
let replacementRequired = false;
|
|
2513
2502
|
if (providerBinding === null || providerBinding === undefined) {
|
|
2514
2503
|
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider Conversation identity is missing; automatic recovery is fenced.", now);
|
|
2515
2504
|
return;
|
|
@@ -2554,13 +2543,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2554
2543
|
deferUnprovenProviderRecovery(store, run, entry.roleName, "Provider replacement lacks an exact missing-Conversation observation.", now);
|
|
2555
2544
|
return;
|
|
2556
2545
|
}
|
|
2557
|
-
|
|
2558
|
-
const currentSession = sessions?.sessions[run.effective.agentId];
|
|
2559
|
-
if (sessions !== null && currentSession !== undefined
|
|
2560
|
-
&& currentSession.status !== "broken") {
|
|
2561
|
-
sessions = updateRoleAgentSessionStatus(sessions, run.effective.agentId, "broken", now);
|
|
2562
|
-
store.saveTaskRoleSessionSet(sessions);
|
|
2563
|
-
}
|
|
2546
|
+
replacementRequired = true;
|
|
2564
2547
|
}
|
|
2565
2548
|
const session = sessions?.sessions[run.effective.agentId];
|
|
2566
2549
|
const identityMatches = session !== undefined
|
|
@@ -2580,21 +2563,25 @@ export class FileSchedulerStoreAdapter {
|
|
|
2580
2563
|
return;
|
|
2581
2564
|
}
|
|
2582
2565
|
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
2583
|
-
|
|
2584
|
-
&& event.payload.runId === run.id))
|
|
2585
|
-
|
|
2566
|
+
let attentionEvent = store.listEvents(entry.taskId).find((event) => (event.type === "runtime.provider-retry-native-resume-unproven"
|
|
2567
|
+
&& event.payload.runId === run.id));
|
|
2568
|
+
if (attentionEvent === undefined) {
|
|
2569
|
+
attentionEvent = createTaskEvent(store.nextEventId(entry.taskId), entry.taskId, "runtime.provider-retry-native-resume-unproven", {
|
|
2586
2570
|
runId: run.id,
|
|
2587
2571
|
consecutiveFailures: String(run.providerRetry.consecutiveFailures),
|
|
2588
2572
|
dispatchedRetries: String(run.providerRetry.dispatchedRetries)
|
|
2589
|
-
}, now)
|
|
2573
|
+
}, now);
|
|
2574
|
+
store.saveEvent(entry.taskId, attentionEvent);
|
|
2590
2575
|
}
|
|
2591
|
-
|
|
2592
|
-
{ type: "task", id: entry.taskId },
|
|
2593
|
-
{ type: "run", taskId: entry.taskId, id: run.id }
|
|
2594
|
-
]);
|
|
2576
|
+
routeRoleEvent(store, attentionEvent, entry.roleName, "provider-retry-native-resume-unproven", now);
|
|
2595
2577
|
return;
|
|
2596
2578
|
}
|
|
2597
|
-
if (
|
|
2579
|
+
if (runHasActiveRuntimeOperations(store.listEvents(entry.taskId), {
|
|
2580
|
+
taskId: entry.taskId,
|
|
2581
|
+
roleName: entry.roleName,
|
|
2582
|
+
runId: run.id,
|
|
2583
|
+
agentId: run.effective.agentId
|
|
2584
|
+
})) {
|
|
2598
2585
|
const deferred = deferProviderRetry(run.providerRetry, now);
|
|
2599
2586
|
if (deferred === null) {
|
|
2600
2587
|
finalizeProviderRetryDeadline(store, run, "Provider retry stopped because tool/subagent outcome remained uncertain through the bounded episode deadline.", "operation-outcome-uncertain", now);
|
|
@@ -2603,6 +2590,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
2603
2590
|
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
2604
2591
|
return;
|
|
2605
2592
|
}
|
|
2593
|
+
if (replacementRequired) {
|
|
2594
|
+
try {
|
|
2595
|
+
recoveryMode = roleSessionDispatchModeWithConversationSwitch(sessions, store.listEvents(entry.taskId), roleMailbox, entry.roleName, run.effective.agentId, run.effective);
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, error instanceof Error
|
|
2599
|
+
? error.message
|
|
2600
|
+
: "Independent Conversation-switch evaluation failed closed.", now);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
if (recoveryMode !== "new") {
|
|
2604
|
+
deferUnprovenProviderRecovery(store, run, entry.roleName, "Independent Conversation-switch evaluation kept the current Conversation.", now);
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2606
2608
|
// Reopen the Run on its original Session: reset transport markers,
|
|
2607
2609
|
// switch to resume, and mark the projection in-flight. The delivery
|
|
2608
2610
|
// pass re-pushes the exact same input in this same pass.
|
|
@@ -2712,7 +2714,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
2712
2714
|
recordCanonicalObservationObsolete(store, input, "bind-state-missing", now);
|
|
2713
2715
|
return "obsolete";
|
|
2714
2716
|
}
|
|
2715
|
-
const
|
|
2717
|
+
const mailbox = store.getWorkMailbox({
|
|
2718
|
+
kind: "role",
|
|
2719
|
+
taskId,
|
|
2720
|
+
roleName: input.fence.roleName
|
|
2721
|
+
});
|
|
2722
|
+
const existingNativeSessionId = sessions.sessions[input.fence.agentId]
|
|
2723
|
+
?.nativeSessionId;
|
|
2724
|
+
const replacingNativeSession = existingNativeSessionId !== undefined
|
|
2725
|
+
&& existingNativeSessionId !== decision.outcome.nativeSessionId;
|
|
2726
|
+
const replacementBasis = replacingNativeSession
|
|
2727
|
+
? conversationReplacementBasis({
|
|
2728
|
+
sessions,
|
|
2729
|
+
events: store.listEvents(taskId),
|
|
2730
|
+
mailbox,
|
|
2731
|
+
roleName: input.fence.roleName,
|
|
2732
|
+
runId: run.id,
|
|
2733
|
+
runMode: run.mode
|
|
2734
|
+
})
|
|
2735
|
+
: null;
|
|
2736
|
+
if (replacingNativeSession && replacementBasis === null) {
|
|
2737
|
+
recordCanonicalObservationObsolete(store, input, "conversation-switch-not-authorized", now);
|
|
2738
|
+
return "obsolete";
|
|
2739
|
+
}
|
|
2740
|
+
const sessionInput = {
|
|
2716
2741
|
agentId: input.fence.agentId,
|
|
2717
2742
|
adapterId,
|
|
2718
2743
|
nativeSessionId: decision.outcome.nativeSessionId,
|
|
@@ -2720,9 +2745,20 @@ export class FileSchedulerStoreAdapter {
|
|
|
2720
2745
|
policy: "fixed",
|
|
2721
2746
|
status: "running",
|
|
2722
2747
|
effective: run.effective
|
|
2723
|
-
}
|
|
2724
|
-
const
|
|
2748
|
+
};
|
|
2749
|
+
const bound = replacementBasis === null
|
|
2750
|
+
? recordRoleAgentSession(sessions, sessionInput, now)
|
|
2751
|
+
: replaceTaskRoleAgentSessionForConversationSwitch(sessions, sessionInput, now);
|
|
2752
|
+
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis ?? undefined);
|
|
2725
2753
|
store.saveTaskRoleSessionSet(withProvider);
|
|
2754
|
+
if (replacementBasis === "actor-request") {
|
|
2755
|
+
resolveConversationSwitchRequest(store, taskId, input.fence.roleName, sessions, "applied", now, {
|
|
2756
|
+
conversationId: input.fence.conversationId
|
|
2757
|
+
?? decision.outcome.nativeSessionId,
|
|
2758
|
+
activationId: input.fence.activationId ?? input.fence.launchId,
|
|
2759
|
+
launchId: input.fence.launchId
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2726
2762
|
const driver = this.drivers.require(input.fence.driverId);
|
|
2727
2763
|
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
2728
2764
|
&& run.pushedAt === undefined) {
|
|
@@ -2943,7 +2979,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
2943
2979
|
return null;
|
|
2944
2980
|
const sessionSet = store.getTaskRoleSessionSet(fence.taskId, fence.roleName);
|
|
2945
2981
|
const session = sessionSet?.sessions[fence.agentId] ?? null;
|
|
2946
|
-
const boundNativeSessionId = session?.nativeSessionId;
|
|
2947
2982
|
if (runId === undefined) {
|
|
2948
2983
|
return {
|
|
2949
2984
|
fence,
|
|
@@ -2952,7 +2987,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2952
2987
|
pushed: false,
|
|
2953
2988
|
accepted: false,
|
|
2954
2989
|
terminal: false,
|
|
2955
|
-
...(
|
|
2990
|
+
...(session?.nativeSessionId === undefined
|
|
2991
|
+
? {}
|
|
2992
|
+
: { boundNativeSessionId: session.nativeSessionId })
|
|
2956
2993
|
};
|
|
2957
2994
|
}
|
|
2958
2995
|
const run = store.getAgentRun(fence.taskId, runId);
|
|
@@ -2964,8 +3001,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
2964
3001
|
|| inFlight.agentId !== run.effective.agentId)
|
|
2965
3002
|
return null;
|
|
2966
3003
|
const owner = { scope: "task", taskId: fence.taskId, roleName: fence.roleName };
|
|
2967
|
-
const
|
|
2968
|
-
|
|
3004
|
+
const freshConversationLaunch = run.mode === "new"
|
|
3005
|
+
&& runtimeHookMatchesReservation(store, owner, fence.launchId);
|
|
3006
|
+
const boundNativeSessionId = freshConversationLaunch
|
|
3007
|
+
? undefined
|
|
3008
|
+
: session?.nativeSessionId;
|
|
3009
|
+
const launchId = freshConversationLaunch
|
|
3010
|
+
? fence.launchId
|
|
3011
|
+
: session?.launchId
|
|
3012
|
+
?? (runtimeHookMatchesReservation(store, owner, fence.launchId) ? fence.launchId : undefined);
|
|
2969
3013
|
if (launchId === undefined)
|
|
2970
3014
|
return null;
|
|
2971
3015
|
const expectedFence = {
|
|
@@ -3027,7 +3071,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
3027
3071
|
&& !runtimeHookMatchesReservation(store, owner, input.launchId)) {
|
|
3028
3072
|
throw new Error("Runtime turn completion does not match the global launch reservation.");
|
|
3029
3073
|
}
|
|
3030
|
-
const
|
|
3074
|
+
const nativeSessionId = globalCompletionNativeSessionId(existing, input);
|
|
3075
|
+
const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
3031
3076
|
const effective = globalSessionEffective(role, effectiveExisting);
|
|
3032
3077
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
3033
3078
|
throw new Error("Runtime turn completion does not match the effective global launch identity.");
|
|
@@ -3039,7 +3084,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
3039
3084
|
current = recordRoleAgentSession(current, {
|
|
3040
3085
|
agentId: input.agentId,
|
|
3041
3086
|
adapterId: input.adapterId,
|
|
3042
|
-
nativeSessionId
|
|
3087
|
+
nativeSessionId,
|
|
3043
3088
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
3044
3089
|
title: effectiveExisting?.title ?? input.title,
|
|
3045
3090
|
preview: effectiveExisting?.preview ?? (input.summary === undefined ? undefined : sessionPreview(input.summary)),
|
|
@@ -3047,7 +3092,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
3047
3092
|
status: completedStatus,
|
|
3048
3093
|
effective
|
|
3049
3094
|
}, now);
|
|
3050
|
-
current = rememberRoleAgentCompletedTurn(current, input.agentId,
|
|
3095
|
+
current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.turnId, now);
|
|
3051
3096
|
store.saveGlobalRoleSessionSet(current);
|
|
3052
3097
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
3053
3098
|
return current.sessions[input.agentId];
|
|
@@ -3066,9 +3111,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
3066
3111
|
if (existing === undefined
|
|
3067
3112
|
&& !runtimeHookMatchesReservation(this.store, owner, input.launchId))
|
|
3068
3113
|
return "obsolete";
|
|
3114
|
+
const nativeSessionId = globalCompletionNativeSessionId(existing, input);
|
|
3069
3115
|
let effectiveExisting;
|
|
3070
3116
|
try {
|
|
3071
|
-
effectiveExisting = nativeTransitionExisting(this.store, owner, existing,
|
|
3117
|
+
effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.launchId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
3072
3118
|
}
|
|
3073
3119
|
catch {
|
|
3074
3120
|
return "obsolete";
|
|
@@ -3080,14 +3126,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
3080
3126
|
return "apply";
|
|
3081
3127
|
}
|
|
3082
3128
|
}
|
|
3083
|
-
function hasUnsettledRuntimeOperations(store, taskId, runId) {
|
|
3084
|
-
return store.listEvents(taskId).some((event) => {
|
|
3085
|
-
const observation = runtimeObservationFromTaskEvent(event);
|
|
3086
|
-
return observation !== null
|
|
3087
|
-
&& observation.fence.runId === runId
|
|
3088
|
-
&& observation.kind === "operation.started";
|
|
3089
|
-
});
|
|
3090
|
-
}
|
|
3091
3129
|
function runtimeTurnFailureSummary(input) {
|
|
3092
3130
|
return [
|
|
3093
3131
|
"Agent runtime turn failed.",
|
|
@@ -3313,9 +3351,12 @@ function markRuntimeOwnerSessionStopped(store, owner, now) {
|
|
|
3313
3351
|
if (sessions === null)
|
|
3314
3352
|
return false;
|
|
3315
3353
|
const active = sessions.sessions[sessions.activeAgentId];
|
|
3316
|
-
if (active === undefined
|
|
3354
|
+
if (active === undefined)
|
|
3317
3355
|
return false;
|
|
3318
|
-
|
|
3356
|
+
const stopped = stopTaskRoleRuntimeAfterPhysicalExit(sessions, now);
|
|
3357
|
+
if (stopped === sessions)
|
|
3358
|
+
return false;
|
|
3359
|
+
store.saveTaskRoleSessionSet(stopped);
|
|
3319
3360
|
return true;
|
|
3320
3361
|
}
|
|
3321
3362
|
const sessions = store.getGlobalRoleSessionSet(owner.roleName);
|
|
@@ -3369,7 +3410,7 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
|
|
|
3369
3410
|
if (terminalization.disposition !== "applied" || terminalization.run === null)
|
|
3370
3411
|
return false;
|
|
3371
3412
|
const terminal = terminalization.run;
|
|
3372
|
-
|
|
3413
|
+
const exhaustionEvent = createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-retry-exhausted", {
|
|
3373
3414
|
runId: run.id,
|
|
3374
3415
|
roleName: run.roleName,
|
|
3375
3416
|
episodeId: retry.episodeId,
|
|
@@ -3377,7 +3418,8 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
|
|
|
3377
3418
|
consecutiveFailures: String(retry.consecutiveFailures),
|
|
3378
3419
|
dispatchedRetries: String(retry.dispatchedRetries),
|
|
3379
3420
|
elapsedMs: String(Math.max(0, now.getTime() - Date.parse(retry.firstFailureAt)))
|
|
3380
|
-
}, now)
|
|
3421
|
+
}, now);
|
|
3422
|
+
store.saveEvent(run.taskId, exhaustionEvent);
|
|
3381
3423
|
if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
|
|
3382
3424
|
const item = store.getWorkItem(run.taskId, terminal.workItemId);
|
|
3383
3425
|
if (item !== null
|
|
@@ -3392,15 +3434,8 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
|
|
|
3392
3434
|
}
|
|
3393
3435
|
if (run.roleName === "leader") {
|
|
3394
3436
|
store.saveLeaderFailure(recordLeaderFailure(run.taskId, retry.nativeSessionId ?? "(unproven)", summary, now, store.getLeaderFailure(run.taskId)));
|
|
3395
|
-
store.saveOperatorNotification(createLeaderRecoveryNotification(run.taskId, summary, now, store.getOperatorNotification(run.taskId)));
|
|
3396
|
-
enqueueWork(store, { kind: "operator" }, "provider-retry-exhausted", now, [
|
|
3397
|
-
{ type: "task", id: run.taskId },
|
|
3398
|
-
{ type: "run", taskId: run.taskId, id: run.id }
|
|
3399
|
-
]);
|
|
3400
|
-
}
|
|
3401
|
-
else {
|
|
3402
|
-
enqueueWork(store, { kind: "role", taskId: run.taskId, roleName: "leader" }, "provider-retry-exhausted", now, [{ type: "run", taskId: run.taskId, id: run.id }]);
|
|
3403
3437
|
}
|
|
3438
|
+
routeRoleEvent(store, exhaustionEvent, run.roleName, "provider-retry-exhausted", now);
|
|
3404
3439
|
return true;
|
|
3405
3440
|
}
|
|
3406
3441
|
function preparedDeliveryFailureSessionFenceMatches(sessions, session, input, expectedReceiptId) {
|
|
@@ -3475,7 +3510,7 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
3475
3510
|
const effective = input.effective === undefined
|
|
3476
3511
|
? resolvedEffective
|
|
3477
3512
|
: validateEffectiveLaunchSnapshot(input.effective);
|
|
3478
|
-
if (!
|
|
3513
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskSession(resolvedEffective, effective)) {
|
|
3479
3514
|
throw new Error("Reserved native Session effective launch changed before persistence.");
|
|
3480
3515
|
}
|
|
3481
3516
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
@@ -3536,6 +3571,17 @@ function nativeTransitionExisting(store, owner, existing, nativeSessionId, launc
|
|
|
3536
3571
|
}
|
|
3537
3572
|
throw new Error(conflictMessage);
|
|
3538
3573
|
}
|
|
3574
|
+
function globalCompletionNativeSessionId(existing, input) {
|
|
3575
|
+
// Codex app-server and its notify Hook expose different native IDs for the
|
|
3576
|
+
// same launch. The launch generation remains the authoritative fence.
|
|
3577
|
+
return input.adapterId === "codex"
|
|
3578
|
+
&& input.launchId !== undefined
|
|
3579
|
+
&& existing?.launchId === input.launchId
|
|
3580
|
+
&& existing.agentId === input.agentId
|
|
3581
|
+
&& existing.adapterId === input.adapterId
|
|
3582
|
+
? existing.nativeSessionId
|
|
3583
|
+
: input.nativeSessionId;
|
|
3584
|
+
}
|
|
3539
3585
|
function mapRole(store, role) {
|
|
3540
3586
|
const binding = activeRoleAgentBinding(role);
|
|
3541
3587
|
const item = store.listWorkItems(role.taskId).find((candidate) => (candidate.assignee === role.name
|
|
@@ -3581,8 +3627,7 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
|
|
|
3581
3627
|
throw new Error(`Native Session registration does not match the effective Run Agent: ${taskId}/${roleName}.`);
|
|
3582
3628
|
}
|
|
3583
3629
|
if (existing !== undefined) {
|
|
3584
|
-
|
|
3585
|
-
if (!effectiveLaunchSnapshotsCompatibleForTaskMain(existing.effective, active.effective, workspace)) {
|
|
3630
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskSession(existing.effective, active.effective)) {
|
|
3586
3631
|
throw new Error(`Native Session effective launch does not match the active Run: ${taskId}/${roleName}.`);
|
|
3587
3632
|
}
|
|
3588
3633
|
return existing.effective;
|
|
@@ -3727,6 +3772,19 @@ function schedulerRoleSessionsMatch(current, expected) {
|
|
|
3727
3772
|
&& current.launchId === expected.launchId
|
|
3728
3773
|
&& isDeepStrictEqual(current.effective, expected.effective);
|
|
3729
3774
|
}
|
|
3775
|
+
/**
|
|
3776
|
+
* The durable Run/mailbox claim is the pre-Host delivery intent. The Host may
|
|
3777
|
+
* create a ProviderTurn only while that exact reservation still owns the Run;
|
|
3778
|
+
* this closes the gap where a Session could start without any user Turn.
|
|
3779
|
+
*/
|
|
3780
|
+
function processingOwnsExactInitialDelivery(mailbox, taskId, runId, attemptId) {
|
|
3781
|
+
const processing = mailbox?.processing;
|
|
3782
|
+
return processing?.batchId === attemptId
|
|
3783
|
+
&& processing.owner === "controller"
|
|
3784
|
+
&& processing.executionRef?.type === "run"
|
|
3785
|
+
&& processing.executionRef.taskId === taskId
|
|
3786
|
+
&& processing.executionRef.id === runId;
|
|
3787
|
+
}
|
|
3730
3788
|
function matchesStallSessionFence(current, expected) {
|
|
3731
3789
|
if (current === null || expected === null)
|
|
3732
3790
|
return current === expected;
|
|
@@ -3788,11 +3846,10 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
3788
3846
|
};
|
|
3789
3847
|
}
|
|
3790
3848
|
/**
|
|
3791
|
-
*
|
|
3792
|
-
*
|
|
3793
|
-
*
|
|
3794
|
-
*
|
|
3795
|
-
* proven before an unchanged later snapshot arrived.
|
|
3849
|
+
* Usage observations and incomplete request boundaries are authoritative
|
|
3850
|
+
* read-only history: retain each one so consecutive deltas remain projectable.
|
|
3851
|
+
* Other activity observations are a current explicit boundary and may replace
|
|
3852
|
+
* their predecessor. Token evidence never becomes lifecycle activity.
|
|
3796
3853
|
*/
|
|
3797
3854
|
function compactedRuntimeObservationIds(events, incoming) {
|
|
3798
3855
|
const existing = events.flatMap((event) => {
|
|
@@ -3809,9 +3866,10 @@ function compactedRuntimeObservationIds(events, incoming) {
|
|
|
3809
3866
|
});
|
|
3810
3867
|
const remove = ({ observation }) => {
|
|
3811
3868
|
if (incoming.kind === "activity.observed") {
|
|
3869
|
+
if (isRuntimeTokenEvidence(incoming))
|
|
3870
|
+
return false;
|
|
3812
3871
|
return observation.kind === "activity.observed"
|
|
3813
|
-
&& (observation
|
|
3814
|
-
=== (incoming.payload.usage === undefined);
|
|
3872
|
+
&& !isRuntimeTokenEvidence(observation);
|
|
3815
3873
|
}
|
|
3816
3874
|
if (incoming.kind === "operation.started") {
|
|
3817
3875
|
return (observation.kind === "operation.started"
|
|
@@ -3854,7 +3912,6 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
|
3854
3912
|
if (current === null || attemptId === undefined || turnId === undefined)
|
|
3855
3913
|
return sessions;
|
|
3856
3914
|
if (current.turn?.attemptId === attemptId
|
|
3857
|
-
&& current.turn.turnId === turnId
|
|
3858
3915
|
&& ["accepted", "running", "completed", "failed", "cancelled"].includes(current.turn.status))
|
|
3859
3916
|
return sessions;
|
|
3860
3917
|
const binding = acceptProviderTurn(current, {
|
|
@@ -3864,7 +3921,21 @@ function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
|
3864
3921
|
});
|
|
3865
3922
|
return updateTaskRoleProviderRuntime(sessions, binding, now);
|
|
3866
3923
|
}
|
|
3867
|
-
|
|
3924
|
+
/**
|
|
3925
|
+
* A Provider may expose the same accepted input through more than one exact
|
|
3926
|
+
* observation surface. Preserve the first native Turn identity bound to the
|
|
3927
|
+
* durable attempt; a later transport alias may settle that same attempt but
|
|
3928
|
+
* must neither replace nor strand the canonical identity.
|
|
3929
|
+
*/
|
|
3930
|
+
function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId) {
|
|
3931
|
+
const turn = sessions.providerBinding?.turn;
|
|
3932
|
+
return attemptId !== undefined
|
|
3933
|
+
&& turn?.attemptId === attemptId
|
|
3934
|
+
&& turn.turnId !== undefined
|
|
3935
|
+
? turn.turnId
|
|
3936
|
+
: observedTurnId;
|
|
3937
|
+
}
|
|
3938
|
+
function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
|
|
3868
3939
|
if (sessions.inFlight === null)
|
|
3869
3940
|
return sessions;
|
|
3870
3941
|
const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
@@ -3899,9 +3970,22 @@ function bindOrSupersedeProviderRuntime(sessions, input, now) {
|
|
|
3899
3970
|
conversationId,
|
|
3900
3971
|
activationId,
|
|
3901
3972
|
switchedAt: input.observedAt ?? input.receivedAt,
|
|
3902
|
-
noUnsettledInputDelivery
|
|
3973
|
+
noUnsettledInputDelivery,
|
|
3974
|
+
...(replacementBasis === undefined ? {} : { basis: replacementBasis })
|
|
3903
3975
|
}), now);
|
|
3904
3976
|
}
|
|
3977
|
+
function resolveConversationSwitchRequest(store, taskId, roleName, previousSessions, status, now, replacement) {
|
|
3978
|
+
const request = pendingConversationSwitch(store.listEvents(taskId), roleName, previousSessions);
|
|
3979
|
+
if (request === null)
|
|
3980
|
+
return;
|
|
3981
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, CONVERSATION_SWITCH_RESOLVED_EVENT, {
|
|
3982
|
+
requestId: request.requestId,
|
|
3983
|
+
roleName,
|
|
3984
|
+
generation: request.generation,
|
|
3985
|
+
status,
|
|
3986
|
+
...(replacement === undefined ? {} : replacement)
|
|
3987
|
+
}, now));
|
|
3988
|
+
}
|
|
3905
3989
|
function rejectSubmittingProviderTurn(store, target, delivery, now, reason) {
|
|
3906
3990
|
if (target.kind !== "role"
|
|
3907
3991
|
|| delivery.executionRef.type !== "run"
|
|
@@ -3931,16 +4015,14 @@ function deferUnprovenProviderRecovery(store, run, roleName, reason, now) {
|
|
|
3931
4015
|
return;
|
|
3932
4016
|
}
|
|
3933
4017
|
store.saveAgentRun(withProviderRetry(run, deferred));
|
|
3934
|
-
|
|
4018
|
+
const attentionEvent = createTaskEvent(store.nextEventId(run.taskId), run.taskId, "runtime.provider-recovery-attention", {
|
|
3935
4019
|
runId: run.id,
|
|
3936
4020
|
roleName,
|
|
3937
4021
|
reason,
|
|
3938
4022
|
nextAttemptAt: deferred.nextAttemptAt ?? ""
|
|
3939
|
-
}, now)
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
{ type: "run", taskId: run.taskId, id: run.id }
|
|
3943
|
-
]);
|
|
4023
|
+
}, now);
|
|
4024
|
+
store.saveEvent(run.taskId, attentionEvent);
|
|
4025
|
+
routeRoleEvent(store, attentionEvent, roleName, "provider-recovery-attention", now);
|
|
3944
4026
|
}
|
|
3945
4027
|
function markSubmittingProviderTurnUnknown(store, target, delivery, now, reason) {
|
|
3946
4028
|
if (target.kind !== "role"
|
|
@@ -3974,39 +4056,6 @@ function settleStructuredProviderTurn(sessions, turnId, status, now) {
|
|
|
3974
4056
|
settledAt: now.toISOString()
|
|
3975
4057
|
}), now);
|
|
3976
4058
|
}
|
|
3977
|
-
function confirmedUsageActivityObservation(events, incoming) {
|
|
3978
|
-
const usage = incoming.payload.usage;
|
|
3979
|
-
if (incoming.kind !== "activity.observed" || usage === undefined)
|
|
3980
|
-
return null;
|
|
3981
|
-
const previous = events
|
|
3982
|
-
.map(runtimeObservationFromTaskEvent)
|
|
3983
|
-
.filter((observation) => (observation !== null
|
|
3984
|
-
&& observation.kind === "activity.observed"
|
|
3985
|
-
&& observation.payload.usage !== undefined
|
|
3986
|
-
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)))
|
|
3987
|
-
.sort(compareCanonicalObservationOrder)
|
|
3988
|
-
.at(-1)?.payload.usage;
|
|
3989
|
-
if (previous === undefined
|
|
3990
|
-
|| usage.inputTokens + usage.outputTokens
|
|
3991
|
-
<= previous.inputTokens + previous.outputTokens)
|
|
3992
|
-
return null;
|
|
3993
|
-
const digest = createHash("sha256")
|
|
3994
|
-
.update(`confirmed-runtime-activity\0${incoming.eventId}`)
|
|
3995
|
-
.digest("hex");
|
|
3996
|
-
return createRuntimeObservation({
|
|
3997
|
-
schemaVersion: 2,
|
|
3998
|
-
eventId: `runtime-activity-${digest}`,
|
|
3999
|
-
semanticKey: `runtime-activity-${digest}`,
|
|
4000
|
-
kind: "activity.observed",
|
|
4001
|
-
authority: "controller",
|
|
4002
|
-
receivedAt: incoming.receivedAt,
|
|
4003
|
-
...(incoming.observedAt === undefined ? {} : { observedAt: incoming.observedAt }),
|
|
4004
|
-
...(incoming.sequence === undefined ? {} : { sequence: incoming.sequence }),
|
|
4005
|
-
...(incoming.ordinal === undefined ? {} : { ordinal: incoming.ordinal }),
|
|
4006
|
-
fence: incoming.fence,
|
|
4007
|
-
payload: { activity: incoming.payload.activity ?? "model" }
|
|
4008
|
-
});
|
|
4009
|
-
}
|
|
4010
4059
|
function usageSnapshotIsSuperseded(events, incoming) {
|
|
4011
4060
|
if (incoming.kind !== "activity.observed" || incoming.payload.usage === undefined) {
|
|
4012
4061
|
return false;
|
|
@@ -4019,6 +4068,15 @@ function usageSnapshotIsSuperseded(events, incoming) {
|
|
|
4019
4068
|
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)
|
|
4020
4069
|
&& observationIsStrictlyNewer(observation, incoming));
|
|
4021
4070
|
}
|
|
4071
|
+
function hasPersistedRuntimeObservation(events, incoming) {
|
|
4072
|
+
const usageIdentity = isRuntimeTokenEvidence(incoming)
|
|
4073
|
+
? incoming.eventId
|
|
4074
|
+
: undefined;
|
|
4075
|
+
return events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
4076
|
+
&& (usageIdentity === undefined
|
|
4077
|
+
? event.payload.semanticKey === incoming.semanticKey
|
|
4078
|
+
: event.payload.eventId === usageIdentity)));
|
|
4079
|
+
}
|
|
4022
4080
|
function observationIsStrictlyNewer(left, right) {
|
|
4023
4081
|
return compareCanonicalObservationOrder(left, right) > 0;
|
|
4024
4082
|
}
|