@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
@@ -1,17 +1,21 @@
1
1
  import { lstat, mkdir, readlink, readdir, rm, rmdir, symlink, unlink } from "node:fs/promises";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { isDeepStrictEqual } from "node:util";
4
- import { retireTaskRoleSessionsForWorkspace } from "../executor/agentExecutor.js";
4
+ import { retireTaskRoleSessionsForWorkspace, updateRoleAgentSessionStatus } from "../executor/agentExecutor.js";
5
5
  import { formatWorkspacePreflightError } from "../executor/workspacePreflightClassification.js";
6
6
  import { updateRole } from "../role/role.js";
7
- import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
7
+ import { taskRoleRuntimeIdentity } from "../runtime/managedCaller.js";
8
+ import { hasRuntimeCleanupObligation, runtimeLifecycleTarget, RUNTIME_HOST_DETACH_REQUIRED_REASON } from "../runtime/lifecycleReservation.js";
9
+ import { taskLocalActor } from "../commands/taskActor.js";
8
10
  import { attachReviewRoundWorkspace, recordReviewWorkspaceDisposition } from "../review/reviewRound.js";
9
11
  import { StorageConflictError } from "../storage/taskStore.js";
10
- import { activateTask, bindTaskProjectCommits, bindTaskWorkspaceIdentity, synchronizeTaskProjectCommits } from "../task/task.js";
12
+ import { activateTask, bindTaskProjectCommits, bindTaskWorkspaceIdentity, synchronizeTaskProjectCommits, taskOwnsManagedWorkspace } from "../task/task.js";
13
+ import { admitStoredTaskActivation, adoptTaskActivationResources, recordFailedTaskActivation, recordAdoptedTaskActivation } from "../task/taskActivationService.js";
11
14
  import { validateDraftTaskForActivation } from "../task/draftPlan.js";
15
+ import { createProjectResources } from "../resources/projectResourceService.js";
12
16
  import { createTaskEvent } from "../event/taskEvent.js";
13
17
  import { enqueueWork } from "../coordination/workMailboxQueue.js";
14
- import { createCandidateGitSnapshot, createDirectTaskMainSnapshot, workItemExecutionGroupById, currentWorkItemExecutionGroup, recordWorkItemWorkspaceDisposition } from "../workItem/workItem.js";
18
+ import { createCandidateGitSnapshot, createDirectTaskMainSnapshot, workItemExecutionGroupById, recordWorkItemWorkspaceDisposition } from "../workItem/workItem.js";
15
19
  import { createManagedWorkspace, isTaskOwnedWorkspace, managedWorkspaceKey, managedWorktreeName, sameManagedWorkspaceIdentity } from "../worktree/managedWorkspace.js";
16
20
  import { NodeGitWorkspace, worktreeIdentity } from "./gitWorkspace.js";
17
21
  import { acquireProjectMaintenanceLocks } from "./projectMaintenanceLock.js";
@@ -103,15 +107,27 @@ export class FileTaskWorkspacePreparer {
103
107
  * transaction that moves the Task from Draft to active. A Draft never owns a
104
108
  * durable writable workspace before this boundary, and any failed attempt
105
109
  * discards its unadopted refs/worktrees.
110
+ *
111
+ * An explicit activation request drives the resource configuration. Its
112
+ * environment is prepared and adopted before this boundary, and the request
113
+ * is marked adopted inside the same transaction as the status change, so a
114
+ * later launch failure can never present itself as "activation never
115
+ * happened".
106
116
  */
107
- async activateTaskWorkspace(taskId) {
117
+ async activateTaskWorkspace(taskId, environment = {}) {
108
118
  for (let attempt = 0;; attempt += 1) {
119
+ let attemptedRequest;
109
120
  try {
110
121
  const task = requireTask(this.store, taskId);
122
+ const caller = taskLocalActor(this.store, environment, task.id);
123
+ const actor = task.activationRequest?.operation.actorId === `task:${task.id}/role:leader`
124
+ ? "leader" : caller;
111
125
  if (task.status === "active") {
112
- const workspace = this.store.getTaskWorkspace(task.id);
113
- if (!isTaskOwnedWorkspace(workspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })))) {
114
- throw new Error(`Active Task workspace adoption is invalid: ${task.id}.`);
126
+ if (taskOwnsManagedWorkspace(task)) {
127
+ const workspace = this.store.getTaskWorkspace(task.id);
128
+ if (!isTaskOwnedWorkspace(workspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })))) {
129
+ throw new Error(`Active Task workspace adoption is invalid: ${task.id}.`);
130
+ }
115
131
  }
116
132
  return {
117
133
  task,
@@ -121,24 +137,186 @@ export class FileTaskWorkspacePreparer {
121
137
  changed: false
122
138
  };
123
139
  }
124
- validateDraftTaskForActivation(this.store, task);
125
- const { release, current } = this.#acquireTaskProjectMaintenanceLocks(task);
126
- try {
127
- return await this.#prepareTaskWorkspaceLocked(current.id, true);
128
- }
129
- finally {
130
- release();
140
+ const provider = this.store.getTaskRoleSessionSet(task.id, LEADER_ROLE)?.providerBinding;
141
+ if (provider?.run != null
142
+ && ["submitting", "accepted", "delivery-unknown"].includes(provider.run.status)) {
143
+ throw new Error("Planning native input is unsettled; preserve activation intent and retry after its exact terminal.");
131
144
  }
145
+ const admission = admitStoredTaskActivation(this.store, task.id);
146
+ if (admission.disposition === "ready")
147
+ attemptedRequest = admission.request;
148
+ validateDraftTaskForActivation(this.store, task);
149
+ const adopted = await this.#adoptActivationEnvironment(task);
150
+ return adopted.workspaceFree
151
+ ? this.#activateWorkspaceFreeTask(task.id, adopted, actor)
152
+ : await this.#prepareActivatedTaskWorkspace(task, adopted, actor);
132
153
  }
133
154
  catch (error) {
134
155
  if (error instanceof StorageConflictError
135
156
  && attempt < TASK_WORKSPACE_PREPARE_MAX_CONFLICT_RETRIES) {
136
157
  continue;
137
158
  }
159
+ if (attemptedRequest !== undefined) {
160
+ const outcome = error instanceof Error ? error.message : String(error);
161
+ const current = this.store.getTask(taskId)?.activationRequest;
162
+ if (current?.disposition !== "failed" || current.outcome !== outcome) {
163
+ recordFailedTaskActivation(this.store, taskId, attemptedRequest, outcome, {}, this.now());
164
+ }
165
+ }
138
166
  throw error;
139
167
  }
140
168
  }
