@zq-silk/yui 0.15.7 → 0.15.8
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 +192 -399
- package/README.md +127 -1149
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +178 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +1 -7
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +458 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +114 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +247 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +62 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +380 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +123 -0
- package/dist/resources/projectResourceService.js +421 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +120 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +353 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +118 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -1,47 +1,50 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { prepareMessageContinuations } from "../message/messageContinuation.js";
|
|
3
|
+
import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
4
|
+
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
2
5
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
6
|
+
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
7
|
+
import { activeLiveRoleAgentSession, bindTaskRoleProviderRuntime, createRoleSessionSet, recordRoleAgentSession, replaceTaskRoleAgentSession, recordTaskRoleNativeTurnBoundary, rememberRoleAgentCompletedTurn, detachRoleAgentSessionHost, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime, selectNewTaskRoleSession, taskRoleControlTarget } from "../executor/agentExecutor.js";
|
|
8
|
+
import { acceptProviderTurn, cancelQuiescentProviderInput, beginProviderTurn, createProviderRuntimeBinding, currentProviderConversation, managedProviderTurnId, clearProviderGoal, settleProviderTurnSubmission, settleProviderTurn, transferProviderAuthority, supersedeProviderConversation, updateProviderConversationRecoverability, updateProviderGoal } from "../runtime/providerRuntimeIdentity.js";
|
|
5
9
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
6
10
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
7
11
|
import { buildTaskWakeEnvelope } from "../context/wakeNotification.js";
|
|
8
|
-
import { createTaskWake, fallbackWakeCursor, latestTaskWake } from "../scheduler/taskWake.js";
|
|
12
|
+
import { createTaskWake, fallbackWakeCursor, latestTaskWake, markTaskWakeConsumed } from "../scheduler/taskWake.js";
|
|
9
13
|
import { answerInputRequest } from "../input/inputRequest.js";
|
|
10
14
|
import { activeRoleAgentBinding } from "../role/role.js";
|
|
11
15
|
import { effectiveLaunchWithTaskMainWorkspace, roleSessionMayContinue, resolveEffectiveLaunch, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
12
16
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
13
|
-
import {
|
|
17
|
+
import { appendRunInput, createRun, withRunContextSnapshot, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
14
18
|
import { transportAgentResult } from "../domain/agentResultTransport.js";
|
|
15
|
-
import {
|
|
19
|
+
import { createRunInput } from "../context/runInputContract.js";
|
|
16
20
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
17
|
-
import {
|
|
21
|
+
import { terminalizeExactTaskRun, cancelQuiescentRoleRuns } from "../lifecycle/exactRunTerminalization.js";
|
|
18
22
|
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
19
23
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
20
24
|
import { recordLeaderAttentionRequired, routeRoleEvent } from "../scheduler/operatorEvent.js";
|
|
21
|
-
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
22
25
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
23
26
|
import { wakeReason } from "../scheduler/wakeReason.js";
|
|
24
|
-
import {
|
|
27
|
+
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_DIAGNOSTIC_FINISHED_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
25
28
|
import { pendingWakeupProjection } from "../storage/taskStore.js";
|
|
26
29
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
27
30
|
import { providerContinuationKey } from "../runtime/providerContinuation.js";
|
|
28
|
-
import {
|
|
29
|
-
import { bindExecution, claimPending, completeProcessing,
|
|
30
|
-
import { enqueueWork,
|
|
31
|
-
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON,
|
|
32
|
-
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
31
|
+
import { formatRunReceiptId } from "../task/taskRecordReference.js";
|
|
32
|
+
import { bindExecution, claimPending, completeProcessing, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
|
|
33
|
+
import { enqueueWork, settleRoleRunDispatch as settleRoleRunDispatchMailbox } from "../coordination/workMailboxQueue.js";
|
|
34
|
+
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
33
35
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
34
36
|
import { standardAgentError } from "../runtime/agentError.js";
|
|
35
|
-
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent,
|
|
36
|
-
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
37
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, isRuntimeTokenEvidence, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
37
38
|
import { snapshotExecutionLaneWorkspaceSync } from "../repository/executionLaneGitSnapshot.js";
|
|
38
|
-
import { contextSnapshotDeltaRefIds, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
|
|
39
39
|
export class AgentHostProviderTurnFenceError extends Error {
|
|
40
40
|
constructor(message) {
|
|
41
41
|
super(message);
|
|
42
42
|
this.name = "AgentHostProviderTurnFenceError";
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
export class AgentHostProviderSessionBusyError extends AgentHostProviderTurnFenceError {
|
|
46
|
+
code = "SESSION_BUSY";
|
|
47
|
+
}
|
|
45
48
|
/** Maps authoritative TaskStore records to the scheduler's narrow port. */
|
|
46
49
|
export class FileSchedulerStoreAdapter {
|
|
47
50
|
store;
|
|
@@ -55,23 +58,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
55
58
|
this.drivers = drivers;
|
|
56
59
|
this.snapshotExecutionLaneWorkspace = snapshotExecutionLaneWorkspace;
|
|
57
60
|
}
|
|
58
|
-
freezeLeaderContextSnapshot(taskId, roleName, now) {
|
|
59
|
-
return this.store.transaction((tx) => {
|
|
60
|
-
const snapshot = freezeTurnContextSnapshot(tx, {
|
|
61
|
-
taskId,
|
|
62
|
-
roleName,
|
|
63
|
-
purpose: "execution"
|
|
64
|
-
}, now);
|
|
65
|
-
return Object.freeze({
|
|
66
|
-
ref: contextSnapshotRef(snapshot),
|
|
67
|
-
deltaRefIds: contextSnapshotDeltaRefIds(tx, snapshot)
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
61
|
/**
|
|
72
62
|
* Sole provider-independent ingress for structured runtime state. Driver
|
|
73
63
|
* mapping has already happened before this boundary; this method validates
|
|
74
|
-
* the exact durable
|
|
64
|
+
* the exact durable AgentRun/session fence, applies the small workflow-relevant
|
|
75
65
|
* transitions, and retains the canonical observation for status projection.
|
|
76
66
|
*/
|
|
77
67
|
observeRuntimeObservation(raw, now = new Date()) {
|
|
@@ -86,10 +76,41 @@ export class FileSchedulerStoreAdapter {
|
|
|
86
76
|
if (adapterId === null) {
|
|
87
77
|
return this.recordObsoleteCanonicalObservation(input, "driver-or-turn-mismatch", now);
|
|
88
78
|
}
|
|
79
|
+
if (input.kind === "input.accepted" && input.payload.input !== undefined
|
|
80
|
+
&& (input.fence.receiptId?.startsWith("native-input:") === true
|
|
81
|
+
|| input.fence.receiptId?.startsWith("steer:") === true)) {
|
|
82
|
+
return this.store.transaction((store) => {
|
|
83
|
+
const run = input.fence.runId === undefined ? null : store.getRun(taskId, input.fence.runId);
|
|
84
|
+
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
85
|
+
const native = sessions?.providerBinding?.run;
|
|
86
|
+
if (run === null || run.roleName !== input.fence.roleName
|
|
87
|
+
|| run.effective.agentId !== input.fence.agentId || native?.runId !== run.id
|
|
88
|
+
|| native.nativeTurnId !== input.fence.nativeTurnId
|
|
89
|
+
|| sessions?.sessions[input.fence.agentId]?.nativeSessionId !== input.fence.nativeSessionId)
|
|
90
|
+
return "obsolete";
|
|
91
|
+
if (!hasPersistedRuntimeObservation(store.listEvents(taskId), input) && run.status === "active") {
|
|
92
|
+
const updated = appendRunInput(run, createRunInput({
|
|
93
|
+
source: input.fence.receiptId.startsWith("native-input:")
|
|
94
|
+
? { type: "provider", channel: "visible-input" }
|
|
95
|
+
: { type: "yui", channel: "input-response" },
|
|
96
|
+
directive: input.payload.input, deltaRefIds: []
|
|
97
|
+
}), now);
|
|
98
|
+
store.saveRun(updated);
|
|
99
|
+
store.saveActiveRun(updated);
|
|
100
|
+
}
|
|
101
|
+
this.persistRuntimeObservation(input, now);
|
|
102
|
+
return "applied";
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if ((input.kind === "input.accepted" || input.kind === "input.rejected" || input.kind === "input.delivery-unknown")
|
|
106
|
+
&& input.payload.input !== undefined
|
|
107
|
+
&& input.fence.receiptId?.startsWith("turn-input:") === true) {
|
|
108
|
+
return this.observeLeaderSteerReceipt(input, now);
|
|
109
|
+
}
|
|
89
110
|
// Receipt dedupe is not business-result dedupe. Terminal folds revalidate
|
|
90
111
|
// their exact binding and commit result + notification + observation
|
|
91
112
|
// together, including a retry after a previously interrupted fold.
|
|
92
|
-
if (!
|
|
113
|
+
if (!isRunTerminalObservation(input)
|
|
93
114
|
&& input.kind !== "turn.accepted" && input.kind !== "input.accepted"
|
|
94
115
|
&& hasPersistedRuntimeObservation(this.store.listEvents(taskId), input))
|
|
95
116
|
return "applied";
|
|
@@ -108,8 +129,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
108
129
|
break;
|
|
109
130
|
case "turn.failed": {
|
|
110
131
|
// A Provider Turn is an activation boundary, not a Task outcome.
|
|
111
|
-
// Keep the
|
|
112
|
-
// restore + submit, even when a provider marks its
|
|
132
|
+
// Keep the AgentRun and Session identity available for Agent-directed
|
|
133
|
+
// restore + submit, even when a provider marks its AgentRun terminal.
|
|
113
134
|
outcome = this.foldProviderTurnBoundary(input, adapterId, "failed", now);
|
|
114
135
|
break;
|
|
115
136
|
}
|
|
@@ -124,16 +145,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
124
145
|
case "continuation.reported":
|
|
125
146
|
case "continuation.settled":
|
|
126
147
|
case "input.delivery-unknown":
|
|
127
|
-
outcome = this.
|
|
148
|
+
outcome = this.validateCanonicalRunObservation(input, now);
|
|
128
149
|
break;
|
|
129
150
|
case "turn.cancelled": {
|
|
130
151
|
outcome = this.foldProviderTurnBoundary(input, adapterId, "cancelled", now);
|
|
131
152
|
break;
|
|
132
153
|
}
|
|
133
154
|
case "conversation.observed":
|
|
134
|
-
case "activation.started":
|
|
135
|
-
case "activation.ended":
|
|
136
|
-
case "activation.failed":
|
|
137
155
|
outcome = this.observeProviderRuntimeIdentity(input, now);
|
|
138
156
|
break;
|
|
139
157
|
case "goal.updated":
|
|
@@ -150,21 +168,86 @@ export class FileSchedulerStoreAdapter {
|
|
|
150
168
|
default:
|
|
151
169
|
outcome = "obsolete";
|
|
152
170
|
}
|
|
153
|
-
if (outcome === "applied" && !
|
|
171
|
+
if (outcome === "applied" && !isRunTerminalObservation(input)) {
|
|
154
172
|
this.persistRuntimeObservation(input, now);
|
|
155
173
|
}
|
|
156
174
|
return outcome;
|
|
157
175
|
}
|
|
176
|
+
/** A delayed steer receipt settles only its original claimed mailbox batch. */
|
|
177
|
+
observeLeaderSteerReceipt(input, now) {
|
|
178
|
+
return this.store.transaction((store) => {
|
|
179
|
+
const taskId = input.fence.taskId;
|
|
180
|
+
const run = input.fence.runId === undefined ? null : store.getRun(taskId, input.fence.runId);
|
|
181
|
+
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
182
|
+
const session = sessions?.sessions[input.fence.agentId];
|
|
183
|
+
const binding = sessions?.providerBinding;
|
|
184
|
+
if (run === null || run.roleName !== "leader" || input.fence.roleName !== "leader"
|
|
185
|
+
|| run.effective.agentId !== input.fence.agentId
|
|
186
|
+
|| session === undefined
|
|
187
|
+
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
188
|
+
|| binding?.run?.runId !== run.id
|
|
189
|
+
|| binding.run.nativeTurnId !== input.fence.nativeTurnId) {
|
|
190
|
+
recordCanonicalObservationObsolete(store, input, "steer-receipt-fence-mismatch", now);
|
|
191
|
+
return "obsolete";
|
|
192
|
+
}
|
|
193
|
+
if (hasPersistedRuntimeObservation(store.listEvents(taskId), input))
|
|
194
|
+
return "applied";
|
|
195
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
196
|
+
const mailbox = store.getWorkMailbox(target);
|
|
197
|
+
const processing = mailbox?.processing;
|
|
198
|
+
const exactBatch = processing?.owner === `leader-steer:${run.id}`
|
|
199
|
+
&& input.fence.receiptId === `turn-input:${taskId}/${run.id}/${processing.batchId}`;
|
|
200
|
+
if (exactBatch && mailbox !== null && processing !== null && processing !== undefined) {
|
|
201
|
+
if (input.kind === "input.accepted") {
|
|
202
|
+
// Terminal AgentRuns remain immutable: the canonical receipt still
|
|
203
|
+
// retains their late input, without touching a successor pointer.
|
|
204
|
+
if (run.status === "active" && store.getActiveRun(taskId, "leader")?.id === run.id) {
|
|
205
|
+
const updated = appendRunInput(run, createRunInput({
|
|
206
|
+
source: { type: "yui", channel: "leader-forced-wakeup" },
|
|
207
|
+
directive: input.payload.input,
|
|
208
|
+
deltaRefIds: []
|
|
209
|
+
}), now);
|
|
210
|
+
store.saveRun(updated);
|
|
211
|
+
store.saveActiveRun(updated);
|
|
212
|
+
}
|
|
213
|
+
store.saveWorkMailbox(completeProcessing(mailbox, processing.batchId));
|
|
214
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.input-submitted", {
|
|
215
|
+
runId: run.id,
|
|
216
|
+
batchId: processing.batchId,
|
|
217
|
+
attemptId: input.fence.receiptId,
|
|
218
|
+
source: "yui/leader-forced-wakeup",
|
|
219
|
+
reasons: processing.batch.reasons.join(",")
|
|
220
|
+
}, now));
|
|
221
|
+
}
|
|
222
|
+
else if (input.payload.failure?.error.inputDisposition === "not-accepted") {
|
|
223
|
+
store.saveWorkMailbox(releaseProcessing(mailbox, processing.batchId));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (input.kind !== "input.accepted" && input.payload.failure !== undefined) {
|
|
227
|
+
const error = input.payload.failure.error;
|
|
228
|
+
this.recordAgentError({
|
|
229
|
+
taskId, roleName: run.roleName, runId: run.id,
|
|
230
|
+
source: error.source, phase: error.phase,
|
|
231
|
+
message: error.message, raw: error.raw,
|
|
232
|
+
inputDisposition: error.inputDisposition,
|
|
233
|
+
sessionDisposition: error.sessionDisposition,
|
|
234
|
+
attemptId: input.fence.receiptId
|
|
235
|
+
}, now);
|
|
236
|
+
}
|
|
237
|
+
this.persistRuntimeObservation(input, now);
|
|
238
|
+
return "applied";
|
|
239
|
+
});
|
|
240
|
+
}
|
|
158
241
|
adapterForRuntimeObservation(input) {
|
|
159
242
|
const taskId = input.fence.taskId;
|
|
160
|
-
const
|
|
243
|
+
const runId = input.fence.runId;
|
|
161
244
|
if (taskId === undefined)
|
|
162
245
|
return null;
|
|
163
|
-
if (
|
|
246
|
+
if (runId === undefined) {
|
|
164
247
|
const role = this.store.getRole(taskId, input.fence.roleName);
|
|
165
248
|
const sessions = this.store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
166
249
|
const session = sessions?.sessions[input.fence.agentId];
|
|
167
|
-
if (role?.activeAgentId !== input.fence.agentId
|
|
250
|
+
if (role === null || sessions?.activeAgentId !== input.fence.agentId
|
|
168
251
|
|| session?.adapterId === undefined)
|
|
169
252
|
return null;
|
|
170
253
|
try {
|
|
@@ -176,7 +259,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
176
259
|
return null;
|
|
177
260
|
}
|
|
178
261
|
}
|
|
179
|
-
const run = this.store.
|
|
262
|
+
const run = this.store.getRun(taskId, runId);
|
|
180
263
|
if (run === null
|
|
181
264
|
|| run.roleName !== input.fence.roleName
|
|
182
265
|
|| run.effective.agentId !== input.fence.agentId)
|
|
@@ -190,32 +273,31 @@ export class FileSchedulerStoreAdapter {
|
|
|
190
273
|
return null;
|
|
191
274
|
}
|
|
192
275
|
}
|
|
193
|
-
|
|
276
|
+
validateCanonicalRunObservation(input, now) {
|
|
194
277
|
return this.store.transaction((store) => {
|
|
195
278
|
if (hasPersistedRuntimeObservation(store.listEvents(input.fence.taskId), input)) {
|
|
196
279
|
return "applied";
|
|
197
280
|
}
|
|
198
|
-
const run = store.
|
|
199
|
-
const active = store.
|
|
281
|
+
const run = input.fence.runId === undefined ? null : store.getRun(input.fence.taskId, input.fence.runId);
|
|
282
|
+
const active = store.getActiveRun(input.fence.taskId, input.fence.roleName);
|
|
200
283
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
201
284
|
const session = sessions?.sessions[input.fence.agentId];
|
|
202
|
-
|
|
203
|
-
&&
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
&&
|
|
207
|
-
&&
|
|
208
|
-
&&
|
|
209
|
-
|
|
285
|
+
if (input.fence.runId === undefined
|
|
286
|
+
&& ["operation.started", "operation.completed", "operation.failed", "activity.observed"].includes(input.kind)) {
|
|
287
|
+
const native = sessions?.providerBinding?.run;
|
|
288
|
+
return native?.runId === undefined && input.fence.receiptId !== undefined
|
|
289
|
+
&& native?.attemptId === input.fence.receiptId && native.status === "accepted"
|
|
290
|
+
&& session?.nativeSessionId === input.fence.nativeSessionId
|
|
291
|
+
&& (native.nativeTurnId === undefined || native.nativeTurnId === input.fence.nativeTurnId)
|
|
292
|
+
? "applied" : "obsolete";
|
|
293
|
+
}
|
|
294
|
+
const knownContinuation = input.kind.startsWith("continuation.") && projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.runId === input.fence.runId
|
|
295
|
+
&& entry.identity.providerNamespace === input.fence.driverId
|
|
296
|
+
&& entry.identity.accountScope === input.fence.agentId
|
|
297
|
+
&& entry.identity.conversationId === input.fence.conversationId
|
|
298
|
+
&& entry.identity.continuationId === input.fence.continuationId));
|
|
210
299
|
const requiresCurrentRuntime = input.kind !== "turn.cancelled" && !knownContinuation;
|
|
211
|
-
const valid = run !== null
|
|
212
|
-
&& (!requiresCurrentRuntime || (run.status === "active" && active?.id === run.id))
|
|
213
|
-
&& run.roleName === input.fence.roleName
|
|
214
|
-
&& run.effective.agentId === input.fence.agentId
|
|
215
|
-
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
216
|
-
&& (knownContinuation || (session?.runtimeGenerationId === input.fence.runtimeGenerationId
|
|
217
|
-
&& session.nativeSessionId === input.fence.nativeSessionId))
|
|
218
|
-
&& runtimeReceiptBelongsToTurn(store, input);
|
|
300
|
+
const valid = run !== null && (!requiresCurrentRuntime || (run.status === "active" && active?.id === run.id)) && run.roleName === input.fence.roleName && run.effective.agentId === input.fence.agentId && this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId && (knownContinuation || (session !== undefined && session.nativeSessionId === input.fence.nativeSessionId)) && runtimeReceiptBelongsToRun(store, input);
|
|
219
301
|
if (!valid) {
|
|
220
302
|
recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
|
|
221
303
|
return "obsolete";
|
|
@@ -234,9 +316,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
234
316
|
}
|
|
235
317
|
: {
|
|
236
318
|
status: "failed",
|
|
319
|
+
...(typeof input.payload.output === "string"
|
|
320
|
+
&& transportAgentResult(input.payload.output).status === "completed"
|
|
321
|
+
? { output: input.payload.output } : {}),
|
|
237
322
|
diagnostic: providerStatus === "cancelled"
|
|
238
|
-
? "Provider cancelled the Agent
|
|
239
|
-
: `Provider Agent
|
|
323
|
+
? "Provider cancelled the Agent AgentRun."
|
|
324
|
+
: `Provider Agent AgentRun failed: ${input.payload.failure?.error.message ?? "unknown provider failure"}`,
|
|
240
325
|
failureReason: providerStatus === "cancelled"
|
|
241
326
|
? "cancelled"
|
|
242
327
|
: "runtime-failed"
|
|
@@ -246,57 +331,44 @@ export class FileSchedulerStoreAdapter {
|
|
|
246
331
|
roleName: input.fence.roleName,
|
|
247
332
|
agentId: input.fence.agentId,
|
|
248
333
|
adapterId,
|
|
249
|
-
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
250
|
-
activationId: input.fence.activationId,
|
|
251
334
|
conversationId: input.fence.conversationId,
|
|
252
335
|
nativeSessionId: input.fence.nativeSessionId,
|
|
253
336
|
nativeTurnId: input.fence.nativeTurnId,
|
|
254
337
|
attemptId: input.fence.receiptId,
|
|
255
|
-
|
|
338
|
+
runId: input.fence.runId,
|
|
256
339
|
...(input.payload.input === undefined ? {} : { input: input.payload.input }),
|
|
257
340
|
providerStatus,
|
|
258
341
|
outcome,
|
|
259
342
|
observation: input
|
|
260
343
|
};
|
|
261
|
-
const classification = this.
|
|
344
|
+
const classification = this.classifyRuntimeRunTerminal(completed);
|
|
262
345
|
if (classification !== "apply")
|
|
263
346
|
return classification;
|
|
264
|
-
const result = this.
|
|
347
|
+
const result = this.observeRuntimeRunTerminal(completed, now);
|
|
265
348
|
return result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
266
349
|
}
|
|
267
350
|
validateCanonicalSessionObservation(input, now) {
|
|
268
351
|
return this.store.transaction((store) => {
|
|
269
|
-
const run = store.
|
|
352
|
+
const run = input.fence.runId === undefined ? null : store.getRun(input.fence.taskId, input.fence.runId);
|
|
270
353
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
271
354
|
const session = sessions?.sessions[input.fence.agentId];
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
355
|
+
const native = sessions?.providerBinding?.run;
|
|
356
|
+
const exactInput = input.fence.receiptId !== undefined && native?.attemptId === input.fence.receiptId;
|
|
357
|
+
const validRun = input.fence.runId === undefined ? exactInput
|
|
358
|
+
: run !== null && run.roleName === input.fence.roleName && run.effective.agentId === input.fence.agentId
|
|
359
|
+
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId;
|
|
360
|
+
if (!validRun || session === undefined || session.nativeSessionId !== input.fence.nativeSessionId
|
|
361
|
+
|| (input.fence.receiptId !== undefined && (!exactInput
|
|
362
|
+
|| (native?.nativeTurnId !== undefined && native.nativeTurnId !== input.fence.nativeTurnId)))) {
|
|
278
363
|
recordCanonicalObservationObsolete(store, input, "runtime-session-not-current", now);
|
|
279
364
|
return "obsolete";
|
|
280
365
|
}
|
|
281
366
|
const status = "ended";
|
|
282
367
|
let updatedSessions = updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now, input.kind === "session.failed" ? "failed" : "stopped");
|
|
283
|
-
if (updatedSessions.providerBinding !== null) {
|
|
284
|
-
const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
|
|
285
|
-
updatedSessions = updateTaskRoleProviderRuntime(updatedSessions, endProviderActivation(updatedSessions.providerBinding, activationId, {
|
|
286
|
-
status: input.kind === "session.failed" ? "failed" : "ended",
|
|
287
|
-
endedAt: input.observedAt ?? input.receivedAt,
|
|
288
|
-
reason: input.kind
|
|
289
|
-
}), now);
|
|
290
|
-
}
|
|
291
368
|
store.saveTaskRoleSessionSet(updatedSessions);
|
|
292
369
|
for (const continuation of projectProviderContinuations(store.listEvents(input.fence.taskId))) {
|
|
293
|
-
if (continuation.
|
|
294
|
-
|| continuation.
|
|
295
|
-
!== (input.fence.conversationId ?? input.fence.nativeSessionId)
|
|
296
|
-
|| continuation.identity.activationId
|
|
297
|
-
!== (input.fence.activationId ?? input.fence.runtimeGenerationId)
|
|
298
|
-
|| continuation.execution === "quiescent"
|
|
299
|
-
|| continuation.attachment === "detached")
|
|
370
|
+
if (continuation.runId !== input.fence.runId || continuation.identity.conversationId
|
|
371
|
+
!== (input.fence.conversationId ?? input.fence.nativeSessionId) || continuation.execution === "quiescent" || continuation.attachment === "detached")
|
|
300
372
|
continue;
|
|
301
373
|
const key = providerContinuationKey(continuation.identity);
|
|
302
374
|
const identityDigest = createHash("sha256").update(key).digest("hex");
|
|
@@ -311,9 +383,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
311
383
|
fence: {
|
|
312
384
|
...input.fence,
|
|
313
385
|
conversationId: continuation.identity.conversationId,
|
|
314
|
-
activationId: continuation.identity.activationId,
|
|
315
386
|
continuationId: continuation.identity.continuationId,
|
|
316
|
-
continuationGeneration: continuation.identity.generation,
|
|
317
387
|
...(continuation.parentContinuationId === undefined
|
|
318
388
|
? {}
|
|
319
389
|
: { parentContinuationId: continuation.parentContinuationId })
|
|
@@ -334,23 +404,23 @@ export class FileSchedulerStoreAdapter {
|
|
|
334
404
|
store.saveEvent(input.fence.taskId, detachedEvent);
|
|
335
405
|
routeContinuationResult(store, detached, detachedEvent, "provider-continuation-detached", now);
|
|
336
406
|
}
|
|
337
|
-
const active = store.
|
|
407
|
+
const active = store.getActiveRun(input.fence.taskId, input.fence.roleName);
|
|
338
408
|
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
339
409
|
if ((input.kind === "session.ended" || input.kind === "session.failed")
|
|
340
410
|
&& active !== null
|
|
341
|
-
&& active.id === input.fence.
|
|
411
|
+
&& active.id === input.fence.runId
|
|
342
412
|
&& active.status === "active"
|
|
343
413
|
&& role !== null) {
|
|
344
414
|
if (role.name === "leader") {
|
|
345
415
|
const message = [
|
|
346
|
-
`Leader native Session became unavailable while
|
|
347
|
-
"Yui preserved the
|
|
348
|
-
"Retire the disposable
|
|
416
|
+
`Leader native Session became unavailable while AgentRun ${active.id} remains active.`,
|
|
417
|
+
"Yui preserved the AgentRun because Session/host termination is not a Task outcome.",
|
|
418
|
+
"Retire the disposable AgentRun, or use Task execution stop/start if the current runtime cannot be settled normally."
|
|
349
419
|
].join(" ");
|
|
350
420
|
recordLeaderAttentionRequired(store, {
|
|
351
421
|
taskId: input.fence.taskId,
|
|
352
422
|
reason: "leader-runtime-detached",
|
|
353
|
-
payload: { message,
|
|
423
|
+
payload: { message, runId: active.id },
|
|
354
424
|
now
|
|
355
425
|
});
|
|
356
426
|
}
|
|
@@ -360,7 +430,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
360
430
|
taskId: input.fence.taskId,
|
|
361
431
|
roleName: "leader"
|
|
362
432
|
}, "role-runtime-detached", now, [
|
|
363
|
-
{ type: "
|
|
433
|
+
{ type: "run", taskId: input.fence.taskId, id: active.id }
|
|
364
434
|
]);
|
|
365
435
|
}
|
|
366
436
|
}
|
|
@@ -385,10 +455,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
385
455
|
&& input.payload.snapshotComplete === true
|
|
386
456
|
&& input.payload.observationQuality === "exact") {
|
|
387
457
|
for (const continuation of projectProviderContinuations(events)) {
|
|
388
|
-
if (continuation.
|
|
389
|
-
|| continuation.identity.conversationId !== input.fence.conversationId
|
|
390
|
-
|| continuation.identity.activationId !== input.fence.activationId
|
|
391
|
-
|| continuation.execution === "quiescent")
|
|
458
|
+
if (continuation.runId !== input.fence.runId || continuation.identity.conversationId !== input.fence.conversationId || continuation.execution === "quiescent")
|
|
392
459
|
continue;
|
|
393
460
|
const identityKey = providerContinuationKey(continuation.identity);
|
|
394
461
|
const digest = createHash("sha256")
|
|
@@ -405,7 +472,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
405
472
|
fence: {
|
|
406
473
|
...input.fence,
|
|
407
474
|
continuationId: continuation.identity.continuationId,
|
|
408
|
-
continuationGeneration: continuation.identity.generation,
|
|
409
475
|
...(continuation.parentContinuationId === undefined
|
|
410
476
|
? {}
|
|
411
477
|
: { parentContinuationId: continuation.parentContinuationId })
|
|
@@ -443,18 +509,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
443
509
|
if (input.kind === "turn.failed" && input.payload.failure !== undefined) {
|
|
444
510
|
const failure = input.payload.failure;
|
|
445
511
|
const error = failure.error;
|
|
446
|
-
const run = input.fence.
|
|
512
|
+
const run = input.fence.runId === undefined
|
|
447
513
|
? null
|
|
448
|
-
: store.
|
|
514
|
+
: store.getRun(taskId, input.fence.runId);
|
|
449
515
|
const errorEvent = createTaskEvent(store.nextEventId(taskId), taskId, "runtime.agent-error", {
|
|
450
516
|
sourceEventId: input.eventId,
|
|
451
517
|
observationEventId,
|
|
452
|
-
|
|
518
|
+
runId: input.fence.runId ?? "",
|
|
453
519
|
roleName: input.fence.roleName,
|
|
454
520
|
agentId: input.fence.agentId,
|
|
455
521
|
adapterId: run?.effective.adapterId ?? "unknown",
|
|
456
522
|
driverId: input.fence.driverId,
|
|
457
|
-
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
458
523
|
nativeSessionId: input.fence.nativeSessionId ?? "",
|
|
459
524
|
nativeTurnId: input.fence.nativeTurnId ?? "",
|
|
460
525
|
source: error.source,
|
|
@@ -471,17 +536,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
471
536
|
...(failure.lastOutput === undefined ? {} : { lastOutput: failure.lastOutput })
|
|
472
537
|
}, now);
|
|
473
538
|
store.saveEvent(taskId, errorEvent);
|
|
474
|
-
|
|
475
|
-
source: input.fence.driverId,
|
|
476
|
-
dedupeKey: `agent-error:${taskId}:${input.eventId}`
|
|
477
|
-
});
|
|
478
|
-
if (input.fence.roleName === "leader"
|
|
479
|
-
&& error.sessionDisposition === "unrecoverable") {
|
|
480
|
-
enqueueWork(store, { kind: "operator" }, "leader-session-unrecoverable", now, [{ type: "event", taskId, id: errorEvent.id }], {
|
|
481
|
-
source: input.fence.driverId,
|
|
482
|
-
dedupeKey: `leader-session-unrecoverable:${taskId}:${input.eventId}`
|
|
483
|
-
});
|
|
484
|
-
}
|
|
539
|
+
routeRoleEvent(store, errorEvent, input.fence.roleName, wakeReason("agent-error", errorEvent.id), now);
|
|
485
540
|
}
|
|
486
541
|
if ((input.kind === "operation.completed" || input.kind === "operation.failed")
|
|
487
542
|
&& input.payload.operation === "subagent") {
|
|
@@ -495,14 +550,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
495
550
|
}
|
|
496
551
|
}
|
|
497
552
|
});
|
|
498
|
-
if (this.telemetry !== null && input.fence.
|
|
553
|
+
if (this.telemetry !== null && input.fence.runId !== undefined) {
|
|
499
554
|
try {
|
|
500
555
|
const entry = runtimeObservationTelemetryEntry(input);
|
|
501
556
|
this.telemetry.sink.observe(entry);
|
|
502
|
-
const run = this.store.
|
|
557
|
+
const run = this.store.getRun(entry.taskId, input.fence.runId);
|
|
503
558
|
if (run !== null && run.status !== "active") {
|
|
504
559
|
void this.telemetry.retention.flush().then(() => {
|
|
505
|
-
this.telemetry?.retention.
|
|
560
|
+
this.telemetry?.retention.pruneRun(entry.taskId, entry.roleName, entry.runId);
|
|
506
561
|
}).catch(() => undefined);
|
|
507
562
|
}
|
|
508
563
|
}
|
|
@@ -539,8 +594,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
539
594
|
const events = this.store.listEvents(taskId);
|
|
540
595
|
task = {
|
|
541
596
|
events,
|
|
542
|
-
|
|
543
|
-
|
|
597
|
+
runFacts: foldRunProgressFacts(events),
|
|
598
|
+
runs: this.store.listRuns(taskId),
|
|
544
599
|
workItems: this.store.listWorkItems(taskId),
|
|
545
600
|
reviewRounds: this.store.listReviewRounds(taskId),
|
|
546
601
|
changeSets: this.store.listChangeSets(taskId),
|
|
@@ -560,6 +615,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
560
615
|
listActiveTaskIds() {
|
|
561
616
|
return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
562
617
|
}
|
|
618
|
+
listPlanningDraftTaskIds() {
|
|
619
|
+
return [...this.store.listPlanningDraftTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
620
|
+
}
|
|
621
|
+
listPendingActivationRequestTaskIds() {
|
|
622
|
+
return [...this.store.listPendingActivationRequestTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
623
|
+
}
|
|
563
624
|
getTask(taskId) { return this.store.getTask(taskId); }
|
|
564
625
|
getTaskWorkspace(taskId) { return this.store.getTaskWorkspace(taskId); }
|
|
565
626
|
getTaskBrief(taskId) { return this.store.getTaskBrief(taskId); }
|
|
@@ -576,7 +637,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
576
637
|
const latest = latestTaskWake(reader.listTaskWakes(taskId));
|
|
577
638
|
const fromCursor = latest?.toCursor ?? fallbackWakeCursor({
|
|
578
639
|
taskCreatedAt: task.createdAt,
|
|
579
|
-
|
|
640
|
+
leaderRunCreatedAt: operationalTaskRecords(reader.listRuns(taskId), reader.listEvents(taskId), "run")
|
|
580
641
|
.filter((run) => run.roleName === "leader")
|
|
581
642
|
.at(-1)?.createdAt
|
|
582
643
|
});
|
|
@@ -595,8 +656,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
595
656
|
const role = this.store.getRole(taskId, roleName);
|
|
596
657
|
return role === null ? null : mapRole(this.store, role);
|
|
597
658
|
}
|
|
598
|
-
|
|
599
|
-
return this.store.
|
|
659
|
+
getActiveRun(taskId, roleName) {
|
|
660
|
+
return this.store.getActiveRun(taskId, roleName);
|
|
600
661
|
}
|
|
601
662
|
hasOpenInputRequest(taskId) {
|
|
602
663
|
return this.store.listOpenInputRequests([taskId]).length > 0;
|
|
@@ -620,7 +681,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
620
681
|
adapterId: effectiveSession.effective.adapterId
|
|
621
682
|
};
|
|
622
683
|
}
|
|
623
|
-
|
|
684
|
+
markOperatorRunStarted(now) {
|
|
624
685
|
void now;
|
|
625
686
|
}
|
|
626
687
|
resolveExpiredInputRecommendations(now, taskIds) {
|
|
@@ -660,8 +721,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
660
721
|
listEvents(taskId) {
|
|
661
722
|
return this.#taskReadProjection(taskId).events;
|
|
662
723
|
}
|
|
663
|
-
|
|
664
|
-
return this.#taskReadProjection(taskId).
|
|
724
|
+
listRuns(taskId) {
|
|
725
|
+
return this.#taskReadProjection(taskId).runs;
|
|
665
726
|
}
|
|
666
727
|
listWorkItems(taskId) {
|
|
667
728
|
return this.#taskReadProjection(taskId).workItems;
|
|
@@ -681,17 +742,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
681
742
|
listMessages(taskId) {
|
|
682
743
|
return this.#taskReadProjection(taskId).messages;
|
|
683
744
|
}
|
|
684
|
-
|
|
685
|
-
return this.#taskReadProjection(taskId).
|
|
745
|
+
getRunProgressFacts(taskId, runId) {
|
|
746
|
+
return this.#taskReadProjection(taskId).runFacts.get(runId);
|
|
686
747
|
}
|
|
687
|
-
|
|
748
|
+
getRunDurableProgress(taskId, roleName, runId) {
|
|
688
749
|
const projected = this.#taskReadProjection(taskId);
|
|
689
750
|
// Serve the related-record fold from the same revision's projection: the
|
|
690
751
|
// event history and the WorkItem/Review/ChangeSet/Integration/Input lists
|
|
691
|
-
// are read once per Task per revision, and the per-
|
|
752
|
+
// are read once per Task per revision, and the per-AgentRun checkpoint/activity
|
|
692
753
|
// facts come from the one-pass fold instead of per-candidate scans.
|
|
693
754
|
const view = {
|
|
694
|
-
|
|
755
|
+
getRun: (id, runId) => this.store.getRun(id, runId),
|
|
695
756
|
listEvents: () => projected.events,
|
|
696
757
|
getWorkItem: (id, workItemId) => this.store.getWorkItem(id, workItemId),
|
|
697
758
|
listReviewRounds: () => projected.reviewRounds,
|
|
@@ -702,22 +763,22 @@ export class FileSchedulerStoreAdapter {
|
|
|
702
763
|
// A missing fold entry is an authoritative empty fold, not a signal to
|
|
703
764
|
// re-scan the whole history. Pass {} so latestTurnDurableProgressAt treats
|
|
704
765
|
// the fold as present and skips the per-candidate fallback scans.
|
|
705
|
-
return
|
|
766
|
+
return latestRunDurableProgressAt(view, taskId, roleName, runId, projected.runFacts.get(runId) ?? {});
|
|
706
767
|
}
|
|
707
|
-
|
|
768
|
+
recordRoleRunDiagnostic(input) {
|
|
708
769
|
return this.store.transaction((store) => {
|
|
709
770
|
const task = store.getTask(input.taskId);
|
|
710
|
-
const run = store.
|
|
711
|
-
if (task === null ||
|
|
712
|
-
|| run
|
|
771
|
+
const run = store.getActiveRun(input.taskId, input.roleName);
|
|
772
|
+
if (task === null || run === null || !runPurposeAdmitsTaskState(run.purpose, task)
|
|
773
|
+
|| run.id !== input.runId || run.status !== "active") {
|
|
713
774
|
return "state-changed";
|
|
714
775
|
}
|
|
715
|
-
const latest =
|
|
776
|
+
const latest = latestRunEventTime(store.listEvents(input.taskId), RUN_DIAGNOSTIC_FINISHED_EVENT, input.runId);
|
|
716
777
|
if (latest !== undefined && Date.parse(latest) >= Date.parse(input.startedAt)) {
|
|
717
778
|
return "already-recorded";
|
|
718
779
|
}
|
|
719
|
-
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId,
|
|
720
|
-
|
|
780
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, RUN_DIAGNOSTIC_FINISHED_EVENT, {
|
|
781
|
+
runId: input.runId,
|
|
721
782
|
roleName: input.roleName,
|
|
722
783
|
outcome: input.outcome,
|
|
723
784
|
startedAt: input.startedAt
|
|
@@ -725,22 +786,21 @@ export class FileSchedulerStoreAdapter {
|
|
|
725
786
|
return "recorded";
|
|
726
787
|
});
|
|
727
788
|
}
|
|
728
|
-
|
|
789
|
+
recordRoleRunStall(input) {
|
|
729
790
|
return this.store.transaction((store) => {
|
|
730
791
|
const task = store.getTask(input.taskId);
|
|
731
792
|
const role = store.getRole(input.taskId, input.roleName);
|
|
732
|
-
const run = store.
|
|
793
|
+
const run = store.getActiveRun(input.taskId, input.roleName);
|
|
733
794
|
if (task === null
|
|
734
|
-
|| task.status !== "active"
|
|
735
|
-
|| task.executionGate.state !== "enabled"
|
|
736
795
|
|| role === null
|
|
737
796
|
|| run === null
|
|
738
|
-
|| run.
|
|
797
|
+
|| !runPurposeAdmitsTaskState(run.purpose, task)
|
|
798
|
+
|| run.id !== input.runId
|
|
739
799
|
|| run.status !== "active"
|
|
740
800
|
|| run.effective.agentId !== input.agentId
|
|
741
801
|
|| run.effective.adapterId !== input.adapterId)
|
|
742
802
|
return "state-changed";
|
|
743
|
-
const progress =
|
|
803
|
+
const progress = latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId);
|
|
744
804
|
if (progress?.progressAt !== input.progressAt)
|
|
745
805
|
return "state-changed";
|
|
746
806
|
const session = store.getRoleSession(input.taskId, input.roleName);
|
|
@@ -749,8 +809,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
749
809
|
const existing = latestStallEvidenceKey(store.listEvents(task.id), run.id);
|
|
750
810
|
if (existing?.progressAt === input.progressAt)
|
|
751
811
|
return "already-raised";
|
|
752
|
-
const event = createTaskEvent(store.nextEventId(task.id), task.id,
|
|
753
|
-
|
|
812
|
+
const event = createTaskEvent(store.nextEventId(task.id), task.id, RUN_STALLED_EVENT, {
|
|
813
|
+
runId: run.id,
|
|
754
814
|
roleName: role.name,
|
|
755
815
|
kind: input.kind,
|
|
756
816
|
classification: input.classification,
|
|
@@ -763,15 +823,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
763
823
|
return "raised";
|
|
764
824
|
});
|
|
765
825
|
}
|
|
766
|
-
|
|
826
|
+
recordRoleRunProgress(input) {
|
|
767
827
|
return this.store.transaction((store) => {
|
|
768
828
|
const task = store.getTask(input.taskId);
|
|
769
|
-
const run = store.
|
|
829
|
+
const run = store.getActiveRun(input.taskId, input.roleName);
|
|
770
830
|
if (task === null
|
|
771
|
-
|| task.status !== "active"
|
|
772
|
-
|| task.executionGate.state !== "enabled"
|
|
773
831
|
|| run === null
|
|
774
|
-
|| run.
|
|
832
|
+
|| !runPurposeAdmitsTaskState(run.purpose, task)
|
|
833
|
+
|| run.id !== input.runId
|
|
775
834
|
|| run.status !== "active")
|
|
776
835
|
return "state-changed";
|
|
777
836
|
const events = store.listEvents(task.id);
|
|
@@ -782,18 +841,18 @@ export class FileSchedulerStoreAdapter {
|
|
|
782
841
|
&& Date.parse(input.progressAt) <= Date.parse(previousStall.progressAt)) {
|
|
783
842
|
return "already-recorded";
|
|
784
843
|
}
|
|
785
|
-
const existing = events.some((event) => (event.type ===
|
|
786
|
-
&& event.payload.
|
|
844
|
+
const existing = events.some((event) => (event.type === RUN_PROGRESS_EVENT
|
|
845
|
+
&& event.payload.runId === run.id
|
|
787
846
|
&& (event.payload.progressAt === input.progressAt
|
|
788
847
|
|| (typeof event.payload.progressAt === "string"
|
|
789
848
|
&& Number.isFinite(Date.parse(event.payload.progressAt))
|
|
790
849
|
&& Date.parse(event.payload.progressAt) >= Date.parse(input.progressAt)))));
|
|
791
850
|
// Advisory 30-minute diagnostics are intentionally not lifecycle
|
|
792
851
|
// episodes and therefore must never synthesize turn.recovered.
|
|
793
|
-
const recovered =
|
|
852
|
+
const recovered = isRoleRunStalled(events, run.id);
|
|
794
853
|
if (!existing) {
|
|
795
|
-
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id,
|
|
796
|
-
|
|
854
|
+
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_PROGRESS_EVENT, {
|
|
855
|
+
runId: run.id,
|
|
797
856
|
roleName: input.roleName,
|
|
798
857
|
kind: "durable-fold",
|
|
799
858
|
progressAt: input.progressAt,
|
|
@@ -801,8 +860,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
801
860
|
}, input.now));
|
|
802
861
|
}
|
|
803
862
|
if (recovered) {
|
|
804
|
-
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id,
|
|
805
|
-
|
|
863
|
+
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_RECOVERED_EVENT, {
|
|
864
|
+
runId: run.id,
|
|
806
865
|
roleName: input.roleName,
|
|
807
866
|
progressAt: input.progressAt,
|
|
808
867
|
kind: "durable-progress"
|
|
@@ -813,35 +872,44 @@ export class FileSchedulerStoreAdapter {
|
|
|
813
872
|
}
|
|
814
873
|
beginAgentHostProviderTurn(input) {
|
|
815
874
|
this.store.transaction((store) => {
|
|
875
|
+
const task = store.getTask(input.taskId);
|
|
876
|
+
if (task === null || !["active", "draft"].includes(task.status) || task.executionGate.state !== "enabled"
|
|
877
|
+
|| hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({
|
|
878
|
+
scope: "task", taskId: input.taskId, roleName: input.roleName
|
|
879
|
+
})))) {
|
|
880
|
+
throw new AgentHostProviderTurnFenceError("Session execution admission is stopped or being replaced.");
|
|
881
|
+
}
|
|
816
882
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
817
883
|
const session = sessions?.sessions[input.agentId];
|
|
818
884
|
const binding = sessions?.providerBinding;
|
|
819
|
-
const active = store.
|
|
820
|
-
if (sessions === null || sessions === undefined
|
|
821
|
-
|| binding === null || binding === undefined
|
|
822
|
-
|| (input.turnId !== undefined
|
|
823
|
-
&& (active?.id !== input.turnId || active.effective.agentId !== input.agentId))
|
|
824
|
-
|| session?.runtimeGenerationId !== input.runtimeGenerationId
|
|
825
|
-
|| session.nativeSessionId !== input.nativeSessionId
|
|
826
|
-
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId
|
|
827
|
-
|| binding.authority.owner !== input.authorityOwner
|
|
828
|
-
|| binding.authority.epoch !== input.authorityEpoch
|
|
829
|
-
|| binding.authority.holderId !== input.holderId) {
|
|
885
|
+
const active = store.getActiveRun(input.taskId, input.roleName);
|
|
886
|
+
if (sessions === null || sessions === undefined || binding === null || binding === undefined || (input.runId !== undefined && (active?.id !== input.runId || active.effective.agentId !== input.agentId)) || session === undefined || session.nativeSessionId !== input.nativeSessionId || currentProviderConversation(binding).conversationId !== input.nativeSessionId || binding.authority.owner !== input.authorityOwner || binding.authority.epoch !== input.authorityEpoch || binding.authority.holderId !== input.holderId) {
|
|
830
887
|
throw new AgentHostProviderTurnFenceError("Agent Host Provider Turn carries a stale durable writer fence. Release and reacquire Provider authority before retrying input.");
|
|
831
888
|
}
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
&&
|
|
838
|
-
|
|
889
|
+
const currentRun = binding.run;
|
|
890
|
+
if (session.effective.executionEnvironment !== undefined) {
|
|
891
|
+
assertExecutionEnvironmentCurrent(store, input.taskId, session.effective.executionEnvironment);
|
|
892
|
+
}
|
|
893
|
+
if (input.runId !== undefined
|
|
894
|
+
&& !isDeepStrictEqual(active?.effective.executionEnvironment, session.effective.executionEnvironment)) {
|
|
895
|
+
throw new AgentHostProviderTurnFenceError("AgentRun and native Session execution environments differ; start a new Session.");
|
|
896
|
+
}
|
|
897
|
+
if (input.runId !== undefined
|
|
898
|
+
&& active?.effective.executionAuthority !== session.effective.executionAuthority) {
|
|
899
|
+
throw new AgentHostProviderTurnFenceError("Execution authority differs from the fixed native Session.");
|
|
900
|
+
}
|
|
901
|
+
const exactReplay = currentRun !== null
|
|
902
|
+
&& currentRun.runId === input.runId
|
|
903
|
+
&& currentRun.attemptId === input.attemptId
|
|
904
|
+
&& currentRun.authorityEpoch === input.authorityEpoch
|
|
905
|
+
&& currentRun.status === "submitting";
|
|
906
|
+
if (!exactReplay && binding.run !== null
|
|
839
907
|
&& ["submitting", "accepted", "delivery-unknown"]
|
|
840
|
-
.includes(binding.
|
|
841
|
-
throw new
|
|
908
|
+
.includes(binding.run.status)) {
|
|
909
|
+
throw new AgentHostProviderSessionBusyError("Provider Conversation already has an unsettled AgentRun.");
|
|
842
910
|
}
|
|
843
911
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, beginProviderTurn(binding, {
|
|
844
|
-
...(input.
|
|
912
|
+
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
845
913
|
attemptId: input.attemptId,
|
|
846
914
|
authorityEpoch: input.authorityEpoch,
|
|
847
915
|
submittedAt: input.now.toISOString()
|
|
@@ -854,8 +922,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
854
922
|
const binding = sessions?.providerBinding;
|
|
855
923
|
if (sessions === null || sessions === undefined
|
|
856
924
|
|| binding === null || binding === undefined
|
|
857
|
-
|| binding.
|
|
858
|
-
|| binding.
|
|
925
|
+
|| binding.run?.runId !== input.runId
|
|
926
|
+
|| binding.run?.attemptId !== input.attemptId) {
|
|
859
927
|
throw new Error("Agent Host Provider Turn submission is no longer current.");
|
|
860
928
|
}
|
|
861
929
|
const updated = settleProviderTurnSubmission(binding, {
|
|
@@ -865,9 +933,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
865
933
|
resolvedAt: input.now.toISOString()
|
|
866
934
|
});
|
|
867
935
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updated, input.now));
|
|
868
|
-
if (input.
|
|
936
|
+
if (input.runId === undefined)
|
|
869
937
|
return;
|
|
870
|
-
const run = store.
|
|
938
|
+
const run = store.getRun(input.taskId, input.runId);
|
|
871
939
|
const session = sessions.sessions[sessions.activeAgentId];
|
|
872
940
|
const driver = run === null
|
|
873
941
|
? null
|
|
@@ -881,11 +949,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
881
949
|
}),
|
|
882
950
|
message: input.reason,
|
|
883
951
|
raw: input.raw,
|
|
884
|
-
inputDisposition: input.status === "
|
|
952
|
+
inputDisposition: input.status === "delivery-unknown" ? "unknown" : "not-accepted"
|
|
885
953
|
});
|
|
886
954
|
const errorEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
|
|
887
955
|
sourceEventId: input.attemptId,
|
|
888
|
-
|
|
956
|
+
runId: input.runId,
|
|
889
957
|
roleName: input.roleName,
|
|
890
958
|
agentId: run?.effective.agentId ?? sessions.activeAgentId,
|
|
891
959
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
@@ -895,7 +963,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
895
963
|
// string reads back as a real value and cannot be told apart from
|
|
896
964
|
// one the Provider genuinely reported.
|
|
897
965
|
...optionalEventFields({
|
|
898
|
-
runtimeGenerationId: session?.runtimeGenerationId,
|
|
899
966
|
nativeSessionId: session?.nativeSessionId
|
|
900
967
|
}),
|
|
901
968
|
source: error.source,
|
|
@@ -908,6 +975,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
908
975
|
sessionDisposition: error.sessionDisposition
|
|
909
976
|
}, input.now);
|
|
910
977
|
store.saveEvent(input.taskId, errorEvent);
|
|
978
|
+
if (input.status === "deferred")
|
|
979
|
+
return;
|
|
911
980
|
const route = input.roleName === "leader"
|
|
912
981
|
? { kind: "operator" }
|
|
913
982
|
: { kind: "role", taskId: input.taskId, roleName: "leader" };
|
|
@@ -923,22 +992,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
923
992
|
const sessions = this.store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
924
993
|
const session = sessions?.sessions[input.agentId];
|
|
925
994
|
const binding = sessions?.providerBinding;
|
|
926
|
-
if (binding === null || binding === undefined
|
|
927
|
-
|| session?.runtimeGenerationId !== input.runtimeGenerationId
|
|
928
|
-
|| session.nativeSessionId !== input.nativeSessionId
|
|
929
|
-
|| currentProviderConversation(binding).conversationId !== input.nativeSessionId)
|
|
930
|
-
return null;
|
|
931
|
-
const activation = currentProviderActivation(binding);
|
|
932
|
-
if (activation === null)
|
|
995
|
+
if (binding === null || binding === undefined || session === undefined || session.nativeSessionId !== input.nativeSessionId || currentProviderConversation(binding).conversationId !== input.nativeSessionId)
|
|
933
996
|
return null;
|
|
934
997
|
return {
|
|
935
998
|
conversationId: currentProviderConversation(binding).conversationId,
|
|
936
|
-
activationId: activation.activationId,
|
|
937
999
|
...binding.authority
|
|
938
1000
|
};
|
|
939
1001
|
}
|
|
940
|
-
|
|
941
|
-
return this.store.
|
|
1002
|
+
peekNextRunId(taskId) {
|
|
1003
|
+
return this.store.peekNextRunId(taskId);
|
|
942
1004
|
}
|
|
943
1005
|
getWorkMailbox(target) { return this.store.getWorkMailbox(target); }
|
|
944
1006
|
listWorkMailboxes() { return this.store.listWorkMailboxes(); }
|
|
@@ -961,7 +1023,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
961
1023
|
}
|
|
962
1024
|
recordAgentError(input, now) {
|
|
963
1025
|
return this.store.transaction((store) => {
|
|
964
|
-
const run = store.
|
|
1026
|
+
const run = store.getRun(input.taskId, input.runId);
|
|
965
1027
|
const sessionSet = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
966
1028
|
const sessionAgentId = run?.effective.agentId ?? sessionSet?.activeAgentId;
|
|
967
1029
|
const session = sessionAgentId === undefined
|
|
@@ -987,7 +1049,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
987
1049
|
: { sessionDisposition: input.sessionDisposition })
|
|
988
1050
|
});
|
|
989
1051
|
const duplicate = [...store.listEvents(input.taskId)].reverse().find((event) => (event.type === "runtime.agent-error"
|
|
990
|
-
&& event.payload.
|
|
1052
|
+
&& event.payload.runId === input.runId
|
|
991
1053
|
&& event.payload.roleName === input.roleName
|
|
992
1054
|
&& event.payload.phase === input.phase
|
|
993
1055
|
&& event.payload.attemptId === input.attemptId
|
|
@@ -996,8 +1058,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
996
1058
|
&& event.payload.sessionDisposition === error.sessionDisposition
|
|
997
1059
|
&& event.payload.registrationDisposition === input.registrationDisposition
|
|
998
1060
|
&& event.payload.hostState === input.hostState
|
|
999
|
-
&& event.payload.expectedRuntimeGenerationId === input.expectedRuntimeGenerationId
|
|
1000
|
-
&& event.payload.observedRuntimeGenerationId === input.observedRuntimeGenerationId
|
|
1001
1061
|
&& (input.attemptId === undefined
|
|
1002
1062
|
? event.payload.raw === error.raw
|
|
1003
1063
|
: event.payload.message === error.message
|
|
@@ -1009,8 +1069,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1009
1069
|
if (duplicate !== undefined)
|
|
1010
1070
|
return duplicate.id;
|
|
1011
1071
|
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.agent-error", {
|
|
1012
|
-
sourceEventId: `${input.
|
|
1013
|
-
|
|
1072
|
+
sourceEventId: `${input.runId}:${input.phase}${input.attemptId === undefined ? "" : `:${input.attemptId}`}`,
|
|
1073
|
+
runId: input.runId,
|
|
1014
1074
|
roleName: input.roleName,
|
|
1015
1075
|
agentId: run?.effective.agentId ?? session?.agentId ?? "unknown",
|
|
1016
1076
|
adapterId: run?.effective.adapterId ?? session?.adapterId ?? "unknown",
|
|
@@ -1028,32 +1088,40 @@ export class FileSchedulerStoreAdapter {
|
|
|
1028
1088
|
// "the Host did not report this" from "the Host reported nothing".
|
|
1029
1089
|
// The Session identities follow the same rule: this failure can
|
|
1030
1090
|
// happen before any Session record exists, and there is no native
|
|
1031
|
-
//
|
|
1091
|
+
// AgentRun to name at all.
|
|
1032
1092
|
...optionalEventFields({
|
|
1033
|
-
runtimeGenerationId: session?.runtimeGenerationId,
|
|
1034
1093
|
nativeSessionId: session?.nativeSessionId,
|
|
1035
1094
|
errorName: input.errorName,
|
|
1036
1095
|
causeName: input.causeName,
|
|
1037
1096
|
hostState: input.hostState,
|
|
1038
|
-
expectedRuntimeGenerationId: input.expectedRuntimeGenerationId,
|
|
1039
|
-
observedRuntimeGenerationId: input.observedRuntimeGenerationId,
|
|
1040
1097
|
attemptId: input.attemptId,
|
|
1041
1098
|
registrationDisposition: input.registrationDisposition
|
|
1042
1099
|
})
|
|
1043
1100
|
}, now);
|
|
1044
1101
|
store.saveEvent(input.taskId, event);
|
|
1102
|
+
if (input.phase === "turn-submit" && error.inputDisposition === "unknown"
|
|
1103
|
+
&& input.attemptId !== undefined && sessionSet?.providerBinding?.run?.attemptId === input.attemptId
|
|
1104
|
+
&& sessionSet.providerBinding.run.status === "submitting") {
|
|
1105
|
+
// A pass clock can precede the asynchronous Host registration.
|
|
1106
|
+
const observedAt = new Date(Math.max(now.getTime(), Date.parse(sessionSet.providerBinding.run.updatedAt)));
|
|
1107
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessionSet, settleProviderTurnSubmission(sessionSet.providerBinding, { attemptId: input.attemptId,
|
|
1108
|
+
status: "delivery-unknown", reason: error.message, resolvedAt: observedAt.toISOString()
|
|
1109
|
+
}), observedAt));
|
|
1110
|
+
}
|
|
1111
|
+
if (input.errorName === "ProviderTurnBusyError" && error.inputDisposition === "not-accepted")
|
|
1112
|
+
return event.id;
|
|
1045
1113
|
const leaderCannotReceive = input.roleName === "leader"
|
|
1046
1114
|
&& ["host-start", "session-start", "session-restore", "turn-submit"].includes(input.phase);
|
|
1047
1115
|
if (!leaderCannotReceive) {
|
|
1048
1116
|
enqueueWork(store, { kind: "role", taskId: input.taskId, roleName: "leader" }, wakeReason("agent-error", event.id), now, [{ type: "event", taskId: input.taskId, id: event.id }], {
|
|
1049
1117
|
source: driver?.id ?? input.source,
|
|
1050
|
-
dedupeKey: `agent-error:${input.taskId}:${input.
|
|
1118
|
+
dedupeKey: `agent-error:${input.taskId}:${input.runId}:${input.phase}:${event.id}`
|
|
1051
1119
|
});
|
|
1052
1120
|
}
|
|
1053
1121
|
if (leaderCannotReceive) {
|
|
1054
1122
|
enqueueWork(store, { kind: "operator" }, "leader-agent-error", now, [{ type: "event", taskId: input.taskId, id: event.id }], {
|
|
1055
1123
|
source: driver?.id ?? input.source,
|
|
1056
|
-
dedupeKey: `leader-agent-error:${input.taskId}:${input.
|
|
1124
|
+
dedupeKey: `leader-agent-error:${input.taskId}:${input.runId}:${event.id}`
|
|
1057
1125
|
});
|
|
1058
1126
|
}
|
|
1059
1127
|
return event.id;
|
|
@@ -1063,7 +1131,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1063
1131
|
* Records that a Leader wake was suppressed by scheduler single-flight
|
|
1064
1132
|
* (the Role runtime lifecycle lane was busy). The wake stays durable and
|
|
1065
1133
|
* is retried after the lane settles; this event is the audit trail that
|
|
1066
|
-
* separates scheduler backpressure from real
|
|
1134
|
+
* separates scheduler backpressure from real AgentRun failures.
|
|
1067
1135
|
*/
|
|
1068
1136
|
recordWakeSuppression(taskId, reason, now) {
|
|
1069
1137
|
this.store.transaction((store) => {
|
|
@@ -1130,8 +1198,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1130
1198
|
return { status: "claimed", processing: claimed.processing };
|
|
1131
1199
|
});
|
|
1132
1200
|
}
|
|
1133
|
-
|
|
1134
|
-
return this.store.transaction((store) => (
|
|
1201
|
+
settleRoleRunDispatch(input) {
|
|
1202
|
+
return this.store.transaction((store) => (settleRoleRunDispatchMailbox(store, input, input.expected)));
|
|
1135
1203
|
}
|
|
1136
1204
|
completeWorkMailbox(target, batchId) {
|
|
1137
1205
|
return this.store.transaction((store) => {
|
|
@@ -1165,16 +1233,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
1165
1233
|
if (mailbox === null || disposition === null)
|
|
1166
1234
|
return false;
|
|
1167
1235
|
if (mailbox.processing !== null) {
|
|
1168
|
-
if (!
|
|
1169
|
-
&& !mailbox.processing.batch.reasons.every(isRuntimeCleanupReason)) {
|
|
1236
|
+
if (disposition !== "replace-session" && !mailbox.processing.batch.reasons.every(isRuntimeCleanupReason)) {
|
|
1170
1237
|
return false;
|
|
1171
1238
|
}
|
|
1172
1239
|
mailbox = completeProcessing(mailbox, mailbox.processing.batchId);
|
|
1173
1240
|
}
|
|
1174
1241
|
const pending = mailbox.pending;
|
|
1175
1242
|
if (pending !== null) {
|
|
1176
|
-
if (pending.reasons.length === 0
|
|
1177
|
-
|| !pending.reasons.every(isRuntimeCleanupReason)) {
|
|
1243
|
+
if (disposition !== "replace-session" && (pending.reasons.length === 0
|
|
1244
|
+
|| !pending.reasons.every(isRuntimeCleanupReason))) {
|
|
1178
1245
|
return false;
|
|
1179
1246
|
}
|
|
1180
1247
|
const batchId = `runtime-cleanup-complete:${pending.fromSequence}-${pending.toSequence}`;
|
|
@@ -1185,9 +1252,46 @@ export class FileSchedulerStoreAdapter {
|
|
|
1185
1252
|
}), batchId);
|
|
1186
1253
|
}
|
|
1187
1254
|
const owner = runtimeOwnerFromTarget(target);
|
|
1188
|
-
if (disposition
|
|
1255
|
+
if (disposition !== "detach-host" && owner.scope === "task") {
|
|
1256
|
+
// Physical quiescence was proven by stopOwner. Runs are engineering
|
|
1257
|
+
// attempts: cancel only this Role, never Task intent or other workers.
|
|
1258
|
+
cancelQuiescentRoleRuns(store, owner.taskId, owner.roleName, "Session execution stopped on request; durable Task context and workspace progress were preserved.", now);
|
|
1259
|
+
let set = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
1260
|
+
if (set !== null) {
|
|
1261
|
+
// Late observations cannot resurrect engineering occupancy after
|
|
1262
|
+
// the exact native/process stop just completed.
|
|
1263
|
+
if (set.providerBinding !== null) {
|
|
1264
|
+
let binding = set.providerBinding;
|
|
1265
|
+
if (binding.run !== null)
|
|
1266
|
+
binding = cancelQuiescentProviderInput(binding, {
|
|
1267
|
+
attemptId: binding.run.attemptId, cancelledAt: now.toISOString(),
|
|
1268
|
+
reason: "Session replacement after verified resource stop."
|
|
1269
|
+
});
|
|
1270
|
+
set = updateTaskRoleProviderRuntime(set, clearProviderGoal(binding), now);
|
|
1271
|
+
}
|
|
1272
|
+
store.saveTaskRoleSessionSet(set);
|
|
1273
|
+
}
|
|
1189
1274
|
endRuntimeOwnerSession(store, owner, now);
|
|
1190
1275
|
}
|
|
1276
|
+
if (disposition === "replace-session" && owner.scope === "task") {
|
|
1277
|
+
const set = store.getTaskRoleSessionSet(owner.taskId, owner.roleName);
|
|
1278
|
+
if (set !== null)
|
|
1279
|
+
store.saveTaskRoleSessionSet(selectNewTaskRoleSession(set, set.activeAgentId, now));
|
|
1280
|
+
const event = createTaskEvent(store.nextEventId(owner.taskId), owner.taskId, "runtime.session-replaced", { roleName: owner.roleName }, now);
|
|
1281
|
+
store.saveEvent(owner.taskId, event);
|
|
1282
|
+
// Accepted notification batches are already consumed; unresolved old
|
|
1283
|
+
// claims belong to the discarded runtime, not to its successor.
|
|
1284
|
+
const leaderTarget = { kind: "role", taskId: owner.taskId, roleName: "leader" };
|
|
1285
|
+
const leaderMailbox = store.getWorkMailbox(leaderTarget);
|
|
1286
|
+
if (owner.roleName === "leader" && leaderMailbox?.processing?.owner.startsWith("leader-notification:")) {
|
|
1287
|
+
store.saveWorkMailbox(releaseProcessing(leaderMailbox, leaderMailbox.processing.batchId));
|
|
1288
|
+
}
|
|
1289
|
+
enqueueWork(store, leaderTarget, "session-replaced", now, [{ type: "event", taskId: owner.taskId, id: event.id }]);
|
|
1290
|
+
}
|
|
1291
|
+
else if (disposition === "end-session") {
|
|
1292
|
+
if (owner.scope === "global")
|
|
1293
|
+
endRuntimeOwnerSession(store, owner, now);
|
|
1294
|
+
}
|
|
1191
1295
|
else {
|
|
1192
1296
|
detachRuntimeOwnerHost(store, owner, now);
|
|
1193
1297
|
}
|
|
@@ -1195,22 +1299,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1195
1299
|
return true;
|
|
1196
1300
|
});
|
|
1197
1301
|
}
|
|
1198
|
-
completeStoppedRuntimeReservation(target, batchId, now) {
|
|
1199
|
-
return this.store.transaction((store) => {
|
|
1200
|
-
const mailbox = store.getWorkMailbox(target);
|
|
1201
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing, batchId)) {
|
|
1202
|
-
return false;
|
|
1203
|
-
}
|
|
1204
|
-
endRuntimeOwnerSession(store, runtimeOwnerFromTarget(target), now);
|
|
1205
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, batchId));
|
|
1206
|
-
return true;
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
1302
|
listDormantRuntimeOwners() {
|
|
1210
1303
|
return this.listRuntimeSessionCandidates().flatMap((candidate) => {
|
|
1211
1304
|
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(candidate.owner)))
|
|
1212
1305
|
|| (candidate.owner.scope === "task"
|
|
1213
|
-
&& this.store.
|
|
1306
|
+
&& this.store.getActiveRun(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
|
|
1214
1307
|
return [];
|
|
1215
1308
|
}
|
|
1216
1309
|
return [{
|
|
@@ -1218,7 +1311,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1218
1311
|
agentId: candidate.agentId,
|
|
1219
1312
|
adapterId: candidate.adapterId,
|
|
1220
1313
|
nativeSessionId: candidate.nativeSessionId,
|
|
1221
|
-
...(candidate.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: candidate.runtimeGenerationId }),
|
|
1222
1314
|
sessionUpdatedAt: candidate.sessionUpdatedAt
|
|
1223
1315
|
}];
|
|
1224
1316
|
});
|
|
@@ -1226,11 +1318,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1226
1318
|
listRuntimeSessionCandidates(query = {}) {
|
|
1227
1319
|
return this.store.listRuntimeSessionCandidates(query);
|
|
1228
1320
|
}
|
|
1229
|
-
enqueueRuntimeCleanup(owner, now = new Date(), expectedDormantCandidate) {
|
|
1321
|
+
enqueueRuntimeCleanup(owner, now = new Date(), expectedDormantCandidate, allowActiveRun = false) {
|
|
1230
1322
|
return this.store.transaction((store) => {
|
|
1231
1323
|
if (expectedDormantCandidate !== undefined
|
|
1232
1324
|
&& (!sameRuntimeOwner(owner, expectedDormantCandidate.owner)
|
|
1233
|
-
|| !dormantRuntimeCandidateIsCurrent(store, expectedDormantCandidate))) {
|
|
1325
|
+
|| !dormantRuntimeCandidateIsCurrent(store, expectedDormantCandidate, allowActiveRun))) {
|
|
1234
1326
|
return null;
|
|
1235
1327
|
}
|
|
1236
1328
|
if (owner.scope === "task" && store.getTask(owner.taskId) === null) {
|
|
@@ -1263,122 +1355,169 @@ export class FileSchedulerStoreAdapter {
|
|
|
1263
1355
|
}
|
|
1264
1356
|
clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
|
|
1265
1357
|
getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
return "state-changed";
|
|
1283
|
-
}
|
|
1284
|
-
if (store.getTurn(input.task.id, input.turn.id) !== null)
|
|
1285
|
-
return "state-changed";
|
|
1286
|
-
if (store.getActiveTurn(input.task.id, input.role.name) !== null)
|
|
1287
|
-
return "busy";
|
|
1288
|
-
const pending = store.getPendingWakeup(input.task.id);
|
|
1289
|
-
if (pending === null || !pendingWakeupsMatch(pending, input.wakeup)) {
|
|
1290
|
-
return "state-changed";
|
|
1358
|
+
prepareMessageContinuations(taskId, now) {
|
|
1359
|
+
const roles = new Set(this.store.listMessages(taskId).flatMap((message) => message.recipient?.ownerRunId !== undefined && message.continuation?.runId === undefined ? [message.recipient.roleName] : []));
|
|
1360
|
+
for (const roleName of roles) {
|
|
1361
|
+
try {
|
|
1362
|
+
this.store.transaction((store) => prepareMessageContinuations(store, taskId, now, roleName));
|
|
1363
|
+
}
|
|
1364
|
+
catch (error) {
|
|
1365
|
+
const reason = `preparation-failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 1000);
|
|
1366
|
+
this.store.transaction((store) => {
|
|
1367
|
+
for (const message of store.listMessages(taskId)) {
|
|
1368
|
+
if (message.recipient?.roleName === roleName && message.recipient.ownerRunId !== undefined && message.continuation?.runId === undefined
|
|
1369
|
+
&& message.continuation?.notDeliveredReason !== reason) {
|
|
1370
|
+
store.updateMessage(taskId, { ...message, continuation: { notDeliveredReason: reason } });
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1291
1374
|
}
|
|
1292
|
-
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/** The first Draft conversation is an explicit planning execution. Later
|
|
1378
|
+
* messages use the same notification path as direct Leader collaboration. */
|
|
1379
|
+
prepareDraftPlanning(taskId, now) {
|
|
1380
|
+
return this.store.transaction((store) => {
|
|
1381
|
+
const task = store.getTask(taskId);
|
|
1382
|
+
if (task?.status !== "draft" || task.executionGate.state !== "enabled")
|
|
1383
|
+
return false;
|
|
1384
|
+
if (store.getActiveRun(taskId, "leader") !== null)
|
|
1385
|
+
return true;
|
|
1386
|
+
if (store.listRuns(taskId).some(run => run.roleName === "leader" && run.purpose === "planning"))
|
|
1387
|
+
return false;
|
|
1388
|
+
const sessions = store.getTaskRoleSessionSet(taskId, "leader");
|
|
1389
|
+
if (activeLiveRoleAgentSession(sessions) !== null)
|
|
1390
|
+
return false;
|
|
1391
|
+
const role = requireRole(store, taskId, "leader");
|
|
1392
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
1293
1393
|
const mailbox = store.getWorkMailbox(target);
|
|
1294
|
-
if (mailbox
|
|
1295
|
-
return
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
store.
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
const allocatedWakeId = store.nextTaskWakeId(input.task.id);
|
|
1315
|
-
if (allocatedWakeId !== input.wakeId) {
|
|
1316
|
-
throw new Error(`Leader TaskWake allocation changed unexpectedly: ${input.task.id}.`);
|
|
1317
|
-
}
|
|
1318
|
-
store.saveTaskWake(input.task.id, createTaskWake({
|
|
1319
|
-
id: allocatedWakeId,
|
|
1320
|
-
taskId: input.task.id,
|
|
1321
|
-
reasons: input.wakeup.reasons,
|
|
1322
|
-
fromCursor: input.wakeFromCursor,
|
|
1323
|
-
toCursor: input.now.toISOString(),
|
|
1324
|
-
turnId: input.turn.id,
|
|
1325
|
-
now: input.now
|
|
1326
|
-
}));
|
|
1327
|
-
}
|
|
1328
|
-
if (input.turn.mode !== "new"
|
|
1329
|
-
&& input.session !== null
|
|
1330
|
-
&& input.session.nativeSessionId !== undefined) {
|
|
1331
|
-
saveTaskSession(store, role, {
|
|
1332
|
-
...input.session,
|
|
1333
|
-
nativeSessionId: input.session.nativeSessionId
|
|
1334
|
-
}, "active", input.now);
|
|
1335
|
-
}
|
|
1336
|
-
store.clearLeaderFailure(input.task.id);
|
|
1337
|
-
return "claimed";
|
|
1394
|
+
if (mailbox?.pending == null || mailbox.processing !== null)
|
|
1395
|
+
return false;
|
|
1396
|
+
const run = createRun(store.nextRunId(taskId), taskId, "leader", "new", createRunInput({ source: { type: "yui", channel: "task-dispatch" },
|
|
1397
|
+
directive: `Plan Task ${taskId} with the user. Persist requirements and decisions. Request activation explicitly; planning grants no delivery authority.`,
|
|
1398
|
+
deltaRefIds: [] }), now, {
|
|
1399
|
+
purpose: "planning", effective: resolveEffectiveLaunch({ role, purpose: "planning" })
|
|
1400
|
+
});
|
|
1401
|
+
const snapshot = freezeRunContextSnapshot(store, run, now);
|
|
1402
|
+
const frozen = withRunContextSnapshot(run, contextSnapshotRef(snapshot));
|
|
1403
|
+
store.saveRun(frozen);
|
|
1404
|
+
store.saveActiveRun(frozen);
|
|
1405
|
+
// Freeze the initial notification prefix against this planning Run.
|
|
1406
|
+
// Later messages remain pending; acceptance or an exact terminal settles
|
|
1407
|
+
// only this batch, so a failed initial launch cannot replay it forever.
|
|
1408
|
+
const batchId = formatRunReceiptId(taskId, run.id);
|
|
1409
|
+
store.saveWorkMailbox(bindExecution(claimPending(mailbox, {
|
|
1410
|
+
batchId, owner: `planning:${run.id}`, startedAt: now.toISOString()
|
|
1411
|
+
}), batchId, { type: "run", taskId, id: run.id }));
|
|
1412
|
+
return true;
|
|
1338
1413
|
});
|
|
1339
1414
|
}
|
|
1340
|
-
|
|
1415
|
+
claimLeaderNotification(taskId, now) {
|
|
1341
1416
|
return this.store.transaction((store) => {
|
|
1342
|
-
const task = store.getTask(
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1417
|
+
const task = store.getTask(taskId);
|
|
1418
|
+
if (task == null || !["active", "draft"].includes(task.status) || task.executionGate.state !== "enabled")
|
|
1419
|
+
return null;
|
|
1420
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
1421
|
+
let mailbox = store.getWorkMailbox(target);
|
|
1422
|
+
if (mailbox === null)
|
|
1423
|
+
return null;
|
|
1424
|
+
const provider = store.getTaskRoleSessionSet(taskId, "leader")?.providerBinding?.run;
|
|
1425
|
+
const processing = mailbox.processing;
|
|
1426
|
+
if (processing !== null) {
|
|
1427
|
+
if (!processing.owner.startsWith("leader-notification:"))
|
|
1428
|
+
return null;
|
|
1429
|
+
const wakeId = processing.owner.slice("leader-notification:".length);
|
|
1430
|
+
const attemptId = processing.batchId;
|
|
1431
|
+
const previous = store.listEvents(taskId).filter((event) => event.type === "notification.delivery" && event.payload.attemptId === attemptId).at(-1);
|
|
1432
|
+
const native = provider?.attemptId === attemptId ? provider : undefined;
|
|
1433
|
+
const accepted = store.listEvents(taskId).some((event) => {
|
|
1434
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
1435
|
+
return observation?.fence.roleName === "leader"
|
|
1436
|
+
&& observation.fence.receiptId === attemptId
|
|
1437
|
+
&& ((observation.kind === "turn.accepted" && observation.authority !== "transport")
|
|
1438
|
+
|| isRunTerminalObservation(observation));
|
|
1439
|
+
});
|
|
1440
|
+
if (accepted || native?.status === "accepted" || previous?.payload.outcome === "accepted") {
|
|
1441
|
+
this.settleLeaderNotification(taskId, attemptId, "accepted", now);
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
if (native?.status === "rejected" || previous?.payload.outcome === "rejected") {
|
|
1445
|
+
this.settleLeaderNotification(taskId, attemptId, "rejected", now);
|
|
1446
|
+
return null;
|
|
1447
|
+
}
|
|
1448
|
+
if (native?.status === "submitting")
|
|
1449
|
+
return { wakeId, attemptId, disposition: "pending" };
|
|
1450
|
+
if (native?.status !== "deferred" && previous?.payload.outcome !== "deferred") {
|
|
1451
|
+
this.settleLeaderNotification(taskId, attemptId, "unknown", now, "Acceptance could not be recovered. Preserve the fixed wake; no automatic replay.");
|
|
1452
|
+
return { wakeId, attemptId, disposition: "unknown" };
|
|
1453
|
+
}
|
|
1454
|
+
if (provider !== null && provider !== undefined && provider.attemptId !== attemptId
|
|
1455
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(provider.status)) {
|
|
1456
|
+
return { wakeId, attemptId, disposition: "pending" };
|
|
1457
|
+
}
|
|
1458
|
+
const nextAttempt = `notification:${taskId}/${wakeId}/${randomUUID()}`;
|
|
1459
|
+
store.saveWorkMailbox({ ...mailbox, processing: { ...processing, batchId: nextAttempt } });
|
|
1460
|
+
return { wakeId, attemptId: nextAttempt, disposition: "submit" };
|
|
1346
1461
|
}
|
|
1347
|
-
if (
|
|
1348
|
-
|
|
1349
|
-
|
|
1462
|
+
if (mailbox.pending === null || (provider !== null && provider !== undefined
|
|
1463
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(provider.status)))
|
|
1464
|
+
return null;
|
|
1465
|
+
const latest = latestTaskWake(store.listTaskWakes(taskId));
|
|
1466
|
+
const wakeId = store.nextTaskWakeId(taskId);
|
|
1467
|
+
const wake = createTaskWake({
|
|
1468
|
+
id: wakeId, taskId, reasons: mailbox.pending.reasons,
|
|
1469
|
+
refs: mailbox.pending.refs,
|
|
1470
|
+
fromCursor: latest?.toCursor ?? task.createdAt,
|
|
1471
|
+
toCursor: new Date(Math.max(now.getTime(), Date.parse(mailbox.pending.lastQueuedAt))).toISOString(), now
|
|
1472
|
+
});
|
|
1473
|
+
const attemptId = `notification:${taskId}/${wakeId}/${randomUUID()}`;
|
|
1474
|
+
mailbox = claimPending(mailbox, { batchId: attemptId,
|
|
1475
|
+
owner: `leader-notification:${wakeId}`, startedAt: now.toISOString() });
|
|
1476
|
+
store.saveTaskWake(taskId, wake);
|
|
1477
|
+
store.saveWorkMailbox(mailbox);
|
|
1478
|
+
return { wakeId, attemptId, disposition: "submit" };
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
settleLeaderNotification(taskId, attemptId, outcome, now, detail) {
|
|
1482
|
+
this.store.transaction((store) => {
|
|
1483
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
1350
1484
|
const mailbox = store.getWorkMailbox(target);
|
|
1351
1485
|
const processing = mailbox?.processing;
|
|
1352
|
-
if (mailbox
|
|
1353
|
-
|| processing
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1486
|
+
if (mailbox == null || processing == null || processing.batchId !== attemptId
|
|
1487
|
+
|| !processing.owner.startsWith("leader-notification:"))
|
|
1488
|
+
return;
|
|
1489
|
+
if (!store.listEvents(taskId).some((event) => event.type === "notification.delivery"
|
|
1490
|
+
&& event.payload.attemptId === attemptId && event.payload.outcome === outcome)) {
|
|
1491
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "notification.delivery", { attemptId, outcome, ...(detail === undefined ? {} : { detail }) }, now));
|
|
1492
|
+
}
|
|
1493
|
+
if (outcome !== "accepted" && outcome !== "rejected")
|
|
1494
|
+
return;
|
|
1495
|
+
store.saveWorkMailbox(completeProcessing(mailbox, attemptId));
|
|
1496
|
+
if (outcome === "accepted") {
|
|
1497
|
+
const wakeId = processing.owner.slice("leader-notification:".length);
|
|
1498
|
+
const wake = store.listTaskWakes(taskId).find((entry) => entry.id === wakeId);
|
|
1499
|
+
if (wake !== undefined)
|
|
1500
|
+
store.saveTaskWake(taskId, markTaskWakeConsumed(wake, now));
|
|
1501
|
+
}
|
|
1367
1502
|
});
|
|
1368
1503
|
}
|
|
1369
|
-
|
|
1504
|
+
saveRoleRunPrepared(input) {
|
|
1370
1505
|
this.store.transaction((store) => {
|
|
1371
1506
|
const task = store.getTask(input.task.id);
|
|
1372
|
-
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1373
|
-
throw new Error(`Task is not active: ${input.task.id}.`);
|
|
1374
|
-
}
|
|
1375
1507
|
const role = requireRole(store, input.task.id, input.role.name);
|
|
1376
|
-
const active = store.
|
|
1377
|
-
if (active === null || active.id !== input.
|
|
1378
|
-
throw new Error(`Active
|
|
1508
|
+
const active = store.getActiveRun(input.task.id, input.role.name);
|
|
1509
|
+
if (active === null || active.id !== input.run.id) {
|
|
1510
|
+
throw new Error(`Active AgentRun changed before preparation was persisted: ${input.run.id}.`);
|
|
1511
|
+
}
|
|
1512
|
+
// The Turn's own purpose decides which Task lifecycle admits it, so a
|
|
1513
|
+
// planning Turn on a Draft persists its Session while every other purpose
|
|
1514
|
+
// still requires an active Task. Read the durable active Turn, never the
|
|
1515
|
+
// caller's copy, so admission cannot be widened by a stale input.
|
|
1516
|
+
if (task === null || !runPurposeAdmitsTaskState(active.purpose, task)) {
|
|
1517
|
+
throw new Error(`Task is not active: ${input.task.id}.`);
|
|
1379
1518
|
}
|
|
1380
1519
|
if (store.getTaskRoleSessionSet(input.task.id, input.role.name) === null) {
|
|
1381
|
-
store.saveTaskRoleSessionSet(createRoleSessionSet({ scope: "task", taskId: input.task.id, roleName: input.role.name }, input.
|
|
1520
|
+
store.saveTaskRoleSessionSet(createRoleSessionSet({ scope: "task", taskId: input.task.id, roleName: input.role.name }, input.run.effective.agentId, input.now));
|
|
1382
1521
|
}
|
|
1383
1522
|
if (input.session !== null && input.session.nativeSessionId !== undefined) {
|
|
1384
1523
|
const existing = store.getRoleSession(input.task.id, input.role.name);
|
|
@@ -1390,63 +1529,61 @@ export class FileSchedulerStoreAdapter {
|
|
|
1390
1529
|
&& existing !== null
|
|
1391
1530
|
&& existing.nativeSessionId !== input.session.nativeSessionId
|
|
1392
1531
|
&& existing.status === "active";
|
|
1393
|
-
if (!defersConversationReplacement
|
|
1394
|
-
&& (existing?.nativeSessionId !== input.session.nativeSessionId
|
|
1395
|
-
|| (input.runtimeGenerationId !== undefined && existing.runtimeGenerationId !== input.runtimeGenerationId)
|
|
1396
|
-
|| existing.status !== "active")) {
|
|
1532
|
+
if (!defersConversationReplacement && (existing?.nativeSessionId !== input.session.nativeSessionId || existing.status !== "active")) {
|
|
1397
1533
|
saveTaskSession(store, role, {
|
|
1398
1534
|
...input.session,
|
|
1399
1535
|
nativeSessionId: input.session.nativeSessionId
|
|
1400
|
-
}, "active", input.now
|
|
1536
|
+
}, "active", input.now);
|
|
1537
|
+
}
|
|
1538
|
+
if (!defersConversationReplacement && !store.listEvents(input.task.id).some((event) => event.type === "run.session-prepared" && event.payload.runId === input.run.id
|
|
1539
|
+
&& event.payload.nativeSessionId === input.session.nativeSessionId)) {
|
|
1540
|
+
store.saveEvent(input.task.id, createTaskEvent(store.nextEventId(input.task.id), input.task.id, "run.session-prepared", { runId: input.run.id, roleName: role.name,
|
|
1541
|
+
nativeSessionId: input.session.nativeSessionId }, input.now));
|
|
1401
1542
|
}
|
|
1402
1543
|
}
|
|
1403
1544
|
});
|
|
1404
1545
|
}
|
|
1405
|
-
|
|
1546
|
+
saveRoleRunDeliveryFailure(input) {
|
|
1406
1547
|
return this.store.transaction((store) => {
|
|
1407
1548
|
const task = store.getTask(input.taskId);
|
|
1408
1549
|
const role = store.getRole(input.taskId, input.roleName);
|
|
1409
|
-
const active = store.
|
|
1550
|
+
const active = store.getActiveRun(input.taskId, input.roleName);
|
|
1410
1551
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1411
1552
|
const session = sessions?.sessions[input.agentId];
|
|
1412
1553
|
if (task === null
|
|
1413
|
-
|| task.status !== "active"
|
|
1414
|
-
|| task.executionGate.state !== "enabled"
|
|
1415
|
-
|| role === null
|
|
1416
1554
|
|| active === null
|
|
1417
|
-
|| active.
|
|
1555
|
+
|| !runPurposeAdmitsTaskState(active.purpose, task)
|
|
1556
|
+
|| role === null
|
|
1557
|
+
|| active.id !== input.runId
|
|
1418
1558
|
|| active.status !== "active"
|
|
1419
1559
|
|| active.effective.agentId !== input.agentId
|
|
1420
1560
|
|| active.effective.adapterId !== input.adapterId) {
|
|
1421
1561
|
return "state-changed";
|
|
1422
1562
|
}
|
|
1423
1563
|
const summary = input.summary
|
|
1424
|
-
?? `Role delivery failed conclusively before exact
|
|
1425
|
-
const result =
|
|
1564
|
+
?? `Role delivery failed conclusively before exact AgentRun input acceptance: ${input.runId}.`;
|
|
1565
|
+
const result = terminalizeExactTaskRun(store, {
|
|
1426
1566
|
taskId: input.taskId,
|
|
1427
1567
|
roleName: input.roleName,
|
|
1428
1568
|
agentId: input.agentId,
|
|
1429
|
-
|
|
1569
|
+
runId: input.runId,
|
|
1430
1570
|
...(session?.nativeSessionId === undefined
|
|
1431
1571
|
? {}
|
|
1432
1572
|
: { nativeSessionId: session.nativeSessionId }),
|
|
1433
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
1434
1573
|
outcome: { status: "failed", diagnostic: summary, failureReason: input.failureReason }
|
|
1435
1574
|
}, input.now);
|
|
1436
|
-
if (result.disposition !== "applied" || result.
|
|
1575
|
+
if (result.disposition !== "applied" || result.run === null) {
|
|
1437
1576
|
return "state-changed";
|
|
1438
1577
|
}
|
|
1439
|
-
const terminal = result.
|
|
1578
|
+
const terminal = result.run;
|
|
1440
1579
|
const deliveryFailureEvent = createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.role-delivery-failed", {
|
|
1441
|
-
|
|
1580
|
+
runId: terminal.id,
|
|
1442
1581
|
roleName: input.roleName,
|
|
1443
1582
|
outcome: terminal.status
|
|
1444
1583
|
}, input.now);
|
|
1445
1584
|
store.saveEvent(input.taskId, deliveryFailureEvent);
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
else {
|
|
1585
|
+
routeRoleEvent(store, deliveryFailureEvent, input.roleName, terminal.purpose === "review" ? "review-failed" : "role-turn-failed", input.now);
|
|
1586
|
+
if (input.roleName === "leader") {
|
|
1450
1587
|
store.saveLeaderFailure(recordLeaderFailure(input.taskId, session?.nativeSessionId ?? "(unregistered)", summary, input.now, store.getLeaderFailure(input.taskId)));
|
|
1451
1588
|
}
|
|
1452
1589
|
return "failed";
|
|
@@ -1456,73 +1593,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1456
1593
|
recordRuntimeNativeSession(input, now = new Date()) {
|
|
1457
1594
|
return this.store.transaction((store) => (recordTaskRuntimeNativeSession(store, input, now)));
|
|
1458
1595
|
}
|
|
1459
|
-
|
|
1596
|
+
recordLaunchedRuntimeNativeSession(input, assertCurrent, now = new Date()) {
|
|
1460
1597
|
return this.store.transaction((store) => {
|
|
1461
1598
|
assertCurrent();
|
|
1462
|
-
const target = runtimeLifecycleTarget(input.owner);
|
|
1463
|
-
const existing = store.getWorkMailbox(target);
|
|
1464
|
-
if (isRuntimeLaunchReservation(existing?.processing)) {
|
|
1465
|
-
if (hasRuntimeCleanupObligation(existing)) {
|
|
1466
|
-
throw new Error("Runtime cleanup is still pending.");
|
|
1467
|
-
}
|
|
1468
|
-
return {
|
|
1469
|
-
status: "existing",
|
|
1470
|
-
runtimeGenerationId: existing.processing.batchId
|
|
1471
|
-
};
|
|
1472
|
-
}
|
|
1473
|
-
if (hasRuntimeLifecycleWork(existing)) {
|
|
1474
|
-
throw new RuntimeLifecycleBusyError("Runtime lifecycle work is already pending.");
|
|
1475
|
-
}
|
|
1476
|
-
enqueueWork(store, target, RUNTIME_LAUNCH_RESERVED_REASON, now, input.owner.scope === "task"
|
|
1477
|
-
? [{ type: "task", id: input.owner.taskId }]
|
|
1478
|
-
: []);
|
|
1479
|
-
const queued = store.getWorkMailbox(target);
|
|
1480
|
-
if (queued === null
|
|
1481
|
-
|| queued.pending === null
|
|
1482
|
-
|| queued.processing !== null) {
|
|
1483
|
-
throw new Error("Runtime launch reservation could not be queued.");
|
|
1484
|
-
}
|
|
1485
|
-
const claimed = claimPending(queued, {
|
|
1486
|
-
batchId: input.runtimeGenerationId,
|
|
1487
|
-
owner: RUNTIME_LIFECYCLE_OWNER,
|
|
1488
|
-
startedAt: now.toISOString()
|
|
1489
|
-
});
|
|
1490
|
-
store.saveWorkMailbox(claimed);
|
|
1491
|
-
return {
|
|
1492
|
-
status: "reserved",
|
|
1493
|
-
runtimeGenerationId: input.runtimeGenerationId
|
|
1494
|
-
};
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
confirmRuntimeLaunchReservation(input, assertCurrent) {
|
|
1498
|
-
return this.store.transaction((store) => {
|
|
1499
|
-
assertCurrent();
|
|
1500
|
-
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(input.owner));
|
|
1501
|
-
if (isRuntimeLaunchReservation(mailbox?.processing, input.runtimeGenerationId)) {
|
|
1502
|
-
return "reserved";
|
|
1503
|
-
}
|
|
1504
|
-
if (hasRuntimeCleanupObligation(mailbox)) {
|
|
1505
|
-
throw new Error("Runtime cleanup is still pending.");
|
|
1506
|
-
}
|
|
1507
|
-
// A synchronous Driver Hook may bind a fresh native Session
|
|
1508
|
-
// and complete the reservation while the host start call is still
|
|
1509
|
-
// unwinding. Preserve that exact hook-won generation instead of treating
|
|
1510
|
-
// the already-settled reservation as a launch failure.
|
|
1511
|
-
const sessions = runtimeOwnerSessionSet(store, input.owner);
|
|
1512
|
-
const active = sessions?.sessions[sessions.activeAgentId];
|
|
1513
|
-
if (active?.status === "active"
|
|
1514
|
-
&& active.runtimeGenerationId === input.runtimeGenerationId)
|
|
1515
|
-
return "provider-bound";
|
|
1516
|
-
throw new Error("Runtime launch reservation no longer matches the launch.");
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
recordReservedRuntimeNativeSession(input, assertCurrent, now = new Date()) {
|
|
1520
|
-
return this.store.transaction((store) => {
|
|
1521
|
-
assertCurrent();
|
|
1522
|
-
const mailbox = requireRuntimeLaunchReservation(store, input.owner, input.runtimeGenerationId);
|
|
1523
|
-
if (hasRuntimeCleanupObligation(mailbox)) {
|
|
1524
|
-
throw new Error("Runtime cleanup is still pending.");
|
|
1525
|
-
}
|
|
1526
1599
|
const session = input.owner.scope === "task"
|
|
1527
1600
|
? recordTaskRuntimeNativeSession(store, {
|
|
1528
1601
|
taskId: input.owner.taskId,
|
|
@@ -1530,7 +1603,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1530
1603
|
agentId: input.agentId,
|
|
1531
1604
|
adapterId: input.adapterId,
|
|
1532
1605
|
nativeSessionId: input.nativeSessionId,
|
|
1533
|
-
runtimeGenerationId: input.runtimeGenerationId,
|
|
1534
1606
|
effective: input.effective
|
|
1535
1607
|
}, now)
|
|
1536
1608
|
: recordGlobalRuntimeNativeSession(store, {
|
|
@@ -1538,61 +1610,39 @@ export class FileSchedulerStoreAdapter {
|
|
|
1538
1610
|
agentId: input.agentId,
|
|
1539
1611
|
adapterId: input.adapterId,
|
|
1540
1612
|
nativeSessionId: input.nativeSessionId,
|
|
1541
|
-
runtimeGenerationId: input.runtimeGenerationId,
|
|
1542
1613
|
effective: input.effective
|
|
1543
1614
|
}, now);
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const sessions = runtimeOwnerSessionSet(store, input.owner);
|
|
1573
|
-
const active = sessions?.sessions[sessions.activeAgentId];
|
|
1574
|
-
if (runtimeSessionMatchesSettledLaunch(active, input)) {
|
|
1575
|
-
endRuntimeOwnerSession(store, input.owner, now);
|
|
1615
|
+
if (input.owner.scope === "task") {
|
|
1616
|
+
let sessions = store.getTaskRoleSessionSet(input.owner.taskId, input.owner.roleName);
|
|
1617
|
+
if (sessions !== null && sessions.providerBinding === null) {
|
|
1618
|
+
sessions = bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
|
|
1619
|
+
providerNamespace: this.drivers.requireByAdapterId(input.adapterId).id,
|
|
1620
|
+
accountScope: input.agentId, conversationId: input.nativeSessionId,
|
|
1621
|
+
startedAt: now.toISOString()
|
|
1622
|
+
}), now);
|
|
1623
|
+
store.saveTaskRoleSessionSet(sessions);
|
|
1624
|
+
}
|
|
1625
|
+
else if (sessions?.providerBinding != null
|
|
1626
|
+
&& currentProviderConversation(sessions.providerBinding).conversationId !== input.nativeSessionId) {
|
|
1627
|
+
sessions = updateTaskRoleProviderRuntime(sessions, supersedeProviderConversation(sessions.providerBinding, {
|
|
1628
|
+
conversationId: input.nativeSessionId, switchedAt: now.toISOString(), basis: "terminal-session"
|
|
1629
|
+
}), now);
|
|
1630
|
+
store.saveTaskRoleSessionSet(sessions);
|
|
1631
|
+
}
|
|
1632
|
+
const binding = sessions?.providerBinding;
|
|
1633
|
+
if (sessions !== null && binding !== null && binding !== undefined
|
|
1634
|
+
&& binding.authority.owner === "none"
|
|
1635
|
+
&& currentProviderConversation(binding).conversationId === input.nativeSessionId) {
|
|
1636
|
+
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, transferProviderAuthority(binding, {
|
|
1637
|
+
expectedEpoch: binding.authority.epoch,
|
|
1638
|
+
expectedOwner: "none",
|
|
1639
|
+
owner: "controller",
|
|
1640
|
+
holderId: "controller",
|
|
1641
|
+
changedAt: now.toISOString()
|
|
1642
|
+
}), now));
|
|
1576
1643
|
}
|
|
1577
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, input.runtimeGenerationId));
|
|
1578
|
-
return true;
|
|
1579
|
-
}
|
|
1580
|
-
if (hasRuntimeLifecycleWork(mailbox))
|
|
1581
|
-
return false;
|
|
1582
|
-
if (input.owner.scope === "task"
|
|
1583
|
-
&& store.getActiveTurn(input.owner.taskId, input.owner.roleName) !== null) {
|
|
1584
|
-
return false;
|
|
1585
|
-
}
|
|
1586
|
-
const sessions = runtimeOwnerSessionSet(store, input.owner);
|
|
1587
|
-
if (sessions === null)
|
|
1588
|
-
return true;
|
|
1589
|
-
const active = sessions.sessions[sessions.activeAgentId];
|
|
1590
|
-
if (!runtimeSessionMatchesSettledLaunch(active, input))
|
|
1591
|
-
return false;
|
|
1592
|
-
if (active.status !== "ended") {
|
|
1593
|
-
endRuntimeOwnerSession(store, input.owner, now);
|
|
1594
1644
|
}
|
|
1595
|
-
return
|
|
1645
|
+
return session;
|
|
1596
1646
|
});
|
|
1597
1647
|
}
|
|
1598
1648
|
/**
|
|
@@ -1600,66 +1650,66 @@ export class FileSchedulerStoreAdapter {
|
|
|
1600
1650
|
* retained as an intermediate child wait or recorded as a ready boundary
|
|
1601
1651
|
* for later mailbox input. It never performs tmux, workspace, or Controller I/O.
|
|
1602
1652
|
*/
|
|
1603
|
-
|
|
1653
|
+
classifyRuntimeRunTerminal(input) {
|
|
1604
1654
|
return resolveTerminalExecution(this.store, input) === null ? "obsolete" : "apply";
|
|
1605
1655
|
}
|
|
1606
|
-
|
|
1656
|
+
observeRuntimeRunTerminal(input, now = new Date()) {
|
|
1607
1657
|
// Resource inspection is bounded but external to SQLite. Revalidate the
|
|
1608
|
-
// immutable
|
|
1609
|
-
const
|
|
1610
|
-
const workspace =
|
|
1611
|
-
&&
|
|
1612
|
-
&&
|
|
1613
|
-
&&
|
|
1658
|
+
// immutable AgentRun workspace after acquiring the write transaction.
|
|
1659
|
+
const preparedRun = resolveTerminalExecution(this.store, input)?.run;
|
|
1660
|
+
const workspace = preparedRun?.status === "active"
|
|
1661
|
+
&& preparedRun.executionGroupId !== undefined
|
|
1662
|
+
&& preparedRun.executionLaneId !== undefined
|
|
1663
|
+
&& preparedRun.workspace !== undefined
|
|
1614
1664
|
&& input.outcome.status === "completed"
|
|
1615
|
-
? this.snapshotExecutionLaneWorkspace(this.store,
|
|
1665
|
+
? this.snapshotExecutionLaneWorkspace(this.store, preparedRun.workspace)
|
|
1616
1666
|
: undefined;
|
|
1617
1667
|
return this.store.transaction((store) => {
|
|
1618
1668
|
const task = store.getTask(input.taskId);
|
|
1619
1669
|
if (task === null)
|
|
1620
1670
|
throw new Error(`Task not found: ${input.taskId}.`);
|
|
1621
1671
|
if (task.status === "archived") {
|
|
1622
|
-
throw new Error(`Cannot complete a runtime
|
|
1672
|
+
throw new Error(`Cannot complete a runtime run for unavailable Task: ${input.taskId}.`);
|
|
1623
1673
|
}
|
|
1624
1674
|
const resolved = resolveTerminalExecution(store, input);
|
|
1625
1675
|
if (resolved === null)
|
|
1626
1676
|
throw new Error("Runtime terminal has no exact execution binding.");
|
|
1627
|
-
const {
|
|
1628
|
-
if (!isDeepStrictEqual(
|
|
1677
|
+
const { run: observedRun, current: recordedProviderTurn, attemptId } = resolved;
|
|
1678
|
+
if (!isDeepStrictEqual(preparedRun?.workspace, observedRun?.workspace)) {
|
|
1629
1679
|
throw new Error("Runtime terminal workspace changed during result preparation.");
|
|
1630
1680
|
}
|
|
1631
|
-
if (workspace !== undefined &&
|
|
1632
|
-
&& !isDeepStrictEqual(store.getManagedWorkspace(
|
|
1681
|
+
if (workspace !== undefined && observedRun?.workspace !== undefined
|
|
1682
|
+
&& !isDeepStrictEqual(store.getManagedWorkspace(observedRun.workspace.owner), observedRun.workspace)) {
|
|
1633
1683
|
throw new Error("Runtime terminal workspace ownership changed during result preparation.");
|
|
1634
1684
|
}
|
|
1635
1685
|
let sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName)
|
|
1636
1686
|
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
1637
|
-
const
|
|
1687
|
+
const canonicalRunId = input.nativeTurnId ?? resolved.nativeTurnId;
|
|
1638
1688
|
const existing = sessions.sessions[input.agentId];
|
|
1639
1689
|
const owner = {
|
|
1640
1690
|
scope: "task",
|
|
1641
1691
|
taskId: input.taskId,
|
|
1642
1692
|
roleName: input.roleName
|
|
1643
1693
|
};
|
|
1644
|
-
assertConsistentTerminal(store, input,
|
|
1694
|
+
assertConsistentTerminal(store, input, observedRun);
|
|
1645
1695
|
// A historical operation failure stays failed. The immutable terminal
|
|
1646
1696
|
// observation retains the late report for explicit Leader adoption.
|
|
1647
1697
|
// Never touch a successor Session, active pointer, mailbox or Review.
|
|
1648
|
-
if (!recordedProviderTurn || (
|
|
1698
|
+
if (!recordedProviderTurn || (observedRun !== null && observedRun.status !== "active")) {
|
|
1649
1699
|
// A failed operation and a subsequently confirmed Provider outcome are
|
|
1650
1700
|
// distinct facts. Settle only the still-owned original attempt, never
|
|
1651
1701
|
// rewriting the historical Task result or a successor's binding.
|
|
1652
|
-
if (recordedProviderTurn && sessions.providerBinding?.
|
|
1653
|
-
&& sessions.providerBinding?.
|
|
1654
|
-
&& ["submitting", "accepted", "delivery-unknown"].includes(sessions.providerBinding.
|
|
1655
|
-
if (sessions.providerBinding.
|
|
1702
|
+
if (recordedProviderTurn && sessions.providerBinding?.run !== null
|
|
1703
|
+
&& sessions.providerBinding?.run !== undefined
|
|
1704
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(sessions.providerBinding.run.status)) {
|
|
1705
|
+
if (sessions.providerBinding.run.status !== "accepted") {
|
|
1656
1706
|
sessions = updateTaskRoleProviderRuntime(sessions, acceptProviderTurn(sessions.providerBinding, {
|
|
1657
1707
|
attemptId,
|
|
1658
|
-
...(
|
|
1708
|
+
...(canonicalRunId === undefined ? {} : { nativeTurnId: canonicalRunId }),
|
|
1659
1709
|
acceptedAt: now.toISOString()
|
|
1660
1710
|
}), now);
|
|
1661
1711
|
}
|
|
1662
|
-
sessions = settleStructuredProviderTurn(sessions,
|
|
1712
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalRunId, input.providerStatus, now, attemptId);
|
|
1663
1713
|
store.saveTaskRoleSessionSet(sessions);
|
|
1664
1714
|
}
|
|
1665
1715
|
if (input.observation !== undefined)
|
|
@@ -1668,41 +1718,40 @@ export class FileSchedulerStoreAdapter {
|
|
|
1668
1718
|
...(sessions.sessions[input.agentId] === undefined
|
|
1669
1719
|
? {} : { session: sessions.sessions[input.agentId] }),
|
|
1670
1720
|
duplicate: true,
|
|
1671
|
-
...(
|
|
1721
|
+
...(observedRun === null ? {} : { run: observedRun })
|
|
1672
1722
|
};
|
|
1673
1723
|
}
|
|
1674
1724
|
const pending = sessions.providerBinding;
|
|
1675
|
-
if (pending.
|
|
1725
|
+
if (pending.run?.status === "submitting" || pending.run?.status === "delivery-unknown") {
|
|
1676
1726
|
sessions = updateTaskRoleProviderRuntime(sessions, acceptProviderTurn(pending, {
|
|
1677
1727
|
attemptId,
|
|
1678
|
-
...(
|
|
1728
|
+
...(canonicalRunId === undefined ? {} : { nativeTurnId: canonicalRunId }),
|
|
1679
1729
|
acceptedAt: now.toISOString()
|
|
1680
1730
|
}), now);
|
|
1681
1731
|
}
|
|
1682
|
-
sessions = settleStructuredProviderTurn(sessions,
|
|
1683
|
-
if (
|
|
1684
|
-
sessions =
|
|
1732
|
+
sessions = settleStructuredProviderTurn(sessions, canonicalRunId, input.providerStatus, now, attemptId);
|
|
1733
|
+
if (canonicalRunId !== undefined)
|
|
1734
|
+
sessions = recordTaskRoleNativeTurnBoundary(sessions, {
|
|
1685
1735
|
agentId: input.agentId,
|
|
1686
1736
|
nativeSessionId: input.nativeSessionId,
|
|
1687
|
-
turnId:
|
|
1737
|
+
turnId: canonicalRunId
|
|
1688
1738
|
}, now);
|
|
1739
|
+
if (input.observation?.payload.failure?.error.sessionDisposition === "unrecoverable"
|
|
1740
|
+
&& sessions.providerBinding !== null) {
|
|
1741
|
+
sessions = updateTaskRoleProviderRuntime(sessions, updateProviderConversationRecoverability(sessions.providerBinding, "unrecoverable"), now);
|
|
1742
|
+
}
|
|
1689
1743
|
store.saveTaskRoleSessionSet(sessions);
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
if (recordedProviderTurn && observedTurn?.status === "active") {
|
|
1744
|
+
let terminalRun;
|
|
1745
|
+
if (recordedProviderTurn && observedRun?.status === "active") {
|
|
1693
1746
|
const binding = sessions.providerBinding;
|
|
1694
1747
|
const conversation = binding.conversations.find((entry) => entry.conversationId === input.nativeSessionId);
|
|
1695
|
-
const activation = binding.activations.find((entry) => (entry.activationId === resolved.activationId));
|
|
1696
|
-
if (activation === undefined) {
|
|
1697
|
-
throw new Error("Provider Turn result has no matching Activation.");
|
|
1698
|
-
}
|
|
1699
1748
|
const providerStatus = input.providerStatus;
|
|
1700
1749
|
let systemEvidence;
|
|
1701
1750
|
let workspaceFailure;
|
|
1702
|
-
if ((
|
|
1703
|
-
&&
|
|
1704
|
-
&&
|
|
1705
|
-
&&
|
|
1751
|
+
if ((observedRun.purpose === "execution" || observedRun.purpose === "review")
|
|
1752
|
+
&& observedRun.executionGroupId !== undefined
|
|
1753
|
+
&& observedRun.executionLaneId !== undefined
|
|
1754
|
+
&& observedRun.workspace !== undefined
|
|
1706
1755
|
&& input.outcome.status === "completed") {
|
|
1707
1756
|
const gitSnapshot = workspace;
|
|
1708
1757
|
if (gitSnapshot.status === "captured") {
|
|
@@ -1719,21 +1768,19 @@ export class FileSchedulerStoreAdapter {
|
|
|
1719
1768
|
};
|
|
1720
1769
|
}
|
|
1721
1770
|
}
|
|
1722
|
-
const terminalized =
|
|
1771
|
+
const terminalized = terminalizeExactTaskRun(store, {
|
|
1723
1772
|
taskId: input.taskId,
|
|
1724
1773
|
roleName: input.roleName,
|
|
1725
1774
|
agentId: input.agentId,
|
|
1726
|
-
|
|
1775
|
+
runId: observedRun.id,
|
|
1727
1776
|
nativeSessionId: input.nativeSessionId,
|
|
1728
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
1729
1777
|
outcome: {
|
|
1730
1778
|
...input.outcome,
|
|
1731
1779
|
provider: {
|
|
1732
1780
|
providerNamespace: binding.providerNamespace,
|
|
1733
1781
|
accountScope: binding.accountScope,
|
|
1734
1782
|
conversationId: conversation.conversationId,
|
|
1735
|
-
|
|
1736
|
-
...(canonicalTurnId === undefined ? {} : { nativeTurnId: canonicalTurnId }),
|
|
1783
|
+
...(canonicalRunId === undefined ? {} : { nativeTurnId: canonicalRunId }),
|
|
1737
1784
|
attemptId,
|
|
1738
1785
|
status: providerStatus
|
|
1739
1786
|
}
|
|
@@ -1741,20 +1788,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
1741
1788
|
...(systemEvidence === undefined ? {} : { systemEvidence }),
|
|
1742
1789
|
...(workspaceFailure === undefined ? {} : { workspaceFailure })
|
|
1743
1790
|
}, now);
|
|
1744
|
-
if (terminalized.disposition !== "applied" || terminalized.
|
|
1745
|
-
throw new Error(`Provider
|
|
1791
|
+
if (terminalized.disposition !== "applied" || terminalized.run === null) {
|
|
1792
|
+
throw new Error(`Provider AgentRun terminal could not complete its exact AgentRun: ${terminalized.reason ?? "obsolete"}.`);
|
|
1746
1793
|
}
|
|
1747
|
-
|
|
1748
|
-
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId,
|
|
1749
|
-
|
|
1750
|
-
roleName:
|
|
1751
|
-
...(
|
|
1794
|
+
terminalRun = terminalized.run;
|
|
1795
|
+
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, terminalRun.status === "completed" ? "run.completed" : "run.failed", {
|
|
1796
|
+
runId: terminalRun.id,
|
|
1797
|
+
roleName: terminalRun.roleName,
|
|
1798
|
+
...(canonicalRunId === undefined ? {} : { providerTurnId: canonicalRunId }),
|
|
1752
1799
|
providerStatus
|
|
1753
1800
|
}, now);
|
|
1754
1801
|
store.saveEvent(input.taskId, event);
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1802
|
+
// A structured failure routes its original runtime.agent-error below;
|
|
1803
|
+
// the AgentRun terminal is the same fact, not a second notification.
|
|
1804
|
+
const structuredFailure = input.observation?.kind === "turn.failed"
|
|
1805
|
+
&& input.observation.payload.failure !== undefined;
|
|
1806
|
+
if (!structuredFailure && terminalRun.roleName === "leader"
|
|
1807
|
+
&& terminalRun.status === "failed") {
|
|
1808
|
+
routeRoleEvent(store, event, terminalRun.roleName, terminalRun.purpose === "review" ? "review-result" : "role-turn-result", now);
|
|
1758
1809
|
}
|
|
1759
1810
|
}
|
|
1760
1811
|
if (input.observation !== undefined)
|
|
@@ -1762,7 +1813,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1762
1813
|
return {
|
|
1763
1814
|
session: sessions.sessions[input.agentId],
|
|
1764
1815
|
duplicate: false,
|
|
1765
|
-
...(
|
|
1816
|
+
...(terminalRun === undefined ? {} : { run: terminalRun })
|
|
1766
1817
|
};
|
|
1767
1818
|
});
|
|
1768
1819
|
}
|
|
@@ -1771,8 +1822,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1771
1822
|
const identity = [
|
|
1772
1823
|
input.taskId,
|
|
1773
1824
|
input.roleName,
|
|
1774
|
-
input.
|
|
1775
|
-
input.runtimeGenerationId ?? "unknown-runtime-generation",
|
|
1825
|
+
input.runId,
|
|
1776
1826
|
String(input.deadStatus ?? "unknown-status")
|
|
1777
1827
|
].join("\0");
|
|
1778
1828
|
const observationId = `tmux-host-exit-${createHash("sha256").update(identity).digest("hex")}`;
|
|
@@ -1780,26 +1830,19 @@ export class FileSchedulerStoreAdapter {
|
|
|
1780
1830
|
if (events.some((event) => (event.type === "runtime.process-exit-observed"
|
|
1781
1831
|
&& event.payload.observationId === observationId)))
|
|
1782
1832
|
return;
|
|
1783
|
-
const stopReceipt = [...events].reverse().find((event) => (event.type === "runtime.session-termination"
|
|
1784
|
-
&& event.payload.roleName === input.roleName
|
|
1785
|
-
&& (input.runtimeGenerationId === undefined || event.payload.runtimeGenerationId === input.runtimeGenerationId)
|
|
1786
|
-
&& ["stop-requested", "graceful-stop", "forced-stop", "stop-confirmed"]
|
|
1787
|
-
.includes(event.payload.outcome ?? "")));
|
|
1788
1833
|
const observation = validateRuntimeProcessExitObservation({
|
|
1789
1834
|
schemaVersion: 2,
|
|
1790
1835
|
observationId,
|
|
1791
1836
|
hostSequence: 1,
|
|
1792
|
-
hostInstanceId: `tmux-${input.
|
|
1837
|
+
hostInstanceId: `tmux-${input.roleName}`,
|
|
1793
1838
|
taskId: input.taskId,
|
|
1794
1839
|
roleName: input.roleName,
|
|
1795
|
-
|
|
1796
|
-
runtimeGenerationId: input.runtimeGenerationId ?? `unknown-${input.turnId}`,
|
|
1840
|
+
runId: input.runId,
|
|
1797
1841
|
...(input.nativeSessionId === undefined
|
|
1798
1842
|
? {}
|
|
1799
1843
|
: { nativeSessionId: input.nativeSessionId }),
|
|
1800
1844
|
processKind: "agent-host",
|
|
1801
1845
|
...(input.deadStatus === undefined ? {} : { exitCode: input.deadStatus }),
|
|
1802
|
-
...(stopReceipt === undefined ? {} : { stopReceiptId: stopReceipt.id }),
|
|
1803
1846
|
observedAt: input.observedAt.toISOString()
|
|
1804
1847
|
});
|
|
1805
1848
|
const classification = classifyRuntimeProcessExit(observation, {});
|
|
@@ -1807,7 +1850,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1807
1850
|
observationId,
|
|
1808
1851
|
processKind: "agent-host",
|
|
1809
1852
|
roleName: input.roleName,
|
|
1810
|
-
runtimeGenerationId: observation.runtimeGenerationId,
|
|
1811
1853
|
observedAt: observation.observedAt,
|
|
1812
1854
|
classification,
|
|
1813
1855
|
observation: JSON.stringify(observation)
|
|
@@ -1815,7 +1857,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1815
1857
|
});
|
|
1816
1858
|
}
|
|
1817
1859
|
/**
|
|
1818
|
-
* Issue 04: reopens each due retry on its original Native Session. A
|
|
1860
|
+
* Issue 04: reopens each due retry on its original Native Session. A AgentRun
|
|
1819
1861
|
* whose Session is proven dead terminalizes with an exact replacement
|
|
1820
1862
|
* blocker; a live Session is reopened for the existing delivery path, which
|
|
1821
1863
|
* re-pushes the exact same input in the same pass.
|
|
@@ -1838,22 +1880,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1838
1880
|
if (input.kind === "conversation.observed") {
|
|
1839
1881
|
binding = updateProviderConversationRecoverability(binding, input.payload.recoverability);
|
|
1840
1882
|
}
|
|
1841
|
-
else if (input.kind === "activation.started") {
|
|
1842
|
-
const active = binding.activations.find((entry) => entry.status === "active");
|
|
1843
|
-
if (active?.activationId !== input.fence.activationId) {
|
|
1844
|
-
binding = startProviderActivation(binding, {
|
|
1845
|
-
activationId: input.fence.activationId,
|
|
1846
|
-
startedAt: input.observedAt ?? input.receivedAt
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
else {
|
|
1851
|
-
binding = endProviderActivation(binding, input.fence.activationId, {
|
|
1852
|
-
status: input.kind === "activation.failed" ? "failed" : "ended",
|
|
1853
|
-
endedAt: input.observedAt ?? input.receivedAt,
|
|
1854
|
-
reason: input.kind
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
1883
|
}
|
|
1858
1884
|
catch {
|
|
1859
1885
|
recordCanonicalObservationObsolete(store, input, "provider-identity-conflict", now);
|
|
@@ -1925,16 +1951,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1925
1951
|
const taskId = input.fence.taskId;
|
|
1926
1952
|
const role = store.getRole(taskId, input.fence.roleName);
|
|
1927
1953
|
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
1928
|
-
const run = store.
|
|
1954
|
+
const run = store.getRun(taskId, input.fence.runId);
|
|
1929
1955
|
if (role === null || sessions === null || run === null) {
|
|
1930
1956
|
recordCanonicalObservationObsolete(store, input, "bind-state-missing", now);
|
|
1931
1957
|
return "obsolete";
|
|
1932
1958
|
}
|
|
1933
|
-
const mailbox = store.getWorkMailbox({
|
|
1934
|
-
kind: "role",
|
|
1935
|
-
taskId,
|
|
1936
|
-
roleName: input.fence.roleName
|
|
1937
|
-
});
|
|
1938
1959
|
const existingSession = sessions.sessions[input.fence.agentId];
|
|
1939
1960
|
const existingNativeSessionId = existingSession?.nativeSessionId;
|
|
1940
1961
|
const replacingNativeSession = existingNativeSessionId !== undefined
|
|
@@ -1948,7 +1969,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
1948
1969
|
agentId: input.fence.agentId,
|
|
1949
1970
|
adapterId,
|
|
1950
1971
|
nativeSessionId: decision.outcome.nativeSessionId,
|
|
1951
|
-
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
1952
1972
|
policy: "fixed",
|
|
1953
1973
|
status: "active",
|
|
1954
1974
|
effective: run.effective
|
|
@@ -1959,15 +1979,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
1959
1979
|
const withProvider = bindOrSupersedeProviderRuntime(bound, input, now, replacementBasis
|
|
1960
1980
|
?? terminalProviderReplacementBasis(bound, input, run.mode));
|
|
1961
1981
|
store.saveTaskRoleSessionSet(withProvider);
|
|
1962
|
-
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.runtimeGenerationId);
|
|
1963
1982
|
}
|
|
1964
1983
|
const current = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
1965
1984
|
const currentSession = current?.sessions[input.fence.agentId];
|
|
1966
1985
|
if (current !== null && current !== undefined
|
|
1967
1986
|
&& currentSession?.nativeSessionId === input.fence.nativeSessionId) {
|
|
1968
|
-
const run = input.fence.
|
|
1987
|
+
const run = input.fence.runId === undefined
|
|
1969
1988
|
? null
|
|
1970
|
-
: store.
|
|
1989
|
+
: store.getRun(input.fence.taskId, input.fence.runId);
|
|
1971
1990
|
const replacementBasis = run === null
|
|
1972
1991
|
? undefined
|
|
1973
1992
|
: terminalSessionReplacementBasis(current, input, run)
|
|
@@ -1989,70 +2008,56 @@ export class FileSchedulerStoreAdapter {
|
|
|
1989
2008
|
/** Provider acceptance is canonical before storage and remains receipt-fenced. */
|
|
1990
2009
|
observeRuntimePromptAccepted(input, adapterId, now) {
|
|
1991
2010
|
return this.store.transaction((store) => {
|
|
2011
|
+
// A pipe write is retained as transport evidence, but cannot acknowledge
|
|
2012
|
+
// the business input. A later native receipt/terminal proves acceptance.
|
|
2013
|
+
if (input.authority === "transport")
|
|
2014
|
+
return "applied";
|
|
1992
2015
|
const continuation = input.fence.receiptId?.startsWith("turn-input:") === true;
|
|
1993
2016
|
const ordinaryAttemptId = input.fence.receiptId;
|
|
1994
|
-
if (!continuation && input.fence.
|
|
2017
|
+
if (!continuation && input.fence.runId !== undefined) {
|
|
1995
2018
|
const exact = resolveTerminalExecution(store, {
|
|
1996
2019
|
taskId: input.fence.taskId,
|
|
1997
2020
|
roleName: input.fence.roleName,
|
|
1998
2021
|
agentId: input.fence.agentId,
|
|
1999
2022
|
adapterId,
|
|
2000
|
-
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2001
|
-
activationId: input.fence.activationId,
|
|
2002
2023
|
conversationId: input.fence.conversationId,
|
|
2003
2024
|
nativeSessionId: input.fence.nativeSessionId,
|
|
2004
2025
|
nativeTurnId: input.fence.nativeTurnId,
|
|
2005
2026
|
attemptId: ordinaryAttemptId,
|
|
2006
|
-
|
|
2027
|
+
runId: input.fence.runId
|
|
2007
2028
|
});
|
|
2008
2029
|
if (exact === null)
|
|
2009
2030
|
return "obsolete";
|
|
2010
2031
|
// A terminal can prove acceptance before the delayed receipt. Retain
|
|
2011
2032
|
// the receipt without recreating lifecycle state or touching a successor.
|
|
2012
|
-
if (!exact.current || exact.
|
|
2033
|
+
if (!exact.current || exact.run?.status !== "active")
|
|
2013
2034
|
return "applied";
|
|
2014
2035
|
}
|
|
2015
|
-
if (input.fence.
|
|
2036
|
+
if (input.fence.runId === undefined
|
|
2016
2037
|
&& ordinaryAttemptId?.startsWith("direct:") === true) {
|
|
2017
2038
|
const taskId = input.fence.taskId;
|
|
2018
2039
|
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
2019
2040
|
const session = sessions?.sessions[input.fence.agentId];
|
|
2020
2041
|
const binding = sessions?.providerBinding;
|
|
2021
2042
|
const nativeTurnId = input.fence.nativeTurnId;
|
|
2022
|
-
|
|
2023
|
-
if (sessions === null || sessions === undefined
|
|
2024
|
-
|| binding === null || binding === undefined
|
|
2025
|
-
|| session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
2026
|
-
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
2027
|
-
|| currentProviderConversation(binding).conversationId !== input.fence.nativeSessionId) {
|
|
2043
|
+
if (sessions === null || sessions === undefined || binding === null || binding === undefined || session === undefined || session.nativeSessionId !== input.fence.nativeSessionId || currentProviderConversation(binding).conversationId !== input.fence.nativeSessionId) {
|
|
2028
2044
|
recordCanonicalObservationObsolete(store, input, "direct-turn-session-mismatch", now);
|
|
2029
2045
|
return "obsolete";
|
|
2030
2046
|
}
|
|
2031
|
-
if (binding.
|
|
2032
|
-
&& binding.
|
|
2033
|
-
&& binding.turn.turnId !== undefined
|
|
2034
|
-
&& active?.id === binding.turn.turnId) {
|
|
2047
|
+
if (binding.run?.attemptId === ordinaryAttemptId
|
|
2048
|
+
&& binding.run.nativeTurnId === nativeTurnId) {
|
|
2035
2049
|
return "applied";
|
|
2036
2050
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
source: goalContinuation
|
|
2046
|
-
? { type: "provider", channel: "goal-continuation" }
|
|
2047
|
-
: { type: "user", channel: "direct" },
|
|
2048
|
-
...(input.payload.input === undefined
|
|
2049
|
-
? {}
|
|
2050
|
-
: { directive: input.payload.input }),
|
|
2051
|
-
deltaRefIds: []
|
|
2052
|
-
}), now, { effective: session.effective });
|
|
2051
|
+
// Native conversation is not an implicit managed assignment. Keep its
|
|
2052
|
+
// observation without borrowing or overwriting another request's
|
|
2053
|
+
// accepted, unknown, or waiting admission evidence.
|
|
2054
|
+
if (binding.run !== null
|
|
2055
|
+
&& (["submitting", "accepted", "delivery-unknown"].includes(binding.run.status)
|
|
2056
|
+
|| (binding.run.runId !== undefined
|
|
2057
|
+
&& store.getRun(taskId, binding.run.runId)?.status === "active")))
|
|
2058
|
+
return "applied";
|
|
2053
2059
|
const submittedAt = input.observedAt ?? input.receivedAt;
|
|
2054
2060
|
const accepted = acceptProviderTurn(beginProviderTurn(binding, {
|
|
2055
|
-
turnId,
|
|
2056
2061
|
attemptId: ordinaryAttemptId,
|
|
2057
2062
|
authorityEpoch: binding.authority.epoch,
|
|
2058
2063
|
submittedAt
|
|
@@ -2061,10 +2066,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2061
2066
|
nativeTurnId,
|
|
2062
2067
|
acceptedAt: submittedAt
|
|
2063
2068
|
});
|
|
2064
|
-
store.saveTurn(direct);
|
|
2065
|
-
store.saveActiveTurn(direct);
|
|
2066
2069
|
store.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, accepted, now));
|
|
2067
|
-
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "turn.dispatched", turnLaunchEventPayload(direct), now));
|
|
2068
2070
|
return "applied";
|
|
2069
2071
|
}
|
|
2070
2072
|
if (ordinaryAttemptId !== undefined) {
|
|
@@ -2073,10 +2075,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2073
2075
|
const binding = sessions?.providerBinding;
|
|
2074
2076
|
if (sessions !== null && sessions !== undefined
|
|
2075
2077
|
&& binding !== null && binding !== undefined
|
|
2076
|
-
&& binding.
|
|
2077
|
-
&& binding.
|
|
2078
|
-
if (session
|
|
2079
|
-
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2078
|
+
&& binding.run?.runId === undefined
|
|
2079
|
+
&& binding.run?.attemptId === ordinaryAttemptId) {
|
|
2080
|
+
if (session === undefined || session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2080
2081
|
recordCanonicalObservationObsolete(store, input, "ordinary-turn-session-mismatch", now);
|
|
2081
2082
|
return "obsolete";
|
|
2082
2083
|
}
|
|
@@ -2085,25 +2086,18 @@ export class FileSchedulerStoreAdapter {
|
|
|
2085
2086
|
}
|
|
2086
2087
|
}
|
|
2087
2088
|
if (continuation) {
|
|
2088
|
-
const active = store.
|
|
2089
|
+
const active = store.getActiveRun(input.fence.taskId, input.fence.roleName);
|
|
2089
2090
|
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
2090
2091
|
const session = sessions?.sessions[input.fence.agentId];
|
|
2091
|
-
if (active === null
|
|
2092
|
-
|| active.id !== input.fence.turnId
|
|
2093
|
-
|| active.status !== "active"
|
|
2094
|
-
|| sessions === null
|
|
2095
|
-
|| sessions === undefined
|
|
2096
|
-
|| session?.runtimeGenerationId !== input.fence.runtimeGenerationId
|
|
2097
|
-
|| session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2092
|
+
if (active === null || active.id !== input.fence.runId || active.status !== "active" || sessions === null || sessions === undefined || session === undefined || session.nativeSessionId !== input.fence.nativeSessionId) {
|
|
2098
2093
|
recordCanonicalObservationObsolete(store, input, "continuation-fence-mismatch", now);
|
|
2099
2094
|
return "obsolete";
|
|
2100
2095
|
}
|
|
2101
2096
|
store.saveTaskRoleSessionSet(recordStructuredProviderAcceptance(sessions, input, now));
|
|
2102
|
-
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "
|
|
2097
|
+
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.input-delivered", {
|
|
2103
2098
|
attemptId: input.fence.receiptId,
|
|
2104
|
-
|
|
2099
|
+
runId: active.id,
|
|
2105
2100
|
conversationId: input.fence.conversationId ?? input.fence.nativeSessionId,
|
|
2106
|
-
activationId: input.fence.activationId ?? input.fence.runtimeGenerationId,
|
|
2107
2101
|
...(input.fence.nativeTurnId === undefined
|
|
2108
2102
|
? {}
|
|
2109
2103
|
: { nativeTurnId: input.fence.nativeTurnId })
|
|
@@ -2125,9 +2119,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2125
2119
|
return "deferred";
|
|
2126
2120
|
if (decision.kind === "idempotent")
|
|
2127
2121
|
return "applied";
|
|
2128
|
-
const active = store.
|
|
2122
|
+
const active = store.getActiveRun(input.fence.taskId, input.fence.roleName);
|
|
2129
2123
|
if (active === null
|
|
2130
|
-
|| active.id !== input.fence.
|
|
2124
|
+
|| active.id !== input.fence.runId
|
|
2131
2125
|
|| active.status !== "active") {
|
|
2132
2126
|
recordCanonicalObservationObsolete(store, input, "turn-not-active", now);
|
|
2133
2127
|
return "obsolete";
|
|
@@ -2140,7 +2134,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2140
2134
|
});
|
|
2141
2135
|
}
|
|
2142
2136
|
foldRuntimeLifecycleEvent(store, event, observation) {
|
|
2143
|
-
const expectation = this.
|
|
2137
|
+
const expectation = this.projectRunExpectation(store, event.fence, observation.fence.runId);
|
|
2144
2138
|
if (expectation === null) {
|
|
2145
2139
|
return preallocatedRuntimeReadyAwaitingProjection(store, observation, this.drivers)
|
|
2146
2140
|
? { kind: "apply", outcome: { outcome: "mark-ready", preInputReady: true } }
|
|
@@ -2160,14 +2154,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
2160
2154
|
return { kind: "apply", outcome };
|
|
2161
2155
|
}
|
|
2162
2156
|
}
|
|
2163
|
-
/** Reads durable
|
|
2164
|
-
|
|
2157
|
+
/** Reads durable AgentRun + Session state into the pure fold's expectation shape. */
|
|
2158
|
+
projectRunExpectation(store, fence, runId) {
|
|
2165
2159
|
const role = store.getRole(fence.taskId, fence.roleName);
|
|
2166
2160
|
if (role === null)
|
|
2167
2161
|
return null;
|
|
2168
2162
|
const sessionSet = store.getTaskRoleSessionSet(fence.taskId, fence.roleName);
|
|
2169
2163
|
const session = sessionSet?.sessions[fence.agentId] ?? null;
|
|
2170
|
-
if (
|
|
2164
|
+
if (runId === undefined) {
|
|
2171
2165
|
return {
|
|
2172
2166
|
fence,
|
|
2173
2167
|
sessionStarted: false,
|
|
@@ -2180,45 +2174,30 @@ export class FileSchedulerStoreAdapter {
|
|
|
2180
2174
|
: { boundNativeSessionId: session.nativeSessionId })
|
|
2181
2175
|
};
|
|
2182
2176
|
}
|
|
2183
|
-
const run = store.
|
|
2177
|
+
const run = store.getRun(fence.taskId, runId);
|
|
2184
2178
|
if (run === null || run.status !== "active")
|
|
2185
2179
|
return null;
|
|
2186
|
-
const
|
|
2187
|
-
const freshConversationLaunch = run.mode === "new"
|
|
2188
|
-
&& runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId);
|
|
2180
|
+
const freshConversationLaunch = run.mode === "new" && session?.status !== "active";
|
|
2189
2181
|
const boundNativeSessionId = freshConversationLaunch
|
|
2190
2182
|
? undefined
|
|
2191
2183
|
: session?.nativeSessionId;
|
|
2192
|
-
const
|
|
2193
|
-
|
|
2194
|
-
: session?.runtimeGenerationId
|
|
2195
|
-
?? (runtimeHookMatchesReservation(store, owner, fence.runtimeGenerationId) ? fence.runtimeGenerationId : undefined);
|
|
2196
|
-
if (runtimeGenerationId === undefined)
|
|
2197
|
-
return null;
|
|
2198
|
-
const providerTurn = sessionSet?.providerBinding?.turn;
|
|
2199
|
-
const managedTurnMatches = managedProviderTurnId(providerTurn) === run.id;
|
|
2184
|
+
const providerTurn = sessionSet?.providerBinding?.run;
|
|
2185
|
+
const managedRunMatches = managedProviderTurnId(providerTurn) === run.id;
|
|
2200
2186
|
const expectedFence = {
|
|
2201
2187
|
taskId: fence.taskId,
|
|
2202
2188
|
roleName: fence.roleName,
|
|
2203
2189
|
agentId: run.effective.agentId,
|
|
2204
2190
|
adapterId: run.effective.adapterId,
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
receiptId: managedTurnMatches && providerTurn !== null && providerTurn !== undefined
|
|
2191
|
+
runId: run.id,
|
|
2192
|
+
receiptId: managedRunMatches && providerTurn !== null && providerTurn !== undefined
|
|
2208
2193
|
? providerTurn.attemptId
|
|
2209
|
-
:
|
|
2194
|
+
: formatRunReceiptId(run.taskId, run.id),
|
|
2210
2195
|
...(boundNativeSessionId === undefined ? {} : { nativeSessionId: boundNativeSessionId })
|
|
2211
2196
|
};
|
|
2212
2197
|
const driverId = this.drivers.requireByAdapterId(run.effective.adapterId).id;
|
|
2213
2198
|
const lifecycleEvents = store.listEvents(fence.taskId).flatMap((event) => {
|
|
2214
2199
|
const observation = runtimeObservationFromTaskEvent(event);
|
|
2215
|
-
return observation !== null
|
|
2216
|
-
&& (observation.kind === "session.started" || observation.kind === "session.ready")
|
|
2217
|
-
&& observation.fence.roleName === fence.roleName
|
|
2218
|
-
&& observation.fence.agentId === run.effective.agentId
|
|
2219
|
-
&& observation.fence.driverId === driverId
|
|
2220
|
-
&& observation.fence.runtimeGenerationId === runtimeGenerationId
|
|
2221
|
-
&& observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
|
|
2200
|
+
return observation !== null && (observation.kind === "session.started" || observation.kind === "session.ready") && observation.fence.roleName === fence.roleName && observation.fence.agentId === run.effective.agentId && observation.fence.driverId === driverId && observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
|
|
2222
2201
|
? [observation]
|
|
2223
2202
|
: [];
|
|
2224
2203
|
});
|
|
@@ -2226,8 +2205,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
2226
2205
|
fence: expectedFence,
|
|
2227
2206
|
sessionStarted: lifecycleEvents.length > 0,
|
|
2228
2207
|
ready: lifecycleEvents.some((event) => event.kind === "session.ready"),
|
|
2229
|
-
pushed:
|
|
2230
|
-
accepted:
|
|
2208
|
+
pushed: managedRunMatches,
|
|
2209
|
+
accepted: managedRunMatches && providerTurn !== null && providerTurn !== undefined
|
|
2231
2210
|
&& ["accepted", "completed", "failed", "cancelled"]
|
|
2232
2211
|
.includes(providerTurn.status),
|
|
2233
2212
|
terminal: run.status !== "active",
|
|
@@ -2244,7 +2223,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2244
2223
|
recordGlobalRuntimeNativeSession(input, now = new Date()) {
|
|
2245
2224
|
return this.store.transaction((store) => (recordGlobalRuntimeNativeSession(store, input, now)));
|
|
2246
2225
|
}
|
|
2247
|
-
|
|
2226
|
+
observeGlobalRuntimeRunTerminal(input, now = new Date()) {
|
|
2248
2227
|
return this.store.transaction((store) => {
|
|
2249
2228
|
const role = store.getGlobalRole(input.roleName);
|
|
2250
2229
|
if (role === null)
|
|
@@ -2256,22 +2235,20 @@ export class FileSchedulerStoreAdapter {
|
|
|
2256
2235
|
scope: "global",
|
|
2257
2236
|
roleName: input.roleName
|
|
2258
2237
|
};
|
|
2259
|
-
if (existing === undefined
|
|
2260
|
-
|
|
2261
|
-
throw new Error("Runtime turn completion does not match the global launch reservation.");
|
|
2238
|
+
if (existing === undefined && !runtimeHookMatchesLaunchIntent(store, owner)) {
|
|
2239
|
+
throw new Error("Runtime turn completion has no matching global Session intent.");
|
|
2262
2240
|
}
|
|
2263
|
-
const nativeSessionId =
|
|
2264
|
-
const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId,
|
|
2241
|
+
const nativeSessionId = input.nativeSessionId;
|
|
2242
|
+
const effectiveExisting = nativeTransitionExisting(store, owner, existing, nativeSessionId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
2265
2243
|
const effective = globalSessionEffective(role, effectiveExisting);
|
|
2266
2244
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2267
|
-
throw new Error("Runtime turn completion does not match the effective global runtime
|
|
2245
|
+
throw new Error("Runtime turn completion does not match the effective global runtime identity.");
|
|
2268
2246
|
}
|
|
2269
2247
|
const completedStatus = effectiveExisting?.status ?? "active";
|
|
2270
2248
|
current = recordRoleAgentSession(current, {
|
|
2271
2249
|
agentId: input.agentId,
|
|
2272
2250
|
adapterId: input.adapterId,
|
|
2273
2251
|
nativeSessionId,
|
|
2274
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2275
2252
|
title: effectiveExisting?.title ?? input.title,
|
|
2276
2253
|
preview: effectiveExisting?.preview ?? sessionPreview(input.outcome.status === "completed"
|
|
2277
2254
|
? input.outcome.output
|
|
@@ -2285,7 +2262,6 @@ export class FileSchedulerStoreAdapter {
|
|
|
2285
2262
|
}, now);
|
|
2286
2263
|
current = rememberRoleAgentCompletedTurn(current, input.agentId, nativeSessionId, input.nativeTurnId, now);
|
|
2287
2264
|
store.saveGlobalRoleSessionSet(current);
|
|
2288
|
-
completeRuntimeHookReservation(store, owner, input.runtimeGenerationId);
|
|
2289
2265
|
if (input.roleName === SYSTEM_OPERATOR_ROLE
|
|
2290
2266
|
&& input.adapterId === "codex"
|
|
2291
2267
|
&& completedStatus === "active") {
|
|
@@ -2300,7 +2276,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2300
2276
|
return current.sessions[input.agentId];
|
|
2301
2277
|
});
|
|
2302
2278
|
}
|
|
2303
|
-
|
|
2279
|
+
classifyGlobalRuntimeRunTerminal(input) {
|
|
2304
2280
|
const role = this.store.getGlobalRole(input.roleName);
|
|
2305
2281
|
if (role === null)
|
|
2306
2282
|
return "obsolete";
|
|
@@ -2310,13 +2286,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
2310
2286
|
scope: "global",
|
|
2311
2287
|
roleName: input.roleName
|
|
2312
2288
|
};
|
|
2313
|
-
if (existing === undefined
|
|
2314
|
-
&& !runtimeHookMatchesReservation(this.store, owner, input.runtimeGenerationId))
|
|
2289
|
+
if (existing === undefined && !runtimeHookMatchesLaunchIntent(this.store, owner))
|
|
2315
2290
|
return "obsolete";
|
|
2316
|
-
const nativeSessionId =
|
|
2291
|
+
const nativeSessionId = input.nativeSessionId;
|
|
2317
2292
|
let effectiveExisting;
|
|
2318
2293
|
try {
|
|
2319
|
-
effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId,
|
|
2294
|
+
effectiveExisting = nativeTransitionExisting(this.store, owner, existing, nativeSessionId, "Runtime turn completion conflicts with the fixed global Role session.");
|
|
2320
2295
|
}
|
|
2321
2296
|
catch {
|
|
2322
2297
|
return "obsolete";
|
|
@@ -2334,8 +2309,7 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2334
2309
|
roleName: input.fence.roleName,
|
|
2335
2310
|
agentId: input.fence.agentId,
|
|
2336
2311
|
adapterId,
|
|
2337
|
-
|
|
2338
|
-
...(input.fence.turnId === undefined ? {} : { turnId: input.fence.turnId }),
|
|
2312
|
+
...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
|
|
2339
2313
|
...(input.fence.nativeSessionId === undefined
|
|
2340
2314
|
? {}
|
|
2341
2315
|
: { nativeSessionId: input.fence.nativeSessionId }),
|
|
@@ -2343,7 +2317,7 @@ function runtimeObservationLifecycleFence(input, adapterId) {
|
|
|
2343
2317
|
};
|
|
2344
2318
|
}
|
|
2345
2319
|
/**
|
|
2346
|
-
* A live parent
|
|
2320
|
+
* A live parent AgentRun still owns its native child result. Only after that AgentRun
|
|
2347
2321
|
* is terminal or absent does Yui route the continuation fact to the original
|
|
2348
2322
|
* Role's supervisor. Mailbox coalescing remains the downstream batching
|
|
2349
2323
|
* mechanism; this guard decides ownership before any wake is enqueued.
|
|
@@ -2352,33 +2326,34 @@ function routeContinuationResult(store, observation, event, reason, now) {
|
|
|
2352
2326
|
const taskId = observation.fence.taskId;
|
|
2353
2327
|
if (taskId === undefined)
|
|
2354
2328
|
return;
|
|
2355
|
-
const
|
|
2329
|
+
const parentRun = observation.fence.runId === undefined
|
|
2356
2330
|
? null
|
|
2357
|
-
: store.
|
|
2358
|
-
if (
|
|
2331
|
+
: store.getRun(taskId, observation.fence.runId);
|
|
2332
|
+
if (parentRun?.status === "active")
|
|
2359
2333
|
return;
|
|
2360
2334
|
if (store.getRole(taskId, observation.fence.roleName) === null)
|
|
2361
2335
|
return;
|
|
2362
2336
|
routeRoleEvent(store, event, observation.fence.roleName, reason, now);
|
|
2363
2337
|
}
|
|
2364
|
-
/** Accepts the original
|
|
2365
|
-
function
|
|
2338
|
+
/** Accepts the original AgentRun receipt or a later mailbox activation receipt. */
|
|
2339
|
+
function runtimeReceiptBelongsToRun(store, input) {
|
|
2366
2340
|
const taskId = input.fence.taskId;
|
|
2367
|
-
const
|
|
2341
|
+
const runId = input.fence.runId;
|
|
2368
2342
|
const receiptId = input.fence.receiptId;
|
|
2369
|
-
if (receiptId ===
|
|
2343
|
+
if (receiptId === formatRunReceiptId(taskId, runId))
|
|
2370
2344
|
return true;
|
|
2371
2345
|
if (receiptId === undefined)
|
|
2372
2346
|
return false;
|
|
2347
|
+
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
2348
|
+
const current = sessions?.providerBinding?.run;
|
|
2349
|
+
if (current?.runId === runId && current.attemptId === receiptId
|
|
2350
|
+
&& current.status !== "rejected" && current.status !== "deferred"
|
|
2351
|
+
&& sessions?.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId) {
|
|
2352
|
+
return true;
|
|
2353
|
+
}
|
|
2373
2354
|
return store.listEvents(taskId).some((event) => {
|
|
2374
2355
|
const accepted = runtimeObservationFromTaskEvent(event);
|
|
2375
|
-
return accepted?.kind === "turn.accepted"
|
|
2376
|
-
&& accepted.fence.turnId === turnId
|
|
2377
|
-
&& accepted.fence.roleName === input.fence.roleName
|
|
2378
|
-
&& accepted.fence.agentId === input.fence.agentId
|
|
2379
|
-
&& accepted.fence.runtimeGenerationId === input.fence.runtimeGenerationId
|
|
2380
|
-
&& accepted.fence.nativeSessionId === input.fence.nativeSessionId
|
|
2381
|
-
&& accepted.fence.receiptId === receiptId;
|
|
2356
|
+
return accepted?.kind === "turn.accepted" && accepted.fence.runId === runId && accepted.fence.roleName === input.fence.roleName && accepted.fence.agentId === input.fence.agentId && accepted.fence.nativeSessionId === input.fence.nativeSessionId && accepted.fence.receiptId === receiptId;
|
|
2382
2357
|
});
|
|
2383
2358
|
}
|
|
2384
2359
|
function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
@@ -2392,9 +2367,8 @@ function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
|
2392
2367
|
taskId,
|
|
2393
2368
|
roleName: input.fence.roleName,
|
|
2394
2369
|
agentId: input.fence.agentId,
|
|
2395
|
-
...(input.fence.
|
|
2396
|
-
|
|
2397
|
-
nativeSessionId: input.fence.nativeSessionId ?? input.fence.runtimeGenerationId
|
|
2370
|
+
...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
|
|
2371
|
+
...(input.fence.nativeSessionId === undefined ? {} : { nativeSessionId: input.fence.nativeSessionId })
|
|
2398
2372
|
}, reason, now);
|
|
2399
2373
|
}
|
|
2400
2374
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
@@ -2407,9 +2381,8 @@ function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
|
2407
2381
|
eventType: input.eventType ?? input.type ?? "unknown",
|
|
2408
2382
|
roleName: input.roleName,
|
|
2409
2383
|
agentId: input.agentId,
|
|
2410
|
-
nativeSessionId: input.nativeSessionId,
|
|
2411
|
-
...(input.
|
|
2412
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2384
|
+
...(input.nativeSessionId === undefined ? {} : { nativeSessionId: input.nativeSessionId }),
|
|
2385
|
+
...(input.runId === undefined ? {} : { runId: input.runId }),
|
|
2413
2386
|
reason
|
|
2414
2387
|
}, now));
|
|
2415
2388
|
}
|
|
@@ -2420,44 +2393,36 @@ function sessionPreview(value) {
|
|
|
2420
2393
|
? truncated.slice(0, -1)
|
|
2421
2394
|
: truncated;
|
|
2422
2395
|
}
|
|
2423
|
-
function
|
|
2424
|
-
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2425
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId)) {
|
|
2426
|
-
throw new Error("Runtime launch reservation no longer matches the launch.");
|
|
2427
|
-
}
|
|
2428
|
-
return mailbox;
|
|
2429
|
-
}
|
|
2430
|
-
function runtimeHookMatchesReservation(store, owner, runtimeGenerationId) {
|
|
2396
|
+
function runtimeHookMatchesLaunchIntent(store, owner) {
|
|
2431
2397
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2432
2398
|
if (hasRuntimeCleanupObligation(mailbox))
|
|
2433
2399
|
return false;
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2400
|
+
if (owner.scope === "global")
|
|
2401
|
+
return true;
|
|
2402
|
+
return store.getActiveRun(owner.taskId, owner.roleName)?.mode === "new";
|
|
2437
2403
|
}
|
|
2438
2404
|
function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers) {
|
|
2439
2405
|
const taskId = observation.fence.taskId;
|
|
2440
|
-
const
|
|
2406
|
+
const runId = observation.fence.runId;
|
|
2441
2407
|
const nativeSessionId = observation.fence.nativeSessionId;
|
|
2442
2408
|
const driver = drivers.find(observation.fence.driverId);
|
|
2443
2409
|
if (observation.kind !== "session.ready"
|
|
2444
2410
|
|| driver?.capabilities.observation.sessionBootstrap !== "preallocated"
|
|
2445
2411
|
|| taskId === undefined
|
|
2446
|
-
||
|
|
2412
|
+
|| runId === undefined
|
|
2447
2413
|
|| nativeSessionId === undefined)
|
|
2448
2414
|
return false;
|
|
2449
2415
|
const task = store.getTask(taskId);
|
|
2450
2416
|
const role = store.getRole(taskId, observation.fence.roleName);
|
|
2451
|
-
const run = store.
|
|
2417
|
+
const run = store.getActiveRun(taskId, observation.fence.roleName);
|
|
2452
2418
|
const sessions = store.getTaskRoleSessionSet(taskId, observation.fence.roleName);
|
|
2453
|
-
if (task
|
|
2454
|
-
|| task.executionGate.state !== "enabled"
|
|
2419
|
+
if (task == null || run == null || !runPurposeAdmitsTaskState(run.purpose, task)
|
|
2455
2420
|
|| role?.activeAgentId !== observation.fence.agentId
|
|
2456
|
-
|| run?.id !==
|
|
2421
|
+
|| run?.id !== runId
|
|
2457
2422
|
|| run.status !== "active"
|
|
2458
2423
|
|| run.effective.agentId !== observation.fence.agentId
|
|
2459
2424
|
|| run.effective.adapterId !== driver.adapterId
|
|
2460
|
-
|| observation.fence.receiptId !==
|
|
2425
|
+
|| observation.fence.receiptId !== formatRunReceiptId(run.taskId, run.id)
|
|
2461
2426
|
|| sessions?.sessions[observation.fence.agentId] !== undefined)
|
|
2462
2427
|
return false;
|
|
2463
2428
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
@@ -2465,17 +2430,7 @@ function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers)
|
|
|
2465
2430
|
taskId,
|
|
2466
2431
|
roleName: observation.fence.roleName
|
|
2467
2432
|
}));
|
|
2468
|
-
return
|
|
2469
|
-
&& !hasRuntimeCleanupObligation(mailbox)
|
|
2470
|
-
&& nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.runtimeGenerationId, observation.fence.agentId, driver.adapterId);
|
|
2471
|
-
}
|
|
2472
|
-
function completeRuntimeHookReservation(store, owner, runtimeGenerationId) {
|
|
2473
|
-
if (runtimeGenerationId === undefined)
|
|
2474
|
-
return;
|
|
2475
|
-
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2476
|
-
if (!isRuntimeLaunchReservation(mailbox?.processing, runtimeGenerationId))
|
|
2477
|
-
return;
|
|
2478
|
-
saveRuntimeLifecycleMailbox(store, completeProcessing(mailbox, runtimeGenerationId));
|
|
2433
|
+
return !hasRuntimeCleanupObligation(mailbox);
|
|
2479
2434
|
}
|
|
2480
2435
|
function saveRuntimeLifecycleMailbox(store, mailbox) {
|
|
2481
2436
|
if (!mailboxHasWork(mailbox)) {
|
|
@@ -2503,24 +2458,18 @@ function sameRuntimeOwner(left, right) {
|
|
|
2503
2458
|
&& left.roleName === right.roleName
|
|
2504
2459
|
: right.scope === "global" && left.roleName === right.roleName;
|
|
2505
2460
|
}
|
|
2506
|
-
function dormantRuntimeCandidateIsCurrent(store, candidate) {
|
|
2461
|
+
function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = false) {
|
|
2507
2462
|
const { owner } = candidate;
|
|
2508
|
-
if (
|
|
2509
|
-
|
|
2510
|
-
}
|
|
2511
|
-
if (owner.scope === "task"
|
|
2512
|
-
&& store.getActiveTurn(owner.taskId, owner.roleName) !== null) {
|
|
2463
|
+
if (!allowActiveRun && owner.scope === "task"
|
|
2464
|
+
&& store.getActiveRun(owner.taskId, owner.roleName) !== null) {
|
|
2513
2465
|
return false;
|
|
2514
2466
|
}
|
|
2515
2467
|
const sessions = runtimeOwnerSessionSet(store, owner);
|
|
2516
|
-
const active =
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
&& active.
|
|
2521
|
-
&& active.nativeSessionId === candidate.nativeSessionId
|
|
2522
|
-
&& active.runtimeGenerationId === candidate.runtimeGenerationId
|
|
2523
|
-
&& active.updatedAt === candidate.sessionUpdatedAt;
|
|
2468
|
+
const active = allowActiveRun && owner.scope === "task"
|
|
2469
|
+
? taskRoleControlTarget(store.getTaskRoleSessionSet(owner.taskId, owner.roleName))
|
|
2470
|
+
: sessions?.sessions[sessions.activeAgentId];
|
|
2471
|
+
return active !== undefined && active.agentId === candidate.agentId && active.adapterId === candidate.adapterId
|
|
2472
|
+
&& active.nativeSessionId === candidate.nativeSessionId && (allowActiveRun || active.updatedAt === candidate.sessionUpdatedAt);
|
|
2524
2473
|
}
|
|
2525
2474
|
function endRuntimeOwnerSession(store, owner, now) {
|
|
2526
2475
|
if (owner.scope === "task") {
|
|
@@ -2533,16 +2482,6 @@ function endRuntimeOwnerSession(store, owner, now) {
|
|
|
2533
2482
|
let stopped = active.status === "ended"
|
|
2534
2483
|
? sessions
|
|
2535
2484
|
: updateRoleAgentSessionStatus(sessions, sessions.activeAgentId, "ended", now, "stopped");
|
|
2536
|
-
const activation = stopped.providerBinding === null
|
|
2537
|
-
? null
|
|
2538
|
-
: currentProviderActivation(stopped.providerBinding);
|
|
2539
|
-
if (activation !== null) {
|
|
2540
|
-
stopped = updateTaskRoleProviderRuntime(stopped, endProviderActivation(stopped.providerBinding, activation.activationId, {
|
|
2541
|
-
status: "ended",
|
|
2542
|
-
endedAt: now.toISOString(),
|
|
2543
|
-
reason: "session-ended"
|
|
2544
|
-
}), now);
|
|
2545
|
-
}
|
|
2546
2485
|
if (stopped === sessions)
|
|
2547
2486
|
return false;
|
|
2548
2487
|
store.saveTaskRoleSessionSet(stopped);
|
|
@@ -2582,13 +2521,6 @@ function runtimeOwnerSessionSet(store, owner) {
|
|
|
2582
2521
|
? store.getTaskRoleSessionSet(owner.taskId, owner.roleName)
|
|
2583
2522
|
: store.getGlobalRoleSessionSet(owner.roleName);
|
|
2584
2523
|
}
|
|
2585
|
-
function runtimeSessionMatchesSettledLaunch(session, input) {
|
|
2586
|
-
return session !== undefined
|
|
2587
|
-
&& session.agentId === input.agentId
|
|
2588
|
-
&& session.adapterId === input.adapterId
|
|
2589
|
-
&& (input.nativeSessionId === undefined
|
|
2590
|
-
|| session.nativeSessionId === input.nativeSessionId);
|
|
2591
|
-
}
|
|
2592
2524
|
function recordTaskRuntimeNativeSession(store, input, now) {
|
|
2593
2525
|
const task = store.getTask(input.taskId);
|
|
2594
2526
|
if (task === null)
|
|
@@ -2596,32 +2528,45 @@ function recordTaskRuntimeNativeSession(store, input, now) {
|
|
|
2596
2528
|
if (task.status === "archived") {
|
|
2597
2529
|
throw new Error(`Cannot register a native session for archived Task: ${input.taskId}.`);
|
|
2598
2530
|
}
|
|
2599
|
-
|
|
2531
|
+
// A Draft registers a Session only for its planning Turn. Admission is decided
|
|
2532
|
+
// by the active Turn's purpose through the one shared invariant, so a Draft
|
|
2533
|
+
// still cannot open an execution Session before activation.
|
|
2534
|
+
const admittingRun = store.getActiveRun(input.taskId, input.roleName);
|
|
2535
|
+
const existingPlanning = activeLiveRoleAgentSession(store.getTaskRoleSessionSet(input.taskId, input.roleName));
|
|
2536
|
+
if (!(admittingRun !== null
|
|
2537
|
+
&& admittingRun.purpose === "planning"
|
|
2538
|
+
&& runPurposeAdmitsTaskState(admittingRun.purpose, task))
|
|
2539
|
+
&& !(task.status === "draft" && task.executionGate.state === "enabled" && input.roleName === "leader"
|
|
2540
|
+
&& existingPlanning?.effective.executionAuthority === "planning"
|
|
2541
|
+
&& existingPlanning.nativeSessionId === input.nativeSessionId)
|
|
2542
|
+
&& (task.status !== "active" || task.executionGate.state !== "enabled")) {
|
|
2600
2543
|
throw new Error(`Cannot register a native session for a Task that is not active: ${input.taskId}.`);
|
|
2601
2544
|
}
|
|
2602
2545
|
const role = requireRole(store, input.taskId, input.roleName);
|
|
2546
|
+
if (role.activeAgentId !== input.agentId
|
|
2547
|
+
|| activeRoleAgentBinding(role).adapterId !== input.adapterId) {
|
|
2548
|
+
throw new Error("Native Session registration does not match the active Role Agent.");
|
|
2549
|
+
}
|
|
2603
2550
|
const current = store.getRoleSessionSet(input.taskId, input.roleName)
|
|
2604
2551
|
?? createRoleSessionSet({ scope: "task", taskId: input.taskId, roleName: input.roleName }, input.agentId, now);
|
|
2605
2552
|
const existing = current.sessions[input.agentId];
|
|
2606
|
-
const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId,
|
|
2607
|
-
if (existing?.status === "active"
|
|
2608
|
-
&& (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
|
|
2553
|
+
const effectiveExisting = nativeTransitionExisting(store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, existing, input.nativeSessionId, "Native session registration conflicts with the fixed Role session.");
|
|
2554
|
+
if (existing?.status === "active")
|
|
2609
2555
|
return existing;
|
|
2610
2556
|
const resolvedEffective = taskSessionEffective(store, input.taskId, input.roleName, input.agentId, effectiveExisting);
|
|
2611
2557
|
const effective = input.effective === undefined
|
|
2612
2558
|
? resolvedEffective
|
|
2613
2559
|
: validateEffectiveLaunchSnapshot(input.effective);
|
|
2614
2560
|
if (!roleSessionMayContinue(resolvedEffective, effective)) {
|
|
2615
|
-
throw new Error("
|
|
2561
|
+
throw new Error("Native Session effective launch changed before persistence.");
|
|
2616
2562
|
}
|
|
2617
2563
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2618
|
-
throw new Error("Native session registration does not match the effective runtime
|
|
2564
|
+
throw new Error("Native session registration does not match the effective runtime identity.");
|
|
2619
2565
|
}
|
|
2620
2566
|
const updated = recordRoleAgentSession(current, {
|
|
2621
2567
|
agentId: input.agentId,
|
|
2622
2568
|
adapterId: input.adapterId,
|
|
2623
2569
|
nativeSessionId: input.nativeSessionId,
|
|
2624
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2625
2570
|
policy: "fixed",
|
|
2626
2571
|
status: "active",
|
|
2627
2572
|
effective: effectiveExisting?.effective ?? effective
|
|
@@ -2633,28 +2578,30 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
|
2633
2578
|
const role = store.getGlobalRole(input.roleName);
|
|
2634
2579
|
if (role === null)
|
|
2635
2580
|
throw new Error(`Global Role not found: ${input.roleName}.`);
|
|
2581
|
+
if (role.activeAgentId !== input.agentId
|
|
2582
|
+
|| role.agentBindings[role.activeAgentId]?.adapterId !== input.adapterId) {
|
|
2583
|
+
throw new Error("Native Session registration does not match the active global Role Agent.");
|
|
2584
|
+
}
|
|
2636
2585
|
const current = store.getGlobalRoleSessionSet(input.roleName)
|
|
2637
2586
|
?? createRoleSessionSet({ scope: "global", roleName: input.roleName }, input.agentId, now);
|
|
2638
2587
|
const existing = current.sessions[input.agentId];
|
|
2639
|
-
const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId,
|
|
2640
|
-
if (existing?.status === "active"
|
|
2641
|
-
&& (input.runtimeGenerationId === undefined || existing.runtimeGenerationId === input.runtimeGenerationId))
|
|
2588
|
+
const effectiveExisting = nativeTransitionExisting(store, { scope: "global", roleName: input.roleName }, existing, input.nativeSessionId, "Native session registration conflicts with the fixed global Role session.");
|
|
2589
|
+
if (existing?.status === "active")
|
|
2642
2590
|
return existing;
|
|
2643
2591
|
const resolvedEffective = globalSessionEffective(role, effectiveExisting);
|
|
2644
2592
|
const effective = input.effective === undefined
|
|
2645
2593
|
? resolvedEffective
|
|
2646
2594
|
: validateEffectiveLaunchSnapshot(input.effective);
|
|
2647
2595
|
if (!roleSessionMayContinue(resolvedEffective, effective)) {
|
|
2648
|
-
throw new Error("
|
|
2596
|
+
throw new Error("Global native Session effective launch changed before persistence.");
|
|
2649
2597
|
}
|
|
2650
2598
|
if (effective.agentId !== input.agentId || effective.adapterId !== input.adapterId) {
|
|
2651
|
-
throw new Error("Native session registration does not match the effective global runtime
|
|
2599
|
+
throw new Error("Native session registration does not match the effective global runtime identity.");
|
|
2652
2600
|
}
|
|
2653
2601
|
const updated = recordRoleAgentSession(current, {
|
|
2654
2602
|
agentId: input.agentId,
|
|
2655
2603
|
adapterId: input.adapterId,
|
|
2656
2604
|
nativeSessionId: input.nativeSessionId,
|
|
2657
|
-
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
2658
2605
|
policy: "fixed",
|
|
2659
2606
|
status: "active",
|
|
2660
2607
|
effective
|
|
@@ -2662,31 +2609,20 @@ function recordGlobalRuntimeNativeSession(store, input, now) {
|
|
|
2662
2609
|
store.saveGlobalRoleSessionSet(updated);
|
|
2663
2610
|
return updated.sessions[input.agentId];
|
|
2664
2611
|
}
|
|
2665
|
-
function nativeTransitionExisting(store, owner, existing, nativeSessionId,
|
|
2612
|
+
function nativeTransitionExisting(store, owner, existing, nativeSessionId, conflictMessage) {
|
|
2666
2613
|
if (existing === undefined || existing.nativeSessionId === nativeSessionId) {
|
|
2667
2614
|
return existing;
|
|
2668
2615
|
}
|
|
2669
|
-
if (existing.status === "ended"
|
|
2670
|
-
&& runtimeHookMatchesReservation(store, owner, runtimeGenerationId)) {
|
|
2616
|
+
if (existing.status === "ended" && runtimeHookMatchesLaunchIntent(store, owner)) {
|
|
2671
2617
|
return undefined;
|
|
2672
2618
|
}
|
|
2673
2619
|
throw new Error(conflictMessage);
|
|
2674
2620
|
}
|
|
2675
|
-
function globalCompletionNativeSessionId(existing, input) {
|
|
2676
|
-
// Codex structured event sources can expose different native IDs for the
|
|
2677
|
-
// same runtime generation. That generation remains the authoritative fence.
|
|
2678
|
-
return input.adapterId === "codex"
|
|
2679
|
-
&& input.runtimeGenerationId !== undefined
|
|
2680
|
-
&& existing?.runtimeGenerationId === input.runtimeGenerationId
|
|
2681
|
-
&& existing.agentId === input.agentId
|
|
2682
|
-
&& existing.adapterId === input.adapterId
|
|
2683
|
-
? existing.nativeSessionId
|
|
2684
|
-
: input.nativeSessionId;
|
|
2685
|
-
}
|
|
2686
2621
|
function mapRole(store, role) {
|
|
2687
2622
|
const binding = activeRoleAgentBinding(role);
|
|
2623
|
+
const purpose = store.getTask(role.taskId)?.status === "draft" ? "planning" : "execution";
|
|
2688
2624
|
const item = store.listWorkItems(role.taskId).find((candidate) => (candidate.assignee === role.name
|
|
2689
|
-
&& !["
|
|
2625
|
+
&& !["accepted", "retired"].includes(candidate.status))) ?? null;
|
|
2690
2626
|
const workspace = (item === null
|
|
2691
2627
|
? store.getTaskWorkspace(role.taskId)
|
|
2692
2628
|
: store.getWorkItemWorkspace(role.taskId, item.id))
|
|
@@ -2695,17 +2631,20 @@ function mapRole(store, role) {
|
|
|
2695
2631
|
const reopened = role.name === "leader"
|
|
2696
2632
|
&& store.getPendingWakeup(role.taskId)?.reasons.includes("task-reopened") === true;
|
|
2697
2633
|
const liveSession = activeLiveRoleAgentSession(store.getTaskRoleSessionSet(role.taskId, role.name));
|
|
2698
|
-
|
|
2634
|
+
// This projection plans future dispatch. A live Session retains its actual
|
|
2635
|
+
// source, but cannot silently override an explicit next-Agent selection.
|
|
2636
|
+
// Active AgentRun delivery separately and exclusively uses turn.effective.
|
|
2637
|
+
const effective = reopened || (liveSession !== null && liveSession.agentId !== role.activeAgentId)
|
|
2699
2638
|
? resolveEffectiveLaunch({
|
|
2700
2639
|
role,
|
|
2701
|
-
purpose
|
|
2640
|
+
purpose,
|
|
2702
2641
|
...(workspace === undefined ? {} : { workspace })
|
|
2703
2642
|
})
|
|
2704
2643
|
: liveSession !== null && workspace?.owner.type === "task"
|
|
2705
2644
|
? effectiveLaunchWithTaskMainWorkspace(liveSession.effective, workspace)
|
|
2706
2645
|
: liveSession?.effective ?? resolveEffectiveLaunch({
|
|
2707
2646
|
role,
|
|
2708
|
-
purpose
|
|
2647
|
+
purpose,
|
|
2709
2648
|
...(workspace === undefined ? {} : { workspace })
|
|
2710
2649
|
});
|
|
2711
2650
|
return {
|
|
@@ -2721,14 +2660,14 @@ function mapRole(store, role) {
|
|
|
2721
2660
|
};
|
|
2722
2661
|
}
|
|
2723
2662
|
function taskSessionEffective(store, taskId, roleName, agentId, existing) {
|
|
2724
|
-
const active = store.
|
|
2663
|
+
const active = store.getActiveRun(taskId, roleName);
|
|
2725
2664
|
if (active !== null) {
|
|
2726
2665
|
if (active.effective.agentId !== agentId) {
|
|
2727
|
-
throw new Error(`Native Session registration does not match the effective
|
|
2666
|
+
throw new Error(`Native Session registration does not match the effective AgentRun Agent: ${taskId}/${roleName}.`);
|
|
2728
2667
|
}
|
|
2729
2668
|
if (existing !== undefined) {
|
|
2730
2669
|
if (!roleSessionMayContinue(existing.effective, active.effective)) {
|
|
2731
|
-
throw new Error(`Native Session effective launch does not match the active
|
|
2670
|
+
throw new Error(`Native Session effective launch does not match the active AgentRun: ${taskId}/${roleName}.`);
|
|
2732
2671
|
}
|
|
2733
2672
|
return existing.effective;
|
|
2734
2673
|
}
|
|
@@ -2740,7 +2679,7 @@ function taskSessionEffective(store, taskId, roleName, agentId, existing) {
|
|
|
2740
2679
|
if (role === null)
|
|
2741
2680
|
throw new Error(`Role not found: ${taskId}/${roleName}.`);
|
|
2742
2681
|
const item = store.listWorkItems(taskId).find((candidate) => (candidate.assignee === roleName
|
|
2743
|
-
&& !["
|
|
2682
|
+
&& !["accepted", "retired"].includes(candidate.status))) ?? null;
|
|
2744
2683
|
const workspace = (item === null
|
|
2745
2684
|
? store.getTaskWorkspace(taskId)
|
|
2746
2685
|
: store.getWorkItemWorkspace(taskId, item.id))
|
|
@@ -2765,7 +2704,6 @@ function mapSession(session) {
|
|
|
2765
2704
|
agentId: session.agentId,
|
|
2766
2705
|
adapterId: session.adapterId,
|
|
2767
2706
|
nativeSessionId: session.nativeSessionId,
|
|
2768
|
-
...(session.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
|
|
2769
2707
|
...(session.title === undefined ? {} : { title: session.title }),
|
|
2770
2708
|
status: session.status,
|
|
2771
2709
|
...(session.endReason === undefined ? {} : { endReason: session.endReason }),
|
|
@@ -2773,14 +2711,13 @@ function mapSession(session) {
|
|
|
2773
2711
|
updatedAt: session.updatedAt
|
|
2774
2712
|
};
|
|
2775
2713
|
}
|
|
2776
|
-
function saveTaskSession(store, role, session, status, now
|
|
2714
|
+
function saveTaskSession(store, role, session, status, now) {
|
|
2777
2715
|
const current = store.getRoleSessionSet(role.taskId, role.name)
|
|
2778
2716
|
?? createRoleSessionSet({ scope: "task", taskId: role.taskId, roleName: role.name }, session.agentId, now);
|
|
2779
2717
|
const updated = recordRoleAgentSession(current, {
|
|
2780
2718
|
agentId: session.agentId,
|
|
2781
2719
|
adapterId: session.adapterId,
|
|
2782
2720
|
nativeSessionId: session.nativeSessionId,
|
|
2783
|
-
...(runtimeGenerationId === undefined ? {} : { runtimeGenerationId }),
|
|
2784
2721
|
...(session.title === undefined ? {} : { title: session.title }),
|
|
2785
2722
|
policy: "fixed",
|
|
2786
2723
|
status,
|
|
@@ -2794,11 +2731,7 @@ function saveTaskSession(store, role, session, status, now, runtimeGenerationId)
|
|
|
2794
2731
|
function matchesStallSessionFence(current, expected) {
|
|
2795
2732
|
if (current === null || expected === null)
|
|
2796
2733
|
return current === expected;
|
|
2797
|
-
return current.agentId === expected.agentId
|
|
2798
|
-
&& current.adapterId === expected.adapterId
|
|
2799
|
-
&& current.nativeSessionId === expected.nativeSessionId
|
|
2800
|
-
&& current.runtimeGenerationId === expected.runtimeGenerationId
|
|
2801
|
-
&& current.status === expected.status;
|
|
2734
|
+
return current.agentId === expected.agentId && current.adapterId === expected.adapterId && current.nativeSessionId === expected.nativeSessionId && current.status === expected.status;
|
|
2802
2735
|
}
|
|
2803
2736
|
function requireRole(store, taskId, roleName) {
|
|
2804
2737
|
const role = store.getRole(taskId, roleName);
|
|
@@ -2806,17 +2739,18 @@ function requireRole(store, taskId, roleName) {
|
|
|
2806
2739
|
throw new Error(`Role not found: ${taskId}/${roleName}.`);
|
|
2807
2740
|
return role;
|
|
2808
2741
|
}
|
|
2809
|
-
function
|
|
2742
|
+
function runLaunchEventPayload(run) {
|
|
2810
2743
|
return {
|
|
2811
|
-
|
|
2812
|
-
role:
|
|
2813
|
-
purpose:
|
|
2814
|
-
mode:
|
|
2815
|
-
agent: `${
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2744
|
+
runId: run.id,
|
|
2745
|
+
role: run.roleName,
|
|
2746
|
+
purpose: run.purpose,
|
|
2747
|
+
mode: run.mode,
|
|
2748
|
+
agent: `${run.effective.agentId}/${run.effective.adapterId}`,
|
|
2749
|
+
component: run.effective.component,
|
|
2750
|
+
effectiveRevision: String(run.effective.sourceDesiredRevision),
|
|
2751
|
+
profileAccess: run.effective.profileAccess,
|
|
2752
|
+
effectivePermission: run.effective.permission.strategy,
|
|
2753
|
+
writeProjectIds: run.effective.writeProjectIds.join(",") || "none"
|
|
2820
2754
|
};
|
|
2821
2755
|
}
|
|
2822
2756
|
/**
|
|
@@ -2831,8 +2765,7 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
2831
2765
|
return {
|
|
2832
2766
|
taskId: input.fence.taskId,
|
|
2833
2767
|
roleName: input.fence.roleName,
|
|
2834
|
-
|
|
2835
|
-
generation: input.fence.runtimeGenerationId,
|
|
2768
|
+
runId: input.fence.runId,
|
|
2836
2769
|
progressId: [
|
|
2837
2770
|
input.kind,
|
|
2838
2771
|
input.payload.operationId ?? input.payload.activity ?? "state"
|
|
@@ -2843,7 +2776,6 @@ function runtimeObservationTelemetryEntry(input) {
|
|
|
2843
2776
|
kind: input.kind,
|
|
2844
2777
|
authority: input.authority,
|
|
2845
2778
|
driverId: input.fence.driverId,
|
|
2846
|
-
runtimeGenerationId: input.fence.runtimeGenerationId,
|
|
2847
2779
|
nativeSessionId: input.fence.nativeSessionId ?? "",
|
|
2848
2780
|
nativeTurnId: input.fence.nativeTurnId ?? "",
|
|
2849
2781
|
...(input.payload.operation === undefined
|
|
@@ -2870,7 +2802,7 @@ function compactedRuntimeObservationIds(events, incoming) {
|
|
|
2870
2802
|
const observation = runtimeObservationFromTaskEvent(event);
|
|
2871
2803
|
const matches = incoming.kind.startsWith("operation.")
|
|
2872
2804
|
? observation !== null
|
|
2873
|
-
&&
|
|
2805
|
+
&& runtimeObservationRunFenceMatches(observation.fence, incoming.fence)
|
|
2874
2806
|
: observation !== null
|
|
2875
2807
|
&& runtimeObservationFenceMatches(observation.fence, incoming.fence);
|
|
2876
2808
|
return observation !== null
|
|
@@ -2922,18 +2854,18 @@ function compactedRuntimeObservationIds(events, incoming) {
|
|
|
2922
2854
|
function recordStructuredProviderAcceptance(sessions, input, now) {
|
|
2923
2855
|
const current = sessions.providerBinding;
|
|
2924
2856
|
const attemptId = input.fence.receiptId;
|
|
2925
|
-
const
|
|
2857
|
+
const runId = input.fence.nativeTurnId;
|
|
2926
2858
|
if (current === null || attemptId === undefined)
|
|
2927
2859
|
return sessions;
|
|
2928
|
-
if (current.
|
|
2929
|
-
|| current.
|
|
2860
|
+
if (current.run === null
|
|
2861
|
+
|| current.run.runId !== input.fence.runId)
|
|
2930
2862
|
return sessions;
|
|
2931
|
-
if (current.
|
|
2932
|
-
&& ["accepted", "completed", "failed", "cancelled"].includes(current.
|
|
2863
|
+
if (current.run.attemptId === attemptId
|
|
2864
|
+
&& ["accepted", "completed", "failed", "cancelled"].includes(current.run.status))
|
|
2933
2865
|
return sessions;
|
|
2934
2866
|
const binding = acceptProviderTurn(current, {
|
|
2935
2867
|
attemptId,
|
|
2936
|
-
nativeTurnId:
|
|
2868
|
+
nativeTurnId: runId,
|
|
2937
2869
|
acceptedAt: input.observedAt ?? input.receivedAt
|
|
2938
2870
|
});
|
|
2939
2871
|
return updateTaskRoleProviderRuntime(sessions, binding, now);
|
|
@@ -2948,18 +2880,16 @@ function resolveTerminalExecution(store, input) {
|
|
|
2948
2880
|
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
2949
2881
|
const session = sessions?.sessions[input.agentId];
|
|
2950
2882
|
const binding = sessions?.providerBinding;
|
|
2951
|
-
const pending = binding?.
|
|
2883
|
+
const pending = binding?.run;
|
|
2952
2884
|
const evidence = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
|
|
2953
2885
|
.filter((event) => event !== null
|
|
2954
|
-
&& (event.kind === "turn.accepted" ||
|
|
2886
|
+
&& (event.kind === "turn.accepted" || isRunTerminalObservation(event)));
|
|
2955
2887
|
if (input.attemptId !== undefined && evidence.some((event) => (event.fence.roleName === input.roleName
|
|
2956
2888
|
&& event.fence.agentId === input.agentId
|
|
2957
2889
|
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
2958
2890
|
&& event.fence.receiptId === input.attemptId
|
|
2959
|
-
&&
|
|
2960
|
-
|
|
2961
|
-
|| input.nativeTurnId !== undefined && event.fence.nativeTurnId !== undefined
|
|
2962
|
-
&& event.fence.nativeTurnId !== input.nativeTurnId))))
|
|
2891
|
+
&& input.nativeTurnId !== undefined && event.fence.nativeTurnId !== undefined
|
|
2892
|
+
&& event.fence.nativeTurnId !== input.nativeTurnId)))
|
|
2963
2893
|
return null;
|
|
2964
2894
|
const identityMatches = (attemptId, nativeTurnId) => ((input.attemptId === undefined
|
|
2965
2895
|
? input.nativeTurnId !== undefined && input.nativeTurnId === nativeTurnId
|
|
@@ -2969,75 +2899,67 @@ function resolveTerminalExecution(store, input) {
|
|
|
2969
2899
|
if (binding !== null && binding !== undefined && pending !== null && pending !== undefined
|
|
2970
2900
|
&& session?.adapterId === input.adapterId
|
|
2971
2901
|
&& session.nativeSessionId === input.nativeSessionId
|
|
2972
|
-
&& (input.runtimeGenerationId === undefined
|
|
2973
|
-
|| (pending.activationId ?? session.runtimeGenerationId) === input.runtimeGenerationId)
|
|
2974
2902
|
&& currentProviderConversation(binding).conversationId === input.nativeSessionId
|
|
2975
2903
|
&& identityMatches(pending.attemptId, pending.nativeTurnId)
|
|
2976
|
-
&& (input.
|
|
2977
|
-
&& pending.status !== "rejected") {
|
|
2978
|
-
const
|
|
2979
|
-
|
|
2980
|
-
&&
|
|
2981
|
-
const turn = pending.turnId === undefined ? null : store.getTurn(input.taskId, pending.turnId);
|
|
2982
|
-
if (activation !== undefined && (pending.turnId === undefined || (turn !== null && turn.roleName === input.roleName
|
|
2983
|
-
&& turn.effective.agentId === input.agentId && turn.effective.adapterId === input.adapterId)))
|
|
2904
|
+
&& (input.runId === undefined || input.runId === pending.runId)
|
|
2905
|
+
&& pending.status !== "rejected" && pending.status !== "deferred") {
|
|
2906
|
+
const run = pending.runId === undefined ? null : store.getRun(input.taskId, pending.runId);
|
|
2907
|
+
if (pending.runId === undefined || (run !== null && run.roleName === input.roleName
|
|
2908
|
+
&& run.effective.agentId === input.agentId && run.effective.adapterId === input.adapterId))
|
|
2984
2909
|
return {
|
|
2985
|
-
|
|
2986
|
-
nativeTurnId: pending.nativeTurnId,
|
|
2910
|
+
run, current: true, attemptId: pending.attemptId,
|
|
2911
|
+
nativeTurnId: pending.nativeTurnId,
|
|
2987
2912
|
};
|
|
2988
2913
|
}
|
|
2989
2914
|
// Old results use immutable acceptance/terminal evidence, not today's Role
|
|
2990
|
-
// config, Session status
|
|
2915
|
+
// config, Session status or active AgentRun pointer.
|
|
2991
2916
|
const matches = evidence.filter((event) => event.fence.roleName === input.roleName
|
|
2992
2917
|
&& event.fence.agentId === input.agentId
|
|
2993
2918
|
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
2994
|
-
&& input.runtimeGenerationId !== undefined
|
|
2995
|
-
&& event.fence.runtimeGenerationId === input.runtimeGenerationId
|
|
2996
|
-
&& (input.activationId === undefined
|
|
2997
|
-
|| (event.fence.activationId ?? event.fence.runtimeGenerationId) === input.activationId)
|
|
2998
2919
|
&& identityMatches(event.fence.receiptId, event.fence.nativeTurnId)
|
|
2999
|
-
&& event.fence.
|
|
3000
|
-
&& (input.turnId === undefined || event.fence.turnId === input.turnId));
|
|
2920
|
+
&& (input.runId === undefined || event.fence.runId === input.runId));
|
|
3001
2921
|
const accepted = matches[0];
|
|
3002
2922
|
if (accepted === undefined || accepted.fence.receiptId === undefined)
|
|
3003
2923
|
return null;
|
|
3004
|
-
if (matches.some((event) => event.fence.
|
|
2924
|
+
if (matches.some((event) => event.fence.runId !== accepted.fence.runId
|
|
3005
2925
|
|| event.fence.receiptId !== accepted.fence.receiptId)) {
|
|
3006
2926
|
throw new Error("Runtime terminal has conflicting durable execution bindings.");
|
|
3007
2927
|
}
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
2928
|
+
if (accepted.fence.runId === undefined) {
|
|
2929
|
+
return { run: null, current: false, attemptId: accepted.fence.receiptId,
|
|
2930
|
+
nativeTurnId: accepted.fence.nativeTurnId };
|
|
2931
|
+
}
|
|
2932
|
+
const run = store.getRun(input.taskId, accepted.fence.runId);
|
|
2933
|
+
if (run === null || run.effective.adapterId !== input.adapterId
|
|
2934
|
+
|| run.effective.agentId !== input.agentId || run.roleName !== input.roleName)
|
|
3011
2935
|
return null;
|
|
3012
2936
|
return {
|
|
3013
|
-
|
|
2937
|
+
run, current: false, attemptId: accepted.fence.receiptId,
|
|
3014
2938
|
nativeTurnId: accepted.fence.nativeTurnId,
|
|
3015
|
-
activationId: accepted.fence.activationId ?? accepted.fence.runtimeGenerationId
|
|
3016
2939
|
};
|
|
3017
2940
|
}
|
|
3018
|
-
function
|
|
2941
|
+
function isRunTerminalObservation(input) {
|
|
3019
2942
|
return ["turn.completed", "turn.failed", "turn.cancelled"].includes(input.kind);
|
|
3020
2943
|
}
|
|
3021
|
-
function assertConsistentTerminal(store, input,
|
|
2944
|
+
function assertConsistentTerminal(store, input, run) {
|
|
3022
2945
|
const previous = store.listEvents(input.taskId).map(runtimeObservationFromTaskEvent)
|
|
3023
2946
|
.filter((event) => event !== null
|
|
3024
|
-
&&
|
|
2947
|
+
&& isRunTerminalObservation(event)
|
|
3025
2948
|
&& event.fence.roleName === input.roleName
|
|
3026
2949
|
&& event.fence.agentId === input.agentId
|
|
3027
2950
|
&& event.fence.nativeSessionId === input.nativeSessionId
|
|
3028
|
-
&& event.fence.runtimeGenerationId === input.runtimeGenerationId
|
|
3029
2951
|
&& (input.attemptId === undefined
|
|
3030
2952
|
? input.nativeTurnId !== undefined && event.fence.nativeTurnId === input.nativeTurnId
|
|
3031
2953
|
: event.fence.receiptId === input.attemptId));
|
|
3032
2954
|
if (input.observation !== undefined && previous.some((event) => (event.kind !== input.observation.kind
|
|
3033
2955
|
|| !isDeepStrictEqual(event.payload, input.observation.payload)
|
|
3034
|
-
|| event.fence.
|
|
2956
|
+
|| event.fence.runId !== input.observation.fence.runId)))
|
|
3035
2957
|
throw new Error("Conflicting terminal content for the same execution.");
|
|
3036
|
-
if (
|
|
3037
|
-
if (
|
|
3038
|
-
|| (input.outcome.status === "completed" &&
|
|
2958
|
+
if (run?.result?.provider !== undefined) {
|
|
2959
|
+
if (run.result.provider.status !== input.providerStatus
|
|
2960
|
+
|| (input.outcome.status === "completed" && run.result.output !== input.outcome.output)
|
|
3039
2961
|
|| (previous.length === 0 && input.outcome.status === "failed"
|
|
3040
|
-
&&
|
|
2962
|
+
&& run.result.diagnostic !== input.outcome.diagnostic)) {
|
|
3041
2963
|
throw new Error("Conflicting result for the same execution.");
|
|
3042
2964
|
}
|
|
3043
2965
|
}
|
|
@@ -3051,49 +2973,30 @@ function terminalSessionReplacementBasis(sessions, input, run) {
|
|
|
3051
2973
|
|| session === undefined
|
|
3052
2974
|
|| incomingConversationId === undefined
|
|
3053
2975
|
|| currentProviderConversation(binding).conversationId === incomingConversationId
|
|
3054
|
-
||
|
|
3055
|
-
|| binding.authority.owner !== "none") {
|
|
2976
|
+
|| session.status !== "ended") {
|
|
3056
2977
|
return undefined;
|
|
3057
2978
|
}
|
|
3058
|
-
|
|
3059
|
-
// before Provider readiness arrives. A matching new Session plus the
|
|
3060
|
-
// quiescent old Provider binding preserves the same terminal-session proof.
|
|
3061
|
-
return session.status === "ended"
|
|
3062
|
-
|| session.nativeSessionId === input.fence.nativeSessionId
|
|
3063
|
-
? "terminal-session"
|
|
3064
|
-
: undefined;
|
|
2979
|
+
return "terminal-session";
|
|
3065
2980
|
}
|
|
3066
2981
|
function bindOrSupersedeProviderRuntime(sessions, input, now, replacementBasis) {
|
|
3067
2982
|
const conversationId = input.fence.conversationId ?? input.fence.nativeSessionId;
|
|
3068
|
-
const activationId = input.fence.activationId ?? input.fence.runtimeGenerationId;
|
|
3069
2983
|
if (sessions.providerBinding === null) {
|
|
3070
2984
|
return bindTaskRoleProviderRuntime(sessions, createProviderRuntimeBinding({
|
|
3071
2985
|
providerNamespace: input.fence.driverId,
|
|
3072
2986
|
accountScope: input.fence.agentId,
|
|
3073
2987
|
conversationId,
|
|
3074
|
-
activationId,
|
|
3075
2988
|
startedAt: input.observedAt ?? input.receivedAt
|
|
3076
2989
|
}), now);
|
|
3077
2990
|
}
|
|
3078
2991
|
const current = currentProviderConversation(sessions.providerBinding);
|
|
3079
2992
|
if (current.conversationId === conversationId) {
|
|
3080
|
-
|
|
3081
|
-
if (active?.activationId === activationId)
|
|
3082
|
-
return sessions;
|
|
3083
|
-
if (active !== null) {
|
|
3084
|
-
throw new Error("Provider Conversation cannot start a second Activation while the current one is active.");
|
|
3085
|
-
}
|
|
3086
|
-
return updateTaskRoleProviderRuntime(sessions, startProviderActivation(sessions.providerBinding, {
|
|
3087
|
-
activationId,
|
|
3088
|
-
startedAt: input.observedAt ?? input.receivedAt
|
|
3089
|
-
}), now);
|
|
2993
|
+
return sessions;
|
|
3090
2994
|
}
|
|
3091
2995
|
if (replacementBasis === undefined) {
|
|
3092
2996
|
throw new Error("A fresh Provider Conversation requires a terminal prior Session.");
|
|
3093
2997
|
}
|
|
3094
2998
|
return updateTaskRoleProviderRuntime(sessions, supersedeProviderConversation(sessions.providerBinding, {
|
|
3095
2999
|
conversationId,
|
|
3096
|
-
activationId,
|
|
3097
3000
|
switchedAt: input.observedAt ?? input.receivedAt,
|
|
3098
3001
|
basis: replacementBasis
|
|
3099
3002
|
}), now);
|
|
@@ -3106,28 +3009,25 @@ function terminalProviderReplacementBasis(sessions, input, mode) {
|
|
|
3106
3009
|
return undefined;
|
|
3107
3010
|
}
|
|
3108
3011
|
const current = sessions.sessions[input.fence.agentId];
|
|
3109
|
-
if (current === undefined
|
|
3110
|
-
|| current.status !== "active"
|
|
3111
|
-
|| current.nativeSessionId !== input.fence.nativeSessionId
|
|
3112
|
-
|| current.runtimeGenerationId !== input.fence.runtimeGenerationId) {
|
|
3012
|
+
if (current === undefined || current.status !== "active" || current.nativeSessionId !== input.fence.nativeSessionId) {
|
|
3113
3013
|
return undefined;
|
|
3114
3014
|
}
|
|
3115
3015
|
const replaced = [...(sessions.history ?? [])].reverse().find((session) => (session.agentId === current.agentId
|
|
3116
3016
|
&& session.adapterId === current.adapterId
|
|
3117
3017
|
&& session.status === "ended"
|
|
3118
|
-
&& session.nativeSessionId
|
|
3018
|
+
&& session.nativeSessionId === currentProviderConversation(sessions.providerBinding).conversationId));
|
|
3119
3019
|
return replaced === undefined ? undefined : "terminal-session";
|
|
3120
3020
|
}
|
|
3121
|
-
function settleStructuredProviderTurn(sessions,
|
|
3021
|
+
function settleStructuredProviderTurn(sessions, runId, status, now, attemptId) {
|
|
3122
3022
|
const binding = sessions.providerBinding;
|
|
3123
|
-
if (binding === null || binding.
|
|
3124
|
-
|| (attemptId === undefined ? binding.
|
|
3023
|
+
if (binding === null || binding.run === null
|
|
3024
|
+
|| (attemptId === undefined ? binding.run.nativeTurnId !== runId : binding.run.attemptId !== attemptId)) {
|
|
3125
3025
|
return sessions;
|
|
3126
3026
|
}
|
|
3127
|
-
if (["completed", "failed", "cancelled"].includes(binding.
|
|
3027
|
+
if (["completed", "failed", "cancelled"].includes(binding.run.status))
|
|
3128
3028
|
return sessions;
|
|
3129
3029
|
return updateTaskRoleProviderRuntime(sessions, settleProviderTurn(binding, {
|
|
3130
|
-
nativeTurnId:
|
|
3030
|
+
nativeTurnId: runId,
|
|
3131
3031
|
...(attemptId === undefined ? {} : { attemptId }),
|
|
3132
3032
|
status,
|
|
3133
3033
|
settledAt: now.toISOString()
|