@zq-silk/yui 0.15.11 → 0.16.0

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 (280) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/ARCHITECTURE.zh-CN.md +8 -4
  3. package/README.md +13 -5
  4. package/dist/agentRun/agentRun.js +3 -0
  5. package/dist/artifacts/artifactCommitLock.js +16 -55
  6. package/dist/artifacts/taskArtifactRepository.js +3 -3
  7. package/dist/cli/agentConfigurationPicker.js +1 -1
  8. package/dist/cli/commandCatalog.js +92 -48
  9. package/dist/cli/completion.js +18 -18
  10. package/dist/cli/completionWizard.js +1 -1
  11. package/dist/cli/dynamicCompletion.js +1 -1
  12. package/dist/cli/interactionPolicy.js +11 -3
  13. package/dist/cli/invocationAuthority.js +64 -0
  14. package/dist/cli/managedDiagnostics.js +2 -2
  15. package/dist/cli/parseRepeatable.js +35 -0
  16. package/dist/cli/updatePorts.js +26 -5
  17. package/dist/cli.js +1303 -1235
  18. package/dist/commands/agentCommands.js +4 -4
  19. package/dist/commands/capabilityCommands.js +1 -1
  20. package/dist/commands/configCommands.js +6 -57
  21. package/dist/commands/configOverview.js +2 -2
  22. package/dist/commands/durableJobCommands.js +12 -6
  23. package/dist/commands/executionAuditCommands.js +10 -0
  24. package/dist/commands/globalRoleCommands.js +78 -29
  25. package/dist/commands/grantCommands.js +0 -3
  26. package/dist/commands/jobCommands.js +1 -4
  27. package/dist/commands/operatorCommands.js +3 -3
  28. package/dist/commands/projectCommands.js +45 -25
  29. package/dist/commands/resourcesCommands.js +10 -40
  30. package/dist/commands/roleConfiguration.js +2 -6
  31. package/dist/commands/taskActivationCommands.js +4 -8
  32. package/dist/commands/taskAgentCapabilities.js +9 -0
  33. package/dist/commands/taskCommandSupport.js +155 -0
  34. package/dist/commands/taskCommandTypes.js +1 -0
  35. package/dist/commands/taskCommands.js +355 -732
  36. package/dist/commands/taskCompletionGate.js +1 -1
  37. package/dist/commands/taskExecutionCommands.js +1 -1
  38. package/dist/commands/taskFactCommands.js +325 -0
  39. package/dist/commands/taskInputCommands.js +12 -42
  40. package/dist/commands/taskIntegrationCommands.js +20 -58
  41. package/dist/commands/taskNextActionCommand.js +7 -6
  42. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  43. package/dist/commands/taskPublicationCommands.js +12 -3
  44. package/dist/commands/taskPublicationVerifyCommand.js +24 -40
  45. package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
  46. package/dist/commands/taskUpstreamCommands.js +19 -8
  47. package/dist/commands/workflowCommands.js +6 -1
  48. package/dist/completion/completionInstaller.js +26 -26
  49. package/dist/completion/completionState.js +26 -26
  50. package/dist/completion/fileCompletionManager.js +6 -11
  51. package/dist/config/configCatalog.js +0 -2
  52. package/dist/config/timeZone.js +14 -0
  53. package/dist/config/yuiConfig.js +0 -29
  54. package/dist/context/runContextPack.js +5 -3
  55. package/dist/context/taskCatalog.js +185 -0
  56. package/dist/context/taskContext.js +152 -151
  57. package/dist/context/wakeRunReferences.js +11 -0
  58. package/dist/controller/agentCapabilities.js +58 -0
  59. package/dist/controller/agentRuntimeObserver.js +6 -11
  60. package/dist/controller/clientRuntime.js +22 -14
  61. package/dist/controller/controller.js +20 -24
  62. package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
  63. package/dist/controller/globalInputDelivery.js +13 -0
  64. package/dist/controller/jobClient.js +4 -4
  65. package/dist/controller/jobControl.js +60 -25
  66. package/dist/controller/jobSupervisor.js +4 -4
  67. package/dist/controller/providerRetryAdmission.js +100 -0
  68. package/dist/controller/providerRetryDelivery.js +218 -0
  69. package/dist/controller/runtime.js +85 -43
  70. package/dist/controller/runtimeEventProcessor.js +0 -5
  71. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  72. package/dist/controller/taskAgentError.js +35 -0
  73. package/dist/coordination/workMailboxQueue.js +2 -24
  74. package/dist/core/controllerClient.js +2 -2
  75. package/dist/core/fileLockOwner.js +74 -0
  76. package/dist/decision/decision.js +17 -0
  77. package/dist/doctor/doctor.js +18 -9
  78. package/dist/event/taskEvent.js +12 -0
  79. package/dist/execution/workItemExecutionProjection.js +2 -2
  80. package/dist/executor/agentCapabilityConfig.js +43 -0
  81. package/dist/executor/agentConfigurationCatalog.js +49 -11
  82. package/dist/executor/agentExecutor.js +5 -8
  83. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  84. package/dist/executor/taskAgentCapabilities.js +64 -0
  85. package/dist/integration/deliveryObligation.js +1 -69
  86. package/dist/integration/gitIntegrationService.js +79 -90
  87. package/dist/integration/integrationAttempt.js +3 -14
  88. package/dist/integration/integrationSourceApplication.js +4 -12
  89. package/dist/integration/manifestTags.js +2 -2
  90. package/dist/job/durableJob.js +4 -25
  91. package/dist/job/jobAssignmentScope.js +22 -0
  92. package/dist/job/jobOperation.js +16 -0
  93. package/dist/job/jobRunner.js +2 -1
  94. package/dist/job/stepDirectory.js +15 -0
  95. package/dist/kernel/builtinCapabilities.js +15 -11
  96. package/dist/kernel/kernelPorts.js +1 -17
  97. package/dist/lifecycle/exactRunTerminalization.js +5 -10
  98. package/dist/message/globalProviderRetry.js +15 -0
  99. package/dist/message/inputControlResolution.js +3 -4
  100. package/dist/message/message.js +17 -20
  101. package/dist/message/messageContinuation.js +1 -1
  102. package/dist/milestone/milestone.js +11 -0
  103. package/dist/observability/executionAudit.js +19 -0
  104. package/dist/observability/orchestrationMetrics.js +24 -1
  105. package/dist/observability/runtimeIdentity.js +1 -17
  106. package/dist/output/agentConfigurationPresentation.js +2 -0
  107. package/dist/output/timePresentation.js +1 -14
  108. package/dist/plugins/pluginService.js +12 -4
  109. package/dist/release/releaseWorkflowPorts.js +45 -96
  110. package/dist/release/runtimeRelease.js +9 -15
  111. package/dist/repository/gitWorkspace.js +371 -105
  112. package/dist/repository/projectMaintenanceLock.js +98 -82
  113. package/dist/repository/taskBaseFreshness.js +1 -1
  114. package/dist/repository/taskWorkspaceCoordinator.js +82 -144
  115. package/dist/repository/taskWorkspacePreparer.js +113 -47
  116. package/dist/repository/workspaceCleanupInspection.js +187 -0
  117. package/dist/resources/autoResourceGc.js +4 -77
  118. package/dist/resources/liveReferences.js +7 -54
  119. package/dist/resources/resourceDiscovery.js +5 -118
  120. package/dist/resources/resourceGc.js +233 -181
  121. package/dist/resources/resourceRegistrar.js +5 -4
  122. package/dist/resources/resourceRegistry.js +3 -37
  123. package/dist/resources/resourceRegistryStore.js +13 -36
  124. package/dist/resources/sqliteResourceRegistry.js +43 -26
  125. package/dist/review/deltaRecheck.js +1 -1
  126. package/dist/review/reviewAcceptance.js +1 -1
  127. package/dist/review/reviewDecision.js +1 -1
  128. package/dist/review/reviewRound.js +8 -7
  129. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  130. package/dist/runtime/acpProtocol.js +4 -51
  131. package/dist/runtime/acpSession.js +10 -75
  132. package/dist/runtime/acpSessionConfiguration.js +1 -8
  133. package/dist/runtime/agentEndpoint.js +3 -2
  134. package/dist/runtime/agentError.js +5 -3
  135. package/dist/runtime/agentFailureContext.js +43 -0
  136. package/dist/runtime/agentHost.js +42 -25
  137. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  138. package/dist/runtime/codexAppServerRuntime.js +34 -3
  139. package/dist/runtime/codexInteractiveHost.js +2 -2
  140. package/dist/runtime/index.js +0 -1
  141. package/dist/runtime/managedCaller.js +21 -1
  142. package/dist/runtime/providerControl.js +5 -1
  143. package/dist/runtime/providerErrors.js +38 -0
  144. package/dist/runtime/providerRetry.js +198 -0
  145. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  146. package/dist/runtime/runtimeObservation.js +0 -5
  147. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  148. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  149. package/dist/runtime/sessionTokenMetrics.js +15 -5
  150. package/dist/runtime/structuredProviderHost.js +10 -43
  151. package/dist/runtime/taskUsageMetrics.js +275 -0
  152. package/dist/runtime/tmuxAdapters.js +6 -8
  153. package/dist/scheduler/activeRoleRunDelivery.js +19 -7
  154. package/dist/scheduler/leaderWakeupProcessor.js +24 -6
  155. package/dist/scheduler/operatorEvent.js +12 -20
  156. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  157. package/dist/scheduler/ports.js +5 -8
  158. package/dist/scheduler/roleRunLiveness.js +4 -4
  159. package/dist/scheduler/roleRunStall.js +16 -23
  160. package/dist/scheduler/taskExecutionProjection.js +49 -36
  161. package/dist/scheduler/taskObservabilityProjection.js +6 -45
  162. package/dist/scheduler/taskWake.js +5 -10
  163. package/dist/scheduler/wakeReason.js +2 -0
  164. package/dist/scheduler/wakeupQueue.js +2 -4
  165. package/dist/setup/setupCommand.js +1 -1
  166. package/dist/storage/contextRecords.js +106 -0
  167. package/dist/storage/currentTaskStore.js +1 -1
  168. package/dist/storage/homeLayout.js +13 -17
  169. package/dist/storage/migrations/agentFailureContext.js +22 -0
  170. package/dist/storage/migrations/currentInputContract.js +86 -0
  171. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  172. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  173. package/dist/storage/migrations/integrationContinuation.js +5 -4
  174. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  175. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  176. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  177. package/dist/storage/migrations/verificationPolicy.js +74 -0
  178. package/dist/storage/migrations/workItemHistory.js +46 -0
  179. package/dist/storage/persistenceWorker.js +12 -4
  180. package/dist/storage/recordValidation.js +87 -0
  181. package/dist/storage/sqliteSchema.js +173 -1
  182. package/dist/storage/sqliteStore.js +172 -161
  183. package/dist/storage/storageSchema.js +2 -14
  184. package/dist/storage/storageVersions.js +1 -1
  185. package/dist/storage/storeRpc.js +16 -7
  186. package/dist/storage/taskCatalog.js +123 -0
  187. package/dist/storage/taskStore.js +5 -7
  188. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  189. package/dist/task/archiveDiagnostics.js +1 -0
  190. package/dist/task/archivePreflight.js +124 -0
  191. package/dist/task/completionReadiness.js +5 -24
  192. package/dist/task/draftPlan.js +0 -53
  193. package/dist/task/nextAction.js +7 -13
  194. package/dist/task/publicationAdoption.js +56 -0
  195. package/dist/task/publicationReference.js +10 -0
  196. package/dist/task/remoteDelivery.js +31 -16
  197. package/dist/task/remoteDeliveryService.js +89 -0
  198. package/dist/task/taskActivation.js +2 -25
  199. package/dist/task/taskActivationService.js +0 -2
  200. package/dist/task/taskRecordReference.js +0 -1
  201. package/dist/task/taskSubmission.js +3 -14
  202. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  203. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  204. package/dist/telemetry/telemetryWiring.js +4 -3
  205. package/dist/tmux/tmuxManager.js +29 -20
  206. package/dist/verification/gateArtifact.js +15 -24
  207. package/dist/verification/gateArtifactStore.js +14 -7
  208. package/dist/verification/verificationGateService.js +29 -88
  209. package/dist/verification/verificationPlan.js +27 -72
  210. package/dist/web/assets/client/app.js +92 -17
  211. package/dist/web/assets/client/components.js +55 -13
  212. package/dist/web/assets/client/i18n.js +72 -4
  213. package/dist/web/assets/client/taskSurface.js +6 -5
  214. package/dist/web/assets/client/view.js +35 -7
  215. package/dist/web/assets/shell.js +6 -0
  216. package/dist/web/assets/styles/layout.js +7 -0
  217. package/dist/web/webServer.js +12 -3
  218. package/dist/web/webSnapshot.js +14 -85
  219. package/dist/web/webTaskSurface.js +17 -46
  220. package/dist/workItem/workItem.js +1 -12
  221. package/dist/workspace/cleanupInspection.js +63 -0
  222. package/dist/workspace/workItemChangeSetManager.js +110 -50
  223. package/docs/agent-result-consumption.md +4 -0
  224. package/docs/agent-result-consumption.zh-CN.md +3 -0
  225. package/docs/agent-runtime-drivers.md +7 -0
  226. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  227. package/docs/architecture/README.md +2 -0
  228. package/docs/architecture/README.zh-CN.md +3 -1
  229. package/docs/architecture/capabilities-and-resources.md +42 -5
  230. package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
  231. package/docs/managed-turn-and-session-runtime.md +121 -1
  232. package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
  233. package/docs/observability/README.md +62 -0
  234. package/docs/observability/README.zh-CN.md +47 -0
  235. package/docs/plugin-sdk.md +4 -2
  236. package/docs/project-refresh.md +77 -0
  237. package/docs/project-refresh.zh-CN.md +59 -0
  238. package/docs/provider-retry.md +70 -0
  239. package/docs/provider-runtime.md +3 -1
  240. package/docs/provider-runtime.zh-CN.md +4 -2
  241. package/docs/release-workflow.md +190 -7
  242. package/docs/release-workflow.zh-CN.md +139 -5
  243. package/docs/roles-and-configuration.md +29 -0
  244. package/docs/roles-and-configuration.zh-CN.md +21 -0
  245. package/docs/sqlite-control-plane-design.md +16 -3
  246. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  247. package/docs/task-dag-semantics.md +3 -3
  248. package/docs/task-dag-semantics.zh-CN.md +2 -2
  249. package/docs/task-delivery.md +223 -17
  250. package/docs/task-delivery.zh-CN.md +171 -14
  251. package/docs/task-discovery.md +101 -0
  252. package/docs/task-discovery.zh-CN.md +85 -0
  253. package/docs/testing/verification-levels.md +161 -9
  254. package/docs/testing/verification-levels.zh-CN.md +116 -9
  255. package/i18n/README.zh-CN.md +13 -7
  256. package/package.json +1 -1
  257. package/skills/yui-leader/SKILL.md +5 -0
  258. package/skills/yui-leader/references/execution.md +3 -2
  259. package/skills/yui-leader/references/integration.md +9 -5
  260. package/skills/yui-leader/references/planning.md +9 -3
  261. package/skills/yui-operator/SKILL.md +24 -8
  262. package/skills/yui-reviewer/SKILL.md +4 -0
  263. package/skills/yui-runtime/SKILL.md +17 -2
  264. package/skills/yui-runtime/references/publication.md +26 -4
  265. package/skills/yui-runtime/references/recovery.md +71 -0
  266. package/dist/agent/agentRegistry.js +0 -10
  267. package/dist/agentRun/runIdentity.js +0 -22
  268. package/dist/commands/deliveryGuardPreflight.js +0 -30
  269. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  270. package/dist/commands/taskOverviewCommand.js +0 -376
  271. package/dist/completion/completionWizard.js +0 -125
  272. package/dist/context/dispatchContext.js +0 -110
  273. package/dist/context/wakeNotification.js +0 -144
  274. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  275. package/dist/integration/integrationQueueEntry.js +0 -191
  276. package/dist/integration/integrationQueueService.js +0 -810
  277. package/dist/release/fakeReleasePorts.js +0 -55
  278. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  279. package/dist/task/deliveryGuard.js +0 -226
  280. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
package/dist/cli.js CHANGED
@@ -1,120 +1,130 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
- import { recordTaskInterruptResult } from "./message/taskInterrupt.js";
5
- import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
6
4
  import { readFileSync } from "node:fs";
7
5
  import { resolve } from "node:path";
8
6
  import { createInterface } from "node:readline/promises";
9
7
  import { fileURLToPath } from "node:url";
10
8
  import { isDeepStrictEqual } from "node:util";
11
- import { renderCommandHelp } from "./cli/helpRenderer.js";
9
+ import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
10
+ import { recordTaskInterruptResult } from "./message/taskInterrupt.js";
11
+ import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
12
+ import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "./artifacts/artifactCapability.js";
13
+ import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
12
14
  import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
13
- import { routeInvocation } from "./cli/invocationRouter.js";
14
15
  import { renderCompletion } from "./cli/completion.js";
16
+ import { runCompletionWizard } from "./cli/completionWizard.js";
15
17
  import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
16
- import { recordGlobalInterruptResult, recordGlobalSteerResult } from "./message/globalInterrupt.js";
18
+ import { renderCommandHelp } from "./cli/helpRenderer.js";
17
19
  import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/interactiveSelection.js";
18
- import { runCompletionWizard } from "./cli/completionWizard.js";
19
- import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
20
- import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
20
+ import { routeInvocation } from "./cli/invocationRouter.js";
21
+ import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
22
+ import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
23
+ import { requireManagedGlobalCaller } from "./runtime/managedCaller.js";
21
24
  import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
25
+ import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
22
26
  import { runUpdateCommand } from "./cli/updateCommand.js";
27
+ import { createUpdatePorts } from "./cli/updatePorts.js";
23
28
  import { runUpgradeCommand } from "./cli/upgradeCommand.js";
24
- import { formatTimestamp } from "./output/timePresentation.js";
25
- import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
26
- import { renderAgentConfigurationCatalog } from "./output/agentConfigurationPresentation.js";
27
- import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
28
29
  import { runAgentCommand } from "./commands/agentCommands.js";
29
- import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
30
- import { runConfigCommand } from "./commands/configCommands.js";
31
30
  import { runCapabilityCommand } from "./commands/capabilityCommands.js";
32
- import { CONFIG_DOMAINS } from "./config/configCatalog.js";
31
+ import { runConfigCommand } from "./commands/configCommands.js";
33
32
  import { runConfigOverview } from "./commands/configOverview.js";
