@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.
Files changed (308) hide show
  1. package/ARCHITECTURE.md +194 -399
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +306 -1131
  4. package/dist/agent/adapterCatalog.js +15 -2
  5. package/dist/agent/agent.js +23 -3
  6. package/dist/agent/argumentPolicy.js +7 -1
  7. package/dist/agent/connectionPlan.js +62 -0
  8. package/dist/agent/executionComponents.js +158 -0
  9. package/dist/agent/launchEnvironment.js +31 -3
  10. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  11. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  12. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  13. package/dist/artifacts/artifactCapability.js +74 -0
  14. package/dist/artifacts/artifactCommitLock.js +249 -0
  15. package/dist/artifacts/artifactPaths.js +151 -0
  16. package/dist/artifacts/gitArtifactRef.js +146 -0
  17. package/dist/artifacts/managedGit.js +332 -0
  18. package/dist/artifacts/taskArtifactRepository.js +277 -0
  19. package/dist/brief/taskBrief.js +12 -0
  20. package/dist/cli/agentConfigurationPicker.js +13 -0
  21. package/dist/cli/commandCatalog.js +165 -74
  22. package/dist/cli/interactionCandidates.js +5 -5
  23. package/dist/cli/interactionPolicy.js +38 -8
  24. package/dist/cli/invocationRouter.js +1 -1
  25. package/dist/cli/managedDiagnostics.js +28 -0
  26. package/dist/cli/operatorWizard.js +1 -7
  27. package/dist/cli/roleOptionOrder.js +27 -0
  28. package/dist/cli/roleWizard.js +50 -14
  29. package/dist/cli/updateOrchestrator.js +1 -1
  30. package/dist/cli/updatePorts.js +3 -4
  31. package/dist/cli.js +245 -95
  32. package/dist/commands/agentCommands.js +72 -14
  33. package/dist/commands/capabilityCommands.js +9 -6
  34. package/dist/commands/configCommands.js +20 -20
  35. package/dist/commands/deliveryGuardPreflight.js +2 -2
  36. package/dist/commands/executionAuditCommands.js +24 -24
  37. package/dist/commands/globalRoleCommands.js +1 -1
  38. package/dist/commands/grantCommands.js +4 -4
  39. package/dist/commands/operatorCommands.js +34 -9
  40. package/dist/commands/projectCommands.js +4 -4
  41. package/dist/commands/resourcesCommands.js +2 -2
  42. package/dist/commands/roleConfiguration.js +25 -5
  43. package/dist/commands/roleRuntimeGuard.js +4 -5
  44. package/dist/commands/sessionCommands.js +3 -7
  45. package/dist/commands/taskActivationCommands.js +281 -0
  46. package/dist/commands/taskActor.js +28 -49
  47. package/dist/commands/taskCommands.js +1461 -720
  48. package/dist/commands/taskContextCommand.js +39 -583
  49. package/dist/commands/taskExecutionCommands.js +32 -32
  50. package/dist/commands/taskInputCommands.js +40 -104
  51. package/dist/commands/taskIntegrationCommands.js +3 -2
  52. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  53. package/dist/commands/taskNextActionCommand.js +8 -8
  54. package/dist/commands/taskOverviewCommand.js +33 -45
  55. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  56. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  57. package/dist/commands/telemetryCommands.js +36 -38
  58. package/dist/config/configCatalog.js +4 -4
  59. package/dist/config/yuiConfig.js +8 -8
  60. package/dist/context/contextSnapshot.js +10 -10
  61. package/dist/context/dispatchContext.js +11 -11
  62. package/dist/context/roleSessionContext.js +6 -3
  63. package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
  64. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  65. package/dist/context/sessionBootstrapManifest.js +21 -2
  66. package/dist/context/sourceRunContext.js +30 -0
  67. package/dist/context/taskContext.js +481 -0
  68. package/dist/context/wakeNotification.js +27 -27
  69. package/dist/controller/agentRuntimeObserver.js +21 -24
  70. package/dist/controller/capabilityBridge.js +17 -6
  71. package/dist/controller/clientRuntime.js +65 -92
  72. package/dist/controller/controller.js +120 -188
  73. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  74. package/dist/controller/jobControl.js +54 -85
  75. package/dist/controller/resourceInventory.js +8 -27
  76. package/dist/controller/resourceInventoryLinux.js +12 -13
  77. package/dist/controller/runtime.js +529 -479
  78. package/dist/controller/runtimeEventInbox.js +55 -25
  79. package/dist/controller/runtimeEventProcessor.js +22 -31
  80. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  81. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  82. package/dist/controller/runtimeObservationHook.js +14 -18
  83. package/dist/controller/sessionNotify.js +16 -24
  84. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  85. package/dist/controller/structuredProviderObservation.js +138 -99
  86. package/dist/coordination/workMailbox.js +3 -3
  87. package/dist/coordination/workMailboxQueue.js +36 -33
  88. package/dist/core/boundedRpc.js +8 -1
  89. package/dist/core/controllerClient.js +20 -1
  90. package/dist/core/controllerServer.js +4 -4
  91. package/dist/doctor/doctor.js +13 -2
  92. package/dist/domain/agentResultTransport.js +9 -9
  93. package/dist/execution/codexThreadNaming.js +2 -8
  94. package/dist/execution/executionHealth.js +51 -63
  95. package/dist/execution/reviewMainRun.js +137 -0
  96. package/dist/execution/workItemExecution.js +28 -29
  97. package/dist/execution/workItemExecutionProjection.js +99 -107
  98. package/dist/execution/workItemMainRun.js +141 -0
  99. package/dist/executor/agentAdapter.js +227 -20
  100. package/dist/executor/agentConfigurationCatalog.js +126 -4
  101. package/dist/executor/agentConfigurationProbe.js +162 -4
  102. package/dist/executor/agentExecutor.js +79 -78
  103. package/dist/executor/effectiveLaunch.js +105 -18
  104. package/dist/executor/executorRegistry.js +29 -44
  105. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  106. package/dist/executor/workspacePreflightClassification.js +16 -16
  107. package/dist/grant/capabilityGrant.js +6 -3
  108. package/dist/input/inputRequest.js +12 -10
  109. package/dist/integration/gitIntegrationService.js +4 -11
  110. package/dist/integration/integrationQueueService.js +4 -4
  111. package/dist/interaction/operatorPresentation.js +1 -1
  112. package/dist/kernel/builtinCapabilities.js +255 -12
  113. package/dist/kernel/capabilityRegistry.js +64 -18
  114. package/dist/kernel/instanceHost.js +12 -1
  115. package/dist/kernel/kernelPorts.js +2 -2
  116. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  117. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  118. package/dist/message/message.js +118 -6
  119. package/dist/message/messageContinuation.js +204 -0
  120. package/dist/observability/executionAudit.js +70 -72
  121. package/dist/observability/faultClassification.js +2 -2
  122. package/dist/observability/orchestrationMetrics.js +8 -8
  123. package/dist/operator/operatorSessionHistory.js +1 -7
  124. package/dist/output/agentConfigurationPresentation.js +8 -3
  125. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  126. package/dist/output/rolePresentation.js +54 -3
  127. package/dist/plugins/pluginChild.js +104 -0
  128. package/dist/plugins/pluginIntent.js +26 -0
  129. package/dist/plugins/pluginInterpreter.js +43 -0
  130. package/dist/plugins/pluginPackage.js +101 -0
  131. package/dist/plugins/pluginProcess.js +112 -0
  132. package/dist/plugins/pluginService.js +388 -0
  133. package/dist/profile/agentProfile.js +1 -1
  134. package/dist/repository/gitWorkspace.js +26 -4
  135. package/dist/repository/project.js +19 -4
  136. package/dist/repository/taskBaseFreshness.js +13 -13
  137. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  138. package/dist/repository/taskWorkspacePreparer.js +344 -83
  139. package/dist/resources/autoResourceGc.js +3 -3
  140. package/dist/resources/liveReferences.js +3 -3
  141. package/dist/resources/projectResource.js +75 -0
  142. package/dist/resources/projectResourceService.js +343 -0
  143. package/dist/resources/resourceDiscovery.js +6 -6
  144. package/dist/resources/resourceGc.js +1 -1
  145. package/dist/resources/resourceRegistrar.js +1 -1
  146. package/dist/resources/resourceTypes.js +1 -1
  147. package/dist/review/deltaRecheck.js +3 -3
  148. package/dist/review/reviewAcceptance.js +16 -16
  149. package/dist/review/reviewDecision.js +7 -7
  150. package/dist/review/reviewRound.js +21 -20
  151. package/dist/review/reviewerAvailability.js +2 -2
  152. package/dist/role/role.js +51 -7
  153. package/dist/role/taskRoleUpdate.js +30 -0
  154. package/dist/runtime/acpProtocol.js +425 -0
  155. package/dist/runtime/acpSession.js +731 -0
  156. package/dist/runtime/acpSessionConfiguration.js +260 -0
  157. package/dist/runtime/agentDriver.js +30 -11
  158. package/dist/runtime/agentEndpoint.js +278 -0
  159. package/dist/runtime/agentEndpointIdentity.js +86 -0
  160. package/dist/runtime/agentEndpointOwnership.js +239 -0
  161. package/dist/runtime/agentError.js +2 -10
  162. package/dist/runtime/agentHost.js +565 -314
  163. package/dist/runtime/agentRunConfiguration.js +258 -0
  164. package/dist/runtime/builtinAgentDrivers.js +134 -18
  165. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  166. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  167. package/dist/runtime/claude-process-owner +0 -0
  168. package/dist/runtime/codexAppServerRuntime.js +38 -30
  169. package/dist/runtime/codexInteractiveHost.js +41 -6
  170. package/dist/runtime/continuationManager.js +2 -6
  171. package/dist/runtime/executionEnvironment.js +30 -0
  172. package/dist/runtime/firstProgressAdvisory.js +11 -11
  173. package/dist/runtime/index.js +4 -3
  174. package/dist/runtime/jsonLineChannel.js +109 -0
  175. package/dist/runtime/launchBroker.js +91 -16
  176. package/dist/runtime/launchDiagnostics.js +2 -2
  177. package/dist/runtime/lifecycleReservation.js +10 -18
  178. package/dist/runtime/managedCaller.js +61 -17
  179. package/dist/runtime/nativeSessionControl.js +102 -0
  180. package/dist/runtime/ports.js +6 -21
  181. package/dist/runtime/processExitObservation.js +8 -7
  182. package/dist/runtime/promptEnvelope.js +17 -6
  183. package/dist/runtime/providerContinuation.js +3 -9
  184. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  185. package/dist/runtime/providerControl.js +2 -7
  186. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  187. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  188. package/dist/runtime/runtimeBinding.js +0 -1
  189. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  190. package/dist/runtime/runtimeDeadlines.js +9 -0
  191. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  192. package/dist/runtime/runtimeObservation.js +29 -65
  193. package/dist/runtime/runtimeProjection.js +43 -51
  194. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  195. package/dist/runtime/sessionLaunchRequest.js +3 -7
  196. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  197. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  198. package/dist/runtime/sessionReconciliation.js +4 -8
  199. package/dist/runtime/sessionTerminationGuard.js +70 -259
  200. package/dist/runtime/sessionTokenMetrics.js +5 -16
  201. package/dist/runtime/structuredProviderHost.js +237 -117
  202. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  203. package/dist/runtime/tmuxAdapters.js +39 -86
  204. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  205. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  206. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  207. package/dist/scheduler/ports.js +80 -9
  208. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  209. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  210. package/dist/scheduler/taskExecutionProjection.js +120 -124
  211. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  212. package/dist/scheduler/taskWake.js +11 -4
  213. package/dist/scheduler/wakeReason.js +9 -1
  214. package/dist/setup/setupCommand.js +3 -7
  215. package/dist/storage/migrations/agentRunContract.js +159 -0
  216. package/dist/storage/migrations/artifactsToGit.js +338 -0
  217. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  218. package/dist/storage/migrations/submitIntent.js +126 -0
  219. package/dist/storage/sqliteSchema.js +467 -7
  220. package/dist/storage/sqliteStore.js +355 -220
  221. package/dist/storage/storageVersions.js +1 -1
  222. package/dist/storage/storeRpc.js +10 -5
  223. package/dist/storage/taskStore.js +13 -11
  224. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  225. package/dist/surface/surfaceContributions.js +102 -0
  226. package/dist/task/completionReadiness.js +32 -6
  227. package/dist/task/deliveryGuard.js +16 -16
  228. package/dist/task/draftPlan.js +72 -12
  229. package/dist/task/nextAction.js +144 -128
  230. package/dist/task/remoteDelivery.js +6 -6
  231. package/dist/task/task.js +184 -18
  232. package/dist/task/taskActivation.js +327 -0
  233. package/dist/task/taskActivationService.js +408 -0
  234. package/dist/task/taskRecordReference.js +5 -4
  235. package/dist/task/taskRecordRetirement.js +1 -1
  236. package/dist/task/taskSubmission.js +236 -0
  237. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  238. package/dist/telemetry/telemetryConfig.js +14 -14
  239. package/dist/telemetry/telemetryWiring.js +2 -2
  240. package/dist/web/assets/assetManifest.js +2 -0
  241. package/dist/web/assets/client/app.js +121 -20
  242. package/dist/web/assets/client/components.js +87 -54
  243. package/dist/web/assets/client/i18n.js +83 -41
  244. package/dist/web/assets/client/markdown.js +1 -1
  245. package/dist/web/assets/client/taskSurface.js +442 -0
  246. package/dist/web/assets/client/view.js +49 -44
  247. package/dist/web/assets/shell.js +1 -1
  248. package/dist/web/assets/styles/cards.js +22 -4
  249. package/dist/web/controllerWeb.js +60 -0
  250. package/dist/web/webMutation.js +28 -0
  251. package/dist/web/webServer.js +133 -8
  252. package/dist/web/webSnapshot.js +81 -74
  253. package/dist/web/webTaskSurface.js +64 -0
  254. package/dist/workItem/dependencyGate.js +1 -1
  255. package/dist/workItem/workItem.js +84 -48
  256. package/dist/workspace/workItemChangeSetManager.js +16 -9
  257. package/docs/agent-result-consumption.md +96 -0
  258. package/docs/agent-result-consumption.zh-CN.md +81 -0
  259. package/docs/agent-runtime-drivers.md +93 -0
  260. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  261. package/docs/architecture/README.md +50 -0
  262. package/docs/architecture/README.zh-CN.md +43 -0
  263. package/docs/architecture/capabilities-and-resources.md +118 -0
  264. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  265. package/docs/managed-turn-and-session-runtime.md +224 -0
  266. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  267. package/docs/observability/README.md +83 -0
  268. package/docs/observability/README.zh-CN.md +71 -0
  269. package/docs/plugin-sdk.md +393 -0
  270. package/docs/plugin-sdk.zh-CN.md +293 -0
  271. package/docs/provider-runtime.md +165 -0
  272. package/docs/provider-runtime.zh-CN.md +132 -0
  273. package/docs/release-workflow.md +305 -0
  274. package/docs/release-workflow.zh-CN.md +237 -0
  275. package/docs/roles-and-configuration.md +115 -0
  276. package/docs/roles-and-configuration.zh-CN.md +96 -0
  277. package/docs/sqlite-control-plane-design.md +78 -0
  278. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  279. package/docs/task-dag-semantics.md +80 -0
  280. package/docs/task-dag-semantics.zh-CN.md +59 -0
  281. package/docs/task-delivery.md +105 -0
  282. package/docs/task-delivery.zh-CN.md +82 -0
  283. package/docs/task-local-identity.md +8 -6
  284. package/docs/task-local-identity.zh-CN.md +58 -0
  285. package/docs/testing/verification-levels.md +88 -0
  286. package/docs/testing/verification-levels.zh-CN.md +69 -0
  287. package/i18n/README.zh-CN.md +270 -722
  288. package/package.json +3 -2
  289. package/skills/yui-leader/SKILL.md +88 -304
  290. package/skills/yui-leader/references/execution.md +303 -0
  291. package/skills/yui-leader/references/integration.md +39 -0
  292. package/skills/yui-leader/references/planning.md +109 -0
  293. package/skills/yui-leader/references/replicated-execution.md +42 -0
  294. package/skills/yui-leader/references/task-plugins.md +37 -0
  295. package/skills/yui-operator/SKILL.md +46 -62
  296. package/skills/yui-reviewer/SKILL.md +35 -36
  297. package/skills/yui-runtime/SKILL.md +88 -24
  298. package/skills/yui-runtime/references/publication.md +22 -0
  299. package/skills/yui-runtime/references/recovery.md +64 -0
  300. package/skills/yui-worker/SKILL.md +37 -39
  301. package/dist/cli/roleOptionCatalog.js +0 -68
  302. package/dist/context/sourceTurnContext.js +0 -30
  303. package/dist/execution/reviewMainTurn.js +0 -161
  304. package/dist/execution/workItemMainTurn.js +0 -164
  305. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  306. package/dist/runtime/preallocatedNativeSession.js +0 -13
  307. package/dist/runtime/runtimeStopReceipt.js +0 -42
  308. 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
+ }
@@ -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.turnId === undefined
29
+ ...(context.intent === undefined
16
30
  ? {}
17
- : { turnId: requireSafeIdentity(context.turnId, "Message Turn id") }),
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.turnId !== undefined)
63
- requireSafeIdentity(message.turnId, "Message Turn id");
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.turnId !== undefined) {
71
- validateTaskRecordReference({ taskId: message.taskId, localId: message.turnId }, "turn");
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.");