@zq-silk/yui 0.15.7 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +157 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1188 -710
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +529 -479
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. 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
+ }
@@ -12,12 +12,15 @@ export function createTaskMessage(id, taskId, body, kind, author, now, context =
12
12
  ...(context.wakePolicy === undefined
13
13
  ? {}
14
14
  : { wakePolicy: context.wakePolicy }),
15
- ...(context.turnId === undefined
15
+ ...(context.runId === undefined
16
16
  ? {}
17
- : { turnId: requireSafeIdentity(context.turnId, "Message Turn id") }),
17
+ : { runId: requireSafeIdentity(context.runId, "Message AgentRun id") }),
18
+ ...(context.resultRef === undefined ? {} : { resultRef: { ...context.resultRef } }),
18
19
  ...(context.workItemId === undefined
19
20
  ? {}
20
21
  : { workItemId: requireSafeIdentity(context.workItemId, "Message Work item id") }),
22
+ ...(context.recipient === undefined ? {} : { recipient: { ...context.recipient },
23
+ ...(context.recipient.ownerRunId === undefined ? {} : { continuation: {} }) }),
21
24
  createdAt: now.toISOString()
22
25
  };
23
26
  validateTaskMessage(message);
@@ -26,6 +29,18 @@ export function createTaskMessage(id, taskId, body, kind, author, now, context =
26
29
  export function taskMessageAuthorLabel(author) {
27
30
  return author.type === "role" ? author.roleName : author.type;
28
31
  }
32
+ /** A role-result reference never duplicates the execution's report body. */
33
+ export function expandTaskMessageResult(message, getRun) {
34
+ if (message.resultRef === undefined)
35
+ return message;
36
+ const run = getRun(message.taskId, message.resultRef.runId);
37
+ if (run === null || run.taskId !== message.taskId
38
+ || message.author.type !== "role" || run.roleName !== message.author.roleName
39
+ || run.status === "active" || run.result === undefined) {
40
+ throw new Error(`Result Message has no matching terminal execution: ${message.id}.`);
41
+ }
42
+ return { ...message, result: run.result };
43
+ }
29
44
  /** Replace only the mutable content of a Draft user/operator Message. */
30
45
  export function updateDraftTaskMessage(message, update) {
31
46
  validateTaskMessage(message);
@@ -59,16 +74,57 @@ export function validateTaskMessage(message) {
59
74
  && message.kind !== "operator") {
60
75
  throw new Error("Message wakePolicy is only valid for user/operator messages.");
61
76
  }
62
- if (message.turnId !== undefined)
63
- requireSafeIdentity(message.turnId, "Message Turn id");
77
+ if (message.runId !== undefined)
78
+ requireSafeIdentity(message.runId, "Message AgentRun id");
79
+ if (message.recipient !== undefined) {
80
+ requireSafeIdentity(message.recipient.roleName, "Recipient Role");
81
+ if (message.recipient.ownerRunId !== undefined) {
82
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.recipient.ownerRunId }, "run");
83
+ }
84
+ else if (message.recipient.roleName !== "leader") {
85
+ throw new Error("An execution recipient requires an owner Assignment.");
86
+ }
87
+ if (message.recipient.workItemId !== undefined) {
88
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.recipient.workItemId }, "workItem");
89
+ }
90
+ if (message.recipient.reviewRoundId !== undefined) {
91
+ requireSafeIdentity(message.recipient.reviewRoundId, "Recipient ReviewRound");
92
+ }
93
+ if (message.recipient.workItemId === undefined && message.recipient.reviewRoundId === undefined) {
94
+ throw new Error("A recipient requires an exact WorkItem or ReviewRound.");
95
+ }
96
+ }
97
+ if (message.continuation !== undefined && message.recipient?.ownerRunId === undefined) {
98
+ throw new Error("Message continuation requires an owner Assignment.");
99
+ }
100
+ if (message.continuation?.runId !== undefined) {
101
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.continuation.runId }, "run");
102
+ }
103
+ if (message.continuation?.notDeliveredReason !== undefined) {
104
+ requireText(message.continuation.notDeliveredReason, "Message nondelivery reason");
105
+ if (message.continuation.runId !== undefined)
106
+ throw new Error("Assigned Message delivery is observed from its AgentRun.");
107
+ }
108
+ for (const handover of message.handovers ?? []) {
109
+ requireSafeIdentity(handover.from.roleName, "Previous Message Role");
110
+ requireSafeIdentity(handover.from.ownerRunId, "Previous Message Assignment");
111
+ if (Number.isNaN(Date.parse(handover.at)))
112
+ throw new Error("Message handover timestamp is invalid.");
113
+ }
114
+ if (message.resultRef !== undefined) {
115
+ if (message.kind !== "role-result" || message.resultRef.type !== "agent-run-result") {
116
+ throw new Error("Execution result references require a role-result Message.");
117
+ }
118
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.resultRef.runId }, "run");
119
+ }
64
120
  if (message.workItemId !== undefined) {
65
121
  validateTaskRecordReference({
66
122
  taskId: message.taskId,
67
123
  localId: message.workItemId
68
124
  }, "workItem");
69
125
  }
70
- if (message.turnId !== undefined) {
71
- validateTaskRecordReference({ taskId: message.taskId, localId: message.turnId }, "turn");
126
+ if (message.runId !== undefined) {
127
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.runId }, "run");
72
128
  }
73
129
  if (typeof message.createdAt !== "string" || Number.isNaN(Date.parse(message.createdAt))) {
74
130
  throw new Error("Message createdAt is invalid.");