141
169
  }
170
+ /** Prepares the managed workspace of a Task being activated, under its fences. */
171
+ async #prepareActivatedTaskWorkspace(task, environment, actor) {
172
+ const { release, current } = this.#acquireTaskProjectMaintenanceLocks(task);
173
+ try {
174
+ return await this.#prepareTaskWorkspaceLocked(current.id, true, environment, actor);
175
+ }
176
+ finally {
177
+ release();
178
+ }
179
+ }
180
+ /**
181
+ * Prepares and adopts the resource configuration an explicit activation
182
+ * request named, before any status change.
183
+ *
184
+ * A Task with no request keeps the historical behavior: its managed workspace
185
+ * is the whole environment. A request whose plan is empty and whose Task
186
+ * binds no Project owns nothing physical, and is reported as workspace-free
187
+ * so activation creates no directory at all.
188
+ *
189
+ * The exact request that was adopted travels with the result, so the later
190
+ * status transaction can prove it is completing that request rather than
191
+ * whichever one occupies the slot by then.
192
+ */
193
+ async #adoptActivationEnvironment(task) {
194
+ const admission = admitStoredTaskActivation(this.store, task.id);
195
+ if (admission.disposition === "absent")
196
+ return { workspaceFree: false };
197
+ if (admission.disposition !== "ready") {
198
+ throw new Error(`Task activation is not adoptable: ${task.id}/${admission.disposition}`
199
+ + (admission.disposition === "unavailable" ? ` (${admission.reason})` : "")
200
+ + ".");
201
+ }
202
+ // Resource adoption owns its own grant, directory-identity and conflict
203
+ // rechecks. A failure here leaves the Task a continuable Draft.
204
+ const adopted = adoptTaskActivationResources(this.store, task.id, this.now);
205
+ return {
206
+ workspaceFree: admission.plan.kind === "empty"
207
+ && task.projectBindings.length === 0,
208
+ // The request whose resources were actually adopted, re-checked at the
209
+ // status transaction: a cancel or replacement landing during workspace
210
+ // preparation must fail rather than silently swap requests.
211
+ request: adopted.request,
212
+ // The exact preparation this request consumed, so the adopted record
213
+ // names the environment it adopted rather than the latest one present.
214
+ ...(adopted.preparation === undefined
215
+ ? {}
216
+ : { preparationId: adopted.preparation.id })
217
+ };
218
+ }
219
+ /**
220
+ * Moves a Task that owns nothing physical from Draft to active.
221
+ *
222
+ * An empty environment plan with no bound Project is a legal Task shape, so
223
+ * no workspace view, worktree or cwd is created to satisfy the framework's
224
+ * own model. The status change and the adopted request commit together.
225
+ */
226
+ #activateWorkspaceFreeTask(taskId, environment, actor) {
227
+ const task = this.store.transaction((tx) => {
228
+ const latest = requireTask(tx, taskId);
229
+ if (latest.status !== "draft") {
230
+ throw new Error(`Task changed while activating it: ${taskId}/${latest.status}.`);
231
+ }
232
+ if (latest.projectBindings.length !== 0
233
+ || latest.cwd !== undefined
234
+ || latest.workspaceIdentity !== undefined
235
+ || tx.getTaskWorkspace(taskId) !== null) {
236
+ throw new Error(`Task acquired workspace state while activating it: ${taskId}.`);
237
+ }
238
+ validateDraftTaskForActivation(tx, latest);
239
+ const timestamp = this.now();
240
+ // Bind before the handover below: the desired environment must be the
241
+ // adopted one before the obligation that ends the old physical Host
242
+ // exists.
243
+ if (environment.preparationId !== undefined) {
244
+ this.#bindAdoptedActivationEnvironment(tx, taskId, environment.preparationId);
245
+ }
246
+ // The physical launch is unchanged, but "unchanged" is not "compatible":
247
+ // the Draft's planning Session runs in the Role's inherited workspace,
248
+ // which is exactly the shared cwd an active Task may not execute in. Hand
249
+ // the conversation over instead of asserting continuity, so the next
250
+ // launch is re-planned under the execution rules and must name a real
251
+ // environment. Its plan and grants are re-proven there, not assumed here.
252
+ handOverPlanningSession(tx, taskId, timestamp);
253
+ const activated = this.#recordAdoptedActivation(tx, activateTask(latest, timestamp), environment, timestamp);
254
+ tx.saveTask(activated);
255
+ recordTaskActivation(tx, latest, activated, timestamp, actor);
256
+ return activated;
257
+ });
258
+ return { task, taskId, status: "ready", changed: true };
259
+ }
260
+ /**
261
+ * Marks the Task's activation request adopted, inside the caller's activation
262
+ * transaction. Returns the Task unchanged when no explicit request exists.
263
+ *
264
+ * The adopted environment and the request identity travel together, so the
265
+ * record can never name a preparation that a different request produced.
266
+ */
267
+ #recordAdoptedActivation(store, task, environment, now) {
268
+ if (task.activationRequest === undefined)
269
+ return task;
270
+ // Re-prove the adopted environment inside the status transaction. Between
271
+ // resource adoption and here a worktree was prepared, which takes real time:
272
+ // a grant may have been revoked or bounded away, or the directory may no
273
+ // longer be the Resource it was adopted as. `resolveExecutionEnvironment` is
274
+ // the same check every launch performs, including the per-resourceRef grant
275
+ // recheck against the reservation adoption charged, so a Task can never go
276
+ // active claiming an environment its authority no longer covers.
277
+ if (environment.preparationId !== undefined) {
278
+ createProjectResources(store, this.now)
279
+ .resolveExecutionEnvironment(task.id, environment.preparationId);
280
+ }
281
+ return recordAdoptedTaskActivation(store, task, {
282
+ ...(environment.preparationId === undefined
283
+ ? {}
284
+ : { preparationId: environment.preparationId }),
285
+ ...(environment.request === undefined ? {} : { expected: environment.request })
286
+ }, now);
287
+ }
288
+ /**
289
+ * Selects the adopted environment for every Role of a Task, so
290
+ * `preparationId` is the Role's actual `executionEnvironment` rather than a
291
+ * record no launch ever reads.
292
+ *
293
+ * This is the existing public `bindEnvironment` path, so the atomic Role
294
+ * update, its event and its mailbox notification are the ones task-21
295
+ * established — the adopted directory, its ownership, its access and its
296
+ * per-resource grant reservation are rechecked there and shown truthfully at
297
+ * launch.
298
+ *
299
+ * Runs inside the caller's activation transaction (the Store is re-entrant),
300
+ * and specifically *before* the planning-Session handover queues the Host
301
+ * detach. That order is required in both directions: the desired
302
+ * configuration must change before the obligation that ends the old physical
303
+ * Host exists, so runtime cleanup never acts on a launch configuration the
304
+ * Task has already replaced — which is exactly what the runtime-lifecycle
305
+ * guard refuses when the two are inverted. Committing with the status change
306
+ * also means a Task can never be observed active holding an adopted
307
+ * preparation that no Role executes in.
308
+ */
309
+ #bindAdoptedActivationEnvironment(store, taskId, preparationId) {
310
+ const resources = createProjectResources(store, this.now);
311
+ for (const role of store.listRoles(taskId)) {
312
+ if (role.executionEnvironment?.preparationId === preparationId)
313
+ continue;
314
+ resources.bindEnvironment(taskId, role.name, preparationId, {
315
+ source: "task.activation-adopted",
316
+ preparationId
317
+ });
318
+ }
319
+ }
142
320
  /**
143
321
  * Acquire the per-Project maintenance fences for every Project bound to a
144
322
  * Task, then re-read the Task under those fences and prove its binding set
@@ -181,7 +359,9 @@ export class FileTaskWorkspacePreparer {
181
359
  acquireTaskProjectMaintenanceLocks(taskId) {
182
360
  return this.#acquireTaskProjectMaintenanceLocks(requireTask(this.store, taskId));
183
361
  }
184
- async #prepareTaskWorkspaceLocked(taskId, activate) {
362
+ async #prepareTaskWorkspaceLocked(taskId, activate,
363
+ /** Environment and request identity the activation adopted, when one exists. */
364
+ activationEnvironment = { workspaceFree: false }, actor = "user") {
185
365
  const task = requireTask(this.store, taskId);
186
366
  if (activate && task.status !== "draft") {
187
367
  throw new Error(`Only a Draft Task can atomically adopt a workspace: ${task.id}/${task.status}.`);
@@ -194,6 +374,20 @@ export class FileTaskWorkspacePreparer {
194
374
  }
195
375
  if (activate)
196
376
  validateDraftTaskForActivation(this.store, task);
377
+ if (activate) {
378
+ const planning = planningLeaderSession(this.store, task.id);
379
+ if (planning !== undefined) {
380
+ const target = resolve(this.#taskWorkspaceRoot(task.id));
381
+ const scratch = resolve(planning.effective.workspace.root);
382
+ const within = (parent, child) => {
383
+ const path = relative(parent, child);
384
+ return path === "" || (!path.startsWith("..") && !isAbsolute(path));
385
+ };
386
+ if (within(target, scratch) || within(scratch, target)) {
387
+ throw new Error("Planning and delivery workspaces overlap; preserve the planning Session and select an isolated delivery workspace.");
388
+ }
389
+ }
390
+ }
197
391
  const existing = this.store.getTaskWorkspace(task.id);
198
392
  if (activate && (task.workspaceIdentity !== undefined
199
393
  || task.cwd !== undefined
@@ -240,23 +434,35 @@ export class FileTaskWorkspacePreparer {
240
434
  throw new Error(`Gitless Task workspace changed during preparation: ${task.id}.`);
241
435
  }
242
436
  const timestamp = this.now();
437
+ // Activation moves the Leader out of the Draft planning cwd, so its
438
+ // planning Session cannot continue. Hand it over explicitly before
439
+ // the migration below, which refuses to retire a live Session. The
440
+ // adopted environment is bound first, so the desired configuration is
441
+ // already correct when the Host detach is queued.
442
+ if (activate && activationEnvironment.preparationId !== undefined) {
443
+ this.#bindAdoptedActivationEnvironment(tx, task.id, activationEnvironment.preparationId);
444
+ }
445
+ if (activate)
446
+ handOverPlanningSession(tx, task.id, timestamp);
243
447
  let next = latest.cwd === root
244
448
  ? latest
245
449
  : { ...latest, cwd: root, updatedAt: timestamp.toISOString() };
246
450
  if (activate)
247
- next = activateTask(next, timestamp);
451
+ next = this.#recordAdoptedActivation(tx, activateTask(next, timestamp), activationEnvironment, timestamp);
248
452
  if (!isDeepStrictEqual(next, latest))
249
453
  tx.saveTask(next);
250
454
  if (current === null)
251
455
  tx.saveManagedWorkspace(workspace);
252
456
  for (const role of tx.listRoles(task.id)) {
457
+ if (activate && role.name === "leader" && planningLeaderSession(tx, task.id) !== undefined)
458
+ continue;
253
459
  if (role.workspace !== root) {
254
460
  retireWorkspaceBoundSession(tx, task.id, role.name, timestamp);
255
461
  tx.saveRole(task.id, updateRole(role, { workspace: root }, timestamp));
256
462
  }
257
463
  }
258
464
  if (activate)
259
- recordTaskActivation(tx, latest, next, timestamp);
465
+ recordTaskActivation(tx, latest, next, timestamp, actor);
260
466
  return next;
261
467
  });
262
468
  return {
@@ -436,6 +642,15 @@ export class FileTaskWorkspacePreparer {
436
642
  const timestamp = this.now();
437
643
  let persistedTask = latest;
438
644
  if (activate) {
645
+ // Activation moves the Leader out of the Draft planning cwd, so its
646
+ // planning Session cannot continue. Hand it over explicitly before
647
+ // the migration below, which refuses to retire a live Session. The
648
+ // adopted environment is bound first, so the desired configuration is
649
+ // already correct when the Host detach is queued.
650
+ if (activationEnvironment.preparationId !== undefined) {
651
+ this.#bindAdoptedActivationEnvironment(tx, task.id, activationEnvironment.preparationId);
652
+ }
653
+ handOverPlanningSession(tx, task.id, timestamp);
439
654
  persistedTask = bindTaskProjectCommits(persistedTask, prepared.map(({ entry }) => ({
440
655
  projectId: entry.projectId,
441
656
  commit: entry.baseCommit
@@ -457,7 +672,7 @@ export class FileTaskWorkspacePreparer {
457
672
  persistedTask = { ...persistedTask, cwd: root, updatedAt: timestamp.toISOString() };
458
673
  }
459
674
  if (activate)
460
- persistedTask = activateTask(persistedTask, timestamp);
675
+ persistedTask = this.#recordAdoptedActivation(tx, activateTask(persistedTask, timestamp), activationEnvironment, timestamp);
461
676
  if (!isDeepStrictEqual(persistedTask, latest)) {
462
677
  tx.saveTask(persistedTask);
463
678
  }
@@ -469,32 +684,31 @@ export class FileTaskWorkspacePreparer {
469
684
  recordTaskBaseProvenanceEvents(tx, task.id, adoptedProvenance, timestamp);
470
685
  }
471
686
  for (const role of tx.listRoles(task.id)) {
687
+ if (activate && role.name === "leader" && planningLeaderSession(tx, task.id) !== undefined)
688
+ continue;
472
689
  // The Role field is only a cwd/snapshot hint. Preserve it while a
473
690
  // durable WorkItem or ReviewRound workspace owns that cwd; Task-main
474
691
  // preparation must not move a Reviewer Session out from under an
475
692
  // active or retained ReviewRound. Other Roles use Task main.
476
- // Prefer the active Turn's exact WorkItem, then a retained direct
693
+ // Prefer the active AgentRun's exact WorkItem, then a retained direct
477
694
  // WorkItem owning this cwd, before considering queued assignments.
478
- const activeRoleTurn = tx.getActiveTurn(task.id, role.name);
479
- const activeTurnItem = activeRoleTurn !== null
480
- && activeRoleTurn.purpose === "execution"
481
- && activeRoleTurn.workItemId !== undefined
482
- ? tx.getWorkItem(task.id, activeRoleTurn.workItemId)
695
+ const activeRoleRun = tx.getActiveRun(task.id, role.name);
696
+ const activeRunItem = activeRoleRun !== null
697
+ && activeRoleRun.purpose === "execution"
698
+ && activeRoleRun.workItemId !== undefined
699
+ ? tx.getWorkItem(task.id, activeRoleRun.workItemId)
483
700
  : null;
484
- // Rejection ends an execution iteration, not its workspace ownership.
485
- // Preserve this direct WorkItem's existing cwd while dispatch prepares
486
- // the Task before atomically reopening the failed WorkItem. Merely
487
- // reading Task context must not migrate its resumable Worker either.
701
+ // Semantic acceptance/retirement does not release a workspace.
702
+ // Preserve its cwd until explicit cleanup/reassignment removes that
703
+ // durable owner; Task-main preparation must not migrate its Session.
488
704
  const retainedItem = tx.listWorkItems(task.id).find((candidate) => (candidate.assignee === role.name
489
- && candidate.status === "failed"
490
- && currentWorkItemExecutionGroup(candidate) === undefined
491
705
  && tx.getWorkItemWorkspace(task.id, candidate.id)?.root === role.workspace));
492
- const assignedItem = activeTurnItem !== null
493
- && activeTurnItem.assignee === role.name
494
- && !["completed", "failed", "retired"].includes(activeTurnItem.status)
495
- ? activeTurnItem
706
+ const assignedItem = activeRunItem !== null
707
+ && activeRunItem.assignee === role.name
708
+ && !["accepted", "retired"].includes(activeRunItem.status)
709
+ ? activeRunItem
496
710
  : retainedItem ?? tx.listWorkItems(task.id).find((candidate) => (candidate.assignee === role.name
497
- && !["completed", "failed", "retired"]
711
+ && !["accepted", "retired"]
498
712
  .includes(candidate.status)));
499
713
  const assignedWorkspace = assignedItem === undefined
500
714
  ? null
@@ -517,7 +731,7 @@ export class FileTaskWorkspacePreparer {
517
731
  }
518
732
  }
519
733
  if (activate)
520
- recordTaskActivation(tx, latest, persistedTask, timestamp);
734
+ recordTaskActivation(tx, latest, persistedTask, timestamp, actor);
521
735
  return persistedTask;
522
736
  });
523
737
  return {
@@ -817,14 +1031,14 @@ export class FileTaskWorkspacePreparer {
817
1031
  || !isDeepStrictEqual(latestItem.writeProjectIds, item.writeProjectIds)) {
818
1032
  throw new Error(`Work item changed while preparing its workspace: ${item.id}.`);
819
1033
  }
820
- const activeDevelopTurn = tx.listTurns(lockedTask.id)
1034
+ const activeDevelopRun = tx.listRuns(lockedTask.id)
821
1035
  .find((run) => run.status === "active" && run.workItemId === item.id);
822
- if (activeDevelopTurn !== undefined) {
823
- throw new Error(`Work Item already has an active Develop Turn: ${activeDevelopTurn.id}.`);
1036
+ if (activeDevelopRun !== undefined) {
1037
+ throw new Error(`Work Item already has an active Develop AgentRun: ${activeDevelopRun.id}.`);
824
1038
  }
825
1039
  if (latestItem.assignee !== undefined
826
- && tx.getActiveTurn(lockedTask.id, latestItem.assignee) !== null) {
827
- throw new Error(`Role has an active Turn: ${lockedTask.id}/${latestItem.assignee}.`);
1040
+ && tx.getActiveRun(lockedTask.id, latestItem.assignee) !== null) {
1041
+ throw new Error(`Role has an active AgentRun: ${lockedTask.id}/${latestItem.assignee}.`);
828
1042
  }
829
1043
  const existingWorkspace = tx.getWorkItemWorkspace(lockedTask.id, item.id);
830
1044
  if (existingWorkspace !== null && (existingWorkspace.owner.type !== "work-item"
@@ -1431,8 +1645,8 @@ export class FileTaskWorkspacePreparer {
1431
1645
  || candidateChanged) {
1432
1646
  throw new ReviewRoundWorkspaceEvidenceError(`ReviewRound changed while preparing its workspace: ${round.id}.`);
1433
1647
  }
1434
- if (tx.getActiveTurn(task.id, reviewer.name) !== null) {
1435
- throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active Turn: ${task.id}/${reviewer.name}.`);
1648
+ if (tx.getActiveRun(task.id, reviewer.name) !== null) {
1649
+ throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active AgentRun: ${task.id}/${reviewer.name}.`);
1436
1650
  }
1437
1651
  const currentWorkspace = tx.getReviewRoundWorkspace(task.id, round.id);
1438
1652
  if (existing !== null
@@ -1497,8 +1711,8 @@ export class FileTaskWorkspacePreparer {
1497
1711
  || previousWorkspace.root !== reviewRoot) {
1498
1712
  throw new ReviewRoundWorkspaceEvidenceError(`Previous Task-final Review workspace cannot be continued: ${previous.id}.`);
1499
1713
  }
1500
- if (this.store.getActiveTurn(task.id, reviewer.name) !== null) {
1501
- throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active Turn: ${task.id}/${reviewer.name}.`);
1714
+ if (this.store.getActiveRun(task.id, reviewer.name) !== null) {
1715
+ throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active AgentRun: ${task.id}/${reviewer.name}.`);
1502
1716
  }
1503
1717
  const expected = new Map(frozenEntries.map((entry) => [entry.projectId, entry]));
1504
1718
  if (previousWorkspace.entries.length !== expected.size) {
@@ -1565,7 +1779,7 @@ export class FileTaskWorkspacePreparer {
1565
1779
  || currentRound.workspace !== undefined
1566
1780
  || !isDeepStrictEqual(currentRound.taskCandidate, round.taskCandidate)
1567
1781
  || tx.getReviewRoundWorkspace(task.id, round.id) !== null
1568
- || tx.getActiveTurn(task.id, reviewer.name) !== null) {
1782
+ || tx.getActiveRun(task.id, reviewer.name) !== null) {
1569
1783
  throw new ReviewRoundWorkspaceEvidenceError(`Task-final Review workspace changed before reassignment: ${previous.id}/${round.id}.`);
1570
1784
  }
1571
1785
  const latestReviewer = tx.getRole(task.id, reviewer.name);
@@ -1693,7 +1907,7 @@ export class FileTaskWorkspacePreparer {
1693
1907
  async cleanupWorkItemWorkspace(taskId, workItemId, disposition) {
1694
1908
  const item = requireWorkItem(this.store, taskId, workItemId);
1695
1909
  const task = requireTask(this.store, item.taskId);
1696
- if (!["completed", "failed", "retired"].includes(item.status)) {
1910
+ if (!["accepted", "retired"].includes(item.status)) {
1697
1911
  throw new Error(`Work item must be terminal before cleanup: ${item.id}.`);
1698
1912
  }
1699
1913
  if (item.workspaceDisposition !== undefined && item.workspaceDisposition !== disposition) {
@@ -1985,10 +2199,17 @@ export class FileTaskWorkspacePreparer {
1985
2199
  });
1986
2200
  }
1987
2201
  }
1988
- function recordTaskActivation(store, previous, active, now) {
1989
- enqueueWork(store, { kind: "role", taskId: active.id, roleName: LEADER_ROLE }, "task-created", now, [{ type: "task", id: active.id }]);
2202
+ function recordTaskActivation(store, previous, active, now, actor) {
2203
+ // Activation is the explicit request to enter delivery, not an ordinary
2204
+ // Leader-local edit. Preserve that transition for the replacement Session.
2205
+ enqueueWork(store, { kind: "role", taskId: active.id, roleName: LEADER_ROLE }, "execution-started", now, [{ type: "task", id: active.id }]);
1990
2206
  enqueueWork(store, { kind: "task", taskId: active.id }, "task-activated", now, [{ type: "task", id: active.id }]);
1991
- store.saveEvent(active.id, createTaskEvent(store.nextEventId(active.id), active.id, "task.activated", { fromStatus: previous.status, status: active.status }, now));
2207
+ store.saveEvent(active.id, createTaskEvent(store.nextEventId(active.id), active.id, "task.activated", { fromStatus: previous.status, status: active.status, by: actor }, now));
2208
+ }
2209
+ function planningLeaderSession(store, taskId) {
2210
+ const sessions = store.getTaskRoleSessionSet(taskId, "leader");
2211
+ const current = sessions?.sessions[sessions.activeAgentId];
2212
+ return current?.status === "active" && current.effective.executionAuthority === "planning" ? current : undefined;
1992
2213
  }
1993
2214
  function requireTask(store, taskId) {
1994
2215
  const task = store.getTask(taskId);
@@ -1997,7 +2218,7 @@ function requireTask(store, taskId) {
1997
2218
  return task;
1998
2219
  }
1999
2220
  function assertTaskArchiveState(current, expected) {
2000
- if ((current.status !== "completed" && current.status !== "retired")
2221
+ if ((current.status !== "completed" && current.status !== "cancelled")
2001
2222
  || !isDeepStrictEqual(current, expected)) {
2002
2223
  throw new WorkspaceCleanupBlockedError("task-changed", `task:${expected.id}`, true, `Task changed during archive cleanup: ${expected.id}.`);
2003
2224
  }
@@ -2030,9 +2251,9 @@ function executionLaneLineage(store, task, executionGroupId, executionLaneId, hi
2030
2251
  throw new Error(`ReviewRound not found: ${hint.reviewRoundId}.`);
2031
2252
  return { purpose: "review", reviewRoundId: round.id };
2032
2253
  }
2033
- // Prefer the active Turn's exact WorkItem for this Lane; fall back to the
2034
- // first queued WorkItem only when no active Turn owns the Lane.
2035
- const activeLaneRun = store.listTurns(task.id)
2254
+ // Prefer the active AgentRun's exact WorkItem for this Lane; fall back to the
2255
+ // first queued WorkItem only when no active AgentRun owns the Lane.
2256
+ const activeLaneRun = store.listRuns(task.id)
2036
2257
  .find((run) => run.status === "active"
2037
2258
  && run.purpose === "execution"
2038
2259
  && run.executionGroupId === executionGroupId
@@ -2057,16 +2278,16 @@ function executionLaneLineage(store, task, executionGroupId, executionLaneId, hi
2057
2278
  function assertWorkItemWorkspaceEligible(store, task, item) {
2058
2279
  if (task.status !== "active")
2059
2280
  throw new Error(`Task is not active: ${task.id}.`);
2060
- if (["completed", "failed", "retired"].includes(item.status)) {
2281
+ if (["accepted", "retired"].includes(item.status)) {
2061
2282
  throw new Error(`Work item is already terminal: ${item.id}.`);
2062
2283
  }
2063
- const activeDevelopTurn = store.listTurns(task.id)
2284
+ const activeDevelopRun = store.listRuns(task.id)
2064
2285
  .find((run) => run.status === "active" && run.workItemId === item.id);
2065
- if (activeDevelopTurn !== undefined) {
2066
- throw new Error(`Work Item already has an active Develop Turn: ${activeDevelopTurn.id}.`);
2286
+ if (activeDevelopRun !== undefined) {
2287
+ throw new Error(`Work Item already has an active Develop AgentRun: ${activeDevelopRun.id}.`);
2067
2288
  }
2068
- if (item.assignee !== undefined && store.getActiveTurn(task.id, item.assignee) !== null) {
2069
- throw new Error(`Role has an active Turn: ${task.id}/${item.assignee}.`);
2289
+ if (item.assignee !== undefined && store.getActiveRun(task.id, item.assignee) !== null) {
2290
+ throw new Error(`Role has an active AgentRun: ${task.id}/${item.assignee}.`);
2070
2291
  }
2071
2292
  }
2072
2293
  function assertWorkItemOwnsWorkspace(item, workspace) {
@@ -2086,27 +2307,78 @@ function preserveWorkspaceCreatedAt(workspace, previous) {
2086
2307
  : { ...workspace, createdAt: previous.createdAt };
2087
2308
  }
2088
2309
  function assertWorkspaceSessionsRetirable(store, taskId, roleName, now) {
2089
- if (store.getActiveTurn(taskId, roleName) !== null) {
2090
- throw new Error(`Role has an active Turn: ${taskId}/${roleName}.`);
2310
+ if (store.getActiveRun(taskId, roleName) !== null) {
2311
+ throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
2091
2312
  }
2092
2313
  const sessions = store.getTaskRoleSessionSet(taskId, roleName);
2093
2314
  if (sessions !== null)
2094
2315
  retireTaskRoleSessionsForWorkspace(sessions, now);
2095
2316
  }
2096
2317
  function retireWorkspaceBoundSession(store, taskId, roleName, now) {
2097
- if (store.getActiveTurn(taskId, roleName) !== null) {
2098
- throw new Error(`Role has an active Turn: ${taskId}/${roleName}.`);
2318
+ if (store.getActiveRun(taskId, roleName) !== null) {
2319
+ throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
2099
2320
  }
2100
2321
  const sessions = store.getTaskRoleSessionSet(taskId, roleName);
2101
2322
  if (sessions !== null) {
2102
2323
  store.saveTaskRoleSessionSet(retireTaskRoleSessionsForWorkspace(sessions, now));
2103
2324
  }
2104
2325
  }
2326
+ /**
2327
+ * Hands a Draft planning Session over to the activated Task.
2328
+ *
2329
+ * A Leader plans and executes as the same logical Role, so activation never
2330
+ * changes the Leader or Task identity. It can change the *physical* launch
2331
+ * though: adopting a workspace or an execution environment moves the Session's
2332
+ * cwd, and a native conversation is bound to the cwd it started in. Such a
2333
+ * Session cannot continue, and that is not a failure to report — the plan lives
2334
+ * in the Task's own durable facts, so the next Turn takes over from persistent
2335
+ * Context instead of the old terminal or its full tool log.
2336
+ *
2337
+ * Ending the record here is what makes the takeover explicit rather than
2338
+ * silent: the next Leader wake resolves `new`, and the queued cleanup ends the
2339
+ * physical Host that the ended record no longer represents. Without this, a
2340
+ * live planning Session would make workspace migration throw and leave the
2341
+ * Task an un-activatable Draft forever.
2342
+ */
2343
+ function handOverPlanningSession(store, taskId, now) {
2344
+ const sessions = store.getTaskRoleSessionSet(taskId, LEADER_ROLE);
2345
+ if (sessions === null)
2346
+ return;
2347
+ const live = Object.values(sessions.sessions).find(({ status }) => status !== "ended");
2348
+ if (live === undefined)
2349
+ return;
2350
+ // A planning Turn that is still running owns this Session. Deferred
2351
+ // activation is adopted only after it ends, so reaching here with an active
2352
+ // Turn means the caller raced it; fail closed rather than cut it off.
2353
+ if (store.getActiveRun(taskId, LEADER_ROLE) !== null) {
2354
+ throw new Error(`Leader has an active AgentRun while activating its Task: ${taskId}/${LEADER_ROLE}.`);
2355
+ }
2356
+ let next = sessions;
2357
+ for (const session of Object.values(sessions.sessions)) {
2358
+ if (session.status === "ended")
2359
+ continue;
2360
+ next = updateRoleAgentSessionStatus(next, session.agentId, "ended", now, "stopped");
2361
+ }
2362
+ store.saveTaskRoleSessionSet(next);
2363
+ // The durable record is ended; the physical Host still exists. Queue the
2364
+ // explicit detach so the runtime lane closes it instead of leaking it.
2365
+ enqueueWork(store, runtimeLifecycleTarget({ scope: "task", taskId, roleName: LEADER_ROLE }), RUNTIME_HOST_DETACH_REQUIRED_REASON, now, [{ type: "task", id: taskId }]);
2366
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "task.planning-session-handover", {
2367
+ roleName: LEADER_ROLE,
2368
+ agentId: live.agentId,
2369
+ adapterId: live.adapterId,
2370
+ ...(live.nativeSessionId === undefined
2371
+ ? {}
2372
+ : { nativeSessionId: live.nativeSessionId }),
2373
+ reason: "activation-changed-physical-launch",
2374
+ continuity: "persistent-context"
2375
+ }, now));
2376
+ }
2105
2377
  function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, workspace) {
2106
2378
  if (role.taskId !== taskId
2107
2379
  || item.taskId !== taskId
2108
2380
  || item.assignee !== role.name
2109
- || ["completed", "failed", "retired"].includes(item.status)
2381
+ || ["accepted", "retired"].includes(item.status)
2110
2382
  || workspace.owner.type !== "work-item"
2111
2383
  || workspace.owner.taskId !== taskId
2112
2384
  || workspace.owner.workItemId !== item.id)
@@ -2117,14 +2389,15 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
2117
2389
  .sort();
2118
2390
  if (!isDeepStrictEqual(writableProjects, [...item.writeProjectIds].sort()))
2119
2391
  return false;
2120
- const run = store.getActiveTurn(taskId, role.name);
2392
+ const run = store.getActiveRun(taskId, role.name);
2393
+ const identity = taskRoleRuntimeIdentity(role, run);
2121
2394
  if (run === null
2122
2395
  || run.status !== "active"
2123
2396
  || run.purpose !== "execution"
2124
2397
  || run.workItemId !== item.id
2125
2398
  || run.workspace === undefined
2126
2399
  || !sameManagedWorkspaceIdentity(run.workspace, workspace)
2127
- || run.effective.agentId !== role.activeAgentId
2400
+ || run.effective.agentId !== identity.agentId
2128
2401
  || !sameEffectiveWorkspace(run.effective.workspace, workspace))
2129
2402
  return false;
2130
2403
  const sessions = store.getTaskRoleSessionSet(taskId, role.name);
@@ -2133,13 +2406,12 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
2133
2406
  || sessions.owner.scope !== "task"
2134
2407
  || sessions.owner.taskId !== taskId
2135
2408
  || sessions.owner.roleName !== role.name
2136
- || sessions.activeAgentId !== role.activeAgentId
2409
+ || sessions.activeAgentId !== identity.agentId
2137
2410
  || session === undefined
2138
- || session.agentId !== role.activeAgentId
2411
+ || session.agentId !== identity.agentId
2139
2412
  || session.adapterId !== run.effective.adapterId
2140
- || session.runtimeGenerationId === undefined
2141
2413
  || session.nativeSessionId === undefined
2142
- || !["ready", "running"].includes(session.status)
2414
+ || session.status !== "active"
2143
2415
  || !isDeepStrictEqual(session.effective, run.effective)
2144
2416
  || !sameEffectiveWorkspace(session.effective.workspace, workspace))
2145
2417
  return false;
@@ -2150,17 +2422,6 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
2150
2422
  }));
2151
2423
  if (hasRuntimeCleanupObligation(lifecycleMailbox))
2152
2424
  return false;
2153
- const lifecycle = lifecycleMailbox?.processing;
2154
- if (lifecycle !== null
2155
- && lifecycle !== undefined
2156
- && isRuntimeLaunchReservation(lifecycle)) {
2157
- const executionRef = lifecycle.executionRef;
2158
- if (!isRuntimeLaunchReservation(lifecycle, session.runtimeGenerationId)
2159
- || executionRef?.type !== "turn"
2160
- || executionRef.taskId !== taskId
2161
- || executionRef.id !== run.id)
2162
- return false;
2163
- }
2164
2425
  return true;
2165
2426
  }
2166
2427
  function sameEffectiveWorkspace(effective, workspace) {