@zq-silk/yui 0.15.6 → 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 +167 -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 +188 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +57 -0
- 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 +1216 -730
- 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 +77 -0
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +127 -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 +530 -476
- 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 +423 -0
- package/dist/kernel/capabilityRegistry.js +314 -0
- package/dist/kernel/capabilitySchema.js +91 -0
- package/dist/kernel/instanceHost.js +18 -1
- package/dist/kernel/kernelPorts.js +4 -1
- 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 +5 -5
- 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,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The smallest stable, provider-neutral Yui lifecycle vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Each phase is a durable semantic fact about a managed Agent Session or the
|
|
5
|
+
* AgentRun whose prompt it carries. Agent Drivers translate native signals into
|
|
6
|
+
* these phases; the scheduler and durable fold reason about phases, sources,
|
|
7
|
+
* and evidence — never about provider names.
|
|
8
|
+
*/
|
|
9
|
+
import { isAgentAdapterId } from "../agent/adapterCatalog.js";
|
|
1
10
|
export function isPreInputReadinessSupported(capability) {
|
|
2
11
|
return capability.status === "supported";
|
|
3
12
|
}
|
|
@@ -9,17 +18,17 @@ export class CanonicalLifecycleError extends Error {
|
|
|
9
18
|
this.name = "CanonicalLifecycleError";
|
|
10
19
|
}
|
|
11
20
|
}
|
|
12
|
-
const
|
|
21
|
+
const RUN_SCOPED_PHASES = new Set([
|
|
13
22
|
"prompt-pushed",
|
|
14
23
|
"provider-accepted",
|
|
15
24
|
"turn-progress",
|
|
16
25
|
"turn-terminal"
|
|
17
26
|
]);
|
|
18
27
|
/**
|
|
19
|
-
* Phases that assert durable provider truth about a
|
|
20
|
-
* the complete turn-scoped identity — turnId AND the
|
|
21
|
-
* (nativeSessionId
|
|
22
|
-
* because a missing or wrong fence must fail closed rather than advance a
|
|
28
|
+
* Phases that assert durable provider truth about a AgentRun. Every one must carry
|
|
29
|
+
* the complete turn-scoped identity — turnId AND the Session fences
|
|
30
|
+
* (nativeSessionId) AND, where the transport owns it, the receiptId —
|
|
31
|
+
* because a missing or wrong fence must fail closed rather than advance a AgentRun or
|
|
23
32
|
* its successor.
|
|
24
33
|
*/
|
|
25
34
|
const PROVIDER_TRUTH_PHASES = new Set([
|
|
@@ -34,7 +43,7 @@ const PROVIDER_TRUTH_PHASES = new Set([
|
|
|
34
43
|
* (acceptance/progress/terminal), so neither a transport/liveness signal nor a
|
|
35
44
|
* shape-only `adapter-mapped` observation can ever forge or terminalize a turn.
|
|
36
45
|
* `adapter-mapped` remains permissible only for the pre-acceptance
|
|
37
|
-
* session-started/ready phases, whose fold never advances
|
|
46
|
+
* session-started/ready phases, whose fold never advances AgentRun truth.
|
|
38
47
|
*/
|
|
39
48
|
const PHASE_EVIDENCE_MATRIX = {
|
|
40
49
|
"host-process-created": { sources: ["controller"], evidence: ["controller"] },
|
|
@@ -77,18 +86,15 @@ export function createCanonicalLifecycleEvent(input) {
|
|
|
77
86
|
throw new CanonicalLifecycleError(`Phase ${input.phase} rejects evidence ${input.evidence}; only ${rule.evidence.join("/")} may prove it.`);
|
|
78
87
|
}
|
|
79
88
|
const fence = normalizeFence(input.fence);
|
|
80
|
-
if (
|
|
81
|
-
throw new CanonicalLifecycleError(`Phase ${input.phase} requires a
|
|
89
|
+
if (RUN_SCOPED_PHASES.has(input.phase) && fence.runId === undefined) {
|
|
90
|
+
throw new CanonicalLifecycleError(`Phase ${input.phase} requires a runId fence.`);
|
|
82
91
|
}
|
|
83
|
-
// Provider-truth phases must carry the complete
|
|
84
|
-
// wrong
|
|
92
|
+
// Provider-truth phases must carry the complete Session fence so a stale or
|
|
93
|
+
// wrong Session can never advance the AgentRun.
|
|
85
94
|
if (PROVIDER_TRUTH_PHASES.has(input.phase)) {
|
|
86
95
|
if (fence.nativeSessionId === undefined) {
|
|
87
96
|
throw new CanonicalLifecycleError(`Phase ${input.phase} requires a nativeSessionId fence.`);
|
|
88
97
|
}
|
|
89
|
-
if (fence.runtimeGenerationId === undefined) {
|
|
90
|
-
throw new CanonicalLifecycleError(`Phase ${input.phase} requires a runtimeGenerationId fence.`);
|
|
91
|
-
}
|
|
92
98
|
}
|
|
93
99
|
if (input.phase === "prompt-pushed" && fence.receiptId === undefined) {
|
|
94
100
|
throw new CanonicalLifecycleError("A prompt-pushed event requires a transport receiptId.");
|
|
@@ -133,7 +139,7 @@ export function createCanonicalLifecycleEvent(input) {
|
|
|
133
139
|
* The provider-neutral decision. Given a validated canonical event and the
|
|
134
140
|
* consumer's current expectation, it returns what should happen — idempotent for
|
|
135
141
|
* duplicates, deferred for not-yet-applicable facts, obsolete for stale/wrong
|
|
136
|
-
*
|
|
142
|
+
* Session, fail-closed for contract violations. It reads only phase / source
|
|
137
143
|
* / evidence / identity, never the adapter name, and mutates nothing.
|
|
138
144
|
*/
|
|
139
145
|
export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
@@ -154,10 +160,7 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
|
154
160
|
}
|
|
155
161
|
// Discovery may bind a previously-unknown native id, but only under the
|
|
156
162
|
// exact launch fence and only when none is bound yet.
|
|
157
|
-
if (expectation.boundNativeSessionId === undefined
|
|
158
|
-
&& event.fence.nativeSessionId !== undefined
|
|
159
|
-
&& event.fence.runtimeGenerationId !== undefined
|
|
160
|
-
&& event.fence.runtimeGenerationId === expectation.fence.runtimeGenerationId) {
|
|
163
|
+
if (expectation.boundNativeSessionId === undefined && event.fence.nativeSessionId !== undefined) {
|
|
161
164
|
return { outcome: "bind-native-session", nativeSessionId: event.fence.nativeSessionId };
|
|
162
165
|
}
|
|
163
166
|
return { outcome: "mark-session-started" };
|
|
@@ -176,7 +179,7 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
|
176
179
|
return { outcome: "obsolete", reason: "push-after-terminal" };
|
|
177
180
|
if (expectation.accepted)
|
|
178
181
|
return { outcome: "obsolete", reason: "push-after-accepted" };
|
|
179
|
-
// Exactly one push: a repeat for an already-pushed
|
|
182
|
+
// Exactly one push: a repeat for an already-pushed AgentRun is a no-op, never a
|
|
180
183
|
// second Enter.
|
|
181
184
|
if (expectation.pushed)
|
|
182
185
|
return { outcome: "idempotent", reason: "already-pushed" };
|
|
@@ -209,7 +212,7 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
|
209
212
|
return { outcome: "idempotent", reason: "already-terminal" };
|
|
210
213
|
if (expectation.accepted)
|
|
211
214
|
return { outcome: "advance-terminal" };
|
|
212
|
-
// A terminal for a pushed-but-unaccepted
|
|
215
|
+
// A terminal for a pushed-but-unaccepted AgentRun must NOT promote acceptance;
|
|
213
216
|
// hold the immutable fact until (or unless) acceptance folds first.
|
|
214
217
|
if (expectation.pushed)
|
|
215
218
|
return { outcome: "deferred", reason: "terminal-before-accept" };
|
|
@@ -219,12 +222,12 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
|
219
222
|
}
|
|
220
223
|
}
|
|
221
224
|
/**
|
|
222
|
-
* Returns null when the event belongs to the expected
|
|
225
|
+
* Returns null when the event belongs to the expected Session/AgentRun, or a
|
|
223
226
|
* mismatch reason otherwise. Owner/adapter and turn-scoped turnId are always
|
|
224
227
|
* compared. Provider-truth phases (acceptance/progress/terminal) require the
|
|
225
|
-
* COMPLETE
|
|
226
|
-
* receiptId must be present and equal, so a missing or wrong
|
|
227
|
-
* fails closed rather than advancing a
|
|
228
|
+
* COMPLETE Session fence — nativeSessionId, and (for acceptance)
|
|
229
|
+
* receiptId must be present and equal, so a missing or wrong Session/receipt
|
|
230
|
+
* fails closed rather than advancing a AgentRun or its successor. Where a native id
|
|
228
231
|
* is already bound, every turn-scoped fact must match it.
|
|
229
232
|
*/
|
|
230
233
|
function matchesFence(expectation, event) {
|
|
@@ -239,29 +242,23 @@ function matchesFence(expectation, event) {
|
|
|
239
242
|
return "fence-mismatch:agentId";
|
|
240
243
|
if (expected.adapterId !== actual.adapterId)
|
|
241
244
|
return "fence-mismatch:adapterId";
|
|
242
|
-
//
|
|
243
|
-
if (
|
|
244
|
-
if (expected.
|
|
245
|
+
// AgentRun-scoped phases must name the exact expected AgentRun.
|
|
246
|
+
if (RUN_SCOPED_PHASES.has(phase)) {
|
|
247
|
+
if (expected.runId === undefined)
|
|
245
248
|
return "fence-mismatch:expected-turn-missing";
|
|
246
|
-
if (actual.
|
|
247
|
-
return "fence-mismatch:
|
|
249
|
+
if (actual.runId !== expected.runId)
|
|
250
|
+
return "fence-mismatch:runId";
|
|
248
251
|
}
|
|
249
|
-
else if (actual.
|
|
250
|
-
&& expected.
|
|
251
|
-
&& actual.
|
|
252
|
-
return "fence-mismatch:
|
|
252
|
+
else if (actual.runId !== undefined
|
|
253
|
+
&& expected.runId !== undefined
|
|
254
|
+
&& actual.runId !== expected.runId) {
|
|
255
|
+
return "fence-mismatch:runId";
|
|
253
256
|
}
|
|
254
|
-
// Provider-truth phases require the complete, equal
|
|
257
|
+
// Provider-truth phases require the complete, equal Session fence. A
|
|
255
258
|
// provider-accepted event additionally must carry the exact receipt it accepts.
|
|
256
259
|
if (PROVIDER_TRUTH_PHASES.has(phase)) {
|
|
257
260
|
if (actual.nativeSessionId === undefined)
|
|
258
261
|
return "fence-mismatch:missing-native-session";
|
|
259
|
-
if (actual.runtimeGenerationId === undefined) {
|
|
260
|
-
return "fence-mismatch:missing-runtime-generation";
|
|
261
|
-
}
|
|
262
|
-
if (expected.runtimeGenerationId !== undefined && actual.runtimeGenerationId !== expected.runtimeGenerationId) {
|
|
263
|
-
return "fence-mismatch:runtimeGenerationId";
|
|
264
|
-
}
|
|
265
262
|
// Once a native id is bound, every provider-truth fact must match it. Before
|
|
266
263
|
// binding (discovery still open) the expected fence's own nativeSessionId, if
|
|
267
264
|
// present, still constrains it.
|
|
@@ -279,7 +276,7 @@ function matchesFence(expectation, event) {
|
|
|
279
276
|
return null;
|
|
280
277
|
}
|
|
281
278
|
// Pre-acceptance phases (host/session/ready and the transport push): compare
|
|
282
|
-
// the
|
|
279
|
+
// the Session dimensions that are present on both sides. A push must match
|
|
283
280
|
// its expected receipt when both name one.
|
|
284
281
|
if (actual.nativeSessionId !== undefined
|
|
285
282
|
&& expectation.boundNativeSessionId !== undefined
|
|
@@ -291,11 +288,6 @@ function matchesFence(expectation, event) {
|
|
|
291
288
|
&& actual.nativeSessionId !== expected.nativeSessionId) {
|
|
292
289
|
return "fence-mismatch:nativeSessionId";
|
|
293
290
|
}
|
|
294
|
-
if (actual.runtimeGenerationId !== undefined
|
|
295
|
-
&& expected.runtimeGenerationId !== undefined
|
|
296
|
-
&& actual.runtimeGenerationId !== expected.runtimeGenerationId) {
|
|
297
|
-
return "fence-mismatch:runtimeGenerationId";
|
|
298
|
-
}
|
|
299
291
|
if (phase === "prompt-pushed"
|
|
300
292
|
&& actual.receiptId !== undefined
|
|
301
293
|
&& expected.receiptId !== undefined
|
|
@@ -308,7 +300,11 @@ function normalizeFence(fence) {
|
|
|
308
300
|
const taskId = requireFenceText(fence.taskId, "taskId");
|
|
309
301
|
const roleName = requireFenceText(fence.roleName, "roleName");
|
|
310
302
|
const agentId = requireFenceText(fence.agentId, "agentId");
|
|
311
|
-
|
|
303
|
+
// The fence names an adapter, and the catalog is what defines which adapters
|
|
304
|
+
// exist. Re-listing them here would silently exclude a catalogued adapter
|
|
305
|
+
// from the durable fold, which reads as an identity mismatch rather than as
|
|
306
|
+
// the missing registration it actually is.
|
|
307
|
+
if (!isAgentAdapterId(fence.adapterId)) {
|
|
312
308
|
throw new CanonicalLifecycleError(`Unsupported adapter fence: ${String(fence.adapterId)}.`);
|
|
313
309
|
}
|
|
314
310
|
return Object.freeze({
|
|
@@ -316,11 +312,10 @@ function normalizeFence(fence) {
|
|
|
316
312
|
roleName,
|
|
317
313
|
agentId,
|
|
318
314
|
adapterId: fence.adapterId,
|
|
319
|
-
...(fence.
|
|
315
|
+
...(fence.runId === undefined ? {} : { runId: requireFenceText(fence.runId, "runId") }),
|
|
320
316
|
...(fence.nativeSessionId === undefined
|
|
321
317
|
? {}
|
|
322
318
|
: { nativeSessionId: requireFenceText(fence.nativeSessionId, "nativeSessionId") }),
|
|
323
|
-
...(fence.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: requireFenceText(fence.runtimeGenerationId, "runtimeGenerationId") }),
|
|
324
319
|
...(fence.receiptId === undefined ? {} : { receiptId: requireFenceText(fence.receiptId, "receiptId") })
|
|
325
320
|
});
|
|
326
321
|
}
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { createTaskMessage } from "../message/message.js";
|
|
3
|
+
import { captureRoleRunDispatch, enqueueWork, settleRoleRunDispatch } from "../coordination/workMailboxQueue.js";
|
|
4
|
+
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
5
|
+
import { completeRun, failRun, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
6
|
+
import { boundedRunFailureDiagnostic } from "../domain/agentResultTransport.js";
|
|
7
|
+
import { updateExecutionLane, updateWorkItemExecutionLane } from "../execution/workItemExecution.js";
|
|
8
|
+
import { managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
|
|
9
|
+
import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
10
|
+
import { markTaskWakeConsumed } from "../scheduler/taskWake.js";
|
|
11
|
+
import { workItemExecutionGroupById, updateWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
12
|
+
/**
|
|
13
|
+
* Validate every immutable identity and frozen Project head needed before a
|
|
14
|
+
* review AgentRun can settle any mailbox or Round state. This is deliberately
|
|
15
|
+
* read-only: callers use it as the compare-and-swap fence immediately before
|
|
16
|
+
* their aggregate mutation.
|
|
17
|
+
*/
|
|
18
|
+
export function validateExactRunReviewRound(store, run, options = {}) {
|
|
19
|
+
if (run.purpose !== "review")
|
|
20
|
+
return { disposition: "applied", round: null };
|
|
21
|
+
if (run.reviewRoundId === undefined) {
|
|
22
|
+
return { disposition: "obsolete", round: null, reason: "review-round-missing" };
|
|
23
|
+
}
|
|
24
|
+
const round = store.getReviewRound(run.taskId, run.reviewRoundId);
|
|
25
|
+
if (round === null) {
|
|
26
|
+
return { disposition: "obsolete", round: null, reason: "review-round-missing" };
|
|
27
|
+
}
|
|
28
|
+
if (!options.allowTerminal && run.executionLaneId === undefined
|
|
29
|
+
&& round.status !== "pending" && round.status !== "running") {
|
|
30
|
+
return { disposition: "obsolete", round, reason: "review-round-terminal" };
|
|
31
|
+
}
|
|
32
|
+
const lane = round.executionGroup?.lanes.find(({ id }) => id === run.executionLaneId);
|
|
33
|
+
const exactReviewerRun = round.reviewerRunId === run.id
|
|
34
|
+
|| lane?.currentRunId === run.id;
|
|
35
|
+
const exactReviewerRole = round.reviewerRoleName === run.roleName || lane?.roleName === run.roleName;
|
|
36
|
+
if (!exactReviewerRun
|
|
37
|
+
|| !exactReviewerRole
|
|
38
|
+
|| round.workItemId !== run.workItemId
|
|
39
|
+
|| round.reviewBaseCommit !== run.effective.reviewBaseCommit) {
|
|
40
|
+
return { disposition: "obsolete", round, reason: "review-round-mismatch" };
|
|
41
|
+
}
|
|
42
|
+
const laneWorkspaceRoot = lane?.workspace?.root;
|
|
43
|
+
if (run.workspace === undefined || round.workspace === undefined
|
|
44
|
+
|| (laneWorkspaceRoot === undefined && !isDeepStrictEqual(run.workspace, round.workspace))
|
|
45
|
+
|| (laneWorkspaceRoot !== undefined && run.workspace.root !== laneWorkspaceRoot)) {
|
|
46
|
+
return { disposition: "obsolete", round, reason: "review-workspace-mismatch" };
|
|
47
|
+
}
|
|
48
|
+
const storedWorkspace = run.workspace.owner.type === "execution-lane"
|
|
49
|
+
? store.getManagedWorkspace(run.workspace.owner)
|
|
50
|
+
: store.getReviewRoundWorkspace(run.taskId, round.id);
|
|
51
|
+
if (storedWorkspace === null
|
|
52
|
+
|| (run.workspace.owner.type !== "execution-lane"
|
|
53
|
+
&& !isDeepStrictEqual(storedWorkspace, round.workspace))
|
|
54
|
+
|| !isDeepStrictEqual(storedWorkspace, run.workspace)) {
|
|
55
|
+
return { disposition: "obsolete", round, reason: "review-workspace-drift" };
|
|
56
|
+
}
|
|
57
|
+
if (run.workspace.owner.type !== "execution-lane") {
|
|
58
|
+
if (storedWorkspace.owner.type !== "review-round"
|
|
59
|
+
|| storedWorkspace.owner.taskId !== run.taskId
|
|
60
|
+
|| storedWorkspace.owner.reviewRoundId !== round.id) {
|
|
61
|
+
return { disposition: "obsolete", round, reason: "review-workspace-owner-mismatch" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (run.workspace.owner.type === "execution-lane"
|
|
65
|
+
&& (run.workspace.owner.purpose !== "review"
|
|
66
|
+
|| run.workspace.owner.executionGroupId !== run.executionGroupId
|
|
67
|
+
|| run.workspace.owner.executionLaneId !== run.executionLaneId
|
|
68
|
+
|| run.workspace.owner.reviewRoundId !== round.id)) {
|
|
69
|
+
return { disposition: "obsolete", round, reason: "review-lane-workspace-owner-mismatch" };
|
|
70
|
+
}
|
|
71
|
+
if (run.workspace.owner.type === "execution-lane") {
|
|
72
|
+
if (lane === undefined
|
|
73
|
+
|| lane.currentRunId !== run.id
|
|
74
|
+
|| lane.roleName !== run.roleName
|
|
75
|
+
|| lane.workspace?.root !== run.workspace.root
|
|
76
|
+
|| lane.workspace.writableProjectIds.length !== run.workspace.entries.length
|
|
77
|
+
|| run.workspace.entries.some((entry) => (entry.access !== "write"
|
|
78
|
+
|| !lane.workspace.writableProjectIds.includes(entry.projectId)))) {
|
|
79
|
+
return { disposition: "obsolete", round, reason: "review-lane-workspace-lineage-mismatch" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const task = store.getTask(run.taskId);
|
|
83
|
+
const taskScope = (round.scope ?? "work-item") === "task";
|
|
84
|
+
const item = taskScope || round.workItemId === undefined
|
|
85
|
+
? null
|
|
86
|
+
: store.getWorkItem(run.taskId, round.workItemId);
|
|
87
|
+
if (!taskScope && item === null) {
|
|
88
|
+
return { disposition: "obsolete", round, reason: "review-work-item-missing" };
|
|
89
|
+
}
|
|
90
|
+
const candidate = taskScope
|
|
91
|
+
? undefined
|
|
92
|
+
: item.candidates.find(({ id }) => id === round.candidateId);
|
|
93
|
+
if (!taskScope && candidate === undefined) {
|
|
94
|
+
return { disposition: "obsolete", round, reason: "review-candidate-missing" };
|
|
95
|
+
}
|
|
96
|
+
const frozenProjects = taskScope
|
|
97
|
+
? round.taskCandidate?.projects
|
|
98
|
+
: candidate?.gitSnapshot?.projects;
|
|
99
|
+
if (taskScope) {
|
|
100
|
+
if (task === null || round.taskCandidate === undefined) {
|
|
101
|
+
return { disposition: "obsolete", round, reason: "review-task-candidate-missing" };
|
|
102
|
+
}
|
|
103
|
+
if (frozenProjects === undefined
|
|
104
|
+
|| new Set(frozenProjects.map(({ projectId }) => projectId)).size
|
|
105
|
+
!== task.projectBindings.length
|
|
106
|
+
|| task.projectBindings.some(({ projectId }) => (!frozenProjects.some((project) => project.projectId === projectId)))) {
|
|
107
|
+
return { disposition: "obsolete", round, reason: "review-frozen-project-scope-drift" };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (candidate?.gitSnapshot !== undefined
|
|
111
|
+
&& candidate.gitSnapshot.reviewBaseCommit !== round.reviewBaseCommit) {
|
|
112
|
+
return { disposition: "obsolete", round, reason: "review-candidate-snapshot-drift" };
|
|
113
|
+
}
|
|
114
|
+
if (!taskScope && frozenProjects === undefined) {
|
|
115
|
+
return { disposition: "applied", round };
|
|
116
|
+
}
|
|
117
|
+
if (frozenProjects === undefined
|
|
118
|
+
|| storedWorkspace.entries.length !== frozenProjects.length) {
|
|
119
|
+
return { disposition: "obsolete", round, reason: "review-frozen-project-scope-drift" };
|
|
120
|
+
}
|
|
121
|
+
const frozenByProject = new Map(frozenProjects.map(({ projectId, commit }) => [projectId, commit]));
|
|
122
|
+
if (storedWorkspace.entries.some((entry) => (entry.access !== "write"
|
|
123
|
+
|| frozenByProject.get(entry.projectId) !== entry.baseCommit
|
|
124
|
+
|| entry.baseRef !== entry.baseCommit))) {
|
|
125
|
+
return { disposition: "obsolete", round, reason: "review-frozen-head-drift" };
|
|
126
|
+
}
|
|
127
|
+
return { disposition: "applied", round };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Atomically terminalizes the exact ReviewRound bound to a review AgentRun.
|
|
131
|
+
* The round must still be pending/running and its reviewer identity must match
|
|
132
|
+
* the AgentRun exactly. This is the sole review-round convergence primitive shared
|
|
133
|
+
* by the exact AgentRun terminalization path and the pre-delivery launch-failure
|
|
134
|
+
* path: a failed review AgentRun must never leave its Round stranded.
|
|
135
|
+
*/
|
|
136
|
+
export function terminalizeExactRunReviewRound(store, input, now) {
|
|
137
|
+
const validation = validateExactRunReviewRound(store, input.run);
|
|
138
|
+
if (validation.disposition !== "applied" || validation.round === null) {
|
|
139
|
+
return validation;
|
|
140
|
+
}
|
|
141
|
+
const reviewRound = validation.round;
|
|
142
|
+
if (input.run.executionGroupId !== undefined
|
|
143
|
+
&& input.run.executionLaneId !== undefined) {
|
|
144
|
+
// Producer AgentRuns own only their immutable AgentRun result. The unified Group
|
|
145
|
+
// is advanced after that result is validated and stored below.
|
|
146
|
+
return { disposition: "applied", round: reviewRound };
|
|
147
|
+
}
|
|
148
|
+
const terminal = finishReviewRound(reviewRound, input.outcome.status, now, input.outcome.status === "failed"
|
|
149
|
+
? { kind: "execution", message: input.outcome.diagnostic }
|
|
150
|
+
: undefined);
|
|
151
|
+
store.saveReviewRound(input.taskId, terminal);
|
|
152
|
+
return { disposition: "applied", round: terminal };
|
|
153
|
+
}
|
|
154
|
+
/** Explicit runtime replacement has already proved this Role's resources
|
|
155
|
+
* stopped. A stale active-pointer/Review projection cannot veto cancellation
|
|
156
|
+
* of its engineering attempts. Never accept work or rewrite an old result. */
|
|
157
|
+
export function cancelQuiescentRoleRuns(store, taskId, roleName, reason, now) {
|
|
158
|
+
for (const run of store.listRuns(taskId).filter(run => run.roleName === roleName && run.status === "active")) {
|
|
159
|
+
const outcome = { status: "failed", failureReason: "cancelled", diagnostic: reason };
|
|
160
|
+
const settled = terminalizeExactTaskRun(store, {
|
|
161
|
+
taskId, roleName, agentId: run.effective.agentId, runId: run.id, outcome
|
|
162
|
+
}, now);
|
|
163
|
+
if (settled.disposition === "applied")
|
|
164
|
+
continue;
|
|
165
|
+
store.saveRun(failRun(run, "cancelled", reason, now));
|
|
166
|
+
settleRoleRunDispatch(store, { taskId, roleName, runId: run.id });
|
|
167
|
+
if (store.getActiveRun(taskId, roleName)?.id === run.id)
|
|
168
|
+
store.clearActiveRun(taskId, roleName);
|
|
169
|
+
if (run.executionGroupId !== undefined && run.executionLaneId !== undefined
|
|
170
|
+
&& store.getActiveExecutionLaneRun(taskId, run.executionGroupId, run.executionLaneId)?.id === run.id) {
|
|
171
|
+
store.clearActiveExecutionLaneRun(taskId, run.executionGroupId, run.executionLaneId);
|
|
172
|
+
}
|
|
173
|
+
if (run.reviewRoundId !== undefined && run.executionGroupId === undefined) {
|
|
174
|
+
const round = store.getReviewRound(taskId, run.reviewRoundId);
|
|
175
|
+
if (round?.reviewerRunId === run.id && (round.status === "pending" || round.status === "running")) {
|
|
176
|
+
store.saveReviewRound(taskId, finishReviewRound(round, "failed", now, { kind: "execution", message: reason }));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (roleName !== "leader") {
|
|
180
|
+
const message = createTaskMessage(store.nextMessageId(taskId), taskId, `Execution ${run.id} cancelled after runtime stop.`, "role-result", { type: "role", roleName }, now, { resultRef: { type: "agent-run-result", runId: run.id } });
|
|
181
|
+
store.saveMessage(taskId, message);
|
|
182
|
+
enqueueWork(store, { kind: "role", taskId, roleName: "leader" }, "role-result", now, [{ type: "message", taskId, id: message.id }]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Retire one stranded active AgentRun only after its exact Provider Turn is
|
|
188
|
+
* terminal and every durable execution fence is quiet. The caller owns the
|
|
189
|
+
* surrounding aggregate transaction so the AgentRun, ReviewRound/Lane, mailbox,
|
|
190
|
+
* Session, and append-only retirement record commit together.
|
|
191
|
+
*/
|
|
192
|
+
export function retireExactActiveRun(store, input, now) {
|
|
193
|
+
const current = store.getRun(input.taskId, input.runId);
|
|
194
|
+
const stateChanged = (reason) => ({
|
|
195
|
+
disposition: "state-changed",
|
|
196
|
+
run: current,
|
|
197
|
+
...(current === null ? {} : { progressAt: latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId)?.progressAt }),
|
|
198
|
+
reason
|
|
199
|
+
});
|
|
200
|
+
const task = store.getTask(input.taskId);
|
|
201
|
+
if (task === null)
|
|
202
|
+
return stateChanged("task-missing");
|
|
203
|
+
if (current === null)
|
|
204
|
+
return stateChanged("turn-missing");
|
|
205
|
+
if (current.status !== "active")
|
|
206
|
+
return stateChanged("turn-terminal");
|
|
207
|
+
// The Turn's purpose decides which Task lifecycle admits it: a planning Turn
|
|
208
|
+
// is admitted on a Draft, so its retirement must land there too. Otherwise a
|
|
209
|
+
// Draft planning Turn could never be retired and would leak an active Turn.
|
|
210
|
+
if (!runPurposeAdmitsTaskState(current.purpose, task)) {
|
|
211
|
+
return stateChanged("task-terminal");
|
|
212
|
+
}
|
|
213
|
+
if (current.taskId !== input.taskId || current.roleName !== input.roleName) {
|
|
214
|
+
return stateChanged("turn-owner-mismatch");
|
|
215
|
+
}
|
|
216
|
+
if (current.effective.agentId !== input.agentId
|
|
217
|
+
|| current.effective.adapterId !== input.adapterId) {
|
|
218
|
+
return stateChanged("turn-launch-identity-mismatch");
|
|
219
|
+
}
|
|
220
|
+
const active = current.executionGroupId !== undefined && current.executionLaneId !== undefined
|
|
221
|
+
? store.getActiveExecutionLaneRun(input.taskId, current.executionGroupId, current.executionLaneId)
|
|
222
|
+
: store.getActiveRun(input.taskId, input.roleName);
|
|
223
|
+
if (active?.id !== current.id)
|
|
224
|
+
return stateChanged("active-turn-mismatch");
|
|
225
|
+
const progress = latestRunDurableProgressAt(store, input.taskId, input.roleName, input.runId);
|
|
226
|
+
if (progress === null)
|
|
227
|
+
return stateChanged("progress-unavailable");
|
|
228
|
+
if (progress.progressAt !== input.expectedProgressAt) {
|
|
229
|
+
return { ...stateChanged("progress-fence-mismatch"), progressAt: progress.progressAt };
|
|
230
|
+
}
|
|
231
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
232
|
+
const terminalInput = {
|
|
233
|
+
taskId: input.taskId,
|
|
234
|
+
roleName: input.roleName,
|
|
235
|
+
agentId: input.agentId,
|
|
236
|
+
runId: input.runId,
|
|
237
|
+
...(input.nativeSessionId === undefined ? {} : { nativeSessionId: input.nativeSessionId }),
|
|
238
|
+
outcome: { status: "failed", diagnostic: input.reason, failureReason: "missing-result" }
|
|
239
|
+
};
|
|
240
|
+
const session = sessions?.sessions[input.agentId];
|
|
241
|
+
const providerBinding = sessions?.providerBinding;
|
|
242
|
+
const providerTurn = providerBinding?.run;
|
|
243
|
+
const providerSettled = managedProviderTurnId(providerTurn) === current.id
|
|
244
|
+
&& (providerTurn?.status === "completed"
|
|
245
|
+
|| providerTurn?.status === "failed"
|
|
246
|
+
|| providerTurn?.status === "cancelled"
|
|
247
|
+
|| providerTurn?.status === "rejected"
|
|
248
|
+
|| providerTurn?.status === "deferred");
|
|
249
|
+
if (session?.status === "active" && !providerSettled) {
|
|
250
|
+
return {
|
|
251
|
+
disposition: "blocked",
|
|
252
|
+
run: current,
|
|
253
|
+
progressAt: progress.progressAt,
|
|
254
|
+
reason: "runtime-not-terminal"
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const terminal = terminalizeExactTaskRun(store, terminalInput, now);
|
|
258
|
+
if (terminal.disposition !== "applied" || terminal.run === null) {
|
|
259
|
+
return stateChanged(terminal.reason ?? "terminalization-fence-mismatch");
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
disposition: "applied",
|
|
263
|
+
run: terminal.run,
|
|
264
|
+
progressAt: progress.progressAt
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Applies one exact application-level terminal fact inside the caller's
|
|
269
|
+
* TaskStore transaction. All caller-owned outcome records can therefore
|
|
270
|
+
* be saved in the same aggregate commit.
|
|
271
|
+
*/
|
|
272
|
+
export function terminalizeExactTaskRun(store, input, now) {
|
|
273
|
+
const run = store.getRun(input.taskId, input.runId);
|
|
274
|
+
if (run === null)
|
|
275
|
+
return obsolete(null, "turn-missing");
|
|
276
|
+
if (run.status !== "active")
|
|
277
|
+
return obsolete(run, "turn-terminal");
|
|
278
|
+
if (run.taskId !== input.taskId || run.roleName !== input.roleName) {
|
|
279
|
+
return obsolete(run, "turn-owner-mismatch");
|
|
280
|
+
}
|
|
281
|
+
if (run.effective.agentId !== input.agentId) {
|
|
282
|
+
return obsolete(run, "turn-agent-mismatch");
|
|
283
|
+
}
|
|
284
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
285
|
+
if (role === null)
|
|
286
|
+
return obsolete(run, "role-missing");
|
|
287
|
+
const active = run.executionGroupId !== undefined && run.executionLaneId !== undefined
|
|
288
|
+
? store.getActiveExecutionLaneRun(input.taskId, run.executionGroupId, run.executionLaneId)
|
|
289
|
+
: store.getActiveRun(input.taskId, input.roleName);
|
|
290
|
+
if (active?.id !== run.id)
|
|
291
|
+
return obsolete(run, "active-turn-mismatch");
|
|
292
|
+
// Validate the exact ReviewRound, Candidate, stored workspace, and frozen
|
|
293
|
+
// Project heads before any mailbox or Round write.
|
|
294
|
+
const reviewValidation = validateExactRunReviewRound(store, run);
|
|
295
|
+
if (reviewValidation.disposition !== "applied") {
|
|
296
|
+
return obsolete(run, reviewValidation.reason ?? "review-round-mismatch");
|
|
297
|
+
}
|
|
298
|
+
const requiredSnapshotProjects = [...new Set(run.effective.writeProjectIds)].sort();
|
|
299
|
+
const observedSnapshotProjects = input.systemEvidence?.workspaceSnapshot?.projects
|
|
300
|
+
.map(({ projectId }) => projectId)
|
|
301
|
+
.sort();
|
|
302
|
+
const laneSnapshotRequired = input.outcome.status === "completed"
|
|
303
|
+
&& run.executionGroupId !== undefined
|
|
304
|
+
&& run.executionLaneId !== undefined
|
|
305
|
+
&& requiredSnapshotProjects.length > 0;
|
|
306
|
+
const missingLaneSnapshot = laneSnapshotRequired
|
|
307
|
+
&& (observedSnapshotProjects === undefined
|
|
308
|
+
|| !isDeepStrictEqual(observedSnapshotProjects, requiredSnapshotProjects));
|
|
309
|
+
const workspaceFailure = laneSnapshotRequired
|
|
310
|
+
? input.workspaceFailure ?? (missingLaneSnapshot
|
|
311
|
+
? {
|
|
312
|
+
failureReason: "workspace-unavailable",
|
|
313
|
+
diagnostic: "Core could not freeze the exact clean writable Lane workspace."
|
|
314
|
+
}
|
|
315
|
+
: undefined)
|
|
316
|
+
: undefined;
|
|
317
|
+
const effectiveOutcome = input.outcome.status === "completed" && workspaceFailure !== undefined
|
|
318
|
+
? {
|
|
319
|
+
status: "failed",
|
|
320
|
+
output: input.outcome.output,
|
|
321
|
+
diagnostic: workspaceFailure.diagnostic,
|
|
322
|
+
failureReason: workspaceFailure.failureReason,
|
|
323
|
+
...(input.outcome.provider === undefined ? {} : { provider: input.outcome.provider })
|
|
324
|
+
}
|
|
325
|
+
: input.outcome;
|
|
326
|
+
const terminalOutcome = effectiveOutcome.status === "failed"
|
|
327
|
+
? {
|
|
328
|
+
...effectiveOutcome,
|
|
329
|
+
diagnostic: boundedRunFailureDiagnostic(effectiveOutcome.diagnostic)
|
|
330
|
+
}
|
|
331
|
+
: effectiveOutcome;
|
|
332
|
+
// All AgentRun, active-pointer, Session, launch, and mailbox fences have passed.
|
|
333
|
+
// Only now may the exact ReviewRound be terminalized alongside the AgentRun so a
|
|
334
|
+
// stale fence never leaves a Round written while the AgentRun stays active.
|
|
335
|
+
const reviewRoundTerminalization = terminalizeExactRunReviewRound(store, {
|
|
336
|
+
taskId: input.taskId,
|
|
337
|
+
run,
|
|
338
|
+
outcome: terminalOutcome
|
|
339
|
+
}, now);
|
|
340
|
+
if (reviewRoundTerminalization.disposition !== "applied") {
|
|
341
|
+
return obsolete(run, reviewRoundTerminalization.reason ?? "review-round-mismatch");
|
|
342
|
+
}
|
|
343
|
+
const terminal = terminalOutcome.status === "completed"
|
|
344
|
+
? completeRun(run, terminalOutcome.output, now, terminalOutcome.provider, input.systemEvidence)
|
|
345
|
+
: failRun(run, terminalOutcome.failureReason, terminalOutcome.diagnostic, now, terminalOutcome.provider, terminalOutcome.output);
|
|
346
|
+
if (run.executionGroupId !== undefined
|
|
347
|
+
&& run.executionLaneId !== undefined
|
|
348
|
+
&& run.purpose === "execution"
|
|
349
|
+
&& run.workItemId !== undefined) {
|
|
350
|
+
const item = store.getWorkItem(input.taskId, run.workItemId);
|
|
351
|
+
const group = item === null
|
|
352
|
+
? undefined
|
|
353
|
+
: workItemExecutionGroupById(item, run.executionGroupId);
|
|
354
|
+
if (item !== null && group !== undefined) {
|
|
355
|
+
if (terminal.status === "completed") {
|
|
356
|
+
const grouped = updateWorkItemExecutionLane(group, run.executionLaneId, {
|
|
357
|
+
currentRunId: run.id,
|
|
358
|
+
successfulRunId: run.id,
|
|
359
|
+
disposition: "succeeded"
|
|
360
|
+
}, now);
|
|
361
|
+
store.saveWorkItem(input.taskId, updateWorkItemExecutionGroup(item, grouped, now));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (run.executionGroupId !== undefined
|
|
366
|
+
&& run.executionLaneId !== undefined
|
|
367
|
+
&& run.purpose === "review"
|
|
368
|
+
&& run.reviewRoundId !== undefined) {
|
|
369
|
+
const round = store.getReviewRound(input.taskId, run.reviewRoundId);
|
|
370
|
+
const group = round?.executionGroup;
|
|
371
|
+
if (round !== null
|
|
372
|
+
&& round !== undefined
|
|
373
|
+
&& group !== undefined
|
|
374
|
+
&& group.id === run.executionGroupId) {
|
|
375
|
+
if (terminal.status === "completed") {
|
|
376
|
+
const grouped = updateExecutionLane(group, run.executionLaneId, {
|
|
377
|
+
currentRunId: run.id,
|
|
378
|
+
successfulRunId: run.id,
|
|
379
|
+
disposition: "succeeded"
|
|
380
|
+
}, now);
|
|
381
|
+
store.saveReviewRound(input.taskId, updateReviewExecutionGroup(round, grouped));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
store.saveRun(terminal);
|
|
386
|
+
const pendingMessages = store.listMessages(terminal.taskId).filter((message) => message.recipient?.roleName === terminal.roleName && message.recipient.ownerRunId !== undefined
|
|
387
|
+
&& message.continuation?.runId === undefined);
|
|
388
|
+
if (pendingMessages.length > 0) {
|
|
389
|
+
enqueueWork(store, { kind: "task", taskId: terminal.taskId }, "message-continuation", now, pendingMessages.slice(0, 16).map((message) => ({ type: "message", taskId: terminal.taskId, id: message.id })), { dedupeKey: `message-after-result:${terminal.taskId}/${terminal.id}` });
|
|
390
|
+
}
|
|
391
|
+
if (terminal.roleName !== "leader") {
|
|
392
|
+
// Keep the report in its execution record; atomically publish only a
|
|
393
|
+
// collaboration reference. Duplicate terminals have already returned.
|
|
394
|
+
const message = createTaskMessage(store.nextMessageId(terminal.taskId), terminal.taskId, `Execution ${terminal.id} ${terminal.status}.`, "role-result", { type: "role", roleName: terminal.roleName }, now, { resultRef: { type: "agent-run-result", runId: terminal.id }, ...(terminal.workItemId === undefined
|
|
395
|
+
? {} : { workItemId: terminal.workItemId }) });
|
|
396
|
+
store.saveMessage(terminal.taskId, message);
|
|
397
|
+
enqueueWork(store, { kind: "role", taskId: terminal.taskId, roleName: "leader" }, "role-result", now, [{ type: "message", taskId: terminal.taskId, id: message.id }], { source: "task-event", dedupeKey: `result:${terminal.taskId}/${terminal.id}` });
|
|
398
|
+
}
|
|
399
|
+
const dispatchIdentity = {
|
|
400
|
+
taskId: terminal.taskId,
|
|
401
|
+
roleName: terminal.roleName,
|
|
402
|
+
runId: terminal.id
|
|
403
|
+
};
|
|
404
|
+
const dispatchToken = captureRoleRunDispatch(store.getWorkMailbox({
|
|
405
|
+
kind: "role",
|
|
406
|
+
taskId: terminal.taskId,
|
|
407
|
+
roleName: terminal.roleName
|
|
408
|
+
}), dispatchIdentity);
|
|
409
|
+
settleRoleRunDispatch(store, dispatchIdentity, dispatchToken);
|
|
410
|
+
if (terminal.roleName === "leader") {
|
|
411
|
+
const wake = store.listTaskWakes(input.taskId)
|
|
412
|
+
.find((candidate) => candidate.runId === terminal.id && candidate.status === "dispatched");
|
|
413
|
+
if (wake !== undefined) {
|
|
414
|
+
store.saveTaskWake(input.taskId, markTaskWakeConsumed(wake, now));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (terminal.executionGroupId !== undefined && terminal.executionLaneId !== undefined) {
|
|
418
|
+
store.clearActiveExecutionLaneRun(input.taskId, terminal.executionGroupId, terminal.executionLaneId);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
store.clearActiveRun(input.taskId, input.roleName);
|
|
422
|
+
}
|
|
423
|
+
releaseDeferredTaskActivation(store, terminal, now);
|
|
424
|
+
return { disposition: "applied", run: terminal };
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* A planning Turn that deferred an activation has just ended, so signal the
|
|
428
|
+
* Task mailbox to look at the request now.
|
|
429
|
+
*
|
|
430
|
+
* The signal carries no decision. Whether the request still applies is re-read
|
|
431
|
+
* at the adoption boundary from facts that are current then, so a request
|
|
432
|
+
* cancelled or a Task retired while this Turn was still running is never
|
|
433
|
+
* replayed. Enqueuing inside the terminalization transaction keeps the release
|
|
434
|
+
* atomic with the Turn becoming non-active: there is no window in which the
|
|
435
|
+
* deferral is unblocked but nothing will look at it.
|
|
436
|
+
*/
|
|
437
|
+
function releaseDeferredTaskActivation(store, terminal, now) {
|
|
438
|
+
if (terminal.purpose !== "planning")
|
|
439
|
+
return;
|
|
440
|
+
const request = store.getTask(terminal.taskId)?.activationRequest;
|
|
441
|
+
if (request?.disposition !== "pending")
|
|
442
|
+
return;
|
|
443
|
+
if (request.afterPlanningRun !== terminal.id)
|
|
444
|
+
return;
|
|
445
|
+
enqueueWork(store, { kind: "task", taskId: terminal.taskId }, "activation-deferral-released", now, [{ type: "task", id: terminal.taskId }]);
|
|
446
|
+
}
|
|
447
|
+
function obsolete(run, reason) {
|
|
448
|
+
return { disposition: "obsolete", run, reason };
|
|
449
|
+
}
|