@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
@@ -1,7 +1,9 @@
1
- import { isDeepStrictEqual } from "node:util";
2
1
  import { lstat } from "node:fs/promises";
2
+ import { isDeepStrictEqual } from "node:util";
3
3
  import { isArchivePersistenceFailure, recordArchiveCleanup } from "../task/archiveDiagnostics.js";
4
- import { projectTaskRemoteDeliveryFromStore } from "../commands/taskRemoteDeliveryCommand.js";
4
+ import { archiveExecutionChecks, archiveSettlementChecks } from "../task/archivePreflight.js";
5
+ import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryService.js";
6
+ import { CleanupInspectionError } from "../workspace/cleanupInspection.js";
5
7
  import { WorkItemChangeSetManager } from "../workspace/workItemChangeSetManager.js";
6
8
  import { hasRuntimeLifecycleWork, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
7
9
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
@@ -85,8 +87,12 @@ export class TaskWorkspaceCoordinator {
85
87
  .map(({ projectId }) => projectId);
86
88
  const releaseMaintenance = projectIds.length === 0
87
89
  ? () => { }
88
- : acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
90
+ : await acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
89
91
  try {
92
+ if (!isDeepStrictEqual(this.store.getWorkItem(taskId, workItemId), item)
93
+ || !isDeepStrictEqual(this.store.getWorkItemWorkspace(taskId, workItemId), workspace)) {
94
+ throw new Error(`Work item changed while waiting for cleanup: ${taskId}/${workItemId}.`);
95
+ }
90
96
  const state = await this.preparer.inspectWorkItemWorkspace(item.taskId, item.id);
91
97
  if (state === "dirty")
92
98
  return "dirty";
@@ -96,7 +102,7 @@ export class TaskWorkspaceCoordinator {
96
102
  const laneCleanup = await this.preparer.cleanupExecutionLaneWorkspacesForWorkItem(item.taskId, item.id);
97
103
  if (laneCleanup === "dirty")
98
104
  return "dirty";
99
- return this.preparer.cleanupWorkItemWorkspace(item.taskId, item.id, disposition);
105
+ return await this.preparer.cleanupWorkItemWorkspace(item.taskId, item.id, disposition);
100
106
  }
101
107
  finally {
102
108
  releaseMaintenance();
@@ -137,8 +143,12 @@ export class TaskWorkspaceCoordinator {
137
143
  .map(({ projectId }) => projectId);
138
144
  const releaseMaintenance = projectIds.length === 0
139
145
  ? () => { }
140
- : acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
146
+ : await acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
141
147
  try {
148
+ if (!isDeepStrictEqual(this.store.getReviewRound(taskId, reviewRoundId), round)
149
+ || !isDeepStrictEqual(this.store.getReviewRoundWorkspace(taskId, reviewRoundId), workspace)) {
150
+ throw new Error(`ReviewRound changed while waiting for cleanup: ${taskId}/${reviewRoundId}.`);
151
+ }
142
152
  const state = await this.preparer.inspectReviewRoundWorkspace(taskId, reviewRoundId);
143
153
  if (state === "dirty")
144
154
  return "dirty";
@@ -146,7 +156,7 @@ export class TaskWorkspaceCoordinator {
146
156
  const laneCleanup = await this.preparer.cleanupExecutionLaneWorkspacesForReviewRound(taskId, reviewRoundId);
147
157
  if (laneCleanup === "dirty")
148
158
  return "dirty";
149
- return this.preparer.cleanupReviewRoundWorkspace(taskId, reviewRoundId);
159
+ return await this.preparer.cleanupReviewRoundWorkspace(taskId, reviewRoundId);
150
160
  }
151
161
  finally {
152
162
  releaseMaintenance();
@@ -161,6 +171,9 @@ export class TaskWorkspaceCoordinator {
161
171
  if (task.status !== "completed" && task.status !== "cancelled") {
162
172
  throw new Error(`Task must be completed or retired before archive cleanup: ${task.id}.`);
163
173
  }
174
+ const settlement = [...archiveSettlementChecks(this.store, task), ...archiveExecutionChecks(this.store, taskId)];
175
+ if (settlement.length > 0)
176
+ throw new CleanupInspectionError(settlement);
164
177
  const managedWorkspaces = [...this.store.listManagedWorkspaces(task.id)]
165
178
  .sort((left, right) => managedWorkspaceKey(left.owner)
166
179
  .localeCompare(managedWorkspaceKey(right.owner)));
@@ -172,7 +185,14 @@ export class TaskWorkspaceCoordinator {
172
185
  for (const entry of workspace.entries)
173
186
  projectIds.add(entry.projectId);
174
187
  }
175
- releaseMaintenance = acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
188
+ releaseMaintenance = await acquireProjectMaintenanceLocks(this.preparer.home, projectIds);
189
+ this.#assertTaskArchiveLifecycle(task);
190
+ const currentWorkspaces = [...this.store.listManagedWorkspaces(task.id)]
191
+ .sort((left, right) => managedWorkspaceKey(left.owner)
192
+ .localeCompare(managedWorkspaceKey(right.owner)));
193
+ if (!isDeepStrictEqual(currentWorkspaces, managedWorkspaces)) {
194
+ throw new Error(`Task workspaces changed while waiting for archive cleanup: ${taskId}.`);
195
+ }
176
196
  const laneWorkspaces = managedWorkspaces.filter(({ owner }) => owner.type === "execution-lane");
177
197
  const integrationWorkspaces = managedWorkspaces.filter(({ owner }) => owner.type === "integration-attempt");
178
198
  const workspaces = managedWorkspaces
@@ -200,93 +220,22 @@ export class TaskWorkspaceCoordinator {
200
220
  workItems: allWorkItems,
201
221
  reviewRounds: allReviewRounds
202
222
  };
203
- for (const round of reviewRounds) {
204
- if (round.status !== "completed" && round.status !== "failed") {
205
- throw new Error(`ReviewRound must be terminal before archive cleanup: ${round.id}.`);
206
- }
207
- if (await this.preparer.inspectReviewRoundWorkspace(task.id, round.id) === "dirty") {
208
- return {
209
- taskId,
210
- status: "retained-dirty",
211
- ...(task.cwd === undefined ? {} : { path: task.cwd }),
212
- error: `ReviewRound worktree is dirty: ${round.id}.`,
213
- reason: "dirty-worktree",
214
- resource: `review-round:${task.id}/${round.id}`,
215
- retryable: true
216
- };
217
- }
218
- }
219
- for (const item of workItems) {
220
- if (await this.preparer.inspectWorkItemWorkspace(task.id, item.id) === "dirty") {
221
- return {
222
- taskId,
223
- status: "retained-dirty",
224
- ...(task.cwd === undefined ? {} : { path: task.cwd }),
225
- error: `WorkItem worktree is dirty: ${item.id}.`,
226
- reason: "dirty-worktree",
227
- resource: `work-item:${task.id}/${item.id}`,
228
- retryable: true
229
- };
230
- }
231
- }
232
- for (const workspace of laneWorkspaces) {
233
- if (workspace.owner.type !== "execution-lane")
234
- continue;
235
- if (await this.preparer.inspectExecutionLaneWorkspace(task.id, workspace.owner.executionGroupId, workspace.owner.executionLaneId) === "dirty") {
236
- return {
237
- taskId,
238
- status: "retained-dirty",
239
- ...(task.cwd === undefined ? {} : { path: task.cwd }),
240
- error: `Execution Lane worktree is dirty: ${managedWorkspaceKey(workspace.owner)}.`,
241
- reason: "dirty-worktree",
242
- resource: managedWorkspaceKey(workspace.owner),
243
- retryable: true
244
- };
245
- }
246
- }
247
- for (const workspace of integrationWorkspaces) {
248
- if (workspace.owner.type !== "integration-attempt")
249
- continue;
250
- const attempt = this.store.getIntegrationAttempt(task.id, workspace.owner.integrationAttemptId);
251
- if (attempt === null
252
- || attempt.status === "running"
253
- || attempt.status === "blocked"
254
- || attempt.status === "conflicted"
255
- || attempt.status === "validating") {
256
- throw new Error(`IntegrationAttempt must be terminal before archive cleanup: ${workspace.owner.integrationAttemptId}.`);
257
- }
258
- if (await this.preparer.inspectIntegrationWorkspace(task.id, attempt.id) === "dirty") {
259
- return {
260
- taskId,
261
- status: "retained-dirty",
262
- ...(task.cwd === undefined ? {} : { path: task.cwd }),
263
- error: `Integration worktree is dirty: ${attempt.id}.`,
264
- reason: "dirty-worktree",
265
- resource: `integration-attempt:${task.id}/${attempt.id}`,
266
- retryable: true
267
- };
268
- }
269
- }
270
- const state = await this.preparer.inspectTaskMainWorkspace(taskId);
271
- if (state === "dirty") {
272
- return {
273
- taskId,
274
- status: "retained-dirty",
275
- ...(task.cwd === undefined ? {} : { path: task.cwd }),
276
- error: `Task main worktree is dirty: ${task.id}.`,
277
- reason: "dirty-worktree",
278
- resource: `task-worktree:${task.id}`,
279
- retryable: true
280
- };
281
- }
282
- const activeRole = this.store.listRoles(taskId)
283
- .find((role) => this.store.getActiveRun(taskId, role.name) !== null);
284
- if (activeRole !== undefined) {
285
- throw new WorkspaceCleanupBlockedError("active-turn", `role:${task.id}/${activeRole.name}`, true, `Task Role still has an active AgentRun: ${task.id}/${activeRole.name}.`);
223
+ const checks = [];
224
+ for (const workspace of managedWorkspaces) {
225
+ const workspaceDisposition = workspace.owner.type === "work-item"
226
+ && this.store.getWorkItem(task.id, workspace.owner.workItemId)?.status === "retired" ? "abandoned" : disposition;
227
+ const inspected = await this.preparer.inspectWorkspaceCleanup(workspace, workspaceDisposition);
228
+ // Child worktrees are removed first below. Their Git registrations
229
+ // necessarily exist now; the clone's removal boundary rechecks them
230
+ // after every child cleanup and never removes an unresolved owner.
231
+ checks.push(...inspected.filter(c => !(workspace.owner.type === "task"
232
+ && c.reason === "git-worktree-registrations")));
286
233
  }
234
+ if (checks.length > 0)
235
+ throw new CleanupInspectionError(checks);
287
236
  const roleNames = this.store.listRoles(taskId).map(({ name }) => name);
288
237
  await this.#stopLiveRoles(taskId, roleNames);
289
- await this.runtime.assertTaskPhysicalResourcesReleased?.(task.id);
238
+ await this.runtime.releaseTaskTerminals(task.id);
290
239
  this.#assertTaskArchiveSnapshot(snapshot);
291
240
  for (const workspace of laneWorkspaces) {
292
241
  this.#assertTaskArchiveLifecycle(task);
@@ -297,7 +246,7 @@ export class TaskWorkspaceCoordinator {
297
246
  for (const workspace of integrationWorkspaces) {
298
247
  this.#assertTaskArchiveLifecycle(task);
299
248
  if (workspace.owner.type === "integration-attempt") {
300
- await this.preparer.cleanupIntegrationWorkspace(task.id, workspace.owner.integrationAttemptId);
249
+ await this.preparer.cleanupIntegrationWorkspace(task.id, workspace.owner.integrationAttemptId, { preserveChanges: true });
301
250
  }
302
251
  }
303
252
  for (const round of reviewRounds) {
@@ -309,26 +258,30 @@ export class TaskWorkspaceCoordinator {
309
258
  await this.preparer.cleanupWorkItemWorkspace(task.id, item.id, item.status === "accepted" ? disposition : "abandoned");
310
259
  }
311
260
  this.#assertTaskArchiveLifecycle(task);
312
- return this.preparer.cleanupTaskForArchive(taskId);
261
+ return await this.preparer.cleanupTaskForArchive(taskId, disposition);
313
262
  }
314
263
  catch (error) {
315
264
  const task = this.store.getTask(taskId);
316
265
  return {
317
266
  taskId,
318
- status: "failed",
267
+ status: error instanceof CleanupInspectionError && error.checks.every(c => c.reason === "dirty-worktree")
268
+ ? "retained-dirty" : "failed",
319
269
  ...(task?.cwd === undefined ? {} : { path: task.cwd }),
320
270
  error: error instanceof Error ? error.message : String(error),
321
- ...(error instanceof WorkspaceCleanupBlockedError
322
- ? {
323
- reason: error.reason,
324
- resource: error.resource,
325
- retryable: error.retryable
326
- }
327
- : {
328
- reason: "cleanup-failed",
329
- resource: `task:${taskId}`,
330
- retryable: true
331
- })
271
+ ...(error instanceof CleanupInspectionError
272
+ ? { checks: error.checks, reason: error.checks[0]?.reason ?? "cleanup-failed",
273
+ resource: error.checks[0]?.resource ?? `task:${taskId}`, retryable: true }
274
+ : error instanceof WorkspaceCleanupBlockedError
275
+ ? {
276
+ reason: error.reason,
277
+ resource: error.resource,
278
+ retryable: error.retryable
279
+ }
280
+ : {
281
+ reason: "cleanup-failed",
282
+ resource: `task:${taskId}`,
283
+ retryable: true
284
+ })
332
285
  };
333
286
  }
334
287
  finally {
@@ -368,15 +321,9 @@ export class TaskWorkspaceCoordinator {
368
321
  for (const role of this.store.listRoles(taskId)) {
369
322
  const released = await attempt({ resource: `role:${taskId}/${role.name}`,
370
323
  detail: "Exact Role runtime stop." }, async () => {
371
- const provider = this.store.getTaskRoleSessionSet(taskId, role.name)?.providerBinding;
372
- const mailbox = this.store.getWorkMailbox({ kind: "role", taskId, roleName: role.name });
373
- if (mailbox?.processing != null && (provider?.run == null
374
- || !["completed", "failed", "cancelled"].includes(provider.run.status))) {
375
- throw new Error(`Original mailbox claim ${mailbox.processing.batchId} retained; execution outcome is not known.`);
376
- }
377
- if (provider?.run != null && !["completed", "failed", "cancelled"].includes(provider.run.status)) {
378
- throw new Error(`Provider input ${provider.run.attemptId}/${provider.run.status} retained; establish exact quiescence before resolving it.`);
379
- }
324
+ const checks = archiveExecutionChecks(this.store, taskId).filter(c => c.resource === `role:${taskId}/${role.name}`);
325
+ if (checks.length > 0)
326
+ throw new CleanupInspectionError(checks);
380
327
  await this.#stopLiveRoles(taskId, [role.name]);
381
328
  return "released";
382
329
  });
@@ -384,17 +331,12 @@ export class TaskWorkspaceCoordinator {
384
331
  }
385
332
  const physicalReleased = await attempt({ resource: `runtime:${taskId}`,
386
333
  detail: "Verify exact physical resource release before workspace deletion." }, async () => {
387
- const activeRun = this.store.listRuns(taskId).find(r => r.status === "active");
388
- const job = this.store.listDurableJobs(taskId).find(j => ["queued", "running", "unknown-needs-attention"].includes(j.status));
389
- if (activeRun !== undefined || job !== undefined) {
390
- throw new Error(`Execution resources retained: ${activeRun?.id ?? job?.id}; archive is not proof of quiescence.`);
391
- }
334
+ const checks = archiveExecutionChecks(this.store, taskId);
335
+ if (checks.length > 0)
336
+ throw new CleanupInspectionError(checks);
392
337
  if (!runtimeReleased)
393
338
  throw new Error("One or more Role runtimes could not be safely released.");
394
- if (this.runtime.assertTaskPhysicalResourcesReleased === undefined) {
395
- throw new Error("Physical resource release inspection is unavailable; workspace references retained.");
396
- }
397
- await this.runtime.assertTaskPhysicalResourcesReleased(taskId);
339
+ await this.runtime.releaseTaskTerminals(taskId);
398
340
  return "released";
399
341
  });
400
342
  if (physicalReleased) {
@@ -406,7 +348,7 @@ export class TaskWorkspaceCoordinator {
406
348
  detail: "Remove only clean, exactly owned workspace resources.",
407
349
  paths: [workspace.root, ...workspace.entries.filter(e => e.access === "write").map(e => e.path)]
408
350
  }, async () => {
409
- const release = acquireProjectMaintenanceLocks(this.preparer.home, workspace.entries.map(e => e.projectId));
351
+ const release = await acquireProjectMaintenanceLocks(this.preparer.home, workspace.entries.map(e => e.projectId));
410
352
  try {
411
353
  if (!isDeepStrictEqual(this.store.getManagedWorkspace(workspace.owner), workspace)) {
412
354
  throw new Error("Workspace ownership changed; retained.");
@@ -418,16 +360,16 @@ export class TaskWorkspaceCoordinator {
418
360
  if (item?.status === "accepted" && disposition === "integrated") {
419
361
  await new WorkItemChangeSetManager(this.store).assertIntegrated(taskId, item.id);
420
362
  }
421
- return this.preparer.cleanupWorkItemWorkspace(taskId, owner.workItemId, item?.status === "retired" ? "abandoned" : disposition);
363
+ return await this.preparer.cleanupWorkItemWorkspace(taskId, owner.workItemId, item?.status === "retired" ? "abandoned" : disposition);
422
364
  }
423
365
  case "review-round":
424
- return this.preparer.cleanupReviewRoundWorkspace(taskId, owner.reviewRoundId);
366
+ return await this.preparer.cleanupReviewRoundWorkspace(taskId, owner.reviewRoundId);
425
367
  case "execution-lane":
426
- return this.preparer.cleanupExecutionLaneWorkspace(taskId, owner.executionGroupId, owner.executionLaneId);
368
+ return await this.preparer.cleanupExecutionLaneWorkspace(taskId, owner.executionGroupId, owner.executionLaneId);
427
369
  case "integration-attempt":
428
370
  if (await this.preparer.inspectIntegrationWorkspace(taskId, owner.integrationAttemptId) === "dirty")
429
371
  return "dirty";
430
- return this.preparer.cleanupIntegrationWorkspace(taskId, owner.integrationAttemptId);
372
+ return await this.preparer.cleanupIntegrationWorkspace(taskId, owner.integrationAttemptId, { preserveChanges: true });
431
373
  case "task": {
432
374
  const current = this.store.getTask(taskId);
433
375
  const delivery = projectTaskRemoteDeliveryFromStore(this.store, current);
@@ -435,7 +377,8 @@ export class TaskWorkspaceCoordinator {
435
377
  throw new Error("Task main retained: local commits are not covered by verified remote delivery. Force does not discard them.");
436
378
  }
437
379
  for (const entry of workspace.entries.filter(e => e.access === "write")) {
438
- const expected = delivery.projects.find(p => p.projectId === entry.projectId)?.expectedLocalCommit;
380
+ const covered = delivery.projects.find(p => p.projectId === entry.projectId);
381
+ const expected = covered?.expectedLocalCommit;
439
382
  const path = await lstat(entry.path).catch(error => {
440
383
  if (error.code === "ENOENT")
441
384
  return null;
@@ -444,11 +387,12 @@ export class TaskWorkspaceCoordinator {
444
387
  if (expected !== null && expected !== undefined
445
388
  && path !== null) {
446
389
  const actual = await this.preparer.git.inspect(entry.path, "HEAD");
447
- if (actual.baseCommit !== expected)
390
+ if (actual.baseCommit !== expected && actual.baseCommit !== covered?.deliveryLocalCommit) {
448
391
  throw new Error(`Task main HEAD changed: ${entry.path}; retained.`);
392
+ }
449
393
  }
450
394
  }
451
- const result = await this.preparer.cleanupTaskForArchive(taskId);
395
+ const result = await this.preparer.cleanupTaskForArchive(taskId, disposition);
452
396
  if (result.status !== "removed")
453
397
  throw new Error(result.error ?? "Task main or dependent workspace retained.");
454
398
  return "removed";
@@ -500,9 +444,7 @@ export class TaskWorkspaceCoordinator {
500
444
  * human/Leader has taken responsibility for the outcome.
501
445
  */
502
446
  #assertNoActiveWorkItemDurableJobs(item) {
503
- const jobs = this.store.listDurableJobs?.(item.taskId);
504
- if (jobs === undefined)
505
- return;
447
+ const jobs = this.store.listDurableJobs(item.taskId);
506
448
  const blocking = jobs.filter((job) => (job.owner.kind === "work-item"
507
449
  && job.owner.workItemId === item.id
508
450
  && (job.status === "queued"
@@ -523,18 +465,16 @@ export class TaskWorkspaceCoordinator {
523
465
  #reviewRoundRoleNames(round) {
524
466
  return [
525
467
  round.reviewerRoleName,
526
- ...(round.executionGroup?.lanes.map(({ roleName }) => roleName) ?? []),
527
468
  ...(round.executionGroup?.lanes.map(({ roleName }) => roleName) ?? [])
528
469
  ];
529
470
  }
530
471
  async #stopLiveRoles(taskId, roleNames) {
531
472
  const targets = [...new Set(roleNames)];
532
- const getActiveRun = this.store.getActiveRun?.bind(this.store);
533
473
  for (const roleName of targets) {
534
- if (getActiveRun !== undefined && getActiveRun(taskId, roleName) !== null) {
474
+ if (this.store.getActiveRun(taskId, roleName) !== null) {
535
475
  throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
536
476
  }
537
- if (this.store.getWorkMailbox !== undefined && hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget({ scope: "task", taskId, roleName })))) {
477
+ if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget({ scope: "task", taskId, roleName })))) {
538
478
  throw new Error(`Role has unsettled runtime lifecycle state: ${taskId}/${roleName}.`);
539
479
  }
540
480
  }
@@ -551,11 +491,9 @@ export class TaskWorkspaceCoordinator {
551
491
  });
552
492
  if (live.length > 0)
553
493
  await this.runtime.stopTaskRoleSessions(taskId, live);
554
- // The aggregate-16 dormant Claude placeholder is the sole exception to
555
- // strict workspace-session retirement. The synchronous pane inspection is
556
- // performed while holding the Task store transaction so a normal launch
557
- // cannot reserve a AgentRun/Session between absence proof and terminalization.
558
- if (inspect === undefined || this.store.transaction === undefined)
494
+ // Recheck exact runtime quiescence under the Task transaction when this
495
+ // runtime exposes physical panes. Store reads are always authoritative.
496
+ if (inspect === undefined)
559
497
  return;
560
498
  this.store.transaction((tx) => {
561
499
  const panes = inspect(taskId);
@@ -566,7 +504,7 @@ export class TaskWorkspaceCoordinator {
566
504
  if (tx.getActiveRun(taskId, roleName) !== null) {
567
505
  throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
568
506
  }
569
- if (tx.getWorkMailbox !== undefined && hasRuntimeLifecycleWork(tx.getWorkMailbox(runtimeLifecycleTarget({ scope: "task", taskId, roleName })))) {
507
+ if (hasRuntimeLifecycleWork(tx.getWorkMailbox(runtimeLifecycleTarget({ scope: "task", taskId, roleName })))) {
570
508
  throw new Error(`Role has unsettled runtime lifecycle state: ${taskId}/${roleName}.`);
571
509
  }
572
510
  }