34
- import { parseControllerCleanupOptions, parseControllerStatusOptions, parseControllerRuntimeSnapshot, renderControllerResourceStatus, renderRuntimeIdentitySection, summarizeDurablePhysicalMismatch, runInteractiveControllerCleanup } from "./commands/controllerCommands.js";
33
+ import { parseControllerCleanupOptions, parseControllerRuntimeSnapshot, parseControllerStatusOptions, renderControllerResourceStatus, renderRuntimeIdentitySection, runInteractiveControllerCleanup, summarizeDurablePhysicalMismatch } from "./commands/controllerCommands.js";
34
+ import { runDurableJobCommand } from "./commands/durableJobCommands.js";
35
35
  import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
36
- import { parseSessionReconcileOptions, parseSessionStopOptions, runSessionReconcileCommand, runSessionStopCommand } from "./commands/sessionCommands.js";
37
- import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliation.js";
36
+ import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
38
37
  import { runJobCommand } from "./commands/jobCommands.js";
39
- import { runDurableJobCommand } from "./commands/durableJobCommands.js";
40
- import { runTelemetryCommand } from "./commands/telemetryCommands.js";
41
- import { runResourcesCommand } from "./commands/resourcesCommands.js";
42
38
  import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
43
- import { runProjectCommand } from "./commands/projectCommands.js";
44
39
  import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
