@zq-silk/yui 0.14.1 → 0.14.2
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 +64 -35
- package/README.md +254 -124
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +16 -82
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +164 -58
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +22 -0
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -12,8 +12,8 @@ import { activeRoleAgentBinding } from "../role/role.js";
|
|
|
12
12
|
import { effectiveLaunchWithTaskMainWorkspace, effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
13
13
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
14
14
|
import { appendTurnInput, createTurn } from "../turn/turn.js";
|
|
15
|
+
import { transportAgentResult } from "../domain/agentResultTransport.js";
|
|
15
16
|
import { createTurnInput } from "../context/turnInputContract.js";
|
|
16
|
-
import { parseReviewResultReport } from "../review/reviewRound.js";
|
|
17
17
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
18
18
|
import { terminalizeExactTaskTurn } from "../lifecycle/exactTurnTerminalization.js";
|
|
19
19
|
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
@@ -28,13 +28,14 @@ import { projectProviderContinuations } from "../runtime/runtimeContinuationProj
|
|
|
28
28
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
29
29
|
import { formatTurnReceiptId } from "../task/taskRecordReference.js";
|
|
30
30
|
import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasPending, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
|
|
31
|
-
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
31
|
+
import { enqueueWork, settleRoleTurnDispatch as settleRoleTurnDispatchMailbox } from "../coordination/workMailboxQueue.js";
|
|
32
32
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, RuntimeLifecycleBusyError, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, isRuntimeLaunchReservation, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
33
33
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
34
34
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
35
35
|
import { standardAgentError } from "../runtime/agentError.js";
|
|
36
36
|
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationTurnFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
37
37
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
38
|
+
import { snapshotExecutionLaneWorkspaceSync } from "../repository/executionLaneGitSnapshot.js";
|
|
38
39
|
import { contextSnapshotDeltaRefIds, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
|
|
39
40
|
export class AgentHostProviderTurnFenceError extends Error {
|
|
40
41
|
constructor(message) {
|
|
@@ -47,11 +48,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
47
48
|
store;
|
|
48
49
|
telemetry;
|
|
49
50
|
drivers;
|
|
51
|
+
snapshotExecutionLaneWorkspace;
|
|
50
52
|
/** Diagnostic telemetry is optional and never participates in runtime truth. */
|
|
51
|
-
constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry()) {
|
|
53
|
+
constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry(), snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspaceSync) {
|
|
52
54
|
this.store = store;
|
|
53
55
|
this.telemetry = telemetry;
|
|
54
56
|
this.drivers = drivers;
|
|
57
|
+
this.snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspace;
|
|
55
58
|
}
|
|
56
59
|
freezeLeaderContextSnapshot(taskId, roleName, now) {
|
|
57
60
|
return this.store.transaction((tx) => {
|
|
@@ -96,29 +99,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
96
99
|
case "input.accepted":
|
|
97
100
|
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
98
101
|
break;
|
|
99
|
-
case "turn.completed":
|
|
100
|
-
|
|
101
|
-
taskId,
|
|
102
|
-
roleName: input.fence.roleName,
|
|
103
|
-
agentId: input.fence.agentId,
|
|
104
|
-
adapterId,
|
|
105
|
-
launchId: input.fence.launchId,
|
|
106
|
-
nativeSessionId: input.fence.nativeSessionId,
|
|
107
|
-
nativeTurnId: input.fence.nativeTurnId,
|
|
108
|
-
attemptId: input.fence.receiptId,
|
|
109
|
-
turnId: input.fence.turnId
|
|
110
|
-
});
|
|
111
|
-
if (classification !== "apply")
|
|
112
|
-
return classification;
|
|
113
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", input.payload.summary ?? "Agent turn completed without a workflow outcome.", now);
|
|
102
|
+
case "turn.completed":
|
|
103
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "completed", now);
|
|
114
104
|
break;
|
|
115
|
-
}
|
|
116
105
|
case "turn.failed": {
|
|
117
|
-
const failureEvidence = input.payload.failure;
|
|
118
106
|
// A Provider Turn is an activation boundary, not a Task outcome.
|
|
119
107
|
// Keep the Turn and Session identity available for Agent-directed
|
|
120
108
|
// restore + submit, even when a provider marks its Turn terminal.
|
|
121
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed",
|
|
109
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now);
|
|
122
110
|
break;
|
|
123
111
|
}
|
|
124
112
|
case "operation.started":
|
|
@@ -135,7 +123,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
135
123
|
outcome = this.validateCanonicalTurnObservation(input, now);
|
|
136
124
|
break;
|
|
137
125
|
case "turn.cancelled": {
|
|
138
|
-
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled",
|
|
126
|
+
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now);
|
|
139
127
|
break;
|
|
140
128
|
}
|
|
141
129
|
case "conversation.observed":
|
|
@@ -220,9 +208,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
220
208
|
&& run.roleName === input.fence.roleName
|
|
221
209
|
&& run.effective.agentId === input.fence.agentId
|
|
222
210
|
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
223
|
-
&& (knownContinuation || (session?.
|
|
224
|
-
&& session.nativeSessionId === input.fence.nativeSessionId
|
|
225
|
-
&& input.fence.sessionGenerationId === input.fence.launchId))
|
|
211
|
+
&& (knownContinuation || (session?.runtimeGenerationId === input.fence.runtimeGenerationId
|
|
212
|
+
&& session.nativeSessionId === input.fence.nativeSessionId))
|
|
226
213
|
&& runtimeReceiptBelongsToTurn(store, input);
|
|
227
214
|
if (!valid) {
|
|
228
215
|
recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
|
|
@@ -231,25 +218,42 @@ export class FileSchedulerStoreAdapter {
|
|
|
231
218
|
return "applied";
|
|
232
219
|
});
|
|
233
220
|
}
|
|
234
|
-
foldProviderTurnBoundary(input, adapterId, providerStatus,
|
|
221
|
+
foldProviderTurnBoundary(input, adapterId, providerStatus, now) {
|
|
222
|
+
const outcome = providerStatus === "completed"
|
|
223
|
+
? input.payload.resultTransportDiagnostic === undefined
|
|
224
|
+
? transportAgentResult(input.payload.output)
|
|
225
|
+
: {
|
|
226
|
+
status: "failed",
|
|
227
|
+
diagnostic: input.payload.resultTransportDiagnostic,
|
|
228
|
+
failureReason: "missing-result"
|
|
229
|
+
}
|
|
230
|
+
: {
|
|
231
|
+
status: "failed",
|
|
232
|
+
diagnostic: providerStatus === "cancelled"
|
|
233
|
+
? "Provider cancelled the Agent Turn."
|
|
234
|
+
: `Provider Agent Turn failed: ${input.payload.failure?.error.message ?? "unknown provider failure"}`,
|
|
235
|
+
failureReason: providerStatus === "cancelled"
|
|
236
|
+
? "cancelled"
|
|
237
|
+
: "runtime-failed"
|
|
238
|
+
};
|
|
235
239
|
const completed = {
|
|
236
240
|
taskId: input.fence.taskId,
|
|
237
241
|
roleName: input.fence.roleName,
|
|
238
242
|
agentId: input.fence.agentId,
|
|
239
243
|
adapterId,
|
|
240
|
-
|
|
244
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
241
245
|
nativeSessionId: input.fence.nativeSessionId,
|
|
242
246
|
nativeTurnId: input.fence.nativeTurnId,
|
|
243
247
|
attemptId: input.fence.receiptId,
|
|
244
248
|
turnId: input.fence.turnId,
|
|
245
249
|
...(input.payload.input === undefined ? {} : { input: input.payload.input }),
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
providerStatus,
|
|
251
|
+
outcome
|
|
248
252
|
};
|
|
249
|
-
const classification = this.
|
|
253
|
+
const classification = this.classifyRuntimeTurnTerminal(completed);
|
|
250
254
|
if (classification !== "apply")
|
|
251
255
|
return classification;
|
|
252
|
-
const result = this.
|
|
256
|
+
const result = this.observeRuntimeTurnTerminal(completed, now);
|
|
253
257
|
return result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
254
258
|
}
|
|
255
259
|
validateCanonicalSessionObservation(input, now) {
|
|
@@ -261,16 +265,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
261
265
|
|| run.roleName !== input.fence.roleName
|
|
262
266
|
|| run.effective.agentId !== input.fence.agentId
|
|
263
267
|
|| this.drivers.requireByAdapterId(run.effective.adapterId).id !== input.fence.driverId
|
|
264
|
-
|| session?.
|
|
265
|
-
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
266
|
-
|| input.fence.sessionGenerationId !== input.fence.launchId) {
|
|
268
|
+
|| session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
269
|
+
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
267
270
|
recordCanonicalObservationObsolete(store, input, "runtime-session-not-current", now);
|
|
268
271
|
return "obsolete";
|
|
269
272
|
}
|
|
270
273
|
const status = "ended";
|
|
271
274
|
let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now, input.kind === "session.failed" ? "failed" : "stopped");
|
|
272
275
|
if (updatedSessions.providerBinding !== null) {
|
|
273
|
-
const activationId = input.fence.activationId ?? input.fence.
|
|
276
|
+
const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
|
|
274
277
|
updatedSessions = updateTaskRoleProviderRuntime(updatedSessions, endProviderActivation(updatedSessions.providerBinding, activationId, {
|
|
275
278
|
status: input.kind === "session.failed" ? "failed" : "ended",
|
|
276
279
|
endedAt: input.observedAt ?? input.receivedAt,
|
|
@@ -283,14 +286,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
283
286
|
|| continuation.identity.conversationId
|
|
284
287
|
!== (input.fence.conversationId ?? input.fence.nativeSessionId)
|
|
285
288
|
|| continuation.identity.activationId
|
|
286
|
-
!== (input.fence.activationId ?? input.fence.
|
|
289
|
+
!== (input.fence.activationId ?? input.fence.runtimeGenerationId)
|
|
287
290
|
|| continuation.execution === "quiescent"
|
|
288
291
|
|| continuation.attachment === "detached")
|
|
289
292
|
continue;
|
|
290
293
|
const key = providerContinuationKey(continuation.identity);
|
|
291
294
|
const identityDigest = createHash("sha256").update(key).digest("hex");
|
|
292
295
|
const detached = createRuntimeObservation({
|
|
293
|
-
schemaVersion:
|
|
296
|
+
schemaVersion: 4,
|
|
294
297
|
eventId: `derived-continuation-detached:${identityDigest}`,
|
|
295
298
|
semanticKey: `continuation-detached:${identityDigest}`,
|
|
296
299
|
kind: "continuation.started",
|
|
@@ -319,17 +322,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
319
322
|
}
|
|
320
323
|
});
|
|
321
324
|
const eventId = store.nextEventId(input.fence.taskId);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
taskId: input.fence.taskId,
|
|
326
|
-
roleName: "leader"
|
|
327
|
-
}, "provider-continuation-detached", now, [
|
|
328
|
-
{ type: "event", taskId: input.fence.taskId, id: eventId }
|
|
329
|
-
], {
|
|
330
|
-
source: input.fence.driverId,
|
|
331
|
-
dedupeKey: detached.semanticKey
|
|
332
|
-
});
|
|
325
|
+
const detachedEvent = createTaskEvent(eventId, input.fence.taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(detached), now);
|
|
326
|
+
store.saveEvent(input.fence.taskId, detachedEvent);
|
|
327
|
+
routeContinuationResult(store, detached, detachedEvent, "provider-continuation-detached", now);
|
|
333
328
|
}
|
|
334
329
|
const active = store.getActiveTurn(input.fence.taskId, input.fence.roleName);
|
|
335
330
|
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
@@ -392,7 +387,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
392
387
|
.update(`${input.semanticKey}\u0000${identityKey}`)
|
|
393
388
|
.digest("hex");
|
|
394
389
|
const settled = createRuntimeObservation({
|
|
395
|
-
schemaVersion:
|
|
390
|
+
schemaVersion: 4,
|
|
396
391
|
eventId: `native-snapshot-settled:${digest}`,
|
|
397
392
|
semanticKey: `native-snapshot-settled:${digest}`,
|
|
398
393
|
kind: "continuation.settled",
|
|
@@ -419,21 +414,16 @@ export class FileSchedulerStoreAdapter {
|
|
|
419
414
|
}
|
|
420
415
|
});
|
|
421
416
|
const settledEventId = store.nextEventId(taskId);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
dedupeKey: settled.semanticKey
|
|
426
|
-
});
|
|
417
|
+
const settledEvent = createTaskEvent(settledEventId, taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(settled), now);
|
|
418
|
+
store.saveEvent(taskId, settledEvent);
|
|
419
|
+
routeContinuationResult(store, settled, settledEvent, "provider-continuation-settled", now);
|
|
427
420
|
}
|
|
428
421
|
}
|
|
429
422
|
if (input.kind === "continuation.reported"
|
|
430
423
|
|| input.kind === "continuation.settled") {
|
|
431
|
-
|
|
424
|
+
routeContinuationResult(store, input, observationEvent, input.kind === "continuation.reported"
|
|
432
425
|
? "provider-continuation-report"
|
|
433
|
-
: "provider-continuation-settled", now
|
|
434
|
-
source: input.fence.driverId,
|
|
435
|
-
dedupeKey: input.semanticKey
|
|
436
|
-
});
|
|
426
|
+
: "provider-continuation-settled", now);
|
|
437
427
|
}
|
|
438
428
|
if ((input.kind === "goal.cleared"
|
|
439
429
|
|| (input.kind === "goal.updated" && input.payload.goalStatus !== "active"))
|
|
@@ -456,7 +446,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
456
446
|
agentId: input.fence.agentId,
|
|
457
447
|
adapterId: run?.effective.adapterId ?? "unknown",
|
|
458
448
|
driverId: input.fence.driverId,
|
|
459
|
-
|
|
449
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
460
450
|
nativeSessionId: input.fence.nativeSessionId ?? "",
|
|
461
451
|
nativeTurnId: input.fence.nativeTurnId ?? "",
|
|
462
452
|
source: error.source,
|
|
@@ -823,7 +813,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
823
813
|
|| binding === null || binding === undefined
|
|
824
814
|
|| (input.turnId !== undefined
|
|
825
815
|
&& (active?.id !== input.turnId || active.effective.agentId !== input.agentId))
|
|
826
|
-
|| session?.
|
|
816
|
+
|| session?.runtimeGenerationId !== input.runtimeGenerationId
|
|
827
817
|
|| session.nativeSessionId !== input.nativeSessionId
|
|
828
818
|
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
829
819
|
|| binding.authority.owner !== input.authorityOwner
|
|
@@ -892,7 +882,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
892
882
|
agentId: run?.effective.agentId ?? sessions.activeAgentId,
|
|
893
883
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
894
884
|
driverId: driver?.id ?? "unknown",
|
|
895
|
-
|
|
885
|
+
runtimeGenerationId: session?.runtimeGenerationId ?? "",
|
|
896
886
|
nativeSessionId: session?.nativeSessionId ?? "",
|
|
897
887
|
nativeTurnId: "",
|
|
898
888
|
source: error.source,
|
|
@@ -921,7 +911,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
921
911
|
const session = sessions?.sessions[input.agentId];
|
|
922
912
|
const binding = sessions?.providerBinding;
|
|
923
913
|
if (binding === null || binding === undefined
|
|
924
|
-
|| session?.
|
|
914
|
+
|| session?.runtimeGenerationId !== input.runtimeGenerationId
|
|
925
915
|
|| session.nativeSessionId !== input.nativeSessionId
|
|
926
916
|
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId)
|
|
927
917
|
return null;
|
|
@@ -996,7 +986,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
996
986
|
agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
|
|
997
987
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
998
988
|
driverId: driver?.id ?? "unknown",
|
|
999
|
-
|
|
989
|
+
runtimeGenerationId: session?.runtimeGenerationId ?? "",
|
|
1000
990
|
nativeSessionId: session?.nativeSessionId ?? "",
|
|
1001
991
|
nativeTurnId: "",
|
|
1002
992
|
source: error.source,
|
|
@@ -1048,9 +1038,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1048
1038
|
* the Leader wakeup. A terminal job without its wakeup enqueued is a lost
|
|
1049
1039
|
* wakeup, so the two writes commit together or not at all.
|
|
1050
1040
|
*
|
|
1051
|
-
* f6: The wakeup targets the Leader role mailbox (not the Task mailbox)
|
|
1052
|
-
*
|
|
1053
|
-
* the
|
|
1041
|
+
* f6: The wakeup targets the Leader role mailbox (not the Task mailbox).
|
|
1042
|
+
* That mailbox is also the PendingWakeup authority consumed by
|
|
1043
|
+
* processLeaderWakeups, so the signal must be enqueued exactly once.
|
|
1054
1044
|
*/
|
|
1055
1045
|
transitionDurableJob(taskId, jobId, transition, now, wakeup) {
|
|
1056
1046
|
return this.store.transaction((store) => {
|
|
@@ -1061,7 +1051,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1061
1051
|
store.saveDurableJob(taskId, next);
|
|
1062
1052
|
if (wakeup !== undefined) {
|
|
1063
1053
|
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, wakeup.reason, now, [...wakeup.refs]);
|
|
1064
|
-
queueLeaderWakeup(store, taskId, wakeup.reason, now);
|
|
1065
1054
|
}
|
|
1066
1055
|
return next;
|
|
1067
1056
|
});
|
|
@@ -1098,17 +1087,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1098
1087
|
return { status: "claimed", processing: claimed.processing };
|
|
1099
1088
|
});
|
|
1100
1089
|
}
|
|
1101
|
-
|
|
1102
|
-
return this.store.transaction((store) =>
|
|
1103
|
-
const mailbox = store.getWorkMailbox(target);
|
|
1104
|
-
if (mailbox?.pending === null || mailbox?.pending === undefined
|
|
1105
|
-
|| mailbox.pending.fromSequence !== expected.fromSequence
|
|
1106
|
-
|| mailbox.pending.toSequence < expected.toSequence) {
|
|
1107
|
-
return false;
|
|
1108
|
-
}
|
|
1109
|
-
store.saveWorkMailbox(consumePendingBatch(mailbox, expected));
|
|
1110
|
-
return true;
|
|
1111
|
-
});
|
|
1090
|
+
settleRoleTurnDispatch(input) {
|
|
1091
|
+
return this.store.transaction((store) => (settleRoleTurnDispatchMailbox(store, input, input.expected)));
|
|
1112
1092
|
}
|
|
1113
1093
|
completeWorkMailbox(target, batchId) {
|
|
1114
1094
|
return this.store.transaction((store) => {
|
|
@@ -1195,7 +1175,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1195
1175
|
agentId: candidate.agentId,
|
|
1196
1176
|
adapterId: candidate.adapterId,
|
|
1197
1177
|
nativeSessionId: candidate.nativeSessionId,
|
|
1198
|
-
...(candidate.
|
|
1178
|
+
...(candidate.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: candidate.runtimeGenerationId }),
|
|
1199
1179
|
sessionUpdatedAt: candidate.sessionUpdatedAt
|
|
1200
1180
|
}];
|
|
1201
1181
|
});
|
|
@@ -1369,12 +1349,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
1369
1349
|
&& existing.status === "active";
|
|
1370
1350
|
if (!defersConversationReplacement
|
|
1371
1351
|
&& (existing?.nativeSessionId !== input.session.nativeSessionId
|
|
1372
|
-
|| (input.
|
|
1352
|
+
|| (input.runtimeGenerationId !== undefined && existing.runtimeGenerationId !== input.runtimeGenerationId)
|
|
1373
1353
|
|| existing.status !== "active")) {
|
|
1374
1354
|
saveTaskSession(store, role, {
|
|
1375
1355
|
...input.session,
|
|
1376
1356
|
nativeSessionId: input.session.nativeSessionId
|
|
1377
|
-
}, "active", input.now, input.
|
|
1357
|
+
}, "active", input.now, input.runtimeGenerationId);
|
|
1378
1358
|
}
|
|
1379
1359
|
}
|
|
1380
1360
|
});
|
|
@@ -1407,8 +1387,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1407
1387
|
...(session?.nativeSessionId === undefined
|
|
1408
1388
|
? {}
|
|
1409
1389
|
: { nativeSessionId: session.nativeSessionId }),
|
|
1410
|
-
...(input.
|
|
1411
|
-
outcome: { status: "failed", summary, failureReason: input.failureReason }
|
|
1390
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
1391
|
+
outcome: { status: "failed", diagnostic: summary, failureReason: input.failureReason }
|
|
1412
1392
|
}, input.now);
|
|
1413
1393
|
if (result.disposition !== "applied" || result.turn === null) {
|
|
1414
1394
|
return "state-changed";
|
|
@@ -1444,7 +1424,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1444
1424
|
}
|
|
1445
1425
|
return {
|
|
1446
1426
|
status: "existing",
|
|
1447
|
-
|
|
1427
|
+
runtimeGenerationId: existing.processing.batchId
|
|
1448
1428
|
};
|
|
1449
1429
|
}
|
|
1450
1430
|
if (hasRuntimeLifecycleWork(existing)) {
|
|
@@ -1460,14 +1440,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
1460
1440
|
throw new Error("Runtime launch reservation could not be queued.");
|
|
1461
1441
|
}
|
|
1462
1442
|
const claimed = claimPending(queued, {
|
|
1463
|
-
batchId: input.
|
|
1443
|
+
batchId: input.runtimeGenerationId,
|
|
1464
1444
|
owner: RUNTIME_LIFECYCLE_OWNER,
|
|
1465
1445
|
startedAt: now.toISOString()
|
|
1466
1446
|
});
|
|
1467
1447
|
store.saveWorkMailbox(claimed);
|
|
1468
1448
|
return {
|
|
1469
1449
|
status: "reserved",
|
|
1470
|
-
|
|
1450
|
+
runtimeGenerationId: input.runtimeGenerationId
|
|
1471
1451
|
};
|
|
1472
1452
|
});
|
|
1473
1453
|
}
|
|
@@ -1475,7 +1455,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1475
1455
|
return this.store.transaction((store) => {
|
|
1476
1456
|
assertCurrent();
|
|
1477
1457
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(input.owner));
|
|
1478
|
-
if (isRuntimeLaunchReservation(mailbox?.processing, input.
|
|
1458
|
+
if (isRuntimeLaunchReservation(mailbox?.processing, input.runtimeGenerationId)) {
|
|
1479
1459
|
return "reserved";
|
|
1480
1460
|
}
|
|
1481
1461
|
if (hasRuntimeCleanupObligation(mailbox)) {
|
|
@@ -1488,7 +1468,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1488
1468
|
const sessions = runtimeOwnerSessionSet(store, input.owner);
|
|
1489
1469
|
const active = sessions?.sessions[sessions.activeAgentId];
|
|
1490
1470
|
if (active?.status === "active"
|
|
1491
|
-
&& active.
|
|
1471
|
+
&& active.runtimeGenerationId === input.runtimeGenerationId)
|
|
1492
1472
|
return "provider-bound";
|
|
1493
1473
|
throw new Error("Runtime launch reservation no longer matches the launch.");
|
|
1494
1474
|
});
|
|
@@ -1496,7 +1476,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1496
1476
|
recordReservedRuntimeNativeSession(input, assertCurrent, now = new Date()) {
|
|
1497
1477
|
return this.store.transaction((store) => {
|
|
1498
1478
|
assertCurrent();
|
|
1499
|
-
const mailbox = requireRuntimeLaunchReservation(store, input.owner, input.
|
|
1479
|
+
const mailbox = requireRuntimeLaunchReservation(store, input.owner, input.runtimeGenerationId);
|
|
1500
1480
|
if (hasRuntimeCleanupObligation(mailbox)) {
|
|
1501
1481
|
throw new Error("Runtime cleanup is still pending.");
|
|
1502
1482
|
}
|
|
@@ -1507,7 +1487,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1507
1487
|
agentId: input.agentId,
|
|
1508
1488
|
adapterId: input.adapterId,
|
|
1509
1489
|
nativeSessionId: input.nativeSessionId,
|
|
1510
|
-
|
|
1490
|
+
runtimeGenerationId: input.runtimeGenerationId,
|
|
1511
1491
|
effective: input.effective
|
|
1512
1492
|
}, now)
|
|
1513
1493
|
: recordGlobalRuntimeNativeSession(store, {
|
|
@@ -1515,21 +1495,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
1515
1495
|
agentId: input.agentId,
|
|
1516
1496
|
adapterId: input.adapterId,
|
|
1517
1497
|
nativeSessionId: input.nativeSessionId,
|
|
1518
|
-
|
|
1498
|
+
runtimeGenerationId: input.runtimeGenerationId,
|
|
1519
1499
|
effective: input.effective
|
|
1520
1500
|
}, now);
|
|
1521
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.
|
|
1501
|
+
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.runtimeGenerationId));
|
|
1522
1502
|
return session;
|
|
1523
1503
|
});
|
|
1524
1504
|
}
|
|
1525
|
-
completeRuntimeLaunchReservation(owner,
|
|
1505
|
+
completeRuntimeLaunchReservation(owner, runtimeGenerationId, beforeComplete) {
|
|
1526
1506
|
return this.store.transaction((store) => {
|
|
1527
1507
|
const target = runtimeLifecycleTarget(owner);
|
|
1528
1508
|
const mailbox = store.getWorkMailbox(target);
|
|
1529
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing,
|
|
1509
|
+
if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId))
|
|
1530
1510
|
return false;
|
|
1531
1511
|
beforeComplete?.();
|
|
1532
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox,
|
|
1512
|
+
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, runtimeGenerationId));
|
|
1533
1513
|
return true;
|
|
1534
1514
|
});
|
|
1535
1515
|
}
|
|
@@ -1545,13 +1525,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
1545
1525
|
return this.store.transaction((store) => {
|
|
1546
1526
|
const target = runtimeLifecycleTarget(input.owner);
|
|
1547
1527
|
const mailbox = store.getWorkMailbox(target);
|
|
1548
|
-
if (isRuntimeLaunchReservation(mailbox?.processing, input.
|
|
1528
|
+
if (isRuntimeLaunchReservation(mailbox?.processing, input.runtimeGenerationId)) {
|
|
1549
1529
|
const sessions = runtimeOwnerSessionSet(store, input.owner);
|
|
1550
1530
|
const active = sessions?.sessions[sessions.activeAgentId];
|
|
1551
1531
|
if (runtimeSessionMatchesSettledLaunch(active, input)) {
|
|
1552
1532
|
endRuntimeOwnerSession(store, input.owner, now);
|
|
1553
1533
|
}
|
|
1554
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.
|
|
1534
|
+
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.runtimeGenerationId));
|
|
1555
1535
|
return true;
|
|
1556
1536
|
}
|
|
1557
1537
|
if (hasRuntimeLifecycleWork(mailbox))
|
|
@@ -1577,7 +1557,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1577
1557
|
* retained as an intermediate child wait or recorded as a ready boundary
|
|
1578
1558
|
* for later mailbox input. It never performs tmux, workspace, or Controller I/O.
|
|
1579
1559
|
*/
|
|
1580
|
-
|
|
1560
|
+
classifyRuntimeTurnTerminal(input) {
|
|
1581
1561
|
const task = this.store.getTask(input.taskId);
|
|
1582
1562
|
const role = this.store.getRole(input.taskId, input.roleName);
|
|
1583
1563
|
if (task === null || task.status === "archived" || role === null)
|
|
@@ -1590,10 +1570,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
1590
1570
|
roleName: input.roleName
|
|
1591
1571
|
};
|
|
1592
1572
|
if (existing === undefined
|
|
1593
|
-
&& !runtimeHookMatchesReservation(this.store, owner, input.
|
|
1573
|
+
&& !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
|
|
1594
1574
|
return "obsolete";
|
|
1595
1575
|
try {
|
|
1596
|
-
const effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.
|
|
1576
|
+
const effectiveExisting = nativeTransitionExisting(this.store, owner, existing, input.nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed Role session.");
|
|
1597
1577
|
const effective = taskSessionEffective(this.store, input.taskId, input.roleName, input.agentId, effectiveExisting);
|
|
1598
1578
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
1599
1579
|
return "obsolete";
|
|
@@ -1621,7 +1601,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1621
1601
|
return active === null ? "apply" : "obsolete";
|
|
1622
1602
|
return active?.id === input.turnId ? "apply" : "obsolete";
|
|
1623
1603
|
}
|
|
1624
|
-
|
|
1604
|
+
observeRuntimeTurnTerminal(input, now = new Date()) {
|
|
1625
1605
|
return this.store.transaction((store) => {
|
|
1626
1606
|
const task = store.getTask(input.taskId);
|
|
1627
1607
|
if (task === null)
|
|
@@ -1648,13 +1628,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
1648
1628
|
roleName: input.roleName
|
|
1649
1629
|
};
|
|
1650
1630
|
if (existing === undefined
|
|
1651
|
-
&& !runtimeHookMatchesReservation(store, owner, input.
|
|
1631
|
+
&& !runtimeHookMatchesReservation(store, owner, input.runtimeGenerationId)) {
|
|
1652
1632
|
throw new Error("Runtime turn completion does not match the launch reservation.");
|
|
1653
1633
|
}
|
|
1654
|
-
const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.
|
|
1634
|
+
const effectiveExisting = nativeTransitionExisting(store, owner, existing, input.nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed Role session.");
|
|
1655
1635
|
const effective = taskSessionEffective(store, task.id, role.name, input.agentId, effectiveExisting);
|
|
1656
1636
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
1657
|
-
throw new Error("Runtime turn completion does not match the effective
|
|
1637
|
+
throw new Error("Runtime turn completion does not match the effective runtime generation identity.");
|
|
1658
1638
|
}
|
|
1659
1639
|
if (effectiveExisting !== undefined
|
|
1660
1640
|
&& hasRecentTurnId(effectiveExisting.recentCompletedTurnIds, canonicalTurnId)) {
|
|
@@ -1679,7 +1659,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1679
1659
|
agentId: input.agentId,
|
|
1680
1660
|
adapterId: input.adapterId,
|
|
1681
1661
|
nativeSessionId: input.nativeSessionId,
|
|
1682
|
-
...(input.
|
|
1662
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
1683
1663
|
policy: "fixed",
|
|
1684
1664
|
status: sessionStatus,
|
|
1685
1665
|
...(effectiveExisting?.endReason === undefined
|
|
@@ -1687,46 +1667,44 @@ export class FileSchedulerStoreAdapter {
|
|
|
1687
1667
|
: { endReason: effectiveExisting.endReason }),
|
|
1688
1668
|
effective
|
|
1689
1669
|
}, now);
|
|
1690
|
-
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus
|
|
1670
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalTurnId, input.providerStatus, now);
|
|
1691
1671
|
sessions = recordTaskRoleTurnBoundary(sessions, {
|
|
1692
1672
|
agentId: input.agentId,
|
|
1693
1673
|
nativeSessionId: input.nativeSessionId,
|
|
1694
1674
|
turnId: canonicalTurnId
|
|
1695
1675
|
}, now);
|
|
1696
1676
|
store.saveTaskRoleSessionSet(sessions);
|
|
1697
|
-
completeRuntimeHookReservation(store, owner, input.
|
|
1677
|
+
completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
|
|
1698
1678
|
let terminalTurn;
|
|
1699
1679
|
if (recordedProviderTurn && observedTurn?.status === "active") {
|
|
1700
1680
|
const binding = sessions.providerBinding;
|
|
1701
1681
|
const conversation = currentProviderConversation(binding);
|
|
1702
|
-
const activation = binding.activations.find((entry) => (entry.activationId === input.
|
|
1682
|
+
const activation = binding.activations.find((entry) => (entry.activationId === input.runtimeGenerationId
|
|
1703
1683
|
|| (entry.conversationId === conversation.conversationId && entry.status === "active")));
|
|
1704
1684
|
if (activation === undefined) {
|
|
1705
1685
|
throw new Error("Provider Turn result has no matching Activation.");
|
|
1706
1686
|
}
|
|
1707
|
-
const providerStatus = input.providerStatus
|
|
1708
|
-
let
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
? {}
|
|
1719
|
-
: { evidenceCommit: parsed.evidenceCommit }),
|
|
1720
|
-
...(parsed.deltaDisposition === undefined
|
|
1721
|
-
? {}
|
|
1722
|
-
: { deltaDisposition: parsed.deltaDisposition }),
|
|
1723
|
-
...(parsed.deltaReasoning === undefined
|
|
1724
|
-
? {}
|
|
1725
|
-
: { deltaReasoning: parsed.deltaReasoning })
|
|
1726
|
-
};
|
|
1687
|
+
const providerStatus = input.providerStatus;
|
|
1688
|
+
let systemEvidence;
|
|
1689
|
+
let workspaceFailure;
|
|
1690
|
+
if ((observedTurn.purpose === "execution" || observedTurn.purpose === "review")
|
|
1691
|
+
&& observedTurn.executionGroupId !== undefined
|
|
1692
|
+
&& observedTurn.executionLaneId !== undefined
|
|
1693
|
+
&& observedTurn.workspace !== undefined
|
|
1694
|
+
&& input.outcome.status === "completed") {
|
|
1695
|
+
const gitSnapshot = this.snapshotExecutionLaneWorkspace(store, observedTurn.workspace);
|
|
1696
|
+
if (gitSnapshot.status === "captured") {
|
|
1697
|
+
systemEvidence = { workspaceSnapshot: gitSnapshot.snapshot };
|
|
1727
1698
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1699
|
+
else {
|
|
1700
|
+
workspaceFailure = {
|
|
1701
|
+
failureReason: gitSnapshot.cause === "workspace-dirty"
|
|
1702
|
+
? "workspace-dirty"
|
|
1703
|
+
: gitSnapshot.cause === "branch-mismatch"
|
|
1704
|
+
? "workspace-branch-mismatch"
|
|
1705
|
+
: "workspace-unavailable",
|
|
1706
|
+
diagnostic: gitSnapshot.diagnostic
|
|
1707
|
+
};
|
|
1730
1708
|
}
|
|
1731
1709
|
}
|
|
1732
1710
|
const terminalized = terminalizeExactTaskTurn(store, {
|
|
@@ -1735,17 +1713,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1735
1713
|
agentId: input.agentId,
|
|
1736
1714
|
turnId: observedTurn.id,
|
|
1737
1715
|
nativeSessionId: input.nativeSessionId,
|
|
1738
|
-
...(input.
|
|
1716
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
1739
1717
|
outcome: {
|
|
1740
|
-
|
|
1741
|
-
summary: input.summary,
|
|
1742
|
-
...(providerStatus === "completed"
|
|
1743
|
-
? {}
|
|
1744
|
-
: {
|
|
1745
|
-
failureReason: providerStatus === "cancelled"
|
|
1746
|
-
? "cancelled"
|
|
1747
|
-
: "runtime-failed"
|
|
1748
|
-
}),
|
|
1718
|
+
...input.outcome,
|
|
1749
1719
|
provider: {
|
|
1750
1720
|
providerNamespace: binding.providerNamespace,
|
|
1751
1721
|
accountScope: binding.accountScope,
|
|
@@ -1755,7 +1725,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1755
1725
|
status: providerStatus
|
|
1756
1726
|
}
|
|
1757
1727
|
},
|
|
1758
|
-
...(
|
|
1728
|
+
...(systemEvidence === undefined ? {} : { systemEvidence }),
|
|
1729
|
+
...(workspaceFailure === undefined ? {} : { workspaceFailure })
|
|
1759
1730
|
}, now);
|
|
1760
1731
|
if (terminalized.disposition !== "applied" || terminalized.turn === null) {
|
|
1761
1732
|
throw new Error(`Provider Turn terminal could not complete its exact Turn: ${terminalized.reason ?? "obsolete"}.`);
|
|
@@ -1765,12 +1736,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1765
1736
|
turnId: terminalTurn.id,
|
|
1766
1737
|
roleName: terminalTurn.roleName,
|
|
1767
1738
|
providerTurnId: canonicalTurnId,
|
|
1768
|
-
providerStatus
|
|
1769
|
-
summary: input.summary
|
|
1739
|
+
providerStatus
|
|
1770
1740
|
}, now);
|
|
1771
1741
|
store.saveEvent(input.taskId, event);
|
|
1772
1742
|
if (terminalTurn.roleName !== "leader"
|
|
1773
|
-
&& (
|
|
1743
|
+
&& (terminalTurn.status !== "completed" || !providerGoalContinues(binding.goal))) {
|
|
1774
1744
|
routeRoleEvent(store, event, terminalTurn.roleName, terminalTurn.purpose === "review" ? "review-result" : "role-turn-result", now);
|
|
1775
1745
|
}
|
|
1776
1746
|
}
|
|
@@ -1787,7 +1757,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1787
1757
|
input.taskId,
|
|
1788
1758
|
input.roleName,
|
|
1789
1759
|
input.turnId,
|
|
1790
|
-
input.
|
|
1760
|
+
input.runtimeGenerationId ?? "unknown-runtime-generation",
|
|
1791
1761
|
String(input.deadStatus ?? "unknown-status")
|
|
1792
1762
|
].join("\0");
|
|
1793
1763
|
const observationId = `tmux-host-exit-${createHash("sha256").update(identity).digest("hex")}`;
|
|
@@ -1797,18 +1767,18 @@ export class FileSchedulerStoreAdapter {
|
|
|
1797
1767
|
return;
|
|
1798
1768
|
const stopReceipt = [...events].reverse().find((event) => (event.type === "runtime.session-termination"
|
|
1799
1769
|
&& event.payload.roleName === input.roleName
|
|
1800
|
-
&& (input.
|
|
1770
|
+
&& (input.runtimeGenerationId === undefined || event.payload.runtimeGenerationId === input.runtimeGenerationId)
|
|
1801
1771
|
&& ["stop-requested", "graceful-stop", "forced-stop", "stop-confirmed"]
|
|
1802
1772
|
.includes(event.payload.outcome ?? "")));
|
|
1803
1773
|
const observation = validateRuntimeProcessExitObservation({
|
|
1804
|
-
schemaVersion:
|
|
1774
|
+
schemaVersion: 2,
|
|
1805
1775
|
observationId,
|
|
1806
1776
|
hostSequence: 1,
|
|
1807
|
-
hostInstanceId: `tmux-${input.
|
|
1777
|
+
hostInstanceId: `tmux-${input.runtimeGenerationId ?? input.roleName}`,
|
|
1808
1778
|
taskId: input.taskId,
|
|
1809
1779
|
roleName: input.roleName,
|
|
1810
1780
|
turnId: input.turnId,
|
|
1811
|
-
|
|
1781
|
+
runtimeGenerationId: input.runtimeGenerationId ?? `unknown-${input.turnId}`,
|
|
1812
1782
|
...(input.nativeSessionId === undefined
|
|
1813
1783
|
? {}
|
|
1814
1784
|
: { nativeSessionId: input.nativeSessionId }),
|
|
@@ -1822,7 +1792,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1822
1792
|
observationId,
|
|
1823
1793
|
processKind: "agent-host",
|
|
1824
1794
|
roleName: input.roleName,
|
|
1825
|
-
|
|
1795
|
+
runtimeGenerationId: observation.runtimeGenerationId,
|
|
1826
1796
|
observedAt: observation.observedAt,
|
|
1827
1797
|
classification,
|
|
1828
1798
|
observation: JSON.stringify(observation)
|
|
@@ -1954,11 +1924,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1954
1924
|
const existingNativeSessionId = existingSession?.nativeSessionId;
|
|
1955
1925
|
const replacingNativeSession = existingNativeSessionId !== undefined
|
|
1956
1926
|
&& existingNativeSessionId !== decision.outcome.nativeSessionId;
|
|
1957
|
-
const replacementBasis =
|
|
1958
|
-
&& run.mode === "new"
|
|
1959
|
-
&& existingSession?.status === "ended"
|
|
1960
|
-
? "terminal-session"
|
|
1961
|
-
: null;
|
|
1927
|
+
const replacementBasis = terminalSessionReplacementBasis(sessions, input, run) ?? null;
|
|
1962
1928
|
if (replacingNativeSession && replacementBasis === null) {
|
|
1963
1929
|
recordCanonicalObservationObsolete(store, input, "session-replacement-not-terminal", now);
|
|
1964
1930
|
return "obsolete";
|
|
@@ -1967,7 +1933,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1967
1933
|
agentId: input.fence.agentId,
|
|
1968
1934
|
adapterId,
|
|
1969
1935
|
nativeSessionId: decision.outcome.nativeSessionId,
|
|
1970
|
-
|
|
1936
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
1971
1937
|
policy: "fixed",
|
|
1972
1938
|
status: "active",
|
|
1973
1939
|
effective: run.effective
|
|
@@ -1975,15 +1941,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
1975
1941
|
const bound = replacementBasis === null
|
|
1976
1942
|
? recordRoleAgentSession(sessions, sessionInput, now)
|
|
1977
1943
|
: replaceTaskRoleAgentSession(sessions, sessionInput, now);
|
|
1978
|
-
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis
|
|
1944
|
+
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis
|
|
1945
|
+
?? terminalProviderReplacementBasis(bound, input, run.mode));
|
|
1979
1946
|
store.saveTaskRoleSessionSet(withProvider);
|
|
1980
|
-
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.
|
|
1947
|
+
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.runtimeGenerationId);
|
|
1981
1948
|
}
|
|
1982
1949
|
const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
1983
1950
|
const currentSession = current?.sessions[input.fence.agentId];
|
|
1984
1951
|
if (current !== null && current !== undefined
|
|
1985
1952
|
&& currentSession?.nativeSessionId === input.fence.nativeSessionId) {
|
|
1986
|
-
|
|
1953
|
+
const run = input.fence.turnId === undefined
|
|
1954
|
+
? null
|
|
1955
|
+
: store.getTurn(input.fence.taskId, input.fence.turnId);
|
|
1956
|
+
const replacementBasis = run === null
|
|
1957
|
+
? undefined
|
|
1958
|
+
: terminalSessionReplacementBasis(current, input, run)
|
|
1959
|
+
?? terminalProviderReplacementBasis(current, input, run.mode);
|
|
1960
|
+
if (current.providerBinding !== null
|
|
1961
|
+
&& currentProviderConversation(current.providerBinding).conversationId
|
|
1962
|
+
!== (input.fence.conversationId ?? input.fence.nativeSessionId)
|
|
1963
|
+
&& replacementBasis === undefined) {
|
|
1964
|
+
recordCanonicalObservationObsolete(store, input, "session-replacement-not-terminal", now);
|
|
1965
|
+
return "obsolete";
|
|
1966
|
+
}
|
|
1967
|
+
store.saveTaskRoleSessionSet(bindOrSupersedeProviderRuntime(current, input, now, replacementBasis));
|
|
1987
1968
|
}
|
|
1988
1969
|
return "applied";
|
|
1989
1970
|
}
|
|
@@ -2005,7 +1986,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2005
1986
|
const active = store.getActiveTurn(taskId, input.fence.roleName);
|
|
2006
1987
|
if (sessions === null || sessions === undefined
|
|
2007
1988
|
|| binding === null || binding === undefined
|
|
2008
|
-
|| session?.
|
|
1989
|
+
|| session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
2009
1990
|
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
2010
1991
|
|| currentProviderConversation(binding).conversationId !== input.fence.nativeSessionId) {
|
|
2011
1992
|
recordCanonicalObservationObsolete(store, input, "direct-turn-session-mismatch", now);
|
|
@@ -2058,7 +2039,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2058
2039
|
&& binding !== null && binding !== undefined
|
|
2059
2040
|
&& binding.turn?.turnId === undefined
|
|
2060
2041
|
&& binding.turn?.attemptId === ordinaryAttemptId) {
|
|
2061
|
-
if (session?.
|
|
2042
|
+
if (session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
2062
2043
|
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2063
2044
|
recordCanonicalObservationObsolete(store, input, "ordinary-turn-session-mismatch", now);
|
|
2064
2045
|
return "obsolete";
|
|
@@ -2076,7 +2057,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2076
2057
|
|| active.status !== "active"
|
|
2077
2058
|
|| sessions === null
|
|
2078
2059
|
|| sessions === undefined
|
|
2079
|
-
|| session?.
|
|
2060
|
+
|| session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
2080
2061
|
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2081
2062
|
recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
|
|
2082
2063
|
return "obsolete";
|
|
@@ -2086,7 +2067,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2086
2067
|
attemptId: input.fence.receiptId,
|
|
2087
2068
|
turnId: active.id,
|
|
2088
2069
|
conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
|
|
2089
|
-
activationId: input.fence.activationId ?? input.fence.
|
|
2070
|
+
activationId: input.fence.activationId ?? input.fence.runtimeGenerationId,
|
|
2090
2071
|
...(input.fence.nativeTurnId === undefined
|
|
2091
2072
|
? {}
|
|
2092
2073
|
: { nativeTurnId: input.fence.nativeTurnId })
|
|
@@ -2168,15 +2149,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
2168
2149
|
return null;
|
|
2169
2150
|
const owner = { scope: "task", taskId: fence.taskId, roleName: fence.roleName };
|
|
2170
2151
|
const freshConversationLaunch = run.mode === "new"
|
|
2171
|
-
&& runtimeHookMatchesReservation(store, owner, fence.
|
|
2152
|
+
&& runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId);
|
|
2172
2153
|
const boundNativeSessionId = freshConversationLaunch
|
|
2173
2154
|
? undefined
|
|
2174
2155
|
: session?.nativeSessionId;
|
|
2175
|
-
const
|
|
2176
|
-
? fence.
|
|
2177
|
-
: session?.
|
|
2178
|
-
?? (runtimeHookMatchesReservation(store, owner, fence.
|
|
2179
|
-
if (
|
|
2156
|
+
const runtimeGenerationId = freshConversationLaunch
|
|
2157
|
+
? fence.runtimeGenerationId
|
|
2158
|
+
: session?.runtimeGenerationId
|
|
2159
|
+
?? (runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId) ? fence.runtimeGenerationId : undefined);
|
|
2160
|
+
if (runtimeGenerationId === undefined)
|
|
2180
2161
|
return null;
|
|
2181
2162
|
const providerTurn = sessionSet?.providerBinding?.turn;
|
|
2182
2163
|
const managedTurnMatches = managedProviderTurnId(providerTurn) === run.id;
|
|
@@ -2186,7 +2167,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2186
2167
|
agentId: run.effective.agentId,
|
|
2187
2168
|
adapterId: run.effective.adapterId,
|
|
2188
2169
|
turnId: run.id,
|
|
2189
|
-
|
|
2170
|
+
runtimeGenerationId,
|
|
2190
2171
|
receiptId: managedTurnMatches && providerTurn !== null && providerTurn !== undefined
|
|
2191
2172
|
? providerTurn.attemptId
|
|
2192
2173
|
: formatTurnReceiptId(run.taskId, run.id),
|
|
@@ -2200,7 +2181,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2200
2181
|
&& observation.fence.roleName === fence.roleName
|
|
2201
2182
|
&& observation.fence.agentId === run.effective.agentId
|
|
2202
2183
|
&& observation.fence.driverId === driverId
|
|
2203
|
-
&& observation.fence.
|
|
2184
|
+
&& observation.fence.runtimeGenerationId === runtimeGenerationId
|
|
2204
2185
|
&& observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
|
|
2205
2186
|
? [observation]
|
|
2206
2187
|
: [];
|
|
@@ -2227,7 +2208,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2227
2208
|
recordGlobalRuntimeNativeSession(input, now = new Date()) {
|
|
2228
2209
|
return this.store.transaction((store) => (recordGlobalRuntimeNativeSession(store, input, now)));
|
|
2229
2210
|
}
|
|
2230
|
-
|
|
2211
|
+
observeGlobalRuntimeTurnTerminal(input, now = new Date()) {
|
|
2231
2212
|
return this.store.transaction((store) => {
|
|
2232
2213
|
const role = store.getGlobalRole(input.roleName);
|
|
2233
2214
|
if (role === null)
|
|
@@ -2240,23 +2221,25 @@ export class FileSchedulerStoreAdapter {
|
|
|
2240
2221
|
roleName: input.roleName
|
|
2241
2222
|
};
|
|
2242
2223
|
if (existing === undefined
|
|
2243
|
-
&& !runtimeHookMatchesReservation(store, owner, input.
|
|
2224
|
+
&& !runtimeHookMatchesReservation(store, owner, input.runtimeGenerationId)) {
|
|
2244
2225
|
throw new Error("Runtime turn completion does not match the global launch reservation.");
|
|
2245
2226
|
}
|
|
2246
2227
|
const nativeSessionId = globalCompletionNativeSessionId(existing, input);
|
|
2247
|
-
const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.
|
|
2228
|
+
const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
2248
2229
|
const effective = globalSessionEffective(role, effectiveExisting);
|
|
2249
2230
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2250
|
-
throw new Error("Runtime turn completion does not match the effective global
|
|
2231
|
+
throw new Error("Runtime turn completion does not match the effective global runtime generation identity.");
|
|
2251
2232
|
}
|
|
2252
2233
|
const completedStatus = effectiveExisting?.status ?? "active";
|
|
2253
2234
|
current = recordRoleAgentSession(current, {
|
|
2254
2235
|
agentId: input.agentId,
|
|
2255
2236
|
adapterId: input.adapterId,
|
|
2256
2237
|
nativeSessionId,
|
|
2257
|
-
...(input.
|
|
2238
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2258
2239
|
title: effectiveExisting?.title ?? input.title,
|
|
2259
|
-
preview: effectiveExisting?.preview ?? (input.
|
|
2240
|
+
preview: effectiveExisting?.preview ?? sessionPreview(input.outcome.status === "completed"
|
|
2241
|
+
? input.outcome.output
|
|
2242
|
+
: input.outcome.diagnostic),
|
|
2260
2243
|
policy: "fixed",
|
|
2261
2244
|
status: completedStatus,
|
|
2262
2245
|
...(effectiveExisting?.endReason === undefined
|
|
@@ -2266,7 +2249,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2266
2249
|
}, now);
|
|
2267
2250
|
current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.nativeTurnId, now);
|
|
2268
2251
|
store.saveGlobalRoleSessionSet(current);
|
|
2269
|
-
completeRuntimeHookReservation(store, owner, input.
|
|
2252
|
+
completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
|
|
2270
2253
|
if (input.roleName === SYSTEM_OPERATOR_ROLE
|
|
2271
2254
|
&& input.adapterId === "codex"
|
|
2272
2255
|
&& completedStatus === "active") {
|
|
@@ -2281,7 +2264,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2281
2264
|
return current.sessions[input.agentId];
|
|
2282
2265
|
});
|
|
2283
2266
|
}
|
|
2284
|
-
|
|
2267
|
+
classifyGlobalRuntimeTurnTerminal(input) {
|
|
2285
2268
|
const role = this.store.getGlobalRole(input.roleName);
|
|
2286
2269
|
if (role === null)
|
|
2287
2270
|
return "obsolete";
|
|
@@ -2292,12 +2275,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
2292
2275
|
roleName: input.roleName
|
|
2293
2276
|
};
|
|
2294
2277
|
if (existing === undefined
|
|
2295
|
-
&& !runtimeHookMatchesReservation(this.store, owner, input.
|
|
2278
|
+
&& !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
|
|
2296
2279
|
return "obsolete";
|
|
2297
2280
|
const nativeSessionId = globalCompletionNativeSessionId(existing, input);
|
|
2298
2281
|
let effectiveExisting;
|
|
2299
2282
|
try {
|
|
2300
|
-
effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.
|
|
2283
|
+
effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, input.runtimeGenerationId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
2301
2284
|
}
|
|
2302
2285
|
catch {
|
|
2303
2286
|
return "obsolete";
|
|
@@ -2315,7 +2298,7 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2315
2298
|
roleName: input.fence.roleName,
|
|
2316
2299
|
agentId: input.fence.agentId,
|
|
2317
2300
|
adapterId,
|
|
2318
|
-
|
|
2301
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2319
2302
|
...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
|
|
2320
2303
|
...(input.fence.nativeSessionId === undefined
|
|
2321
2304
|
? {}
|
|
@@ -2323,6 +2306,25 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2323
2306
|
...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
|
|
2324
2307
|
};
|
|
2325
2308
|
}
|
|
2309
|
+
/**
|
|
2310
|
+
* A live parent Turn still owns its native child result. Only after that Turn
|
|
2311
|
+
* is terminal or absent does Yui route the continuation fact to the original
|
|
2312
|
+
* Role's supervisor. Mailbox coalescing remains the downstream batching
|
|
2313
|
+
* mechanism; this guard decides ownership before any wake is enqueued.
|
|
2314
|
+
*/
|
|
2315
|
+
function routeContinuationResult(store, observation, event, reason, now) {
|
|
2316
|
+
const taskId = observation.fence.taskId;
|
|
2317
|
+
if (taskId === undefined)
|
|
2318
|
+
return;
|
|
2319
|
+
const parentTurn = observation.fence.turnId === undefined
|
|
2320
|
+
? null
|
|
2321
|
+
: store.getTurn(taskId, observation.fence.turnId);
|
|
2322
|
+
if (parentTurn?.status === "active")
|
|
2323
|
+
return;
|
|
2324
|
+
if (store.getRole(taskId, observation.fence.roleName) === null)
|
|
2325
|
+
return;
|
|
2326
|
+
routeRoleEvent(store, event, observation.fence.roleName, reason, now);
|
|
2327
|
+
}
|
|
2326
2328
|
/** Accepts the original Turn receipt or a later mailbox activation receipt. */
|
|
2327
2329
|
function runtimeReceiptBelongsToTurn(store, input) {
|
|
2328
2330
|
const taskId = input.fence.taskId;
|
|
@@ -2338,7 +2340,7 @@ function runtimeReceiptBelongsToTurn(store, input) {
|
|
|
2338
2340
|
&& accepted.fence.turnId === turnId
|
|
2339
2341
|
&& accepted.fence.roleName === input.fence.roleName
|
|
2340
2342
|
&& accepted.fence.agentId === input.fence.agentId
|
|
2341
|
-
&& accepted.fence.
|
|
2343
|
+
&& accepted.fence.runtimeGenerationId === input.fence.runtimeGenerationId
|
|
2342
2344
|
&& accepted.fence.nativeSessionId === input.fence.nativeSessionId
|
|
2343
2345
|
&& accepted.fence.receiptId === receiptId;
|
|
2344
2346
|
});
|
|
@@ -2355,8 +2357,8 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
|
2355
2357
|
roleName: input.fence.roleName,
|
|
2356
2358
|
agentId: input.fence.agentId,
|
|
2357
2359
|
...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
|
|
2358
|
-
|
|
2359
|
-
nativeSessionId: input.fence.nativeSessionId ?? input.fence.
|
|
2360
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2361
|
+
nativeSessionId: input.fence.nativeSessionId ?? input.fence.runtimeGenerationId
|
|
2360
2362
|
}, reason, now);
|
|
2361
2363
|
}
|
|
2362
2364
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
@@ -2371,7 +2373,7 @@ function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
|
2371
2373
|
agentId: input.agentId,
|
|
2372
2374
|
nativeSessionId: input.nativeSessionId,
|
|
2373
2375
|
...(input.turnId === undefined ? {} : { turnId: input.turnId }),
|
|
2374
|
-
...(input.
|
|
2376
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2375
2377
|
reason
|
|
2376
2378
|
}, now));
|
|
2377
2379
|
}
|
|
@@ -2410,20 +2412,20 @@ function sessionPreview(value) {
|
|
|
2410
2412
|
? truncated.slice(0, -1)
|
|
2411
2413
|
: truncated;
|
|
2412
2414
|
}
|
|
2413
|
-
function requireRuntimeLaunchReservation(store, owner,
|
|
2415
|
+
function requireRuntimeLaunchReservation(store, owner, runtimeGenerationId) {
|
|
2414
2416
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2415
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing,
|
|
2417
|
+
if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId)) {
|
|
2416
2418
|
throw new Error("Runtime launch reservation no longer matches the launch.");
|
|
2417
2419
|
}
|
|
2418
2420
|
return mailbox;
|
|
2419
2421
|
}
|
|
2420
|
-
function runtimeHookMatchesReservation(store, owner,
|
|
2422
|
+
function runtimeHookMatchesReservation(store, owner, runtimeGenerationId) {
|
|
2421
2423
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2422
2424
|
if (hasRuntimeCleanupObligation(mailbox))
|
|
2423
2425
|
return false;
|
|
2424
2426
|
const processing = mailbox?.processing;
|
|
2425
|
-
return
|
|
2426
|
-
&& isRuntimeLaunchReservation(processing,
|
|
2427
|
+
return runtimeGenerationId !== undefined
|
|
2428
|
+
&& isRuntimeLaunchReservation(processing, runtimeGenerationId);
|
|
2427
2429
|
}
|
|
2428
2430
|
function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers) {
|
|
2429
2431
|
const taskId = observation.fence.taskId;
|
|
@@ -2455,17 +2457,17 @@ function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers)
|
|
|
2455
2457
|
taskId,
|
|
2456
2458
|
roleName: observation.fence.roleName
|
|
2457
2459
|
}));
|
|
2458
|
-
return isRuntimeLaunchReservation(mailbox?.processing, observation.fence.
|
|
2460
|
+
return isRuntimeLaunchReservation(mailbox?.processing, observation.fence.runtimeGenerationId)
|
|
2459
2461
|
&& !hasRuntimeCleanupObligation(mailbox)
|
|
2460
|
-
&& nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.
|
|
2462
|
+
&& nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.runtimeGenerationId, observation.fence.agentId, driver.adapterId);
|
|
2461
2463
|
}
|
|
2462
|
-
function completeRuntimeHookReservation(store, owner,
|
|
2463
|
-
if (
|
|
2464
|
+
function completeRuntimeHookReservation(store, owner, runtimeGenerationId) {
|
|
2465
|
+
if (runtimeGenerationId === undefined)
|
|
2464
2466
|
return;
|
|
2465
2467
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2466
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing,
|
|
2468
|
+
if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId))
|
|
2467
2469
|
return;
|
|
2468
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox,
|
|
2470
|
+
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, runtimeGenerationId));
|
|
2469
2471
|
}
|
|
2470
2472
|
function saveRuntimeLifecycleMailbox(store, mailbox) {
|
|
2471
2473
|
if (!mailboxHasWork(mailbox)) {
|
|
@@ -2509,7 +2511,7 @@ function dormantRuntimeCandidateIsCurrent(store, candidate) {
|
|
|
2509
2511
|
&& active.agentId === candidate.agentId
|
|
2510
2512
|
&& active.adapterId === candidate.adapterId
|
|
2511
2513
|
&& active.nativeSessionId === candidate.nativeSessionId
|
|
2512
|
-
&& active.
|
|
2514
|
+
&& active.runtimeGenerationId === candidate.runtimeGenerationId
|
|
2513
2515
|
&& active.updatedAt === candidate.sessionUpdatedAt;
|
|
2514
2516
|
}
|
|
2515
2517
|
function endRuntimeOwnerSession(store, owner, now) {
|
|
@@ -2593,9 +2595,9 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
2593
2595
|
const current = store.getRoleSessionSet(input.taskId, input.roleName)
|
|
2594
2596
|
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
2595
2597
|
const existing = current.sessions[input.agentId];
|
|
2596
|
-
const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId, input.
|
|
2598
|
+
const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId, input.runtimeGenerationId, "Native session registration conflicts with the fixed Role session.");
|
|
2597
2599
|
if (existing?.status === "active"
|
|
2598
|
-
&& (input.
|
|
2600
|
+
&& (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
|
|
2599
2601
|
return existing;
|
|
2600
2602
|
const resolvedEffective = taskSessionEffective(store, input.taskId, input.roleName, input.agentId, effectiveExisting);
|
|
2601
2603
|
const effective = input.effective === undefined
|
|
@@ -2605,13 +2607,13 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
2605
2607
|
throw new Error("Reserved native Session effective launch changed before persistence.");
|
|
2606
2608
|
}
|
|
2607
2609
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2608
|
-
throw new Error("Native session registration does not match the effective
|
|
2610
|
+
throw new Error("Native session registration does not match the effective runtime generation identity.");
|
|
2609
2611
|
}
|
|
2610
2612
|
const updated = recordRoleAgentSession(current, {
|
|
2611
2613
|
agentId: input.agentId,
|
|
2612
2614
|
adapterId: input.adapterId,
|
|
2613
2615
|
nativeSessionId: input.nativeSessionId,
|
|
2614
|
-
...(input.
|
|
2616
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2615
2617
|
policy: "fixed",
|
|
2616
2618
|
status: "active",
|
|
2617
2619
|
effective: effectiveExisting?.effective ?? effective
|
|
@@ -2626,9 +2628,9 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
|
2626
2628
|
const current = store.getGlobalRoleSessionSet(input.roleName)
|
|
2627
2629
|
?? createRoleSessionSet({ scope: "global", roleName: input.roleName }, input.agentId, now);
|
|
2628
2630
|
const existing = current.sessions[input.agentId];
|
|
2629
|
-
const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId, input.
|
|
2631
|
+
const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId, input.runtimeGenerationId, "Native session registration conflicts with the fixed global Role session.");
|
|
2630
2632
|
if (existing?.status === "active"
|
|
2631
|
-
&& (input.
|
|
2633
|
+
&& (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
|
|
2632
2634
|
return existing;
|
|
2633
2635
|
const resolvedEffective = globalSessionEffective(role, effectiveExisting);
|
|
2634
2636
|
const effective = input.effective === undefined
|
|
@@ -2638,13 +2640,13 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
|
2638
2640
|
throw new Error("Reserved global native Session effective launch changed before persistence.");
|
|
2639
2641
|
}
|
|
2640
2642
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2641
|
-
throw new Error("Native session registration does not match the effective global
|
|
2643
|
+
throw new Error("Native session registration does not match the effective global runtime generation identity.");
|
|
2642
2644
|
}
|
|
2643
2645
|
const updated = recordRoleAgentSession(current, {
|
|
2644
2646
|
agentId: input.agentId,
|
|
2645
2647
|
adapterId: input.adapterId,
|
|
2646
2648
|
nativeSessionId: input.nativeSessionId,
|
|
2647
|
-
...(input.
|
|
2649
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2648
2650
|
policy: "fixed",
|
|
2649
2651
|
status: "active",
|
|
2650
2652
|
effective
|
|
@@ -2652,22 +2654,22 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
|
2652
2654
|
store.saveGlobalRoleSessionSet(updated);
|
|
2653
2655
|
return updated.sessions[input.agentId];
|
|
2654
2656
|
}
|
|
2655
|
-
function nativeTransitionExisting(store, owner, existing, nativeSessionId,
|
|
2657
|
+
function nativeTransitionExisting(store, owner, existing, nativeSessionId, runtimeGenerationId, conflictMessage) {
|
|
2656
2658
|
if (existing === undefined || existing.nativeSessionId === nativeSessionId) {
|
|
2657
2659
|
return existing;
|
|
2658
2660
|
}
|
|
2659
2661
|
if (existing.status === "ended"
|
|
2660
|
-
&& runtimeHookMatchesReservation(store, owner,
|
|
2662
|
+
&& runtimeHookMatchesReservation(store, owner, runtimeGenerationId)) {
|
|
2661
2663
|
return undefined;
|
|
2662
2664
|
}
|
|
2663
2665
|
throw new Error(conflictMessage);
|
|
2664
2666
|
}
|
|
2665
2667
|
function globalCompletionNativeSessionId(existing, input) {
|
|
2666
2668
|
// Codex structured event sources can expose different native IDs for the
|
|
2667
|
-
// same
|
|
2669
|
+
// same runtime generation. That generation remains the authoritative fence.
|
|
2668
2670
|
return input.adapterId === "codex"
|
|
2669
|
-
&& input.
|
|
2670
|
-
&& existing?.
|
|
2671
|
+
&& input.runtimeGenerationId !== undefined
|
|
2672
|
+
&& existing?.runtimeGenerationId === input.runtimeGenerationId
|
|
2671
2673
|
&& existing.agentId === input.agentId
|
|
2672
2674
|
&& existing.adapterId === input.adapterId
|
|
2673
2675
|
? existing.nativeSessionId
|
|
@@ -2755,7 +2757,7 @@ function mapSession(session) {
|
|
|
2755
2757
|
agentId: session.agentId,
|
|
2756
2758
|
adapterId: session.adapterId,
|
|
2757
2759
|
nativeSessionId: session.nativeSessionId,
|
|
2758
|
-
...(session.
|
|
2760
|
+
...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
|
|
2759
2761
|
...(session.title === undefined ? {} : { title: session.title }),
|
|
2760
2762
|
status: session.status,
|
|
2761
2763
|
...(session.endReason === undefined ? {} : { endReason: session.endReason }),
|
|
@@ -2763,14 +2765,14 @@ function mapSession(session) {
|
|
|
2763
2765
|
updatedAt: session.updatedAt
|
|
2764
2766
|
};
|
|
2765
2767
|
}
|
|
2766
|
-
function saveTaskSession(store, role, session, status, now,
|
|
2768
|
+
function saveTaskSession(store, role, session, status, now, runtimeGenerationId) {
|
|
2767
2769
|
const current = store.getRoleSessionSet(role.taskId, role.name)
|
|
2768
2770
|
?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, session.agentId, now);
|
|
2769
2771
|
const updated = recordRoleAgentSession(current, {
|
|
2770
2772
|
agentId: session.agentId,
|
|
2771
2773
|
adapterId: session.adapterId,
|
|
2772
2774
|
nativeSessionId: session.nativeSessionId,
|
|
2773
|
-
...(
|
|
2775
|
+
...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
|
|
2774
2776
|
...(session.title === undefined ? {} : { title: session.title }),
|
|
2775
2777
|
policy: "fixed",
|
|
2776
2778
|
status,
|
|
@@ -2787,7 +2789,7 @@ function matchesStallSessionFence(current, expected) {
|
|
|
2787
2789
|
return current.agentId === expected.agentId
|
|
2788
2790
|
&& current.adapterId === expected.adapterId
|
|
2789
2791
|
&& current.nativeSessionId === expected.nativeSessionId
|
|
2790
|
-
&& current.
|
|
2792
|
+
&& current.runtimeGenerationId === expected.runtimeGenerationId
|
|
2791
2793
|
&& current.status === expected.status;
|
|
2792
2794
|
}
|
|
2793
2795
|
function requireRole(store, taskId, roleName) {
|
|
@@ -2814,7 +2816,7 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
2814
2816
|
taskId: input.fence.taskId,
|
|
2815
2817
|
roleName: input.fence.roleName,
|
|
2816
2818
|
turnId: input.fence.turnId,
|
|
2817
|
-
generation: input.fence.
|
|
2819
|
+
generation: input.fence.runtimeGenerationId,
|
|
2818
2820
|
progressId: [
|
|
2819
2821
|
input.kind,
|
|
2820
2822
|
input.payload.operationId ?? input.payload.activity ?? "state"
|
|
@@ -2825,7 +2827,7 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
2825
2827
|
kind: input.kind,
|
|
2826
2828
|
authority: input.authority,
|
|
2827
2829
|
driverId: input.fence.driverId,
|
|
2828
|
-
|
|
2830
|
+
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2829
2831
|
nativeSessionId: input.fence.nativeSessionId ?? "",
|
|
2830
2832
|
nativeTurnId: input.fence.nativeTurnId ?? "",
|
|
2831
2833
|
...(input.payload.operation === undefined
|
|
@@ -2934,9 +2936,30 @@ function canonicalStructuredProviderTurnId(sessions, observedTurnId, attemptId)
|
|
|
2934
2936
|
? turn.nativeTurnId
|
|
2935
2937
|
: observedTurnId;
|
|
2936
2938
|
}
|
|
2939
|
+
function terminalSessionReplacementBasis(sessions, input, run) {
|
|
2940
|
+
const binding = sessions.providerBinding;
|
|
2941
|
+
const session = sessions.sessions[input.fence.agentId];
|
|
2942
|
+
const incomingConversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
2943
|
+
if (run.mode !== "new"
|
|
2944
|
+
|| binding === null
|
|
2945
|
+
|| session === undefined
|
|
2946
|
+
|| incomingConversationId === undefined
|
|
2947
|
+
|| currentProviderConversation(binding).conversationId === incomingConversationId
|
|
2948
|
+
|| currentProviderActivation(binding) !== null
|
|
2949
|
+
|| binding.authority.owner !== "none") {
|
|
2950
|
+
return undefined;
|
|
2951
|
+
}
|
|
2952
|
+
// Pre-start persistence may already have replaced the terminal Role Session
|
|
2953
|
+
// before Provider readiness arrives. A matching new Session plus the
|
|
2954
|
+
// quiescent old Provider binding preserves the same terminal-session proof.
|
|
2955
|
+
return session.status === "ended"
|
|
2956
|
+
|| session.nativeSessionId === input.fence.nativeSessionId
|
|
2957
|
+
? "terminal-session"
|
|
2958
|
+
: undefined;
|
|
2959
|
+
}
|
|
2937
2960
|
function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
|
|
2938
2961
|
const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
2939
|
-
const activationId = input.fence.activationId ?? input.fence.
|
|
2962
|
+
const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
|
|
2940
2963
|
if (sessions.providerBinding === null) {
|
|
2941
2964
|
return bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
|
|
2942
2965
|
providerNamespace: input.fence.driverId,
|
|
@@ -2969,6 +2992,26 @@ function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis)
|
|
|
2969
2992
|
basis: replacementBasis
|
|
2970
2993
|
}), now);
|
|
2971
2994
|
}
|
|
2995
|
+
function terminalProviderReplacementBasis(sessions, input, mode) {
|
|
2996
|
+
if (mode !== "new" || sessions.providerBinding === null)
|
|
2997
|
+
return undefined;
|
|
2998
|
+
if (sessions.providerBinding.providerNamespace !== input.fence.driverId
|
|
2999
|
+
|| sessions.providerBinding.accountScope !== input.fence.agentId) {
|
|
3000
|
+
return undefined;
|
|
3001
|
+
}
|
|
3002
|
+
const current = sessions.sessions[input.fence.agentId];
|
|
3003
|
+
if (current === undefined
|
|
3004
|
+
|| current.status !== "active"
|
|
3005
|
+
|| current.nativeSessionId !== input.fence.nativeSessionId
|
|
3006
|
+
|| current.runtimeGenerationId !== input.fence.runtimeGenerationId) {
|
|
3007
|
+
return undefined;
|
|
3008
|
+
}
|
|
3009
|
+
const replaced = [...(sessions.history ?? [])].reverse().find((session) => (session.agentId === current.agentId
|
|
3010
|
+
&& session.adapterId === current.adapterId
|
|
3011
|
+
&& session.status === "ended"
|
|
3012
|
+
&& session.nativeSessionId !== current.nativeSessionId));
|
|
3013
|
+
return replaced === undefined ? undefined : "terminal-session";
|
|
3014
|
+
}
|
|
2972
3015
|
function settleStructuredProviderTurn(sessions, turnId, status, now) {
|
|
2973
3016
|
const binding = sessions.providerBinding;
|
|
2974
3017
|
if (binding === null || binding.turn === null || binding.turn.nativeTurnId !== turnId) {
|