@zq-silk/yui 0.15.7 → 0.15.9
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 +194 -399
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +306 -1131
- 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/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +165 -74
- 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 +245 -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 +34 -9
- 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 +281 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1461 -720
- 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} +158 -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 +481 -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 +120 -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 +255 -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 +118 -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 +388 -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 +75 -0
- package/dist/resources/projectResourceService.js +343 -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 +3 -7
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/sqliteSchema.js +467 -7
- package/dist/storage/sqliteStore.js +355 -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 +327 -0
- package/dist/task/taskActivationService.js +408 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/task/taskSubmission.js +236 -0
- 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 +121 -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 +442 -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 +133 -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 +84 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +96 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +93 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +50 -0
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -0
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +224 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +83 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +393 -0
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +165 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +305 -0
- package/docs/release-workflow.zh-CN.md +237 -0
- package/docs/roles-and-configuration.md +115 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +78 -0
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -0
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +105 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +8 -6
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +88 -0
- package/docs/testing/verification-levels.zh-CN.md +69 -0
- package/i18n/README.zh-CN.md +270 -722
- package/package.json +3 -2
- package/skills/yui-leader/SKILL.md +88 -304
- package/skills/yui-leader/references/execution.md +303 -0
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +37 -0
- package/skills/yui-operator/SKILL.md +46 -62
- 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
|
@@ -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
|
+
}
|
package/dist/message/message.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
2
|
export const TASK_MESSAGE_KINDS = ["user", "operator", "role-result", "system"];
|
|
3
|
+
/**
|
|
4
|
+
* How a user/operator submission asks its Task to react (task-32 Requirement A).
|
|
5
|
+
*
|
|
6
|
+
* - `record`: save only. No Leader wake, no planning, no activation.
|
|
7
|
+
* - `discuss`: save and route to planning (the default when a submission omits
|
|
8
|
+
* an intent, so an old client keeps its existing Leader-waking behaviour).
|
|
9
|
+
* - `develop`: save the requirement and its activation intent; an unplanned
|
|
10
|
+
* Draft records an activation request and queues only activation processing.
|
|
11
|
+
*
|
|
12
|
+
* The intent is never inferred from body text and is only meaningful on a
|
|
13
|
+
* user/operator submission: a role-result or system Message can never carry one,
|
|
14
|
+
* so an internal Agent report cannot acquire develop authority.
|
|
15
|
+
*/
|
|
16
|
+
export const TASK_SUBMISSION_INTENTS = ["record", "discuss", "develop"];
|
|
3
17
|
export function createTaskMessage(id, taskId, body, kind, author, now, context = {}) {
|
|
4
18
|
validateKindAndAuthor(kind, author);
|
|
5
19
|
const message = {
|
|
@@ -12,12 +26,21 @@ export function createTaskMessage(id, taskId, body, kind, author, now, context =
|
|
|
12
26
|
...(context.wakePolicy === undefined
|
|
13
27
|
? {}
|
|
14
28
|
: { wakePolicy: context.wakePolicy }),
|
|
15
|
-
...(context.
|
|
29
|
+
...(context.intent === undefined
|
|
16
30
|
? {}
|
|
17
|
-
: {
|
|
31
|
+
: { intent: context.intent }),
|
|
32
|
+
...(context.submissionKey === undefined
|
|
33
|
+
? {}
|
|
34
|
+
: { submissionKey: requireText(context.submissionKey, "Message submission key") }),
|
|
35
|
+
...(context.runId === undefined
|
|
36
|
+
? {}
|
|
37
|
+
: { runId: requireSafeIdentity(context.runId, "Message AgentRun id") }),
|
|
38
|
+
...(context.resultRef === undefined ? {} : { resultRef: { ...context.resultRef } }),
|
|
18
39
|
...(context.workItemId === undefined
|
|
19
40
|
? {}
|
|
20
41
|
: { workItemId: requireSafeIdentity(context.workItemId, "Message Work item id") }),
|
|
42
|
+
...(context.recipient === undefined ? {} : { recipient: { ...context.recipient },
|
|
43
|
+
...(context.recipient.ownerRunId === undefined ? {} : { continuation: {} }) }),
|
|
21
44
|
createdAt: now.toISOString()
|
|
22
45
|
};
|
|
23
46
|
validateTaskMessage(message);
|
|
@@ -26,6 +49,18 @@ export function createTaskMessage(id, taskId, body, kind, author, now, context =
|
|
|
26
49
|
export function taskMessageAuthorLabel(author) {
|
|
27
50
|
return author.type === "role" ? author.roleName : author.type;
|
|
28
51
|
}
|
|
52
|
+
/** A role-result reference never duplicates the execution's report body. */
|
|
53
|
+
export function expandTaskMessageResult(message, getRun) {
|
|
54
|
+
if (message.resultRef === undefined)
|
|
55
|
+
return message;
|
|
56
|
+
const run = getRun(message.taskId, message.resultRef.runId);
|
|
57
|
+
if (run === null || run.taskId !== message.taskId
|
|
58
|
+
|| message.author.type !== "role" || run.roleName !== message.author.roleName
|
|
59
|
+
|| run.status === "active" || run.result === undefined) {
|
|
60
|
+
throw new Error(`Result Message has no matching terminal execution: ${message.id}.`);
|
|
61
|
+
}
|
|
62
|
+
return { ...message, result: run.result };
|
|
63
|
+
}
|
|
29
64
|
/** Replace only the mutable content of a Draft user/operator Message. */
|
|
30
65
|
export function updateDraftTaskMessage(message, update) {
|
|
31
66
|
validateTaskMessage(message);
|
|
@@ -42,6 +77,24 @@ export function updateDraftTaskMessage(message, update) {
|
|
|
42
77
|
validateTaskMessage(updated);
|
|
43
78
|
return updated;
|
|
44
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Attach the §2.3 receipt a keyed submission earned, in the same transaction that
|
|
82
|
+
* saved and routed the Message. The receipt is a write-once fact: it is only set
|
|
83
|
+
* on a keyed Message that has none yet, so a replay (which never re-routes) can
|
|
84
|
+
* never overwrite the disposition the original submission recorded.
|
|
85
|
+
*/
|
|
86
|
+
export function withSubmissionReceipt(message, receipt) {
|
|
87
|
+
validateTaskMessage(message);
|
|
88
|
+
if (message.submissionKey === undefined) {
|
|
89
|
+
throw new Error(`A submission receipt requires a submission key: ${message.id}.`);
|
|
90
|
+
}
|
|
91
|
+
if (message.submissionReceipt !== undefined) {
|
|
92
|
+
throw new Error(`Submission receipt is already recorded: ${message.id}.`);
|
|
93
|
+
}
|
|
94
|
+
const updated = { ...message, submissionReceipt: receipt };
|
|
95
|
+
validateTaskMessage(updated);
|
|
96
|
+
return updated;
|
|
97
|
+
}
|
|
45
98
|
export function validateTaskMessage(message) {
|
|
46
99
|
if (message.schemaVersion !== 3)
|
|
47
100
|
throw new Error("Task Message must use schemaVersion 3.");
|
|
@@ -59,16 +112,75 @@ export function validateTaskMessage(message) {
|
|
|
59
112
|
&& message.kind !== "operator") {
|
|
60
113
|
throw new Error("Message wakePolicy is only valid for user/operator messages.");
|
|
61
114
|
}
|
|
62
|
-
if (message.
|
|
63
|
-
|
|
115
|
+
if (message.intent !== undefined
|
|
116
|
+
&& !TASK_SUBMISSION_INTENTS.includes(message.intent)) {
|
|
117
|
+
throw new Error(`Message intent is invalid: ${String(message.intent)}.`);
|
|
118
|
+
}
|
|
119
|
+
if (message.intent !== undefined
|
|
120
|
+
&& message.kind !== "user"
|
|
121
|
+
&& message.kind !== "operator") {
|
|
122
|
+
throw new Error("Message intent is only valid for user/operator messages.");
|
|
123
|
+
}
|
|
124
|
+
if (message.submissionKey !== undefined) {
|
|
125
|
+
requireText(message.submissionKey, "Message submission key");
|
|
126
|
+
if (message.kind !== "user" && message.kind !== "operator") {
|
|
127
|
+
throw new Error("Message submission key is only valid for user/operator messages.");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (message.submissionReceipt !== undefined && message.submissionKey === undefined) {
|
|
131
|
+
throw new Error("Message submission receipt requires a submission key.");
|
|
132
|
+
}
|
|
133
|
+
if (message.runId !== undefined)
|
|
134
|
+
requireSafeIdentity(message.runId, "Message AgentRun id");
|
|
135
|
+
if (message.recipient !== undefined) {
|
|
136
|
+
requireSafeIdentity(message.recipient.roleName, "Recipient Role");
|
|
137
|
+
if (message.recipient.ownerRunId !== undefined) {
|
|
138
|
+
validateTaskRecordReference({ taskId: message.taskId, localId: message.recipient.ownerRunId }, "run");
|
|
139
|
+
}
|
|
140
|
+
else if (message.recipient.roleName !== "leader") {
|
|
141
|
+
throw new Error("An execution recipient requires an owner Assignment.");
|
|
142
|
+
}
|
|
143
|
+
if (message.recipient.workItemId !== undefined) {
|
|
144
|
+
validateTaskRecordReference({ taskId: message.taskId, localId: message.recipient.workItemId }, "workItem");
|
|
145
|
+
}
|
|
146
|
+
if (message.recipient.reviewRoundId !== undefined) {
|
|
147
|
+
requireSafeIdentity(message.recipient.reviewRoundId, "Recipient ReviewRound");
|
|
148
|
+
}
|
|
149
|
+
if (message.recipient.workItemId === undefined && message.recipient.reviewRoundId === undefined) {
|
|
150
|
+
throw new Error("A recipient requires an exact WorkItem or ReviewRound.");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (message.continuation !== undefined && message.recipient?.ownerRunId === undefined) {
|
|
154
|
+
throw new Error("Message continuation requires an owner Assignment.");
|
|
155
|
+
}
|
|
156
|
+
if (message.continuation?.runId !== undefined) {
|
|
157
|
+
validateTaskRecordReference({ taskId: message.taskId, localId: message.continuation.runId }, "run");
|
|
158
|
+
}
|
|
159
|
+
if (message.continuation?.notDeliveredReason !== undefined) {
|
|
160
|
+
requireText(message.continuation.notDeliveredReason, "Message nondelivery reason");
|
|
161
|
+
if (message.continuation.runId !== undefined)
|
|
162
|
+
throw new Error("Assigned Message delivery is observed from its AgentRun.");
|
|
163
|
+
}
|
|
164
|
+
for (const handover of message.handovers ?? []) {
|
|
165
|
+
requireSafeIdentity(handover.from.roleName, "Previous Message Role");
|
|
166
|
+
requireSafeIdentity(handover.from.ownerRunId, "Previous Message Assignment");
|
|
167
|
+
if (Number.isNaN(Date.parse(handover.at)))
|
|
168
|
+
throw new Error("Message handover timestamp is invalid.");
|
|
169
|
+
}
|
|
170
|
+
if (message.resultRef !== undefined) {
|
|
171
|
+
if (message.kind !== "role-result" || message.resultRef.type !== "agent-run-result") {
|
|
172
|
+
throw new Error("Execution result references require a role-result Message.");
|
|
173
|
+
}
|
|
174
|
+
validateTaskRecordReference({ taskId: message.taskId, localId: message.resultRef.runId }, "run");
|
|
175
|
+
}
|
|
64
176
|
if (message.workItemId !== undefined) {
|
|
65
177
|
validateTaskRecordReference({
|
|
66
178
|
taskId: message.taskId,
|
|
67
179
|
localId: message.workItemId
|
|
68
180
|
}, "workItem");
|
|
69
181
|
}
|
|
70
|
-
if (message.
|
|
71
|
-
validateTaskRecordReference({ taskId: message.taskId, localId: message.
|
|
182
|
+
if (message.runId !== undefined) {
|
|
183
|
+
validateTaskRecordReference({ taskId: message.taskId, localId: message.runId }, "run");
|
|
72
184
|
}
|
|
73
185
|
if (typeof message.createdAt !== "string" || Number.isNaN(Date.parse(message.createdAt))) {
|
|
74
186
|
throw new Error("Message createdAt is invalid.");
|