45
- import { assertWorkItemDependenciesCompletedForCommand, requireWorkItemAssignee, dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, previewTaskRoleAgentConfigurationMutation, preflightTaskCompletion, runTaskCommand, planReplicatedWorkItemLanes, validateTaskArchiveRequest } from "./commands/taskCommands.js";
46
- import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./commands/taskRemoteDeliveryCommand.js";
47
- import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
48
- import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
49
- import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
50
- import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
51
- import { taskLocalActor, assertTaskDeliveryAuthority } from "./commands/taskActor.js";
52
- import { saveArtifactCapability, readArtifactCapability, listArtifactsCapability } from "./artifacts/artifactCapability.js";
53
- import { parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, finalizeStoppedTaskExecution, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
54
- import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
40
+ import { runProjectCommand } from "./commands/projectCommands.js";
41
+ import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
42
+ import { runResourcesCommand } from "./commands/resourcesCommands.js";
43
+ import { parseSessionReconcileOptions, parseSessionStopOptions, runSessionReconcileCommand, runSessionStopCommand } from "./commands/sessionCommands.js";
44
+ import { assertTaskDeliveryAuthority, taskLocalActor } from "./task/taskAuthority.js";
45
+ import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
55
46
  import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
47
+ import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, parseTaskArchiveArguments, parseTaskCompletionRequest, planReplicatedWorkItemLanes, preflightTaskCompletion, preserveReviewRoundWorkspace, previewTaskRoleAgentConfigurationMutation, requireWorkItemAssignee, runTaskCommand, validateTaskArchiveRequest } from "./commands/taskCommands.js";
48
+ import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
49
+ import { finalizeStoppedTaskExecution, parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
50
+ import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
56
51
  import { runTaskOverlapCommand } from "./commands/taskOverlapCommands.js";
57
- import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
52
+ import { runTaskPublicationAdoptCommand } from "./commands/taskPublicationAdoptCommand.js";
53
+ import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
54
+ import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
58
55
  import { runTaskWorkspaceCommand } from "./commands/taskWorkspaceCommands.js";
56
+ import { runTelemetryCommand } from "./commands/telemetryCommands.js";
59
57
  import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
60
- import { createUpdatePorts } from "./cli/updatePorts.js";
61
- import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
62
- import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
63
- import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
64
- import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
65
- import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
66
- import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
67
- import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
68
- import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
69
- import { FileCompletionManager, resolveCliIdentity } from "./completion/fileCompletionManager.js";
58
+ import { FileCompletionManager } from "./completion/fileCompletionManager.js";
59
+ import { CONFIG_DOMAINS } from "./config/configCatalog.js";
60
+ import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
61
+ import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
70
62
  import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
71
- import { callController, ControllerClientError } from "./core/controllerClient.js";
72
63
  import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
64
+ import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
73
65
  import { cleanControllerResource } from "./controller/resourceCleanupLinux.js";
74
66
  import { scanControllerResourceInventory } from "./controller/resourceInventoryLinux.js";
75
- import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
76
- import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
77
67
  import { runRuntimeObservationHookCommand } from "./controller/runtimeObservationHook.js";
68
+ import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
69
+ import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliation.js";
70
+ import { callController, ControllerClientError } from "./core/controllerClient.js";
78
71
  import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
79
72
  import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
73
+ import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
80
74
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
81
- import { AGENT_HOST_CONTROL_PROTOCOL, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl, sendAgentHostSteerControl, sendAgentHostCancelControl, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "./runtime/agentHost.js";
82
- import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
75
+ import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
76
+ import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
77
+ import { recordGlobalInterruptResult, recordGlobalSteerResult } from "./message/globalInterrupt.js";
78
+ import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth } from "./observability/runtimeIdentity.js";
79
+ import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
80
+ import { renderAgentConfigurationCatalog } from "./output/agentConfigurationPresentation.js";
81
+ import { parseTaskAgentCapabilityQuery } from "./commands/taskAgentCapabilities.js";
82
+ import { capabilityReaderIdentity } from "./controller/agentCapabilities.js";
83
+ import { formatTimestamp } from "./output/timePresentation.js";
84
+ import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
85
+ import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
86
+ import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
87
+ import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
88
+ import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
89
+ import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
83
90
  import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
84
91
  import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
85
92
  import { snapshotWorkItemCandidate } from "./repository/workItemCandidateSnapshot.js";
93
+ import { assessDeltaRecheck } from "./review/deltaRecheck.js";
94
+ import { isCompletedTaskReviewEvidence } from "./review/reviewAcceptance.js";
95
+ import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
96
+ import { resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractResolution.js";
97
+ import { AGENT_HOST_CONTROL_PROTOCOL, foldInterruptLiveReceipt, foldSteerLiveReceipt, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl, sendAgentHostCancelControl, sendAgentHostSteerControl } from "./runtime/agentHost.js";
98
+ import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
99
+ import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/managedCaller.js";
100
+ import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
101
+ import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
102
+ import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
103
+ import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
86
104
  import { inspectStorageSchema } from "./storage/storageSchema.js";
87
- import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
88
105
  import { resolveYuiHome } from "./storage/taskStore.js";
89
- import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
106
+ import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
107
+ import { inspectTaskArchive, renderTaskArchivePreflight } from "./task/archivePreflight.js";
108
+ import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./task/remoteDeliveryService.js";
90
109
  import { resolveTaskRecordReference } from "./task/taskRecordReference.js";
91
- import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
110
+ import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
92
111
  import { NodeCommandExecutor } from "./tmux/commandExecutor.js";
93
112
  import { TmuxManager } from "./tmux/tmuxManager.js";
94
- import { WorkItemChangeSetManager } from "./workspace/workItemChangeSetManager.js";
95
- import { workspaceProjectEntry } from "./worktree/managedWorkspace.js";
96
- import { parseWebCommandOptions } from "./web/webServer.js";
97
- import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
98
- import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
99
- import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
100
113
  import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
101
- import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
102
- import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
103
- import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/managedCaller.js";
104
- import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
105
- import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
106
- import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
107
- import { resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractResolution.js";
108
- import { assessDeltaRecheck } from "./review/deltaRecheck.js";
109
- import { isCompletedTaskReviewEvidence } from "./review/reviewAcceptance.js";
110
- import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
114
+ import { parseWebCommandOptions } from "./web/webServer.js";
111
115
  import { currentWorkItemExecutionGroup } from "./workItem/workItem.js";
116
+ import { CleanupInspectionError } from "./workspace/cleanupInspection.js";
117
+ import { WorkItemChangeSetManager } from "./workspace/workItemChangeSetManager.js";
118
+ import { workspaceProjectEntry } from "./worktree/managedWorkspace.js";
112
119
  const VERSION = YUI_VERSION;
113
120
  const taskFinalReviewInvocation = extractTaskFinalReviewRequest(process.argv.slice(2));
114
121
  const rawArgs = [...taskFinalReviewInvocation.args];
115
122
  const jsonOutput = rawArgs.includes("--json");
116
123
  const args = normalizeAliases(jsonOutput ? rawArgs.filter((argument) => argument !== "--json") : rawArgs);
117
124
  void main().catch((error) => {
125
+ if (error instanceof CleanupInspectionError) {
126
+ error = cleanupCliError(error, error.checks[0]?.resource ?? "workspace");
127
+ }
118
128
  if (error instanceof CliError) {
119
129
  const rendered = jsonOutput
120
130
  ? JSON.stringify({ ok: false, code: error.code, message: error.message, details: error.details })
@@ -200,6 +210,18 @@ export async function main() {
200
210
  if (args[0] === "version")
201
211
  throw usageError("Version usage: yui version");
202
212
  const { contract: taskFinalReviewContract, verifiedStore } = await preflightManagedTaskControlPlane();
213
+ assertTaskInvocationScope(args, process.env);
214
+ if (["config", "resources"].includes(args[0] ?? "") && (managedInvocation
215
+ || process.env.YUI_ROLE !== undefined || process.env.YUI_AGENT_ID !== undefined
216
+ || process.env.YUI_NATIVE_SESSION_ID !== undefined)) {
217
+ const ownedStore = verifiedStore === undefined ? openCurrentTaskStore(home) : undefined;
218
+ try {
219
+ assertConfigurationAuthority(args, verifiedStore ?? ownedStore, process.env);
220
+ }
221
+ finally {
222
+ ownedStore?.close();
223
+ }
224
+ }
203
225
  if (args[0] === "update") {
204
226
  if (jsonOutput)
205
227
  throw usageError("Update does not support --json.");
@@ -450,38 +472,32 @@ export async function main() {
450
472
  scope: options.scope,
451
473
  environment: process.env
452
474
  });
453
- if (resolveStatusIdentityEnabled(process.env)) {
454
- // Issue 11 read-only identity/metrics section. Every fact is observed;
455
- // missing producers render `unsupported` and storage contradictions
456
- // fail closed with exit code 5.
457
- const cliEntry = fileURLToPath(import.meta.url);
458
- const packageRoot = resolve(cliEntry, "..", "..");
459
- const build = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(packageRoot, cliEntry, process.env));
460
- const storage = collectStorageIdentity(home);
461
- const droppedEvents = countDroppedInboxEvents(home);
462
- let runtime;
463
- try {
464
- const result = await callController(home, "controller.status", {}, { timeoutMs: 2_000 });
465
- runtime = parseControllerRuntimeSnapshot(result, droppedEvents);
466
- }
467
- catch {
468
- runtime = { source: "unsupported", droppedEvents };
469
- }
470
- const mismatch = summarizeDurablePhysicalMismatch(snapshot);
471
- const identitySection = renderRuntimeIdentitySection({
472
- build,
473
- storage,
474
- runtime,
475
- mismatch,
476
- inventoryRssBytes: snapshot.summary.rssBytes
477
- });
478
- emit(`${renderControllerResourceStatus(snapshot, options.verbose)}\n\n${identitySection}`, false, { ...snapshot, identity: { build, storage, runtime, mismatch } });
479
- // The exact current storage contract fails closed on contradictions.
480
- if (evaluateStorageHealth(storage).status === "fail")
481
- process.exitCode = 5;
482
- return;
475
+ // Status always includes identity/health. Missing producers stay
476
+ // `unsupported`; storage contradictions retain the nonzero health exit.
477
+ const cliEntry = fileURLToPath(import.meta.url);
478
+ const packageRoot = resolve(cliEntry, "..", "..");
479
+ const build = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(packageRoot, cliEntry, process.env));
480
+ const storage = collectStorageIdentity(home);
481
+ const droppedEvents = countDroppedInboxEvents(home);
482
+ let runtime;
483
+ try {
484
+ const result = await callController(home, "controller.status", {}, { timeoutMs: 2_000 });
485
+ runtime = parseControllerRuntimeSnapshot(result, droppedEvents);
483
486
  }
484
- emit(renderControllerResourceStatus(snapshot, options.verbose), false, snapshot);
487
+ catch {
488
+ runtime = { source: "unsupported", droppedEvents };
489
+ }
490
+ const mismatch = summarizeDurablePhysicalMismatch(snapshot);
491
+ const identitySection = renderRuntimeIdentitySection({
492
+ build,
493
+ storage,
494
+ runtime,
495
+ mismatch,
496
+ inventoryRssBytes: snapshot.summary.rssBytes
497
+ });
498
+ emit(`${renderControllerResourceStatus(snapshot, options.verbose)}\n\n${identitySection}`, false, { ...snapshot, identity: { build, storage, runtime, mismatch } });
499
+ if (evaluateStorageHealth(storage).status === "fail")
500
+ process.exitCode = 5;
485
501
  return;
486
502
  }
487
503
  if (method === "cleanup") {
@@ -522,7 +538,10 @@ export async function main() {
522
538
  throw usageError("Controller usage: yui controller status [--all] [--verbose] | "
523
539
  + "cleanup [--all] | stop | restart.");
524
540
  }
525
- validateCurrentTaskStore(home);
541
+ // Stopping an exactly identified Controller is a recovery operation:
542
+ // malformed domain records must not prevent quiescing its process.
543
+ if (method === "restart")
544
+ validateCurrentTaskStore(home);
526
545
  const controllerMethod = method;
527
546
  const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
528
547
  // A pre-fix updater cannot pass the owner environment variable to the
@@ -574,6 +593,17 @@ export async function main() {
574
593
  emit("Cancelled.");
575
594
  return;
576
595
  }
596
+ assertTaskInvocationScope(resolved, process.env);
597
+ if (resolved[0] === "task" && resolved[1] === "role" && resolved[2] === "capabilities") {
598
+ const query = parseTaskAgentCapabilityQuery(resolved.slice(3));
599
+ const result = await callController(home, "task.role-capabilities", {
600
+ ...query, reader: capabilityReaderIdentity(process.env)
601
+ }, { timeoutMs: 15_000 });
602
+ emit(`Configuration: ${result.context.selection} ${result.context.taskId}/${result.context.roleName}\n`
603
+ + "Discovery uses the Controller's current native account environment; settings files and secret values are not historical snapshots.\n"
604
+ + renderAgentConfigurationCatalog(result), false, result);
605
+ return;
606
+ }
577
607
  const validateAgentConfiguration = await preflightAgentConfigurationMutation(resolved, store, catalogs);
578
608
  const executor = new NodeCommandExecutor();
579
609
  const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), executor, {
@@ -582,1244 +612,1262 @@ export async function main() {
582
612
  terminalInput: process.stdin,
583
613
  onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
584
614
  });
585
- const schedulerStore = new FileSchedulerStoreAdapter(store, openSchedulerTelemetry(home, store.getConfig()));
586
- const planner = new FileRoleLaunchPlanner(home, store, { environment: process.env });
587
- const workspacePreparer = new FileTaskWorkspacePreparer(home, store);
588
- const runtime = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
589
- environment: process.env,
590
- onError: (error) => {
591
- const message = error instanceof Error ? error.message : String(error);
592
- process.stderr.write(`Controller runtime error: ${message}\n`);
593
- }
594
- });
595
- const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
596
- if (resolved[0] === "web") {
597
- if (managedInvocation || process.env.YUI_ROLE || process.env.YUI_NATIVE_SESSION_ID) {
598
- throw usageError("The Web user ingress must be started from a local user terminal, not a managed Session.");
599
- }
600
- if (resolved.length === 2 && (resolved[1] === "--status" || resolved[1] === "--stop")) {
601
- const status = await callController(home, "web.status", {});
602
- if (resolved[1] === "--status")
603
- emit(status ? `Yui web control room: ${status.url}\n` : "Web is not running.\n", false, status);
604
- else {
605
- if (status)
606
- await callController(home, "web.stop", { id: status.id });
607
- emit("Web listener stopped; Controller and Agents are unchanged.\n");
615
+ const telemetry = openSchedulerTelemetry(home, store.getConfig());
616
+ try {
617
+ const schedulerStore = new FileSchedulerStoreAdapter(store, telemetry);
618
+ const planner = new FileRoleLaunchPlanner(home, store, { environment: process.env });
619
+ const workspacePreparer = new FileTaskWorkspacePreparer(home, store);
620
+ const runtime = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
621
+ environment: process.env,
622
+ onError: (error) => {
623
+ const message = error instanceof Error ? error.message : String(error);
624
+ process.stderr.write(`Controller runtime error: ${message}\n`);
608
625
  }
609
- return;
610
- }
611
- if (jsonOutput)
612
- throw usageError("Web start does not support --json; use --status.");
613
- const options = parseWebCommandOptions(resolved.slice(1));
614
- const id = randomUUID();
615
- await callController(home, "web.start", { ...options, id });
616
- const displayHost = options.host === "::1" ? "[::1]" : options.host;
617
- process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
618
- await new Promise((resolve) => {
619
- const stop = () => {
620
- process.off("SIGINT", stop);
621
- process.off("SIGTERM", stop);
622
- resolve();
623
- };
624
- process.once("SIGINT", stop);
625
- process.once("SIGTERM", stop);
626
626
  });
627
- await callController(home, "web.stop", { id });
628
- return;
629
- }
630
- if (resolved[0] === "config") {
631
- const domain = resolved[1];
632
- const roleOptions = {
633
- yuiHome: home,
634
- env: process.env
635
- };
636
- if (domain === "show") {
637
- const result = runConfigOverview(resolved.slice(2), store, process.env, resolveCliIdentity(process.env), roleOptions);
638
- emit(result.output, false, result.data);
639
- return;
640
- }
641
- if (CONFIG_DOMAINS.includes(domain ?? "")) {
642
- const configDomain = domain;
643
- const domainArgs = resolved.slice(2);
644
- const result = runConfigCommand(configDomain, domainArgs, store);
645
- if (domainArgs[0] === "set"
646
- && domainArgs[1] === "reconciliation-interval-seconds") {
647
- const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
648
- emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
627
+ const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
628
+ if (resolved[0] === "web") {
629
+ if (managedInvocation || process.env.YUI_ROLE || process.env.YUI_NATIVE_SESSION_ID) {
630
+ throw usageError("The Web user ingress must be started from a local user terminal, not a managed Session.");
631
+ }
632
+ if (resolved.length === 2 && (resolved[1] === "--status" || resolved[1] === "--stop")) {
633
+ const status = await callController(home, "web.status", {});
634
+ if (resolved[1] === "--status")
635
+ emit(status ? `Yui web control room: ${status.url}\n` : "Web is not running.\n", false, status);
636
+ else {
637
+ if (status)
638
+ await callController(home, "web.stop", { id: status.id });
639
+ emit("Web listener stopped; Controller and Agents are unchanged.\n");
640
+ }
649
641
  return;
650
642
  }
651
- emit(result.output, false, result.data);
643
+ if (jsonOutput)
644
+ throw usageError("Web start does not support --json; use --status.");
645
+ const options = parseWebCommandOptions(resolved.slice(1));
646
+ const id = randomUUID();
647
+ await callController(home, "web.start", { ...options, id });
648
+ const displayHost = options.host === "::1" ? "[::1]" : options.host;
649
+ process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
650
+ await new Promise((resolve) => {
651
+ const stop = () => {
652
+ process.off("SIGINT", stop);
653
+ process.off("SIGTERM", stop);
654
+ resolve();
655
+ };
656
+ process.once("SIGINT", stop);
657
+ process.once("SIGTERM", stop);
658
+ });
659
+ await callController(home, "web.stop", { id });
652
660
  return;
653
661
  }
654
- if (domain === "agent") {
655
- const agentArgs = resolved.slice(2);
656
- if (agentArgs[0] === "capabilities") {
657
- if (agentArgs.length !== 2) {
658
- throw usageError("Agent capabilities usage: yui config agent capabilities <agent-id>");
659
- }
660
- const agent = store.getConfiguredAgent(agentArgs[1] ?? "");
661
- if (agent === null)
662
- throw agentNotFound(agentArgs[1] ?? "");
663
- const result = await catalogs.resolve({
664
- agent,
665
- cwd: store.getConfig().defaultWorkspace ?? process.cwd()
666
- });
667
- emit(renderAgentConfigurationCatalog(result), false, result);
662
+ if (resolved[0] === "config") {
663
+ const domain = resolved[1];
664
+ const roleOptions = {
665
+ yuiHome: home,
666
+ env: process.env
667
+ };
668
+ if (domain === "show") {
669
+ const result = runConfigOverview(resolved.slice(2), store, process.env, roleOptions);
670
+ emit(result.output, false, result.data);
671
+ return;
672
+ }
673
+ if (CONFIG_DOMAINS.includes(domain ?? "")) {
674
+ const configDomain = domain;
675
+ const domainArgs = resolved.slice(2);
676
+ const result = runConfigCommand(configDomain, domainArgs, store);
677
+ if (domainArgs[0] === "set"
678
+ && domainArgs[1] === "reconciliation-interval-seconds") {
679
+ const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
680
+ emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
681
+ return;
682
+ }
683
+ emit(result.output, false, result.data);
668
684
  return;
669
685
  }
670
- const affectedAgentId = agentArgs[1];
671
- const previousAgent = typeof affectedAgentId === "string"
672
- ? store.getConfiguredAgent(affectedAgentId)
673
- : null;
674
- const output = runAgentCommand(agentArgs, store);
675
- if (agentArgs[0] === "add"
676
- || agentArgs[0] === "update"
677
- || agentArgs[0] === "remove") {
678
- const currentAgent = typeof affectedAgentId === "string"
686
+ if (domain === "agent") {
687
+ const agentArgs = resolved.slice(2);
688
+ if (agentArgs[0] === "capabilities") {
689
+ if (agentArgs.length !== 2 && !(agentArgs.length === 3 && agentArgs[2] === "--refresh")) {
690
+ throw usageError("Agent capabilities usage: yui config agent capabilities <agent-id> [--refresh]");
691
+ }
692
+ const agent = store.getConfiguredAgent(agentArgs[1] ?? "");
693
+ if (agent === null)
694
+ throw agentNotFound(agentArgs[1] ?? "");
695
+ const result = await catalogs.resolve({
696
+ agent,
697
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd(),
698
+ refresh: agentArgs[2] === "--refresh"
699
+ });
700
+ emit(renderAgentConfigurationCatalog(result), false, result);
701
+ return;
702
+ }
703
+ const affectedAgentId = agentArgs[1];
704
+ const previousAgent = typeof affectedAgentId === "string"
679
705
  ? store.getConfiguredAgent(affectedAgentId)
680
706
  : null;
681
- const capabilityNotice = currentAgent !== null
682
- && (agentArgs[0] === "add" || agentArgs[0] === "update")
683
- ? renderAgentConfigurationResolutionNotice(await catalogs.resolve({
684
- agent: currentAgent,
685
- cwd: store.getConfig().defaultWorkspace ?? process.cwd()
686
- }))
687
- : "";
688
- const scope = agentEnvironmentRefreshScope(previousAgent, currentAgent, store.listConfiguredAgents());
689
- const refresh = await refreshRunningFileTaskControllerEnvironment(home, store, process.env, scope);
690
- emit(withControllerRefreshWarning(`${output.trimEnd()}${capabilityNotice.length === 0 ? "\n" : `\n${capabilityNotice}`}`, refresh, "Agent environment"));
707
+ const output = runAgentCommand(agentArgs, store);
708
+ if (agentArgs[0] === "add"
709
+ || agentArgs[0] === "update"
710
+ || agentArgs[0] === "remove") {
711
+ const currentAgent = typeof affectedAgentId === "string"
712
+ ? store.getConfiguredAgent(affectedAgentId)
713
+ : null;
714
+ const capabilityNotice = currentAgent !== null
715
+ && (agentArgs[0] === "add" || agentArgs[0] === "update")
716
+ ? renderAgentConfigurationResolutionNotice(await catalogs.resolve({
717
+ agent: currentAgent,
718
+ cwd: store.getConfig().defaultWorkspace ?? process.cwd()
719
+ }))
720
+ : "";
721
+ const scope = agentEnvironmentRefreshScope(previousAgent, currentAgent, store.listConfiguredAgents());
722
+ const refresh = await refreshRunningFileTaskControllerEnvironment(home, store, process.env, scope);
723
+ emit(withControllerRefreshWarning(`${output.trimEnd()}${capabilityNotice.length === 0 ? "\n" : `\n${capabilityNotice}`}`, refresh, "Agent environment"));
724
+ return;
725
+ }
726
+ emit(output);
691
727
  return;
692
728
  }
693
- emit(output);
694
- return;
695
- }
696
- if (domain === "profile") {
697
- const result = runProfileCommand(resolved.slice(2), store, () => new Date(), validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration });
698
- emit(result.output, false, result.data);
699
- return;
700
- }
701
- if (domain === "role") {
702
- const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
703
- if (typeof result !== "string") {
704
- throw new Error("Config Role commands cannot enter a runtime Session.");
729
+ if (domain === "profile") {
730
+ const result = runProfileCommand(resolved.slice(2), store, () => new Date(), validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration });
731
+ emit(result.output, false, result.data);
732
+ return;
705
733
  }
706
- emit(result);
707
- return;
734
+ if (domain === "role") {
735
+ const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
736
+ if (typeof result !== "string") {
737
+ throw new Error("Config Role commands cannot enter a runtime Session.");
738
+ }
739
+ emit(result);
740
+ return;
741
+ }
742
+ throw usageError(`Unknown configuration domain: ${domain ?? ""}.`);
708
743
  }
709
- throw usageError(`Unknown configuration domain: ${domain ?? ""}.`);
710
- }
711
- if (resolved[0] === "project") {
712
- const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
713
- emit(result.output, false, result.data);
714
- return;
715
- }
716
- if (resolved[0] === "session") {
717
- if (resolved[1] === "stop") {
718
- const options = parseSessionStopOptions(resolved.slice(2));
719
- const result = await runSessionStopCommand({
720
- options,
721
- runtime: {
722
- beginMaintenance: () => acquireHandoverLock(home),
723
- snapshot: () => ({
724
- candidates: schedulerStore.listRuntimeSessionCandidates(),
725
- dormant: schedulerStore.listDormantRuntimeOwners()
726
- }),
727
- drainController: () => runtime.drainController(),
728
- stopController: () => stopFileTaskController(home, {
729
- environment: process.env
730
- }),
731
- startController: async () => {
732
- await ensureFileTaskController(home, { environment: process.env });
733
- },
734
- stopDormantSession: (candidate) => runtime.stopDormantSession(candidate)
735
- },
736
- environment: process.env
737
- });
738
- process.exitCode = result.exitCode;
744
+ if (resolved[0] === "project") {
745
+ const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
739
746
  emit(result.output, false, result.data);
740
747
  return;
741
748
  }
742
- const roleOptions = {
743
- yuiHome: home,
744
- env: process.env,
745
- jsonOutput
746
- };
747
- const sessionArgs = resolved[1] === "enter" || resolved[1] === "context"
748
- ? [resolved[1], ...resolved.slice(2)]
749
- : ["session", resolved[1] ?? "", ...resolved.slice(2)];
750
- const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
751
- if (typeof result === "string") {
752
- emit(result);
753
- return;
754
- }
755
- // The session surface (record/replace/enter/context) only ever yields the
756
- // enter control; the live input actions live under the top-level `role`
757
- // command below, never here.
758
- if (result.kind !== "enter") {
759
- throw new Error("Session commands cannot perform a live input control.");
760
- }
761
- await ensureFileTaskController(home, { environment: process.env });
762
- await runtime.prepareGlobalRoleEnter(result.role.name);
763
- tmux.attachRole("operator", result.role.name, "auto");
764
- return;
765
- }
766
- if (resolved[0] === "role") {
767
- // decision-3 §7 CLI grammar: the durable Global input actions are top-level
768
- // `role message queue|steer <global-role> …` and `role interrupt
769
- // <global-role> …`, distinct from `config role …` (desired configuration)
770
- // and `session …` (native session lifecycle). Core persists the durable
771
- // Global-owned Message, proves owner/target/capability/writer-fence from
772
- // durable state, and returns either a string disposition (queued,
773
- // idempotent-replay, or an explicit not-steered/not-interrupted failure with
774
- // its exact code) or a resolved live intent. The CLI performs at most one
775
- // native edge with scope "global" and taskId omitted — the same shared
776
- // resolver and transport as a Task Role, never a fabricated Task.
777
- let globalInputFailure;
778
- const roleOptions = {
779
- yuiHome: home,
780
- env: process.env,
781
- jsonOutput,
782
- onInputFailure: failure => { globalInputFailure = failure; }
783
- };
784
- const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
785
- if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
786
- await callController(home, "scheduler.signal", {
787
- key: `global-role:${encodeURIComponent(resolved[3])}`
788
- }).catch(() => { });
789
- }
790
- if (typeof result === "string") {
791
- if (globalInputFailure !== undefined) {
792
- emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
749
+ if (resolved[0] === "session") {
750
+ if (resolved[1] === "stop") {
751
+ const options = parseSessionStopOptions(resolved.slice(2));
752
+ const result = await runSessionStopCommand({
753
+ options,
754
+ runtime: {
755
+ beginMaintenance: () => acquireHandoverLock(home),
756
+ snapshot: () => ({
757
+ candidates: schedulerStore.listRuntimeSessionCandidates(),
758
+ dormant: schedulerStore.listDormantRuntimeOwners()
759
+ }),
760
+ drainController: () => runtime.drainController(),
761
+ stopController: () => stopFileTaskController(home, {
762
+ environment: process.env
763
+ }),
764
+ startController: async () => {
765
+ await ensureFileTaskController(home, { environment: process.env });
766
+ },
767
+ stopDormantSession: (candidate) => runtime.stopDormantSession(candidate)
768
+ },
769
+ environment: process.env
770
+ });
771
+ process.exitCode = result.exitCode;
772
+ emit(result.output, false, result.data);
793
773
  return;
794
774
  }
795
- emit(result);
775
+ const roleOptions = {
776
+ yuiHome: home,
777
+ env: process.env,
778
+ jsonOutput
779
+ };
780
+ const sessionArgs = resolved[1] === "enter" || resolved[1] === "context"
781
+ ? [resolved[1], ...resolved.slice(2)]
782
+ : ["session", resolved[1] ?? "", ...resolved.slice(2)];
783
+ const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
784
+ if (typeof result === "string") {
785
+ emit(result);
786
+ return;
787
+ }
788
+ // The session surface (record/replace/enter/context) only ever yields the
789
+ // enter control; the live input actions live under the top-level `role`
790
+ // command below, never here.
791
+ if (result.kind !== "enter") {
792
+ throw new Error("Session commands cannot perform a live input control.");
793
+ }
794
+ await ensureFileTaskController(home, { environment: process.env });
795
+ await runtime.prepareGlobalRoleEnter(result.role.name);
796
+ tmux.attachRole("operator", result.role.name, "auto");
796
797
  return;
797
798
  }
798
- if (result.kind === "input-steer") {
799
- // Core already persisted the durable Global Message and proved target +
800
- // capability + writer fence from the Global Role's own Session set. This
801
- // is the single live edge: one native steer of the exact current Turn,
802
- // scope "global", no retarget and no fallback to interrupt or queue.
803
- await ensureFileTaskController(home, { environment: process.env });
804
- let control;
805
- try {
806
- control = await sendAgentHostSteerControl({
807
- home,
808
- scope: "global",
799
+ if (resolved[0] === "role") {
800
+ // decision-3 §7 CLI grammar: the durable Global input actions are top-level
801
+ // `role message queue|steer <global-role> …` and `role interrupt
802
+ // <global-role> …`, distinct from `config role …` (desired configuration)
803
+ // and `session …` (native session lifecycle). Core persists the durable
804
+ // Global-owned Message, proves owner/target/capability/writer-fence from
805
+ // durable state, and returns either a string disposition (queued,
806
+ // idempotent-replay, or an explicit not-steered/not-interrupted failure with
807
+ // its exact code) or a resolved live intent. The CLI performs at most one
808
+ // native edge with scope "global" and taskId omitted — the same shared
809
+ // resolver and transport as a Task Role, never a fabricated Task.
810
+ let globalInputFailure;
811
+ const roleOptions = {
812
+ yuiHome: home,
813
+ env: process.env,
814
+ jsonOutput,
815
+ onInputFailure: failure => { globalInputFailure = failure; }
816
+ };
817
+ const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
818
+ if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
819
+ await callController(home, "scheduler.signal", {
820
+ key: `global-role:${encodeURIComponent(resolved[3])}`
821
+ }).catch(() => { });
822
+ }
823
+ if (typeof result === "string") {
824
+ if (globalInputFailure !== undefined) {
825
+ emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
826
+ return;
827
+ }
828
+ emit(result);
829
+ return;
830
+ }
831
+ if (result.kind === "input-steer") {
832
+ // Core already persisted the durable Global Message and proved target +
833
+ // capability + writer fence from the Global Role's own Session set. This
834
+ // is the single live edge: one native steer of the exact current Turn,
835
+ // scope "global", no retarget and no fallback to interrupt or queue.
836
+ await ensureFileTaskController(home, { environment: process.env });
837
+ let control;
838
+ try {
839
+ control = await sendAgentHostSteerControl({
840
+ home,
841
+ scope: "global",
842
+ roleName: result.roleName,
843
+ control: {
844
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
845
+ type: "steer-turn",
846
+ nativeSessionId: result.target.nativeSessionId,
847
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
848
+ authority: result.target.authority,
849
+ run: { attemptId: result.receiptId, boundedText: result.text }
850
+ }
851
+ });
852
+ }
853
+ catch (error) {
854
+ recordGlobalSteerResult(store, result.roleName, result.messageId, {
855
+ state: "steer-unknown", outcome: "pending",
856
+ detail: error instanceof Error ? error.message : String(error)
857
+ });
858
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
859
+ + `${error instanceof Error ? error.message : String(error)}. `
860
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
861
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
862
+ + "do not reissue the same input under a new requestId or a different action.");
863
+ }
864
+ const steer = foldSteerLiveReceipt(control);
865
+ recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
866
+ if (steer.state !== "steered") {
867
+ emitControlFailure(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_NOT_DELIVERED", { roleName: result.roleName, messageId: result.messageId, steer });
868
+ return;
869
+ }
870
+ emit(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), false, {
809
871
  roleName: result.roleName,
810
- control: {
811
- protocol: AGENT_HOST_CONTROL_PROTOCOL,
812
- type: "steer-turn",
813
- nativeSessionId: result.target.nativeSessionId,
814
- nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
815
- authority: result.target.authority,
816
- run: { attemptId: result.receiptId, boundedText: result.text }
817
- }
872
+ messageId: result.messageId, steer
818
873
  });
874
+ return;
819
875
  }
820
- catch (error) {
821
- recordGlobalSteerResult(store, result.roleName, result.messageId, {
822
- state: "steer-unknown", outcome: "pending",
823
- detail: error instanceof Error ? error.message : String(error)
876
+ if (result.kind === "input-interrupt") {
877
+ // The single live edge for a Global interrupt: one native cancel of the
878
+ // exact current Turn, scope "global". Never a kill/restart/detach. Any
879
+ // then-handoff was already claimed durably by Core (an existing Global
880
+ // Message owned by this Role) and is delivered once by the ordinary
881
+ // continuation path after this Turn reaches a proven terminal.
882
+ await ensureFileTaskController(home, { environment: process.env });
883
+ let control;
884
+ try {
885
+ control = await sendAgentHostCancelControl({
886
+ home,
887
+ scope: "global",
888
+ roleName: result.roleName,
889
+ control: {
890
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
891
+ type: "cancel",
892
+ nativeOnly: true,
893
+ nativeSessionId: result.target.nativeSessionId,
894
+ attemptId: result.target.attemptId,
895
+ authority: result.target.authority
896
+ }
897
+ });
898
+ }
899
+ catch (error) {
900
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, {
901
+ state: "interrupt-unknown", outcome: "cancel-requested",
902
+ detail: error instanceof Error ? error.message : String(error)
903
+ });
904
+ throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
905
+ + `${error instanceof Error ? error.message : String(error)}. `
906
+ + "No process was killed; re-read the Session before retrying.");
907
+ }
908
+ const interrupt = foldInterruptLiveReceipt(control);
909
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
910
+ if (interrupt.state !== "interrupt-requested") {
911
+ emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
912
+ return;
913
+ }
914
+ emit(interruptReceiptOutput(result.output, result.roleName, interrupt), false, {
915
+ roleName: result.roleName,
916
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
917
+ interrupt
824
918
  });
825
- throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
826
- + `${error instanceof Error ? error.message : String(error)}. `
827
- + "The Message is retained and its outcome is recorded from the Host; whether the "
828
- + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
829
- + "do not reissue the same input under a new requestId or a different action.");
830
- }
831
- const steer = foldSteerLiveReceipt(control);
832
- recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
833
- if (steer.state !== "steered") {
834
- emitControlFailure(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_NOT_DELIVERED", { roleName: result.roleName, messageId: result.messageId, steer });
835
919
  return;
836
920
  }
837
- emit(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), false, {
838
- roleName: result.roleName,
839
- messageId: result.messageId, steer
840
- });
841
- return;
921
+ if (result.kind !== "enter") {
922
+ throw new Error("Role command returned an invalid control result.");
923
+ }
924
+ // A top-level `role` command never enters a runtime Session; that is
925
+ // `session enter`.
926
+ throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
842
927
  }
843
- if (result.kind === "input-interrupt") {
844
- // The single live edge for a Global interrupt: one native cancel of the
845
- // exact current Turn, scope "global". Never a kill/restart/detach. Any
846
- // then-handoff was already claimed durably by Core (an existing Global
847
- // Message owned by this Role) and is delivered once by the ordinary
848
- // continuation path after this Turn reaches a proven terminal.
849
- await ensureFileTaskController(home, { environment: process.env });
850
- let control;
851
- try {
852
- control = await sendAgentHostCancelControl({
853
- home,
854
- scope: "global",
855
- roleName: result.roleName,
856
- control: {
857
- protocol: AGENT_HOST_CONTROL_PROTOCOL,
858
- type: "cancel",
859
- nativeOnly: true,
860
- nativeSessionId: result.target.nativeSessionId,
861
- attemptId: result.target.attemptId,
862
- authority: result.target.authority
863
- }
864
- });
928
+ if (resolved[0] === "operator") {
929
+ if (resolved[1] === "enter") {
930
+ if (resolved.length !== 2)
931
+ throw usageError("Operator enter usage: yui operator enter.");
932
+ await ensureFileTaskController(home, { environment: process.env });
933
+ await runtime.prepareGlobalRoleEnter("operator");
934
+ tmux.attachRole("operator", "operator", "auto");
935
+ return;
865
936
  }
866
- catch (error) {
867
- recordGlobalInterruptResult(store, result.roleName, result.receiptId, {
868
- state: "interrupt-unknown", outcome: "cancel-requested",
869
- detail: error instanceof Error ? error.message : String(error)
870
- });
871
- throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
872
- + `${error instanceof Error ? error.message : String(error)}. `
873
- + "No process was killed; re-read the Session before retrying.");
874
- }
875
- const interrupt = foldInterruptLiveReceipt(control);
876
- recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
877
- if (interrupt.state !== "interrupt-requested") {
878
- emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
937
+ const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
938
+ if (result.kind === "output") {
939
+ emit(result.output, false, result.data);
879
940
  return;
880
941
  }
881
- emit(interruptReceiptOutput(result.output, result.roleName, interrupt), false, {
882
- roleName: result.roleName,
883
- ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
884
- interrupt
885
- });
886
- return;
887
- }
888
- if (result.kind !== "enter") {
889
- throw new Error("Role command returned an invalid control result.");
890
- }
891
- // A top-level `role` command never enters a runtime Session; that is
892
- // `session enter`.
893
- throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
894
- }
895
- if (resolved[0] === "operator") {
896
- if (resolved[1] === "enter") {
897
- if (resolved.length !== 2)
898
- throw usageError("Operator enter usage: yui operator enter.");
899
- await ensureFileTaskController(home, { environment: process.env });
900
- await runtime.prepareGlobalRoleEnter("operator");
901
- tmux.attachRole("operator", "operator", "auto");
902
- return;
903
- }
904
- const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
905
- if (result.kind === "output") {
906
- emit(result.output, false, result.data);
942
+ if (result.kind !== "session") {
943
+ throw new Error("Operator command returned an invalid control result.");
944
+ }
945
+ await executeOperatorSessionControl(result, home, store, runtime, tmux, catalogs);
907
946
  return;
908
947
  }
909
- if (result.kind !== "session") {
910
- throw new Error("Operator command returned an invalid control result.");
911
- }
912
- await executeOperatorSessionControl(result, home, store, runtime, tmux, catalogs);
913
- return;
914
- }
915
- if (resolved[0] === "task") {
916
- if (resolved[1] === "artifact") {
917
- // File/directory artifacts live in the Task's local Git repository, so
918
- // their save/read/list are asynchronous and handled here rather than in
919
- // the synchronous runTaskCommand chain. Save commits exactly one path and
920
- // returns a self-certifying commit-pinned reference; read pins to a commit
921
- // for frozen evidence; list is an ordinary current read.
922
- const action = resolved[2];
923
- const taskId = resolved[3];
924
- const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
925
- + "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
926
- if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
927
- throw usageError(usage);
928
- }
929
- if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
930
- throw usageError("Artifact is outside the managed Task scope.");
931
- }
932
- if (store.getTask(taskId) === null)
933
- throw usageError(`Task not found: ${taskId}.`);
934
- let data;
935
- if (action === "list") {
936
- if (resolved.length !== 4)
937
- throw usageError(usage);
938
- data = await listArtifactsCapability(home, taskId);
948
+ if (resolved[0] === "task") {
949
+ if (resolved[1] === "archive-preflight") {
950
+ const request = parseTaskArchiveArguments(resolved.slice(2), "archive-preflight");
951
+ if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== request.taskId) {
952
+ throw usageError("Archive inspection must remain within this Session's Task.");
953
+ }
954
+ const data = await inspectTaskArchive(workspaceCoordinator, request);
955
+ emit(renderTaskArchivePreflight(data), false, data);
956
+ return;
939
957
  }
940
- else if (action === "read") {
941
- const relativePath = resolved[4];
942
- const commit = resolved[5];
943
- if (relativePath === undefined || resolved.length > 6)
958
+ if (resolved[1] === "artifact") {
959
+ // File/directory artifacts live in the Task's local Git repository, so
960
+ // their save/read/list are asynchronous and handled here rather than in
961
+ // the synchronous runTaskCommand chain. Save commits exactly one path and
962
+ // returns a self-certifying commit-pinned reference; read pins to a commit
963
+ // for frozen evidence; list is an ordinary current read.
964
+ const action = resolved[2];
965
+ const taskId = resolved[3];
966
+ const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
967
+ + "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
968
+ if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
944
969
  throw usageError(usage);
945
- data = await readArtifactCapability(home, taskId, {
946
- relativePath, ...(commit === undefined ? {} : { commit })
947
- });
970
+ }
971
+ if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
972
+ throw usageError("Artifact is outside the managed Task scope.");
973
+ }
974
+ if (store.getTask(taskId) === null)
975
+ throw usageError(`Task not found: ${taskId}.`);
976
+ let data;
977
+ if (action === "list") {
978
+ if (resolved.length !== 4)
979
+ throw usageError(usage);
980
+ data = await listArtifactsCapability(home, taskId);
981
+ }
982
+ else if (action === "read") {
983
+ const relativePath = resolved[4];
984
+ const commit = resolved[5];
985
+ if (relativePath === undefined || resolved.length > 6)
986
+ throw usageError(usage);
987
+ data = await readArtifactCapability(home, taskId, {
988
+ relativePath, ...(commit === undefined ? {} : { commit })
989
+ });
990
+ }
991
+ else {
992
+ // save: a delivery-authoritative action; a managed Task caller must be the current Leader.
993
+ taskLocalActor(store, process.env, taskId);
994
+ const relativePath = resolved[4];
995
+ const content = resolved[5];
996
+ if (relativePath === undefined || content === undefined)
997
+ throw usageError(usage);
998
+ const rest = resolved.slice(6);
999
+ let message;
1000
+ let expectedHead;
1001
+ for (let index = 0; index < rest.length; index += 1) {
1002
+ const value = rest[index + 1];
1003
+ if (rest[index] === "--message" && value !== undefined) {
1004
+ message = value;
1005
+ index += 1;
1006
+ continue;
1007
+ }
1008
+ if (rest[index] === "--expected-head" && value !== undefined) {
1009
+ expectedHead = value;
1010
+ index += 1;
1011
+ continue;
1012
+ }
1013
+ throw usageError(usage);
1014
+ }
1015
+ data = await saveArtifactCapability(home, taskId, {
1016
+ relativePath, content,
1017
+ ...(message === undefined ? {} : { message }),
1018
+ ...(expectedHead === undefined ? {} : { expectedHead })
1019
+ });
1020
+ }
1021
+ emit(JSON.stringify(data, null, 2), false, data);
1022
+ return;
948
1023
  }
949
- else {
950
- // save: a delivery-authoritative action; a managed Task caller must be the current Leader.
951
- taskLocalActor(store, process.env, taskId);
952
- const relativePath = resolved[4];
953
- const content = resolved[5];
954
- if (relativePath === undefined || content === undefined)
955
- throw usageError(usage);
956
- const rest = resolved.slice(6);
957
- let message;
958
- let expectedHead;
959
- for (let index = 0; index < rest.length; index += 1) {
960
- const value = rest[index + 1];
961
- if (rest[index] === "--message" && value !== undefined) {
962
- message = value;
963
- index += 1;
964
- continue;
1024
+ if (resolved[1] === "execution") {
1025
+ if (resolved[2] === "stop") {
1026
+ const request = parseTaskExecutionStopRequest(resolved.slice(3));
1027
+ const result = stopTaskExecutionCommand(request, store, { environment: process.env });
1028
+ try {
1029
+ await ensureFileTaskController(home, { environment: process.env });
1030
+ await runtime.stopTaskDurableJobs(result.taskId);
1031
+ await runtime.stopTaskRoleSessions(result.taskId, result.roleNames);
1032
+ await runtime.assertTaskPhysicalResourcesReleased(result.taskId);
1033
+ finalizeStoppedTaskExecution(result.taskId, store);
965
1034
  }
966
- if (rest[index] === "--expected-head" && value !== undefined) {
967
- expectedHead = value;
968
- index += 1;
969
- continue;
1035
+ catch (error) {
1036
+ const message = error instanceof Error ? error.message : String(error);
1037
+ throw runtimeError(`Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed: ${message}`);
970
1038
  }
971
- throw usageError(usage);
1039
+ emit(result.output, false, result);
1040
+ return;
1041
+ }
1042
+ if (resolved[2] === "start") {
1043
+ const taskId = parseTaskExecutionStartRequest(resolved.slice(3));
1044
+ const task = store.getTask(taskId);
1045
+ if (task === null)
1046
+ throw usageError(`Task not found: ${taskId}.`);
1047
+ // Reject managed Task callers before inspecting or starting runtime resources.
1048
+ if (taskLocalActor(store, process.env, taskId) === "leader") {
1049
+ throw usageError("Task execution stop/start requires the global Operator or a human user.");
1050
+ }
1051
+ if (task.executionGate.state === "stopped") {
1052
+ await runtime.assertTaskPhysicalResourcesReleased(taskId);
1053
+ }
1054
+ await ensureFileTaskController(home, { environment: process.env });
1055
+ const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
1056
+ // Idempotent start is also a reliable kick: if an earlier caller
1057
+ // committed the gate but lost its Controller acknowledgement, retrying
1058
+ // start re-signals the same durable wake without creating another one.
1059
+ await runtime.notifyMailboxChanged({ kind: "role", taskId, roleName: "leader" });
1060
+ emit(result.output, false, result);
1061
+ return;
972
1062
  }
973
- data = await saveArtifactCapability(home, taskId, {
974
- relativePath, content,
975
- ...(message === undefined ? {} : { message }),
976
- ...(expectedHead === undefined ? {} : { expectedHead })
1063
+ throw usageError("Task execution usage: yui task execution <stop|start> ...");
1064
+ }
1065
+ if (resolved[1] === "integration") {
1066
+ const result = await runTaskIntegrationCommand(resolved.slice(2), store, home, {
1067
+ environment: process.env,
1068
+ jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
977
1069
  });
1070
+ emit(result.output, false, result.data);
1071
+ return;
978
1072
  }
979
- emit(JSON.stringify(data, null, 2), false, data);
980
- return;
981
- }
982
- if (resolved[1] === "execution") {
983
- if (resolved[2] === "stop") {
984
- const request = parseTaskExecutionStopRequest(resolved.slice(3));
985
- const result = stopTaskExecutionCommand(request, store, { environment: process.env });
986
- try {
987
- await ensureFileTaskController(home, { environment: process.env });
988
- await runtime.stopTaskDurableJobs(result.taskId);
989
- await runtime.stopTaskRoleSessions(result.taskId, result.roleNames);
990
- await runtime.assertTaskPhysicalResourcesReleased(result.taskId);
991
- finalizeStoppedTaskExecution(result.taskId, store);
1073
+ if (resolved[1] === "change-set") {
1074
+ const result = await runTaskChangeSetCommand(resolved.slice(2), store);
1075
+ emit(result.output, false, result.data);
1076
+ return;
1077
+ }
1078
+ if (resolved[1] === "publication" && (resolved[2] === "diff" || resolved[2] === "adopt")) {
1079
+ const result = await runTaskPublicationAdoptCommand(resolved.slice(2), store, {
1080
+ environment: process.env
1081
+ });
1082
+ emit(result.output, false, result.data);
1083
+ return;
1084
+ }
1085
+ if (resolved[1] === "publication" && resolved[2] === "verify") {
1086
+ const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
1087
+ verifiers: {
1088
+ github: createGitHubCliPublicationVerifier({
1089
+ environmentPath: process.env.PATH
1090
+ }),
1091
+ gitlab: createGitLabCliPublicationVerifier({
1092
+ environmentPath: process.env.PATH
1093
+ })
1094
+ },
1095
+ environment: process.env
1096
+ });
1097
+ emit(result.output, false, result.data);
1098
+ return;
1099
+ }
1100
+ if (resolved[1] === "overlap") {
1101
+ const result = await runTaskOverlapCommand(resolved.slice(2), store);
1102
+ emit(result.output, false, result.data);
1103
+ return;
1104
+ }
1105
+ if (resolved[1] === "workflow"
1106
+ && (resolved[2] === "run" || resolved[2] === "resume")) {
1107
+ const result = await runWorkflowCommandAsync(resolved.slice(2), store, {
1108
+ environment: process.env,
1109
+ yuiHome: home,
1110
+ ports: createReleaseWorkflowPorts({
1111
+ home,
1112
+ updatePorts: createUpdatePorts(process.env),
1113
+ projectStore: store
1114
+ })
1115
+ });
1116
+ if (result.kind !== "output") {
1117
+ throw new Error(`Task workflow ${resolved[2]} returned an invalid control result.`);
992
1118
  }
993
- catch (error) {
994
- const message = error instanceof Error ? error.message : String(error);
995
- throw runtimeError(`Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed: ${message}`);
1119
+ emit(result.output, false, result.data);
1120
+ return;
1121
+ }
1122
+ if (resolved[1] === "replace") {
1123
+ const result = await runTaskWorkspaceCommand(resolved.slice(1), store, workspacePreparer);
1124
+ emit(result.output, false, result.data);
1125
+ return;
1126
+ }
1127
+ if (resolved[1] === "work" && resolved[2] === "isolate") {
1128
+ const workItemId = resolved[3];
1129
+ if (workItemId === undefined || resolved.length !== 4) {
1130
+ throw usageError("Task work isolate usage: yui task work isolate <task>/<work>.");
996
1131
  }
997
- emit(result.output, false, result);
1132
+ const reference = cliWorkItemReference(workItemId, process.env);
1133
+ const workspace = await workspaceCoordinator.isolateWorkItem(reference.taskId, reference.localId);
1134
+ emit(`Created WorkItem workspace for ${reference.taskId}/${reference.localId}\nWorkspace: ${workspace.root}\n`, false, { workItemRef: reference, workspace });
998
1135
  return;
999
1136
  }
1000
- if (resolved[2] === "start") {
1001
- const taskId = parseTaskExecutionStartRequest(resolved.slice(3));
1002
- const task = store.getTask(taskId);
1003
- if (task === null)
1004
- throw usageError(`Task not found: ${taskId}.`);
1005
- // Reject managed Task callers before inspecting or starting runtime resources.
1006
- if (taskLocalActor(store, process.env, taskId) === "leader") {
1007
- throw usageError("Task execution stop/start requires the global Operator or a human user.");
1137
+ if (resolved[1] === "work" && resolved[2] === "review"
1138
+ && resolved[3] === "cleanup") {
1139
+ const reviewRoundId = resolved[4];
1140
+ if (reviewRoundId === undefined || resolved.length !== 5) {
1141
+ throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
1008
1142
  }
1009
- if (task.executionGate.state === "stopped") {
1010
- await runtime.assertTaskPhysicalResourcesReleased(taskId);
1143
+ const reference = cliTaskRecordReference(reviewRoundId, "reviewRound", process.env);
1144
+ const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
1145
+ if (removal === "dirty") {
1146
+ throw usageError(`ReviewRound workspace is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
1011
1147
  }
1012
- await ensureFileTaskController(home, { environment: process.env });
1013
- const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
1014
- // Idempotent start is also a reliable kick: if an earlier caller
1015
- // committed the gate but lost its Controller acknowledgement, retrying
1016
- // start re-signals the same durable wake without creating another one.
1017
- await runtime.notifyMailboxChanged?.({ kind: "role", taskId, roleName: "leader" });
1018
- emit(result.output, false, result);
1148
+ emit(`Cleaned ReviewRound workspace ${reference.taskId}/${reference.localId} (${removal})\n`, false, { reviewRoundRef: reference, workspace: { removal } });
1019
1149
  return;
1020
1150
  }
1021
- throw usageError("Task execution usage: yui task execution <stop|start> ...");
1022
- }
1023
- if (resolved[1] === "integration") {
1024
- const result = await runTaskIntegrationCommand(resolved.slice(2), store, home, {
1025
- environment: process.env,
1026
- jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
1027
- });
1028
- emit(result.output, false, result.data);
1029
- return;
1030
- }
1031
- if (resolved[1] === "change-set") {
1032
- const result = await runTaskChangeSetCommand(resolved.slice(2), store);
1033
- emit(result.output, false, result.data);
1034
- return;
1035
- }
1036
- if (resolved[1] === "publication" && resolved[2] === "verify") {
1037
- const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
1038
- verifiers: {
1039
- github: createGitHubCliPublicationVerifier({
1040
- environmentPath: process.env.PATH
1041
- }),
1042
- gitlab: createGitLabCliPublicationVerifier({
1043
- environmentPath: process.env.PATH
1044
- })
1045
- },
1046
- candidateForTask: async (taskId) => {
1047
- const status = store.getTask(taskId)?.status;
1048
- return status === "active" || status === "cancelled"
1049
- ? snapshotActualTaskReviewCandidate(taskId, store, workspacePreparer)
1050
- : null;
1051
- },
1052
- environment: process.env
1053
- });
1054
- emit(result.output, false, result.data);
1055
- return;
1056
- }
1057
- if (resolved[1] === "overlap") {
1058
- const result = await runTaskOverlapCommand(resolved.slice(2), store);
1059
- emit(result.output, false, result.data);
1060
- return;
1061
- }
1062
- if (resolved[1] === "workflow"
1063
- && (resolved[2] === "run" || resolved[2] === "resume")) {
1064
- const result = await runWorkflowCommandAsync(resolved.slice(2), store, {
1065
- environment: process.env,
1066
- yuiHome: home,
1067
- ports: createReleaseWorkflowPorts({
1068
- home,
1069
- updatePorts: createUpdatePorts(process.env),
1070
- projectStore: store
1071
- })
1072
- });
1073
- if (result.kind !== "output") {
1074
- throw new Error(`Task workflow ${resolved[2]} returned an invalid control result.`);
1151
+ if (resolved[1] === "work" && resolved[2] === "capture") {
1152
+ const workItemId = resolved[3];
1153
+ if (workItemId === undefined || resolved.length !== 4) {
1154
+ throw usageError("Task work capture usage: yui task work capture <task>/<work>.");
1155
+ }
1156
+ const reference = cliWorkItemReference(workItemId, process.env);
1157
+ assertTaskDeliveryAuthority(store, process.env, reference.taskId);
1158
+ const changeSets = await new WorkItemChangeSetManager(store).capture(reference.taskId, reference.localId, taskFinalReviewContract === undefined
1159
+ ? {}
1160
+ : { taskFinalReviewContract });
1161
+ const qualified = `${reference.taskId}/${reference.localId}`;
1162
+ emit(changeSets.length === 0
1163
+ ? `WorkItem workspace has no changes to capture: ${qualified}\n`
1164
+ : `Captured ChangeSets ${changeSets.map(({ id }) => id).join(", ")} from ${qualified}\n`, false, { workItemRef: reference, changeSets });
1165
+ return;
1075
1166
  }
1076
- emit(result.output, false, result.data);
1077
- return;
1078
- }
1079
- if (resolved[1] === "replace") {
1080
- const result = await runTaskWorkspaceCommand(resolved.slice(1), store, workspacePreparer);
1081
- emit(result.output, false, result.data);
1082
- return;
1083
- }
1084
- if (resolved[1] === "work" && resolved[2] === "isolate") {
1085
- const workItemId = resolved[3];
1086
- if (workItemId === undefined || resolved.length !== 4) {
1087
- throw usageError("Task work isolate usage: yui task work isolate <task>/<work>.");
1088
- }
1089
- const reference = cliWorkItemReference(workItemId, process.env);
1090
- const workspace = await workspaceCoordinator.isolateWorkItem(reference.taskId, reference.localId);
1091
- emit(`Created WorkItem workspace for ${reference.taskId}/${reference.localId}\nWorkspace: ${workspace.root}\n`, false, { workItemRef: reference, workspace });
1092
- return;
1093
- }
1094
- if (resolved[1] === "work" && resolved[2] === "review"
1095
- && resolved[3] === "cleanup") {
1096
- const reviewRoundId = resolved[4];
1097
- if (reviewRoundId === undefined || resolved.length !== 5) {
1098
- throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
1099
- }
1100
- const reference = cliTaskRecordReference(reviewRoundId, "reviewRound", process.env);
1101
- const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
1102
- if (removal === "dirty") {
1103
- throw usageError(`ReviewRound workspace is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
1104
- }
1105
- emit(`Cleaned ReviewRound workspace ${reference.taskId}/${reference.localId} (${removal})\n`, false, { reviewRoundRef: reference, workspace: { removal } });
1106
- return;
1107
- }
1108
- if (resolved[1] === "work" && resolved[2] === "capture") {
1109
- const workItemId = resolved[3];
1110
- if (workItemId === undefined || resolved.length !== 4) {
1111
- throw usageError("Task work capture usage: yui task work capture <task>/<work>.");
1112
- }
1113
- const reference = cliWorkItemReference(workItemId, process.env);
1114
- assertTaskDeliveryAuthority(store, process.env, reference.taskId);
1115
- const changeSets = await new WorkItemChangeSetManager(store).capture(reference.taskId, reference.localId, taskFinalReviewContract === undefined
1116
- ? {}
1117
- : { taskFinalReviewContract });
1118
- const qualified = `${reference.taskId}/${reference.localId}`;
1119
- emit(changeSets.length === 0
1120
- ? `WorkItem workspace has no changes to capture: ${qualified}\n`
1121
- : `Captured ChangeSets ${changeSets.map(({ id }) => id).join(", ")} from ${qualified}\n`, false, { workItemRef: reference, changeSets });
1122
- return;
1123
- }
1124
- if (resolved[1] === "work" && resolved[2] === "cleanup") {
1125
- const workItemId = resolved[3];
1126
- const disposition = resolved[4];
1127
- if (workItemId === undefined
1128
- || !["--runtime-only", "--integrated", "--abandon"].includes(disposition ?? "")
1129
- || resolved.length !== 5) {
1130
- throw usageError("Task work cleanup usage: yui task work cleanup <task>/<work> "
1131
- + "(--runtime-only|--integrated|--abandon).");
1132
- }
1133
- const reference = cliWorkItemReference(workItemId, process.env);
1134
- const qualified = `${reference.taskId}/${reference.localId}`;
1135
- assertTaskDeliveryAuthority(store, process.env, reference.taskId);
1136
- if (disposition === "--runtime-only") {
1137
- let runtimeCleanup;
1167
+ if (resolved[1] === "work" && resolved[2] === "cleanup") {
1168
+ const workItemId = resolved[3];
1169
+ const disposition = resolved[4];
1170
+ if (workItemId === undefined
1171
+ || !["--runtime-only", "--integrated", "--abandon"].includes(disposition ?? "")
1172
+ || resolved.length !== 5) {
1173
+ throw usageError("Task work cleanup usage: yui task work cleanup <task>/<work> "
1174
+ + "(--runtime-only|--integrated|--abandon).");
1175
+ }
1176
+ const reference = cliWorkItemReference(workItemId, process.env);
1177
+ const qualified = `${reference.taskId}/${reference.localId}`;
1178
+ assertTaskDeliveryAuthority(store, process.env, reference.taskId);
1179
+ if (disposition === "--runtime-only") {
1180
+ let runtimeCleanup;
1181
+ try {
1182
+ runtimeCleanup = await workspaceCoordinator.cleanupWorkItemRuntime(reference.taskId, reference.localId);
1183
+ }
1184
+ catch (error) {
1185
+ throw cleanupCliError(error, `work-item:${qualified}`);
1186
+ }
1187
+ emit(`Released WorkItem runtime ${qualified}; retained its Session and worktree\n`, false, {
1188
+ workItem: store.getWorkItem(reference.taskId, reference.localId),
1189
+ runtime: { cleanup: runtimeCleanup },
1190
+ worktree: { retained: true }
1191
+ });
1192
+ return;
1193
+ }
1194
+ const cleanedAs = disposition === "--integrated" ? "integrated" : "abandoned";
1195
+ if (cleanedAs === "integrated") {
1196
+ try {
1197
+ await new WorkItemChangeSetManager(store).assertIntegrated(reference.taskId, reference.localId);
1198
+ }
1199
+ catch (error) {
1200
+ throw cleanupCliError(error, `work-item:${qualified}`);
1201
+ }
1202
+ }
1203
+ let removal;
1138
1204
  try {
1139
- runtimeCleanup = await workspaceCoordinator.cleanupWorkItemRuntime(reference.taskId, reference.localId);
1205
+ removal = await workspaceCoordinator.cleanupWorkItem(reference.taskId, reference.localId, cleanedAs);
1140
1206
  }
1141
1207
  catch (error) {
1142
1208
  throw cleanupCliError(error, `work-item:${qualified}`);
1143
1209
  }
1144
- emit(`Released WorkItem runtime ${qualified}; retained its Session and worktree\n`, false, {
1210
+ if (removal === "dirty") {
1211
+ throw usageError(`WorkItem worktree is dirty and was retained: ${qualified}.`, undefined, cleanupBlockedDetails("dirty-worktree", `work-item:${qualified}`, true));
1212
+ }
1213
+ emit(`Cleaned WorkItem worktree ${qualified} (${cleanedAs})\n`, false, {
1145
1214
  workItem: store.getWorkItem(reference.taskId, reference.localId),
1146
- runtime: { cleanup: runtimeCleanup },
1147
- worktree: { retained: true }
1215
+ worktree: { removal, disposition: cleanedAs }
1148
1216
  });
1149
1217
  return;
1150
1218
  }
1151
- const cleanedAs = disposition === "--integrated" ? "integrated" : "abandoned";
1152
- if (cleanedAs === "integrated") {
1153
- try {
1154
- await new WorkItemChangeSetManager(store).assertIntegrated(reference.taskId, reference.localId);
1219
+ if (resolved[1] === "work" && resolved[2] === "review"
1220
+ && resolved[3] === "cleanup") {
1221
+ const reviewRef = resolved[4];
1222
+ if (reviewRef === undefined || resolved.length !== 5) {
1223
+ throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
1155
1224
  }
1156
- catch (error) {
1157
- throw usageError(error instanceof Error ? error.message : String(error));
1225
+ const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
1226
+ const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
1227
+ if (removal === "dirty") {
1228
+ throw usageError(`ReviewRound worktree is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
1158
1229
  }
1230
+ emit(`Cleaned ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
1231
+ reviewRound: store.getReviewRound(reference.taskId, reference.localId),
1232
+ worktree: { removal }
1233
+ });
1234
+ return;
1159
1235
  }
1160
- let removal;
1161
- try {
1162
- removal = await workspaceCoordinator.cleanupWorkItem(reference.taskId, reference.localId, cleanedAs);
1163
- }
1164
- catch (error) {
1165
- throw cleanupCliError(error, `work-item:${qualified}`);
1166
- }
1167
- if (removal === "dirty") {
1168
- throw usageError(`WorkItem worktree is dirty and was retained: ${qualified}.`, undefined, cleanupBlockedDetails("dirty-worktree", `work-item:${qualified}`, true));
1236
+ if (resolved[1] === "work" && resolved[2] === "review"
1237
+ && resolved[3] === "preserve") {
1238
+ const reviewRef = resolved[4];
1239
+ if (reviewRef === undefined || resolved.length !== 5) {
1240
+ throw usageError("Task work review preserve usage: yui task work review preserve <task>/<review-round>.");
1241
+ }
1242
+ const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
1243
+ const round = preserveReviewRoundWorkspace(reference.taskId, reference.localId, store, { runtime, environment: process.env, yuiHome: home });
1244
+ emit(`Preserved ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
1245
+ reviewRound: round
1246
+ });
1247
+ return;
1169
1248
  }
1170
- emit(`Cleaned WorkItem worktree ${qualified} (${cleanedAs})\n`, false, {
1171
- workItem: store.getWorkItem(reference.taskId, reference.localId),
1172
- worktree: { removal, disposition: cleanedAs }
1173
- });
1174
- return;
1175
- }
1176
- if (resolved[1] === "work" && resolved[2] === "review"
1177
- && resolved[3] === "cleanup") {
1178
- const reviewRef = resolved[4];
1179
- if (reviewRef === undefined || resolved.length !== 5) {
1180
- throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
1181
- }
1182
- const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
1183
- const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
1184
- if (removal === "dirty") {
1185
- throw usageError(`ReviewRound worktree is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
1186
- }
1187
- emit(`Cleaned ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
1188
- reviewRound: store.getReviewRound(reference.taskId, reference.localId),
1189
- worktree: { removal }
1190
- });
1191
- return;
1192
- }
1193
- if (resolved[1] === "work" && resolved[2] === "review"
1194
- && resolved[3] === "preserve") {
1195
- const reviewRef = resolved[4];
1196
- if (reviewRef === undefined || resolved.length !== 5) {
1197
- throw usageError("Task work review preserve usage: yui task work review preserve <task>/<review-round>.");
1198
- }
1199
- const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
1200
- const round = preserveReviewRoundWorkspace(reference.taskId, reference.localId, store, { runtime, environment: process.env, yuiHome: home });
1201
- emit(`Preserved ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
1202
- reviewRound: round
1203
- });
1204
- return;
1205
- }
1206
- let archiveRemoteDeliveryProof;
1207
- let archiveTaskReviewCandidate;
1208
- if (resolved[1] === "archive") {
1209
- const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1210
- runtime,
1211
- environment: process.env,
1212
- yuiHome: home
1213
- });
1214
- const task = store.getTask(taskId);
1215
- if (task === null)
1216
- throw new Error(`Task disappeared after archive validation: ${taskId}.`);
1217
- if (force || task.status === "archived") {
1218
- // Archive admission and mandatory audit commit before any fallible
1219
- // filesystem/provider work. Repeats report facts, never replay cleanup.
1220
- const admitted = runTaskCommand(resolved.slice(1), store, {
1221
- runtime, environment: process.env, yuiHome: home
1249
+ let archiveRemoteDeliveryProof;
1250
+ let archiveTaskReviewCandidate;
1251
+ if (resolved[1] === "archive") {
1252
+ const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1253
+ runtime,
1254
+ environment: process.env,
1255
+ yuiHome: home
1222
1256
  });
1223
- if (force && admitted.kind === "output"
1224
- && admitted.data.changed) {
1225
- await workspaceCoordinator.cleanupArchivedTask(taskId, disposition);
1257
+ const task = store.getTask(taskId);
1258
+ if (task === null)
1259
+ throw new Error(`Task disappeared after archive validation: ${taskId}.`);
1260
+ if (force || task.status === "archived") {
1261
+ // Archive admission and mandatory audit commit before any fallible
1262
+ // filesystem/provider work. Repeats report facts, never replay cleanup.
1263
+ const admitted = runTaskCommand(resolved.slice(1), store, {
1264
+ runtime, environment: process.env, yuiHome: home
1265
+ });
1266
+ if (force && admitted.kind === "output"
1267
+ && admitted.data.changed) {
1268
+ await workspaceCoordinator.cleanupArchivedTask(taskId, disposition);
1269
+ }
1270
+ const current = store.getTask(taskId);
1271
+ const archive = taskArchiveDiagnostics(store, current);
1272
+ emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
1273
+ return;
1274
+ }
1275
+ {
1276
+ if (disposition === "integrated") {
1277
+ archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
1278
+ archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
1279
+ assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
1280
+ }
1281
+ const workItemIds = store.listManagedWorkspaces(task.id)
1282
+ .flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
1283
+ for (const workItemId of workItemIds) {
1284
+ const item = store.getWorkItem(task.id, workItemId);
1285
+ if (item?.status !== "accepted" || disposition !== "integrated")
1286
+ continue;
1287
+ try {
1288
+ await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
1289
+ }
1290
+ catch (error) {
1291
+ throw cleanupCliError(error, `work-item:${task.id}/${item.id}`);
1292
+ }
1293
+ }
1294
+ const cleanup = await workspaceCoordinator.cleanupTaskForArchive(task.id, disposition);
1295
+ if (cleanup.status === "retained-dirty") {
1296
+ throw usageError(cleanup.error ?? `Task ${task.id} has dirty managed worktrees and remains terminal.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "dirty-worktree", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true, cleanup.checks));
1297
+ }
1298
+ if (cleanup.status === "failed") {
1299
+ throw usageError(`Task ${task.id} worktree cleanup failed: ${cleanup.error ?? "unknown error"}.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "cleanup-failed", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true, cleanup.checks));
1300
+ }
1226
1301
  }
1227
- const current = store.getTask(taskId);
1228
- const archive = taskArchiveDiagnostics(store, current);
1229
- emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
1230
- return;
1231
1302
  }
1232
- {
1233
- if (disposition === "integrated") {
1234
- archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
1235
- archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
1236
- assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
1237
- }
1238
- const workItemIds = store.listManagedWorkspaces(task.id)
1239
- .flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
1240
- for (const workItemId of workItemIds) {
1241
- const item = store.getWorkItem(task.id, workItemId);
1242
- if (item?.status !== "accepted" || disposition !== "integrated")
1243
- continue;
1244
- try {
1245
- await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
1246
- }
1247
- catch (error) {
1248
- throw usageError(error instanceof Error ? error.message : String(error));
1303
+ let taskRetirementProof;
1304
+ if (resolved[1] === "retire") {
1305
+ const taskId = resolved[2];
1306
+ if (taskId !== undefined && !taskId.startsWith("--")) {
1307
+ const task = store.getTask(taskId);
1308
+ if (task?.status === "active" || task?.status === "draft") {
1309
+ try {
1310
+ taskRetirementProof = await new WorkItemChangeSetManager(store)
1311
+ .assertRetirable(taskId);
1312
+ }
1313
+ catch (error) {
1314
+ throw usageError(error instanceof Error ? error.message : String(error));
1315
+ }
1249
1316
  }
1250
1317
  }
1251
- const cleanup = await workspaceCoordinator.cleanupTaskForArchive(task.id, disposition);
1252
- if (cleanup.status === "retained-dirty") {
1253
- throw usageError(cleanup.error ?? `Task ${task.id} has dirty managed worktrees and remains terminal.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "dirty-worktree", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true));
1318
+ }
1319
+ assertWorkItemExecutionDependenciesForCommand(resolved, store, process.env);
1320
+ if (resolved[1] === "work" && resolved[2] === "dispatch") {
1321
+ const workItemId = resolved[3];
1322
+ const reference = workItemId === undefined
1323
+ ? null
1324
+ : cliWorkItemReference(workItemId, process.env);
1325
+ const item = reference === null
1326
+ ? null
1327
+ : store.getWorkItem(reference.taskId, reference.localId);
1328
+ const task = item === null ? null : store.getTask(item.taskId);
1329
+ if (item !== null && task !== null) {
1330
+ // Authority and pure Lane-shape checks precede every physical or
1331
+ // durable workspace preparation performed for dispatch.
1332
+ assertTaskDeliveryAuthority(store, process.env, task.id);
1333
+ requireWorkItemAssignee(item);
1334
+ workItemDispatchLanePlan(resolved, store, item);
1335
+ }
1336
+ // A rejected Candidate starts a new execution iteration. Release every
1337
+ // terminal Lane Role runtime before preparing the new Lane workspaces;
1338
+ // durable AgentRuns, Groups, Candidates, and workspace owners remain intact.
1339
+ if (item?.status === "open"
1340
+ && currentWorkItemExecutionGroup(item)?.lanes.every(({ disposition }) => disposition !== "open")) {
1341
+ await workspaceCoordinator.cleanupWorkItemRuntime(item.taskId, item.id);
1342
+ }
1343
+ // Every Task needs an authoritative runtime owner before dispatch. A
1344
+ // Gitless Task uses an empty Task-owned view; Project-backed WorkItems
1345
+ // additionally receive their isolated Develop owner below.
1346
+ if (item !== null && task !== null) {
1347
+ await workspacePreparer.prepareTaskWorkspace(task.id);
1348
+ }
1349
+ // A Project-backed Worker WorkItem gets an isolated Develop owner before
1350
+ // its Lane is prepared. A Leader-owned WorkItem intentionally executes
1351
+ // in the Task main worktree and must not enter this isolation path.
1352
+ if (item !== null
1353
+ && task !== null
1354
+ && task.projectBindings.length > 0
1355
+ && item.assignee !== "leader"
1356
+ && store.getWorkItemWorkspace(task.id, item.id) === null) {
1357
+ await workspaceCoordinator.isolateWorkItem(item.taskId, item.id);
1254
1358
  }
1255
- if (cleanup.status === "failed") {
1256
- throw usageError(`Task ${task.id} worktree cleanup failed: ${cleanup.error ?? "unknown error"}.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "cleanup-failed", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true));
1359
+ if (item !== null && task !== null) {
1360
+ // For a new Group the preparer has already created deterministic
1361
+ // worktrees, but the owner record is adopted by dispatch's aggregate
1362
+ // transaction once its exact Lane ids exist.
1257
1363
  }
1258
1364
  }
1259
- }
1260
- let taskRetirementProof;
1261
- if (resolved[1] === "retire") {
1262
- const taskId = resolved[2];
1263
- if (taskId !== undefined && !taskId.startsWith("--")) {
1264
- const task = store.getTask(taskId);
1265
- if (task?.status === "active" || task?.status === "draft") {
1365
+ let executionLaneWorkspaces;
1366
+ // Held only for a new Group's dispatch: the per-Project maintenance fence
1367
+ // spans Lane preparation and the adoption transaction, and projectPaths is
1368
+ // the under-fence snapshot the adoption CAS revalidates.
1369
+ let laneDispatchRelease;
1370
+ let laneDispatchProjectPaths;
1371
+ let workItemIntegrationProof;
1372
+ if (resolved[1] === "work" && resolved[2] === "accept") {
1373
+ const workItemId = resolved[3];
1374
+ if (workItemId !== undefined && !workItemId.startsWith("--")) {
1266
1375
  try {
1267
- taskRetirementProof = await new WorkItemChangeSetManager(store)
1268
- .assertRetirable(taskId);
1376
+ const reference = cliWorkItemReference(workItemId, process.env);
1377
+ workItemIntegrationProof = await new WorkItemChangeSetManager(store)
1378
+ .assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
1269
1379
  }
1270
1380
  catch (error) {
1271
1381
  throw usageError(error instanceof Error ? error.message : String(error));
1272
1382
  }
1273
1383
  }
1274
1384
  }
1275
- }
1276
- assertWorkItemExecutionDependenciesForCommand(resolved, store, process.env);
1277
- if (resolved[1] === "work" && resolved[2] === "dispatch") {
1278
- const workItemId = resolved[3];
1279
- const reference = workItemId === undefined
1280
- ? null
1281
- : cliWorkItemReference(workItemId, process.env);
1282
- const item = reference === null
1283
- ? null
1284
- : store.getWorkItem(reference.taskId, reference.localId);
1285
- const task = item === null ? null : store.getTask(item.taskId);
1286
- if (item !== null && task !== null) {
1287
- // Authority and pure Lane-shape checks precede every physical or
1288
- // durable workspace preparation performed for dispatch.
1289
- assertTaskDeliveryAuthority(store, process.env, task.id);
1290
- requireWorkItemAssignee(item);
1291
- workItemDispatchLanePlan(resolved, store, item);
1292
- }
1293
- // A rejected Candidate starts a new execution iteration. Release every
1294
- // terminal Lane Role runtime before preparing the new Lane workspaces;
1295
- // durable AgentRuns, Groups, Candidates, and workspace owners remain intact.
1296
- if (item?.status === "open"
1297
- && currentWorkItemExecutionGroup(item)?.lanes.every(({ disposition }) => disposition !== "open")) {
1298
- await workspaceCoordinator.cleanupWorkItemRuntime(item.taskId, item.id);
1299
- }
1300
- // Every Task needs an authoritative runtime owner before dispatch. A
1301
- // Gitless Task uses an empty Task-owned view; Project-backed WorkItems
1302
- // additionally receive their isolated Develop owner below.
1303
- if (item !== null && task !== null) {
1304
- await workspacePreparer.prepareTaskWorkspace(task.id);
1305
- }
1306
- // A Project-backed Worker WorkItem gets an isolated Develop owner before
1307
- // its Lane is prepared. A Leader-owned WorkItem intentionally executes
1308
- // in the Task main worktree and must not enter this isolation path.
1309
- if (item !== null
1310
- && task !== null
1311
- && task.projectBindings.length > 0
1312
- && item.assignee !== "leader"
1313
- && store.getWorkItemWorkspace(task.id, item.id) === null) {
1314
- await workspaceCoordinator.isolateWorkItem(item.taskId, item.id);
1315
- }
1316
- if (item !== null && task !== null) {
1317
- // For a new Group the preparer has already created deterministic
1318
- // worktrees, but the owner record is adopted by dispatch's aggregate
1319
- // transaction once its exact Lane ids exist.
1385
+ let completionSummary;
1386
+ let completionPublishedTreeProof;
1387
+ if (resolved[1] === "base" && resolved[2] === "status") {
1388
+ const result = await runTaskBaseStatusCommand(resolved.slice(3), store);
1389
+ emit(result.output, false, result.data);
1390
+ return;
1320
1391
  }
1321
- }
1322
- let executionLaneWorkspaces;
1323
- // Held only for a new Group's dispatch: the per-Project maintenance fence
1324
- // spans Lane preparation and the adoption transaction, and projectPaths is
1325
- // the under-fence snapshot the adoption CAS revalidates.
1326
- let laneDispatchRelease;
1327
- let laneDispatchProjectPaths;
1328
- let workItemIntegrationProof;
1329
- if (resolved[1] === "work" && resolved[2] === "accept") {
1330
- const workItemId = resolved[3];
1331
- if (workItemId !== undefined && !workItemId.startsWith("--")) {
1332
- try {
1333
- const reference = cliWorkItemReference(workItemId, process.env);
1334
- workItemIntegrationProof = await new WorkItemChangeSetManager(store)
1335
- .assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
1336
- }
1337
- catch (error) {
1338
- throw usageError(error instanceof Error ? error.message : String(error));
1339
- }
1392
+ if (resolved[1] === "upstream") {
1393
+ const result = await runTaskUpstreamCommand(resolved.slice(2), store, home, {
1394
+ environment: process.env,
1395
+ jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
1396
+ });
1397
+ emit(result.output, false, result.data);
1398
+ return;
1340
1399
  }
1341
- }
1342
- let completionSummary;
1343
- let completionPublishedTreeProof;
1344
- if (resolved[1] === "base" && resolved[2] === "status") {
1345
- const result = await runTaskBaseStatusCommand(resolved.slice(3), store);
1346
- emit(result.output, false, result.data);
1347
- return;
1348
- }
1349
- if (resolved[1] === "upstream") {
1350
- const result = await runTaskUpstreamCommand(resolved.slice(2), store, home, {
1351
- environment: process.env
1352
- });
1353
- emit(result.output, false, result.data);
1354
- return;
1355
- }
1356
- if (resolved[1] === "complete" && resolved[2] !== undefined) {
1357
- const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
1358
- completionSummary = completionRequest.summary;
1359
- const refreshRemote = resolved.includes("--refresh-remote");
1360
- const completion = preflightTaskCompletion(resolved[2], store, {
1361
- environment: process.env,
1362
- ...(taskFinalReviewContract === undefined
1363
- ? {}
1364
- : { taskFinalReviewContract })
1365
- }, completionRequest);
1366
- if (!completion.completed && !completion.activeTaskReview) {
1367
- // An explicit refresh must fetch the remote object graph before the
1368
- // Publication proof resolves its exact commit. Without the flag the
1369
- // command remains offline and preserves the existing proof-first path.
1370
- const refreshedFreshness = refreshRemote
1371
- ? await inspectTaskBaseFreshness(resolved[2], store, { refresh: true })
1372
- : undefined;
1373
- if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
1374
- completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
1375
- }
1376
- const freshness = refreshedFreshness
1377
- ?? await inspectTaskBaseFreshness(resolved[2], store);
1378
- for (const warning of assertTaskBaseFreshnessForCompletion(freshness, {
1379
- ...(completionPublishedTreeProof === undefined
1400
+ if (resolved[1] === "complete" && resolved[2] !== undefined) {
1401
+ const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
1402
+ completionSummary = completionRequest.summary;
1403
+ const refreshRemote = resolved.includes("--refresh-remote");
1404
+ const completion = preflightTaskCompletion(resolved[2], store, {
1405
+ environment: process.env,
1406
+ ...(taskFinalReviewContract === undefined
1380
1407
  ? {}
1381
- : {
1382
- acceptedPublishedTreeProjectId: completionPublishedTreeProof.projectId
1383
- })
1384
- })) {
1385
- process.stderr.write(`Warning: ${warning}\n`);
1386
- }
1387
- // Keep completion offline by default. An explicit refresh is the only
1388
- // path that may fetch and reconcile a moved remote baseline.
1389
- if (refreshRemote) {
1390
- const reconciled = await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
1391
- if (reconciled.length > 0) {
1392
- const updates = reconciled.map((entry) => (`${entry.projectId}: ${entry.fromCommit} -> ${entry.toCommit} `
1393
- + `(Integration ${entry.integrationId})`)).join("; ");
1394
- throw usageError(`Remote baseline reconciliation advanced Task ${resolved[2]} (${updates}). `
1395
- + "The Task remains active so the Leader can inspect the new authoritative head, "
1396
- + "decide how prior Review evidence applies, and retry task complete.");
1408
+ : { taskFinalReviewContract })
1409
+ }, completionRequest);
1410
+ if (!completion.completed && !completion.activeTaskReview) {
1411
+ // An explicit refresh must fetch the remote object graph before the
1412
+ // Publication proof resolves its exact commit. Without the flag the
1413
+ // command remains offline and preserves the existing proof-first path.
1414
+ const refreshedFreshness = refreshRemote
1415
+ ? await inspectTaskBaseFreshness(resolved[2], store, { refresh: true })
1416
+ : undefined;
1417
+ if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
1418
+ completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
1419
+ }
1420
+ const freshness = refreshedFreshness
1421
+ ?? await inspectTaskBaseFreshness(resolved[2], store);
1422
+ for (const warning of assertTaskBaseFreshnessForCompletion(freshness, {
1423
+ ...(completionPublishedTreeProof === undefined
1424
+ ? {}
1425
+ : {
1426
+ acceptedPublishedTreeProjectId: completionPublishedTreeProof.projectId
1427
+ })
1428
+ })) {
1429
+ process.stderr.write(`Warning: ${warning}\n`);
1430
+ }
1431
+ // Keep completion offline by default. An explicit refresh is the only
1432
+ // path that may fetch and reconcile a moved remote baseline.
1433
+ if (refreshRemote) {
1434
+ const reconciled = await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
1435
+ if (reconciled.length > 0) {
1436
+ const updates = reconciled.map((entry) => (`${entry.projectId}: ${entry.fromCommit} -> ${entry.toCommit} `
1437
+ + `(Integration ${entry.integrationId})`)).join("; ");
1438
+ throw usageError(`Remote baseline reconciliation advanced Task ${resolved[2]} (${updates}). `
1439
+ + "The Task remains active so the Leader can inspect the new authoritative head, "
1440
+ + "decide how prior Review evidence applies, and retry task complete.");
1441
+ }
1397
1442
  }
1398
1443
  }
1399
1444
  }
1400
- }
1401
- let releaseReviewHandoverLock;
1402
- if ((resolved[1] === "review"
1403
- && ["request", "retry"].includes(resolved[2] ?? ""))
1404
- || resolved[1] === "complete") {
1405
- const handoverLock = acquireHandoverLock(home);
1406
- releaseReviewHandoverLock = handoverLock.release;
1407
- }
1408
- try {
1409
- const preparedLanes = await prepareExecutionLaneWorkspacesForCommand(resolved, store, workspacePreparer, process.env);
1410
- if (preparedLanes !== undefined) {
1411
- executionLaneWorkspaces = preparedLanes.workspaces;
1412
- laneDispatchRelease = preparedLanes.release;
1413
- laneDispatchProjectPaths = preparedLanes.projectPaths;
1414
- }
1415
- const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1416
- const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
1417
- ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
1418
- : archiveTaskReviewCandidate;
1419
- const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
1420
- // Read-only Host evidence for Session inspect and Role status/list. The command stays
1421
- // synchronous over persisted state; this is the live reading it prints
1422
- // beside those facts, prepared here because the Host is reached over a
1423
- // socket.
1424
- const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
1425
- const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
1426
- // Physical preparation may precede the durable write, but Task status,
1427
- // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1428
- // one transaction. A failed attempt therefore leaves the Task Draft and
1429
- // owning no writable workspace.
1430
- let taskWorkspaceActivation;
1431
- if (resolved[1] === "activate" && resolved.length === 3) {
1432
- const taskId = resolved[2];
1433
- const task = taskId === undefined ? null : store.getTask(taskId);
1434
- if (task !== null && task.status === "draft") {
1435
- taskLocalActor(store, process.env, task.id);
1436
- taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id, process.env);
1437
- }
1438
- }
1439
- const result = runTaskCommand(resolved.slice(1), store, {
1440
- runtime,
1441
- environment: process.env,
1442
- yuiHome: home,
1443
- ...(taskFinalReviewContract === undefined
1444
- ? {}
1445
- : { taskFinalReviewContract }),
1446
- ...(completionSummary === undefined ? {} : { completionSummary }),
1447
- ...(completionPublishedTreeProof === undefined
1448
- ? {}
1449
- : { completionPublishedTreeProof }),
1450
- ...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
1451
- ...candidateSnapshots,
1452
- ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1453
- ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1454
- ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1455
- ...(actualTaskReviewCandidate === undefined
1456
- ? {}
1457
- : { actualTaskReviewCandidate }),
1458
- ...(archiveRemoteDeliveryProof === undefined
1459
- ? {}
1460
- : { archiveRemoteDeliveryProof }),
1461
- ...(deltaRecheckPreflight === undefined
1462
- ? {}
1463
- : { deltaRecheckPreflight }),
1464
- ...(liveRunConfiguration === undefined
1465
- ? {}
1466
- : { liveRunConfiguration }),
1467
- ...(liveHostObservations === undefined ? {} : { liveHostObservations }),
1468
- ...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
1469
- ...(validateAgentConfiguration === undefined
1470
- ? {}
1471
- : { validateAgentConfiguration })
1472
- });
1473
- // The dispatch transaction has now adopted (or rejected) the prepared
1474
- // Lane workspaces. Release the held fence so later output/review
1475
- // handling can take the per-Project fence itself.
1476
- if (laneDispatchRelease !== undefined) {
1477
- laneDispatchRelease();
1478
- laneDispatchRelease = undefined;
1445
+ let releaseReviewHandoverLock;
1446
+ if ((resolved[1] === "review"
1447
+ && ["request", "retry"].includes(resolved[2] ?? ""))
1448
+ || resolved[1] === "complete") {
1449
+ const handoverLock = acquireHandoverLock(home);
1450
+ releaseReviewHandoverLock = handoverLock.release;
1479
1451
  }
1480
- if (result.kind === "output") {
1481
- const requestedRound = reviewRoundFromCommandData(result.data);
1482
- const persistedRequestedRound = requestedRound === undefined
1483
- ? null
1484
- : store.getReviewRound(requestedRound.taskId, requestedRound.id);
1485
- let reviewOutput = "";
1486
- let reviewData;
1487
- const resumesReviewDispatch = (resolved[1] === "review" && resolved[2] === "request")
1488
- || (resolved[1] === "work" && resolved[2] === "review")
1489
- || (resolved[1] === "run" && resolved[2] === "retry");
1490
- const reviewDispatchNeeded = requestedRound?.status === "pending"
1491
- || (requestedRound?.status === "running"
1492
- && resumesReviewDispatch
1493
- && persistedRequestedRound?.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
1494
- && (lane.currentRunId === undefined
1495
- || store.getRun(requestedRound.taskId, lane.currentRunId)?.status === "failed"))) === true);
1496
- if (reviewDispatchNeeded) {
1497
- try {
1498
- const workspace = requestedRound.status === "running"
1499
- ? store.getReviewRoundWorkspace(requestedRound.taskId, requestedRound.id)
1500
- : await workspacePreparer.prepareReviewRoundWorkspace(requestedRound.taskId, requestedRound.id);
1501
- if (workspace === null) {
1502
- throw new Error(`ReviewRound workspace is not ready: ${requestedRound.id}.`);
1503
- }
1504
- const reviewLaneWorkspaces = await prepareReviewLaneWorkspaces(requestedRound.taskId, requestedRound.id, store, workspacePreparer);
1505
- if (reviewLaneWorkspaces !== undefined) {
1506
- executionLaneWorkspaces = reviewLaneWorkspaces;
1452
+ try {
1453
+ const preparedLanes = await prepareExecutionLaneWorkspacesForCommand(resolved, store, workspacePreparer, process.env);
1454
+ if (preparedLanes !== undefined) {
1455
+ executionLaneWorkspaces = preparedLanes.workspaces;
1456
+ laneDispatchRelease = preparedLanes.release;
1457
+ laneDispatchProjectPaths = preparedLanes.projectPaths;
1458
+ }
1459
+ const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1460
+ const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
1461
+ ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
1462
+ : archiveTaskReviewCandidate;
1463
+ const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
1464
+ // Read-only Host evidence for Session inspect and Role status/list. The command stays
1465
+ // synchronous over persisted state; this is the live reading it prints
1466
+ // beside those facts, prepared here because the Host is reached over a
1467
+ // socket.
1468
+ const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
1469
+ const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
1470
+ // Physical preparation may precede the durable write, but Task status,
1471
+ // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1472
+ // one transaction. A failed attempt therefore leaves the Task Draft and
1473
+ // owning no writable workspace.
1474
+ let taskWorkspaceActivation;
1475
+ if (resolved[1] === "activate" && resolved.length === 3) {
1476
+ const taskId = resolved[2];
1477
+ const task = taskId === undefined ? null : store.getTask(taskId);
1478
+ if (task !== null && task.status === "draft") {
1479
+ taskLocalActor(store, process.env, task.id);
1480
+ taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id, process.env);
1481
+ }
1482
+ }
1483
+ const result = runTaskCommand(resolved.slice(1), store, {
1484
+ runtime,
1485
+ environment: process.env,
1486
+ yuiHome: home,
1487
+ ...(taskFinalReviewContract === undefined
1488
+ ? {}
1489
+ : { taskFinalReviewContract }),
1490
+ ...(completionSummary === undefined ? {} : { completionSummary }),
1491
+ ...(completionPublishedTreeProof === undefined
1492
+ ? {}
1493
+ : { completionPublishedTreeProof }),
1494
+ ...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
1495
+ ...candidateSnapshots,
1496
+ ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1497
+ ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1498
+ ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1499
+ ...(actualTaskReviewCandidate === undefined
1500
+ ? {}
1501
+ : { actualTaskReviewCandidate }),
1502
+ ...(archiveRemoteDeliveryProof === undefined
1503
+ ? {}
1504
+ : { archiveRemoteDeliveryProof }),
1505
+ ...(deltaRecheckPreflight === undefined
1506
+ ? {}
1507
+ : { deltaRecheckPreflight }),
1508
+ ...(liveRunConfiguration === undefined
1509
+ ? {}
1510
+ : { liveRunConfiguration }),
1511
+ ...(liveHostObservations === undefined ? {} : { liveHostObservations }),
1512
+ ...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
1513
+ ...(validateAgentConfiguration === undefined
1514
+ ? {}
1515
+ : { validateAgentConfiguration })
1516
+ });
1517
+ // The dispatch transaction has now adopted (or rejected) the prepared
1518
+ // Lane workspaces. Release the held fence so later output/review
1519
+ // handling can take the per-Project fence itself.
1520
+ if (laneDispatchRelease !== undefined) {
1521
+ laneDispatchRelease();
1522
+ laneDispatchRelease = undefined;
1523
+ }
1524
+ if (result.kind === "output") {
1525
+ const requestedRound = reviewRoundFromCommandData(result.data);
1526
+ const persistedRequestedRound = requestedRound === undefined
1527
+ ? null
1528
+ : store.getReviewRound(requestedRound.taskId, requestedRound.id);
1529
+ let reviewOutput = "";
1530
+ let reviewData;
1531
+ const resumesReviewDispatch = (resolved[1] === "review" && resolved[2] === "request")
1532
+ || (resolved[1] === "work" && resolved[2] === "review")
1533
+ || (resolved[1] === "run" && resolved[2] === "retry");
1534
+ const reviewDispatchNeeded = requestedRound?.status === "pending"
1535
+ || (requestedRound?.status === "running"
1536
+ && resumesReviewDispatch
1537
+ && persistedRequestedRound?.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
1538
+ && (lane.currentRunId === undefined
1539
+ || store.getRun(requestedRound.taskId, lane.currentRunId)?.status === "failed"))) === true);
1540
+ if (reviewDispatchNeeded) {
1541
+ try {
1542
+ const workspace = requestedRound.status === "running"
1543
+ ? store.getReviewRoundWorkspace(requestedRound.taskId, requestedRound.id)
1544
+ : await workspacePreparer.prepareReviewRoundWorkspace(requestedRound.taskId, requestedRound.id);
1545
+ if (workspace === null) {
1546
+ throw new Error(`ReviewRound workspace is not ready: ${requestedRound.id}.`);
1547
+ }
1548
+ const reviewLaneWorkspaces = await prepareReviewLaneWorkspaces(requestedRound.taskId, requestedRound.id, store, workspacePreparer);
1549
+ if (reviewLaneWorkspaces !== undefined) {
1550
+ executionLaneWorkspaces = reviewLaneWorkspaces;
1551
+ }
1552
+ const storedRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
1553
+ const freshTaskCandidate = storedRound?.scope === "task"
1554
+ ? await snapshotActualTaskReviewCandidate(requestedRound.taskId, store, workspacePreparer)
1555
+ : undefined;
1556
+ const run = dispatchPreparedReviewRound(requestedRound.taskId, requestedRound.id, store, {
1557
+ runtime,
1558
+ environment: process.env,
1559
+ yuiHome: home,
1560
+ ...(taskFinalReviewContract === undefined
1561
+ ? {}
1562
+ : { taskFinalReviewContract }),
1563
+ ...(freshTaskCandidate === undefined
1564
+ ? {}
1565
+ : { actualTaskReviewCandidate: freshTaskCandidate }),
1566
+ ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1567
+ ...(storedRound?.deltaRecheck === undefined
1568
+ || deltaRecheckPreflight === undefined
1569
+ ? {}
1570
+ : {
1571
+ deltaRecheckDiff: deltaRecheckPreflight.diffByProject
1572
+ })
1573
+ });
1574
+ reviewOutput = run === null
1575
+ ? `Review ${requestedRound.id} remains running\n`
1576
+ : `Review queued as ${requestedRound.id} (${run.id})\n`;
1577
+ reviewData = {
1578
+ reviewRequest: run === null
1579
+ ? {
1580
+ kind: "running",
1581
+ reviewerRoleName: requestedRound.reviewerRoleName,
1582
+ activeReviewRoundId: requestedRound.id,
1583
+ retryable: false
1584
+ }
1585
+ : {
1586
+ kind: "started",
1587
+ reviewerRoleName: requestedRound.reviewerRoleName,
1588
+ reviewRoundId: requestedRound.id,
1589
+ runId: run.id
1590
+ },
1591
+ reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
1592
+ ...(run === null ? {} : { reviewRun: run }),
1593
+ workspace
1594
+ };
1507
1595
  }
1508
- const storedRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
1509
- const freshTaskCandidate = (storedRound?.scope ?? "work-item") === "task"
1510
- ? await snapshotActualTaskReviewCandidate(requestedRound.taskId, store, workspacePreparer)
1511
- : undefined;
1512
- const run = dispatchPreparedReviewRound(requestedRound.taskId, requestedRound.id, store, {
1513
- runtime,
1514
- environment: process.env,
1515
- yuiHome: home,
1516
- ...(taskFinalReviewContract === undefined
1517
- ? {}
1518
- : { taskFinalReviewContract }),
1519
- ...(freshTaskCandidate === undefined
1520
- ? {}
1521
- : { actualTaskReviewCandidate: freshTaskCandidate }),
1522
- ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1523
- ...(storedRound?.deltaRecheck === undefined
1524
- || deltaRecheckPreflight === undefined
1525
- ? {}
1526
- : {
1527
- deltaRecheckDiff: deltaRecheckPreflight.diffByProject
1528
- })
1529
- });
1530
- reviewOutput = run === null
1531
- ? `Review ${requestedRound.id} remains running\n`
1532
- : `Review queued as ${requestedRound.id} (${run.id})\n`;
1533
- reviewData = {
1534
- reviewRequest: run === null
1535
- ? {
1536
- kind: "running",
1537
- reviewerRoleName: requestedRound.reviewerRoleName,
1538
- activeReviewRoundId: requestedRound.id,
1539
- retryable: false
1540
- }
1541
- : {
1542
- kind: "started",
1596
+ catch (error) {
1597
+ const message = error instanceof Error ? error.message : String(error);
1598
+ await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1599
+ const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
1600
+ runtime,
1601
+ environment: process.env,
1602
+ yuiHome: home
1603
+ });
1604
+ reviewOutput = `Review could not start: ${message}\n`
1605
+ + "The failed ReviewRound was retained for Leader routing.\n";
1606
+ reviewData = {
1607
+ reviewRequest: {
1608
+ kind: "unavailable",
1543
1609
  reviewerRoleName: requestedRound.reviewerRoleName,
1544
- reviewRoundId: requestedRound.id,
1545
- runId: run.id
1610
+ reviewRoundId: failed.id,
1611
+ reason: message,
1612
+ retryable: true
1546
1613
  },
1547
- reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
1548
- ...(run === null ? {} : { reviewRun: run }),
1549
- workspace
1550
- };
1614
+ reviewRound: failed
1615
+ };
1616
+ }
1551
1617
  }
1552
- catch (error) {
1553
- const message = error instanceof Error ? error.message : String(error);
1554
- await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1555
- const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
1556
- runtime,
1557
- environment: process.env,
1558
- yuiHome: home
1559
- });
1560
- reviewOutput = `Review could not start: ${message}\n`
1561
- + "The failed ReviewRound was retained for Leader routing.\n";
1562
- reviewData = {
1563
- reviewRequest: {
1564
- kind: "unavailable",
1565
- reviewerRoleName: requestedRound.reviewerRoleName,
1566
- reviewRoundId: failed.id,
1567
- reason: message,
1568
- retryable: true
1569
- },
1570
- reviewRound: failed
1571
- };
1618
+ if (resolved[1] === "project" && resolved[2] === "add") {
1619
+ const taskId = resolved[3];
1620
+ const task = taskId === undefined ? null : store.getTask(taskId);
1621
+ if (task?.status === "active") {
1622
+ await workspacePreparer.prepareTaskWorkspace(task.id);
1623
+ }
1572
1624
  }
1573
- }
1574
- if (resolved[1] === "project" && resolved[2] === "add") {
1575
- const taskId = resolved[3];
1576
- const task = taskId === undefined ? null : store.getTask(taskId);
1577
- if (task?.status === "active") {
1578
- await workspacePreparer.prepareTaskWorkspace(task.id);
1625
+ const controlData = result.data;
1626
+ const failureCode = controlData?.steer?.code ?? controlData?.interrupt?.code;
1627
+ if (failureCode !== undefined) {
1628
+ emitControlFailure(result.output, failureCode, result.data);
1629
+ return;
1579
1630
  }
1580
- }
1581
- const controlData = result.data;
1582
- const failureCode = controlData?.steer?.code ?? controlData?.interrupt?.code;
1583
- if (failureCode !== undefined) {
1584
- emitControlFailure(result.output, failureCode, result.data);
1631
+ emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
1632
+ ? result.data
1633
+ : { command: result.data, ...reviewData });
1585
1634
  return;
1586
1635
  }
1587
- emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
1588
- ? result.data
1589
- : { command: result.data, ...reviewData });
1590
- return;
1591
- }
1592
- if (jsonOutput && result.kind !== "session-stop"
1593
- && result.kind !== "input-steer" && result.kind !== "input-interrupt") {
1594
- throw usageError("Task Role view/takeover requires an interactive terminal.");
1595
- }
1596
- if (result.kind === "session-stop") {
1597
- await ensureFileTaskController(home, { environment: process.env });
1598
- try {
1599
- await runtime.stopExactTaskRoleSession({
1636
+ if (jsonOutput && result.kind !== "session-stop"
1637
+ && result.kind !== "input-steer" && result.kind !== "input-interrupt") {
1638
+ throw usageError("Task Role view/takeover requires an interactive terminal.");
1639
+ }
1640
+ if (result.kind === "session-stop") {
1641
+ await ensureFileTaskController(home, { environment: process.env });
1642
+ try {
1643
+ await runtime.stopExactTaskRoleSession({
1644
+ taskId: result.taskId,
1645
+ roleName: result.roleName,
1646
+ agentId: result.agentId,
1647
+ adapterId: result.adapterId,
1648
+ nativeSessionId: result.nativeSessionId,
1649
+ sessionUpdatedAt: result.sessionUpdatedAt
1650
+ });
1651
+ }
1652
+ catch (error) {
1653
+ throw runtimeError(`Session stop was requested but physical Host cleanup did not complete: ${error instanceof Error ? error.message : String(error)}`);
1654
+ }
1655
+ emit(result.output, false, {
1600
1656
  taskId: result.taskId,
1601
1657
  roleName: result.roleName,
1602
- agentId: result.agentId,
1603
- adapterId: result.adapterId,
1604
- nativeSessionId: result.nativeSessionId,
1605
- sessionUpdatedAt: result.sessionUpdatedAt
1658
+ stopped: true,
1659
+ reason: result.reason
1606
1660
  });
1661
+ return;
1607
1662
  }
1608
- catch (error) {
1609
- throw runtimeError(`Session stop was requested but physical Host cleanup did not complete: ${error instanceof Error ? error.message : String(error)}`);
1663
+ if (result.kind === "view") {
1664
+ if (result.output !== undefined)
1665
+ emit(result.output);
1666
+ tmux.attachRole(result.taskId, result.roleName, "read-only");
1667
+ return;
1610
1668
  }
1611
- emit(result.output, false, {
1612
- taskId: result.taskId,
1613
- roleName: result.roleName,
1614
- stopped: true,
1615
- reason: result.reason
1616
- });
1617
- return;
1618
- }
1619
- if (result.kind === "view") {
1620
- if (result.output !== undefined)
1621
- emit(result.output);
1622
- tmux.attachRole(result.taskId, result.roleName, "read-only");
1623
- return;
1624
- }
1625
- if (result.kind === "input-steer") {
1626
- // Core already persisted the Message and proved target + capability +
1627
- // writer fence. This is the single live edge: one native steer of the
1628
- // exact current Turn, with no retarget and no fallback to interrupt or
1629
- // queue. Its durable settlement flows through the steer receipt fold.
1630
- await ensureFileTaskController(home, { environment: process.env });
1631
- let control;
1632
- try {
1633
- control = await sendAgentHostSteerControl({
1634
- home,
1635
- scope: "task",
1636
- taskId: result.taskId,
1637
- roleName: result.roleName,
1638
- control: {
1639
- protocol: AGENT_HOST_CONTROL_PROTOCOL,
1640
- type: "steer-turn",
1641
- nativeSessionId: result.target.nativeSessionId,
1642
- nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
1643
- authority: result.target.authority,
1644
- run: { attemptId: result.receiptId, boundedText: result.text }
1645
- }
1669
+ if (result.kind === "input-steer") {
1670
+ // Core already persisted the Message and proved target + capability +
1671
+ // writer fence. This is the single live edge: one native steer of the
1672
+ // exact current Turn, with no retarget and no fallback to interrupt or
1673
+ // queue. Its durable settlement flows through the steer receipt fold.
1674
+ await ensureFileTaskController(home, { environment: process.env });
1675
+ let control;
1676
+ try {
1677
+ control = await sendAgentHostSteerControl({
1678
+ home,
1679
+ scope: "task",
1680
+ taskId: result.taskId,
1681
+ roleName: result.roleName,
1682
+ control: {
1683
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1684
+ type: "steer-turn",
1685
+ nativeSessionId: result.target.nativeSessionId,
1686
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
1687
+ authority: result.target.authority,
1688
+ run: { attemptId: result.receiptId, boundedText: result.text }
1689
+ }
1690
+ });
1691
+ }
1692
+ catch (error) {
1693
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
1694
+ + `${error instanceof Error ? error.message : String(error)}. `
1695
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
1696
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
1697
+ + "do not reissue the same input under a new requestId or a different action.");
1698
+ }
1699
+ const steer = foldSteerLiveReceipt(control);
1700
+ if (steer.state !== "steered") {
1701
+ emitControlFailure(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_REJECTED", { taskId: result.taskId, roleName: result.roleName, messageId: result.messageId, steer });
1702
+ return;
1703
+ }
1704
+ emit(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), false, {
1705
+ taskId: result.taskId, roleName: result.roleName,
1706
+ messageId: result.messageId, steer
1646
1707
  });
1708
+ return;
1647
1709
  }
1648
- catch (error) {
1649
- throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
1650
- + `${error instanceof Error ? error.message : String(error)}. `
1651
- + "The Message is retained and its outcome is recorded from the Host; whether the "
1652
- + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
1653
- + "do not reissue the same input under a new requestId or a different action.");
1654
- }
1655
- const steer = foldSteerLiveReceipt(control);
1656
- if (steer.state !== "steered") {
1657
- emitControlFailure(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_REJECTED", { taskId: result.taskId, roleName: result.roleName, messageId: result.messageId, steer });
1710
+ if (result.kind === "input-interrupt") {
1711
+ // The single live edge for interrupt: one native cancel of the exact
1712
+ // current Turn. Never a kill/restart/detach. Any then-handoff was
1713
+ // already claimed durably by Core and is delivered once by the ordinary
1714
+ // continuation path after this Turn reaches a proven terminal.
1715
+ await ensureFileTaskController(home, { environment: process.env });
1716
+ let control;
1717
+ try {
1718
+ control = await sendAgentHostCancelControl({
1719
+ home,
1720
+ scope: "task",
1721
+ taskId: result.taskId,
1722
+ roleName: result.roleName,
1723
+ control: {
1724
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1725
+ type: "cancel",
1726
+ nativeOnly: true,
1727
+ nativeSessionId: result.target.nativeSessionId,
1728
+ // Native cancel names the exact original execution attempt it stops
1729
+ // (Host matches request.attemptId === activeRunAttemptId). That is
1730
+ // distinct from receiptId, the durable identity of this interrupt
1731
+ // control operation — never send the operation id as the turn id.
1732
+ attemptId: result.target.attemptId,
1733
+ authority: result.target.authority
1734
+ }
1735
+ });
1736
+ }
1737
+ catch (error) {
1738
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
1739
+ throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
1740
+ + `${error instanceof Error ? error.message : String(error)}. `
1741
+ + "No process was killed; re-read the Session before retrying.");
1742
+ }
1743
+ const interrupt = foldInterruptLiveReceipt(control);
1744
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
1745
+ if (interrupt.state !== "interrupt-requested") {
1746
+ emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
1747
+ : interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
1748
+ return;
1749
+ }
1750
+ emit(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), false, {
1751
+ taskId: result.taskId, roleName: result.roleName,
1752
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
1753
+ interrupt
1754
+ });
1658
1755
  return;
1659
1756
  }
1660
- emit(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), false, {
1661
- taskId: result.taskId, roleName: result.roleName,
1662
- messageId: result.messageId, steer
1663
- });
1664
- return;
1665
- }
1666
- if (result.kind === "input-interrupt") {
1667
- // The single live edge for interrupt: one native cancel of the exact
1668
- // current Turn. Never a kill/restart/detach. Any then-handoff was
1669
- // already claimed durably by Core and is delivered once by the ordinary
1670
- // continuation path after this Turn reaches a proven terminal.
1671
- await ensureFileTaskController(home, { environment: process.env });
1672
- let control;
1673
- try {
1674
- control = await sendAgentHostCancelControl({
1675
- home,
1676
- scope: "task",
1757
+ const syncAuthority = async (authorityResult) => {
1758
+ let control;
1759
+ try {
1760
+ control = await sendAgentHostAuthorityControl({
1761
+ home,
1762
+ scope: "task",
1763
+ taskId: authorityResult.taskId,
1764
+ roleName: authorityResult.roleName,
1765
+ control: {
1766
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1767
+ type: "set-authority",
1768
+ nativeSessionId: authorityResult.nativeSessionId,
1769
+ authority: authorityResult.authority
1770
+ }
1771
+ });
1772
+ }
1773
+ catch (error) {
1774
+ throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
1775
+ + `${error instanceof Error ? error.message : String(error)}. `
1776
+ + `Durable authority is ${authorityResult.authority.owner}-owned; retry `
1777
+ + `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
1778
+ + "to reconcile the Host.");
1779
+ }
1780
+ if (control.outcome !== "accepted"
1781
+ || control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
1782
+ || control.snapshot.authorityEpoch !== authorityResult.authority.epoch
1783
+ || control.snapshot.authorityOwner !== authorityResult.authority.owner
1784
+ || control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
1785
+ throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
1786
+ + (control.snapshot.detail ?? control.outcome)
1787
+ + `. Durable authority is ${authorityResult.authority.owner}-owned; `
1788
+ + "retry 'yui task role release "
1789
+ + `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
1790
+ }
1791
+ return control;
1792
+ };
1793
+ await syncAuthority(result);
1794
+ emit(result.output);
1795
+ if (result.action === "release") {
1796
+ runtime.notifyMailboxChanged({
1797
+ kind: "role",
1677
1798
  taskId: result.taskId,
1678
- roleName: result.roleName,
1679
- control: {
1680
- protocol: AGENT_HOST_CONTROL_PROTOCOL,
1681
- type: "cancel",
1682
- nativeOnly: true,
1683
- nativeSessionId: result.target.nativeSessionId,
1684
- // Native cancel names the exact original execution attempt it stops
1685
- // (Host matches request.attemptId === activeRunAttemptId). That is
1686
- // distinct from receiptId, the durable identity of this interrupt
1687
- // control operation — never send the operation id as the turn id.
1688
- attemptId: result.target.attemptId,
1689
- authority: result.target.authority
1690
- }
1799
+ roleName: result.roleName
1691
1800
  });
1692
- }
1693
- catch (error) {
1694
- recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
1695
- throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
1696
- + `${error instanceof Error ? error.message : String(error)}. `
1697
- + "No process was killed; re-read the Session before retrying.");
1698
- }
1699
- const interrupt = foldInterruptLiveReceipt(control);
1700
- recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
1701
- if (interrupt.state !== "interrupt-requested") {
1702
- emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
1703
- : interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
1704
1801
  return;
1705
1802
  }
1706
- emit(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), false, {
1707
- taskId: result.taskId, roleName: result.roleName,
1708
- ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
1709
- interrupt
1710
- });
1711
- return;
1712
- }
1713
- const syncAuthority = async (authorityResult) => {
1714
- let control;
1803
+ process.stdout.write("Provider input is now routed through the Agent Host PTY gateway. "
1804
+ + "Use tmux detach (Ctrl-b d) to return authority to the Controller.\n");
1715
1805
  try {
1716
- control = await sendAgentHostAuthorityControl({
1717
- home,
1718
- scope: "task",
1719
- taskId: authorityResult.taskId,
1720
- roleName: authorityResult.roleName,
1721
- control: {
1722
- protocol: AGENT_HOST_CONTROL_PROTOCOL,
1723
- type: "set-authority",
1724
- nativeSessionId: authorityResult.nativeSessionId,
1725
- authority: authorityResult.authority
1806
+ tmux.attachRole(result.taskId, result.roleName, "read-write");
1807
+ }
1808
+ finally {
1809
+ const currentTask = store.getTask(result.taskId);
1810
+ // Completing or retiring the Task from inside the takeover AgentRun owns
1811
+ // Provider shutdown and clears the live binding. Do not turn that
1812
+ // successful terminal transition into a failing best-effort release.
1813
+ if (currentTask?.status === "active") {
1814
+ const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
1815
+ if (released.kind !== "authority" || released.action !== "release") {
1816
+ throw runtimeError("Provider authority release returned an invalid result.");
1726
1817
  }
1727
- });
1818
+ await syncAuthority(released);
1819
+ emit(released.output);
1820
+ runtime.notifyMailboxChanged({
1821
+ kind: "role",
1822
+ taskId: released.taskId,
1823
+ roleName: released.roleName
1824
+ });
1825
+ }
1728
1826
  }
1729
- catch (error) {
1730
- throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
1731
- + `${error instanceof Error ? error.message : String(error)}. `
1732
- + `Durable authority is ${authorityResult.authority.owner}-owned; retry `
1733
- + `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
1734
- + "to reconcile the Host.");
1735
- }
1736
- if (control.outcome !== "accepted"
1737
- || control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
1738
- || control.snapshot.authorityEpoch !== authorityResult.authority.epoch
1739
- || control.snapshot.authorityOwner !== authorityResult.authority.owner
1740
- || control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
1741
- throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
1742
- + (control.snapshot.detail ?? control.outcome)
1743
- + `. Durable authority is ${authorityResult.authority.owner}-owned; `
1744
- + "retry 'yui task role release "
1745
- + `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
1746
- }
1747
- return control;
1748
- };
1749
- await syncAuthority(result);
1750
- emit(result.output);
1751
- if (result.action === "release") {
1752
- runtime.notifyMailboxChanged({
1753
- kind: "role",
1754
- taskId: result.taskId,
1755
- roleName: result.roleName
1756
- });
1757
1827
  return;
1758
1828
  }
1759
- process.stdout.write("Provider input is now routed through the Agent Host PTY gateway. "
1760
- + "Use tmux detach (Ctrl-b d) to return authority to the Controller.\n");
1761
- try {
1762
- tmux.attachRole(result.taskId, result.roleName, "read-write");
1829
+ catch (error) {
1830
+ await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1831
+ if (laneDispatchRelease !== undefined) {
1832
+ laneDispatchRelease();
1833
+ laneDispatchRelease = undefined;
1834
+ }
1835
+ throw error;
1763
1836
  }
1764
1837
  finally {
1765
- const currentTask = store.getTask(result.taskId);
1766
- // Completing or retiring the Task from inside the takeover AgentRun owns
1767
- // Provider shutdown and clears the live binding. Do not turn that
1768
- // successful terminal transition into a failing best-effort release.
1769
- if (currentTask?.status === "active") {
1770
- const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
1771
- if (released.kind !== "authority" || released.action !== "release") {
1772
- throw runtimeError("Provider authority release returned an invalid result.");
1773
- }
1774
- await syncAuthority(released);
1775
- emit(released.output);
1776
- runtime.notifyMailboxChanged({
1777
- kind: "role",
1778
- taskId: released.taskId,
1779
- roleName: released.roleName
1780
- });
1838
+ if (releaseReviewHandoverLock !== undefined) {
1839
+ releaseReviewHandoverLock();
1840
+ releaseReviewHandoverLock = undefined;
1781
1841
  }
1782
1842
  }
1843
+ }
1844
+ if (resolved[0] === "jobs") {
1845
+ emit(runJobCommand(resolved.slice(1), store, { runtime }));
1783
1846
  return;
1784
1847
  }
1785
- catch (error) {
1786
- await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1787
- if (laneDispatchRelease !== undefined) {
1788
- laneDispatchRelease();
1789
- laneDispatchRelease = undefined;
1790
- }
1791
- throw error;
1848
+ if (resolved[0] === "job") {
1849
+ emit(await runDurableJobCommand(resolved.slice(1), {
1850
+ home,
1851
+ json: jsonOutput,
1852
+ environment: process.env,
1853
+ store
1854
+ }));
1855
+ return;
1792
1856
  }
1793
- finally {
1794
- if (releaseReviewHandoverLock !== undefined) {
1795
- releaseReviewHandoverLock();
1796
- releaseReviewHandoverLock = undefined;
1797
- }
1857
+ if (resolved[0] === "telemetry") {
1858
+ emit(await runTelemetryCommand(resolved.slice(1), {
1859
+ home,
1860
+ json: jsonOutput,
1861
+ environment: process.env,
1862
+ store
1863
+ }));
1864
+ return;
1798
1865
  }
1866
+ throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
1799
1867
  }
1800
- if (resolved[0] === "jobs") {
1801
- emit(runJobCommand(resolved.slice(1), store, { runtime }));
1802
- return;
1803
- }
1804
- if (resolved[0] === "job") {
1805
- emit(await runDurableJobCommand(resolved.slice(1), {
1806
- home,
1807
- json: jsonOutput,
1808
- environment: process.env,
1809
- store
1810
- }));
1811
- return;
1812
- }
1813
- if (resolved[0] === "telemetry") {
1814
- emit(await runTelemetryCommand(resolved.slice(1), {
1815
- home,
1816
- json: jsonOutput,
1817
- environment: process.env,
1818
- store
1819
- }));
1820
- return;
1868
+ finally {
1869
+ await telemetry?.sink.close();
1821
1870
  }
1822
- throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
1823
1871
  }
1824
1872
  function explicitReleaseActivationDriver() {
1825
1873
  if (args.length !== 3
@@ -1846,7 +1894,7 @@ async function preflightManagedTaskControlPlane() {
1846
1894
  const internalCallback = args[0] === "internal"
1847
1895
  && ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
1848
1896
  const home = resolveYuiHome(process.env);
1849
- const manifest = assertManagedSessionManifest(home, "task");
1897
+ assertManagedSessionManifest(home, "task");
1850
1898
  const diagnosticTarget = taskDiagnosticTarget(args);
1851
1899
  const diagnostic = diagnosticTarget !== undefined
1852
1900
  && diagnosticTarget === process.env.YUI_TASK_ID;
@@ -1921,7 +1969,19 @@ async function preflightManagedGlobalControlPlane() {
1921
1969
  await assertRuntimeCoherence({ actualHome: home }, {
1922
1970
  checkController: !(expectedRoleKind === "operator" && operatorOfflineCommand(args))
1923
1971
  });
1924
- return { contract: undefined, verifiedStore: openCurrentTaskStore(home) };
1972
+ const verifiedStore = openCurrentTaskStore(home);
1973
+ const internalCallback = args[0] === "internal"
1974
+ && ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
1975
+ if (!internalCallback && !operatorOfflineCommand(args)) {
1976
+ try {
1977
+ requireManagedGlobalCaller(verifiedStore, process.env);
1978
+ }
1979
+ catch (error) {
1980
+ verifiedStore.close();
1981
+ throw error;
1982
+ }
1983
+ }
1984
+ return { contract: undefined, verifiedStore };
1925
1985
  }
1926
1986
  /**
1927
1987
  * The entry point the current invocation resolves to. Only its executable and
@@ -1953,17 +2013,21 @@ function assertManagedSessionManifest(home, scope) {
1953
2013
  return manifest;
1954
2014
  }
1955
2015
  function cleanupCliError(error, fallbackResource) {
2016
+ if (error instanceof CleanupInspectionError) {
2017
+ return usageError(error.message, undefined, cleanupBlockedDetails(error.checks[0]?.reason ?? "cleanup-failed", fallbackResource, true, error.checks));
2018
+ }
1956
2019
  if (error instanceof WorkspaceCleanupBlockedError) {
1957
2020
  return usageError(error.message, undefined, cleanupBlockedDetails(error.reason, error.resource, error.retryable));
1958
2021
  }
1959
2022
  return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true));
1960
2023
  }
1961
- function cleanupBlockedDetails(reason, resource, retryable) {
2024
+ function cleanupBlockedDetails(reason, resource, retryable, checks) {
1962
2025
  return {
1963
2026
  status: "blocked",
1964
2027
  blockedBy: [{ resource, reason, retryable }],
1965
2028
  remainingResources: [resource],
1966
- retryable
2029
+ retryable,
2030
+ ...(checks === undefined ? {} : { checks })
1967
2031
  };
1968
2032
  }
1969
2033
  function cliWorkItemReference(value, environment) {
@@ -2042,9 +2106,20 @@ async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, e
2042
2106
  throw usageError(`${item.taskId}/${roleName} already has an active turn.`);
2043
2107
  }
2044
2108
  }
2045
- const held = preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
2109
+ const held = await preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
2046
2110
  const map = new Map();
2047
2111
  try {
2112
+ assertTaskDeliveryAuthority(store, environment, item.taskId);
2113
+ const currentItem = store.getWorkItem(item.taskId, item.id);
2114
+ if (currentItem?.revision !== item.revision
2115
+ || JSON.stringify(workItemDispatchLanePlan(args, store, currentItem)) !== JSON.stringify(plan)) {
2116
+ throw new Error(`Work item dispatch changed while waiting for Project maintenance: ${item.id}.`);
2117
+ }
2118
+ if (held.current.status !== "active" || held.current.executionGate.state !== "enabled"
2119
+ || plan.roles.some(roleName => store.getRole(item.taskId, roleName) === null
2120
+ || store.getActiveRun(item.taskId, roleName) !== null)) {
2121
+ throw new Error(`Task/Role dispatch state changed while waiting for Project maintenance: ${item.taskId}.`);
2122
+ }
2048
2123
  const projectPaths = new Map();
2049
2124
  for (const { projectId } of held.current.projectBindings) {
2050
2125
  const project = store.getProject(projectId);
@@ -2128,7 +2203,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
2128
2203
  && args[3] !== undefined) {
2129
2204
  const reference = cliTaskRecordReference(args[3], "reviewRound", environment);
2130
2205
  const round = store.getReviewRound(reference.taskId, reference.localId);
2131
- if (round !== null && (round.scope ?? "work-item") === "task") {
2206
+ if (round !== null && round.scope === "task") {
2132
2207
  taskId = reference.taskId;
2133
2208
  }
2134
2209
  }
@@ -2138,7 +2213,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
2138
2213
  && args[4] !== undefined) {
2139
2214
  const reference = cliTaskRecordReference(args[4], "reviewRound", environment);
2140
2215
  const round = store.getReviewRound(reference.taskId, reference.localId);
2141
- if (round !== null && (round.scope ?? "work-item") === "task") {
2216
+ if (round !== null && round.scope === "task") {
2142
2217
  taskId = reference.taskId;
2143
2218
  }
2144
2219
  }
@@ -2152,7 +2227,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
2152
2227
  : store.getReviewRound(reference.taskId, run.reviewRoundId);
2153
2228
  if (run?.purpose === "review"
2154
2229
  && round !== null
2155
- && (round.scope ?? "work-item") === "task") {
2230
+ && round.scope === "task") {
2156
2231
  taskId = reference.taskId;
2157
2232
  }
2158
2233
  }
@@ -2409,7 +2484,7 @@ async function completionCommand(home, node) {
2409
2484
  const store = openCurrentTaskStore(home);
2410
2485
  const ioHandle = terminalIo();
2411
2486
  try {
2412
- const manager = new FileCompletionManager(store, process.env, resolveCliIdentity(process.env));
2487
+ const manager = new FileCompletionManager(store, process.env);
2413
2488
  emit(await runCompletionWizard(manager, ioHandle.io, shell === undefined ? {} : { shell }));
2414
2489
  }
2415
2490
  finally {
@@ -2490,7 +2565,7 @@ async function preflightAgentConfigurationMutation(commandArgs, store, catalogs)
2490
2565
  const mutation = profileAgentConfigurationMutation(commandArgs, store)
2491
2566
  ?? taskRoleAgentConfigurationMutation(commandArgs, store);
2492
2567
  if (mutation === undefined) {
2493
- await warmLegacyRoleConfigurationMutation(commandArgs, store, catalogs);
2568
+ await warmConfigurationMutationCatalog(commandArgs, store, catalogs);
2494
2569
  return undefined;
2495
2570
  }
2496
2571
  const agent = store.getConfiguredAgent(mutation.agentId);
@@ -2531,7 +2606,7 @@ function taskRoleAgentConfigurationMutation(args, store) {
2531
2606
  ? previewTaskRoleAgentConfigurationMutation(args.slice(1), store)
2532
2607
  : undefined;
2533
2608
  }
2534
- async function warmLegacyRoleConfigurationMutation(commandArgs, store, catalogs) {
2609
+ async function warmConfigurationMutationCatalog(commandArgs, store, catalogs) {
2535
2610
  if (!hasModelOrEffortMutation(commandArgs))
2536
2611
  return;
2537
2612
  const agentId = configurationMutationAgentId(commandArgs, store);
@@ -2667,7 +2742,7 @@ function selectionCall(store, catalogs, method, params) {
2667
2742
  case "role.list": return store.listGlobalRoles();
2668
2743
  case "role.show": return store.getGlobalRole(String(params.name ?? ""));
2669
2744
  case "project.list": return callOptional(reader, "listProjects");
2670
- case "task.list": return callOptional(reader, "listTasks");
2745
+ case "task.list": return store.listTaskChoices();
2671
2746
  case "task.integration.list": return store.listIntegrationAttempts(String(params.taskId ?? ""));
2672
2747
  case "task.change-set.list": return store.listChangeSets(String(params.taskId ?? ""));
2673
2748
  case "task.role.list": return callOptional(reader, "listRoles", [params.taskId]);
@@ -2818,15 +2893,8 @@ function agentEnvironmentRefreshScope(previous, current, configured) {
2818
2893
  nativeNames: [...new Set([...currentNative, ...previousOnlyNative])]
2819
2894
  };
2820
2895
  }
2821
- export function cliIdentity(env) {
2822
- return env.YUI_CLI_NAME === "yui-dev" ? "yui-dev" : "yui";
2823
- }
2824
2896
  function normalizeAliases(input) {
2825
2897
  const normalized = [...input];
2826
- // Existing immutable Session Manifests (through 0.15.8) name this entry.
2827
- // Remove once those Sessions are retired; both names share one handler.
2828
- if (normalized[0] === "task" && normalized[1] === "turn")
2829
- normalized[1] = "run";
2830
2898
  if (normalized.length === 1 && (normalized[0] === "-v" || normalized[0] === "--version")) {
2831
2899
  return ["version"];
2832
2900
  }