@zq-silk/yui 0.15.6 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +167 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +188 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +57 -0
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1216 -730
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +77 -0
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +127 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +530 -476
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +423 -0
  106. package/dist/kernel/capabilityRegistry.js +314 -0
  107. package/dist/kernel/capabilitySchema.js +91 -0
  108. package/dist/kernel/instanceHost.js +18 -1
  109. package/dist/kernel/kernelPorts.js +4 -1
  110. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  111. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  112. package/dist/message/message.js +62 -6
  113. package/dist/message/messageContinuation.js +204 -0
  114. package/dist/observability/executionAudit.js +70 -72
  115. package/dist/observability/faultClassification.js +2 -2
  116. package/dist/observability/orchestrationMetrics.js +8 -8
  117. package/dist/operator/operatorSessionHistory.js +1 -7
  118. package/dist/output/agentConfigurationPresentation.js +8 -3
  119. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  120. package/dist/output/rolePresentation.js +54 -3
  121. package/dist/plugins/pluginChild.js +104 -0
  122. package/dist/plugins/pluginIntent.js +26 -0
  123. package/dist/plugins/pluginInterpreter.js +43 -0
  124. package/dist/plugins/pluginPackage.js +101 -0
  125. package/dist/plugins/pluginProcess.js +112 -0
  126. package/dist/plugins/pluginService.js +380 -0
  127. package/dist/profile/agentProfile.js +1 -1
  128. package/dist/repository/gitWorkspace.js +26 -4
  129. package/dist/repository/project.js +19 -4
  130. package/dist/repository/taskBaseFreshness.js +13 -13
  131. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  132. package/dist/repository/taskWorkspacePreparer.js +344 -83
  133. package/dist/resources/autoResourceGc.js +3 -3
  134. package/dist/resources/liveReferences.js +3 -3
  135. package/dist/resources/projectResource.js +123 -0
  136. package/dist/resources/projectResourceService.js +421 -0
  137. package/dist/resources/resourceDiscovery.js +6 -6
  138. package/dist/resources/resourceGc.js +1 -1
  139. package/dist/resources/resourceRegistrar.js +1 -1
  140. package/dist/resources/resourceTypes.js +1 -1
  141. package/dist/review/deltaRecheck.js +3 -3
  142. package/dist/review/reviewAcceptance.js +16 -16
  143. package/dist/review/reviewDecision.js +7 -7
  144. package/dist/review/reviewRound.js +21 -20
  145. package/dist/review/reviewerAvailability.js +2 -2
  146. package/dist/role/role.js +51 -7
  147. package/dist/role/taskRoleUpdate.js +30 -0
  148. package/dist/runtime/acpProtocol.js +425 -0
  149. package/dist/runtime/acpSession.js +731 -0
  150. package/dist/runtime/acpSessionConfiguration.js +260 -0
  151. package/dist/runtime/agentDriver.js +30 -11
  152. package/dist/runtime/agentEndpoint.js +278 -0
  153. package/dist/runtime/agentEndpointIdentity.js +86 -0
  154. package/dist/runtime/agentEndpointOwnership.js +239 -0
  155. package/dist/runtime/agentError.js +2 -10
  156. package/dist/runtime/agentHost.js +565 -314
  157. package/dist/runtime/agentRunConfiguration.js +258 -0
  158. package/dist/runtime/builtinAgentDrivers.js +134 -18
  159. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  160. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  161. package/dist/runtime/claude-process-owner +0 -0
  162. package/dist/runtime/codexAppServerRuntime.js +38 -30
  163. package/dist/runtime/codexInteractiveHost.js +41 -6
  164. package/dist/runtime/continuationManager.js +2 -6
  165. package/dist/runtime/executionEnvironment.js +30 -0
  166. package/dist/runtime/firstProgressAdvisory.js +11 -11
  167. package/dist/runtime/index.js +4 -3
  168. package/dist/runtime/jsonLineChannel.js +109 -0
  169. package/dist/runtime/launchBroker.js +91 -16
  170. package/dist/runtime/launchDiagnostics.js +2 -2
  171. package/dist/runtime/lifecycleReservation.js +10 -18
  172. package/dist/runtime/managedCaller.js +61 -17
  173. package/dist/runtime/nativeSessionControl.js +102 -0
  174. package/dist/runtime/ports.js +6 -21
  175. package/dist/runtime/processExitObservation.js +8 -7
  176. package/dist/runtime/promptEnvelope.js +17 -6
  177. package/dist/runtime/providerContinuation.js +3 -9
  178. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  179. package/dist/runtime/providerControl.js +2 -7
  180. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  181. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  182. package/dist/runtime/runtimeBinding.js +0 -1
  183. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  184. package/dist/runtime/runtimeDeadlines.js +9 -0
  185. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  186. package/dist/runtime/runtimeObservation.js +29 -65
  187. package/dist/runtime/runtimeProjection.js +43 -51
  188. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  189. package/dist/runtime/sessionLaunchRequest.js +3 -7
  190. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  191. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  192. package/dist/runtime/sessionReconciliation.js +4 -8
  193. package/dist/runtime/sessionTerminationGuard.js +70 -259
  194. package/dist/runtime/sessionTokenMetrics.js +5 -16
  195. package/dist/runtime/structuredProviderHost.js +237 -117
  196. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  197. package/dist/runtime/tmuxAdapters.js +39 -86
  198. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  199. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  200. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  201. package/dist/scheduler/ports.js +80 -9
  202. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  203. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  204. package/dist/scheduler/taskExecutionProjection.js +120 -124
  205. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  206. package/dist/scheduler/taskWake.js +11 -4
  207. package/dist/scheduler/wakeReason.js +9 -1
  208. package/dist/setup/setupCommand.js +1 -0
  209. package/dist/storage/migrations/agentRunContract.js +159 -0
  210. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  211. package/dist/storage/sqliteSchema.js +431 -5
  212. package/dist/storage/sqliteStore.js +375 -220
  213. package/dist/storage/storageVersions.js +1 -1
  214. package/dist/storage/storeRpc.js +10 -5
  215. package/dist/storage/taskStore.js +13 -11
  216. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  217. package/dist/surface/surfaceContributions.js +102 -0
  218. package/dist/task/completionReadiness.js +32 -6
  219. package/dist/task/deliveryGuard.js +16 -16
  220. package/dist/task/draftPlan.js +72 -12
  221. package/dist/task/nextAction.js +144 -128
  222. package/dist/task/remoteDelivery.js +6 -6
  223. package/dist/task/task.js +184 -18
  224. package/dist/task/taskActivation.js +301 -0
  225. package/dist/task/taskActivationService.js +392 -0
  226. package/dist/task/taskRecordReference.js +5 -4
  227. package/dist/task/taskRecordRetirement.js +1 -1
  228. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  229. package/dist/telemetry/telemetryConfig.js +14 -14
  230. package/dist/telemetry/telemetryWiring.js +2 -2
  231. package/dist/web/assets/assetManifest.js +2 -0
  232. package/dist/web/assets/client/app.js +120 -20
  233. package/dist/web/assets/client/components.js +87 -54
  234. package/dist/web/assets/client/i18n.js +83 -41
  235. package/dist/web/assets/client/markdown.js +1 -1
  236. package/dist/web/assets/client/taskSurface.js +353 -0
  237. package/dist/web/assets/client/view.js +49 -44
  238. package/dist/web/assets/shell.js +1 -1
  239. package/dist/web/assets/styles/cards.js +22 -4
  240. package/dist/web/controllerWeb.js +60 -0
  241. package/dist/web/webMutation.js +28 -0
  242. package/dist/web/webServer.js +118 -8
  243. package/dist/web/webSnapshot.js +81 -74
  244. package/dist/web/webTaskSurface.js +64 -0
  245. package/dist/workItem/dependencyGate.js +1 -1
  246. package/dist/workItem/workItem.js +80 -48
  247. package/dist/workspace/workItemChangeSetManager.js +16 -9
  248. package/docs/agent-result-consumption.md +94 -0
  249. package/docs/agent-runtime-drivers.md +91 -0
  250. package/docs/architecture/README.md +38 -0
  251. package/docs/architecture/capabilities-and-resources.md +79 -0
  252. package/docs/managed-turn-and-session-runtime.md +222 -0
  253. package/docs/observability/README.md +81 -0
  254. package/docs/plugin-sdk.md +290 -0
  255. package/docs/provider-runtime.md +163 -0
  256. package/docs/release-workflow.md +303 -0
  257. package/docs/roles-and-configuration.md +113 -0
  258. package/docs/sqlite-control-plane-design.md +76 -0
  259. package/docs/task-dag-semantics.md +57 -0
  260. package/docs/task-delivery.md +103 -0
  261. package/docs/task-local-identity.md +6 -6
  262. package/docs/testing/verification-levels.md +86 -0
  263. package/i18n/README.zh-CN.md +98 -739
  264. package/package.json +5 -5
  265. package/skills/yui-leader/SKILL.md +130 -103
  266. package/skills/yui-leader/references/integration.md +39 -0
  267. package/skills/yui-leader/references/replicated-execution.md +42 -0
  268. package/skills/yui-leader/references/task-plugins.md +33 -0
  269. package/skills/yui-operator/SKILL.md +30 -59
  270. package/skills/yui-reviewer/SKILL.md +35 -36
  271. package/skills/yui-runtime/SKILL.md +88 -24
  272. package/skills/yui-runtime/references/publication.md +22 -0
  273. package/skills/yui-runtime/references/recovery.md +64 -0
  274. package/skills/yui-worker/SKILL.md +37 -39
  275. package/dist/cli/roleOptionCatalog.js +0 -68
  276. package/dist/context/sourceTurnContext.js +0 -30
  277. package/dist/execution/reviewMainTurn.js +0 -161
  278. package/dist/execution/workItemMainTurn.js +0 -164
  279. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  280. package/dist/runtime/preallocatedNativeSession.js +0 -13
  281. package/dist/runtime/runtimeStopReceipt.js +0 -42
  282. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
package/dist/task/task.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { validateTaskWorkspaceIdentity } from "../repository/taskWorkspaceIdentity.js";
2
+ import { cancelTaskActivationRequest, recordTaskActivationEvidence, validateTaskActivationRequest } from "./taskActivation.js";
2
3
  export function createTask(id, title, now, metadata = {}) {
3
4
  const timestamp = now.toISOString();
4
5
  return {
@@ -38,13 +39,106 @@ export function bindTaskWorkspaceIdentity(task, identity, now) {
38
39
  updatedAt: now.toISOString()
39
40
  });
40
41
  }
42
+ /**
43
+ * How many terminal activation requests the Task payload *displays* beside its
44
+ * latest one.
45
+ *
46
+ * This is a display bound, not an authority. Whether a requestId was already
47
+ * cancelled or adopted is answered from the durable activation event ledger,
48
+ * which is never compacted, so overflow trims only what the payload shows, never
49
+ * what replay enforcement can see: an evicted entry is still refused. The oldest
50
+ * request is not "least likely to be replayed" — it is simply the oldest, and it
51
+ * keeps its authority exactly like the newest.
52
+ */
53
+ export const MAX_SETTLED_ACTIVATION_REQUESTS = 16;
54
+ /** Terminal dispositions: the request will never adopt again on its own. */
55
+ function isSettledActivationRequest(request) {
56
+ return request.disposition === "cancelled" || request.disposition === "adopted";
57
+ }
58
+ /**
59
+ * Persists the Task's latest activation request.
60
+ *
61
+ * The request is intent only, so this never touches `status`: a Draft stays a
62
+ * continuable Draft after a request is recorded, cancelled, or failed. Only
63
+ * `activateTask` inside the adoption transaction moves the lifecycle.
64
+ *
65
+ * A request being displaced from the slot is projected into the bounded settled
66
+ * history when its outcome was terminal, so the recent decisions stay visible on
67
+ * the Task payload. This history is a display projection, not the authority:
68
+ * whether a cancelled or adopted id may be replayed is decided from the durable
69
+ * activation event ledger, so trimming the oldest entries here never lets a
70
+ * decided outcome be replayed away. A `failed` request is deliberately not
71
+ * projected: it stays replayable by contract, and it is still in the slot until
72
+ * something replaces it.
73
+ */
74
+ export function setTaskActivationRequest(task, request, now) {
75
+ validateTask(task);
76
+ const valid = validateTaskActivationRequest(request);
77
+ if (valid.operation.targetId !== task.id) {
78
+ throw new Error(`Activation request belongs to another Task: ${valid.operation.targetId}.`);
79
+ }
80
+ const existing = task.activationRequest;
81
+ if (existing !== undefined
82
+ && existing.operation.requestId === valid.operation.requestId
83
+ && existing.operation.inputDigest !== valid.operation.inputDigest) {
84
+ throw new Error(`Activation requestId ${valid.operation.requestId} was already used for different inputs.`);
85
+ }
86
+ // Replacing the slot: keep the outgoing terminal outcome as evidence. The
87
+ // incoming request's own id is dropped from the history because it is now the
88
+ // slot's occupant, so exactly one record per id exists.
89
+ const displaced = existing !== undefined
90
+ && existing.operation.requestId !== valid.operation.requestId
91
+ && isSettledActivationRequest(existing)
92
+ ? [existing]
93
+ : [];
94
+ const retained = [
95
+ ...(task.settledActivationRequests ?? []),
96
+ ...displaced
97
+ ].filter(({ operation }) => operation.requestId !== valid.operation.requestId)
98
+ .slice(-MAX_SETTLED_ACTIVATION_REQUESTS);
99
+ return validateTask({
100
+ ...task,
101
+ activationRequest: valid,
102
+ ...(retained.length === 0 ? {} : { settledActivationRequests: retained }),
103
+ updatedAt: now.toISOString()
104
+ });
105
+ }
106
+ /**
107
+ * Records operation evidence against one activation request by id, wherever the
108
+ * Task still holds it — the live slot or the settled history.
109
+ *
110
+ * Evidence has to survive the request losing the slot: resources adopted for A
111
+ * remain adopted after A is cancelled and B is requested, and that effect must
112
+ * stay attached to A rather than following the slot to B. The underlying ratchet
113
+ * only raises `effect` and adds refs, so this never rewrites an outcome.
114
+ */
115
+ export function recordTaskActivationRequestEvidence(task, requestId, evidence, now) {
116
+ const current = task.activationRequest;
117
+ if (current?.operation.requestId === requestId) {
118
+ const updated = recordTaskActivationEvidence(current, evidence, now);
119
+ if (updated === current)
120
+ return task;
121
+ return validateTask({ ...task, activationRequest: updated, updatedAt: now.toISOString() });
122
+ }
123
+ const settled = task.settledActivationRequests;
124
+ if (settled?.some(({ operation }) => operation.requestId === requestId) !== true) {
125
+ throw new Error(`Activation request not found on Task ${task.id}: ${requestId}.`);
126
+ }
127
+ return validateTask({
128
+ ...task,
129
+ settledActivationRequests: settled.map((request) => (request.operation.requestId === requestId
130
+ ? recordTaskActivationEvidence(request, evidence, now)
131
+ : request)),
132
+ updatedAt: now.toISOString()
133
+ });
134
+ }
41
135
  export function activateTask(task, now) {
42
136
  if (task.status === "archived")
43
137
  throw new Error(`Cannot activate archived Task: ${task.id}.`);
44
138
  if (task.status === "completed")
45
139
  throw new Error(`Cannot activate completed Task ${task.id}; reopen it instead.`);
46
- if (task.status === "retired")
47
- throw new Error(`Cannot activate retired Task: ${task.id}.`);
140
+ if (task.status === "cancelled")
141
+ throw new Error(`Cannot activate cancelled Task ${task.id}; reopen it with user or Operator authority.`);
48
142
  if (task.status === "active")
49
143
  return task;
50
144
  return { ...task, status: "active", updatedAt: now.toISOString() };
@@ -146,7 +240,7 @@ export function retireTask(task, input, now) {
146
240
  throw new Error("A Task cannot replace itself.");
147
241
  }
148
242
  }
149
- if (task.status === "retired") {
243
+ if (task.status === "cancelled") {
150
244
  if (task.retiredBy === by
151
245
  && task.retirementSummary === summary
152
246
  && task.replacementTaskId === input.replacementTaskId) {
@@ -160,10 +254,11 @@ export function retireTask(task, input, now) {
160
254
  const timestamp = now.toISOString();
161
255
  return validateTask({
162
256
  ...task,
163
- status: "retired",
257
+ status: "cancelled",
164
258
  retiredAt: timestamp,
165
259
  retiredBy: by,
166
260
  retirementSummary: summary,
261
+ ...(input.isolated === true ? { retirementIsolation: true } : {}),
167
262
  ...(input.replacementTaskId === undefined
168
263
  ? {}
169
264
  : { replacementTaskId: input.replacementTaskId }),
@@ -183,24 +278,42 @@ export function completeTask(task, now, completion) {
183
278
  completedAt: timestamp,
184
279
  completedBy: completion.by,
185
280
  completionSummary: requireText(completion.summary, "Task completion summary"),
281
+ ...(completion.artifactRefs === undefined ? {} : {
282
+ completionArtifactRefs: completion.artifactRefs.map((ref) => requireText(ref, "Artifact ref"))
283
+ }),
186
284
  updatedAt: timestamp
187
285
  };
188
286
  }
189
287
  export function reopenTask(task, now) {
190
288
  if (task.status === "archived")
191
289
  throw new Error(`Cannot reopen archived Task: ${task.id}.`);
192
- if (task.status !== "completed") {
193
- throw new Error(`Only a completed Task can be reopened: ${task.id}.`);
194
- }
195
- const { completedAt: _completedAt, completedBy: _completedBy, completionSummary: _completionSummary, ...reopened } = task;
196
- return { ...reopened, status: "active", updatedAt: now.toISOString() };
290
+ if (task.status !== "completed" && task.status !== "cancelled") {
291
+ throw new Error(`Only a completed or cancelled Task can be reopened: ${task.id}.`);
292
+ }
293
+ const { completedAt: _completedAt, completedBy: _completedBy, completionSummary: _completionSummary, completionArtifactRefs: _completionArtifactRefs, retiredAt: _retiredAt, retiredBy: _retiredBy, retirementSummary: _retirementSummary, replacementTaskId: _replacementTaskId, retirementIsolation: _retirementIsolation, ...reopened } = task;
294
+ // A request that never adopted is superseded by the completion it predates:
295
+ // it is cancelled rather than deleted, so it cannot be replayed into the new
296
+ // delivery cycle and the intent stays visible. An adopted request records a
297
+ // real environment and status change and is left exactly as it is.
298
+ const request = reopened.activationRequest;
299
+ return validateTask({
300
+ ...reopened,
301
+ ...(request?.disposition === "pending"
302
+ ? {
303
+ activationRequest: cancelTaskActivationRequest(request, `Task was completed and reopened at ${now.toISOString()}; request superseded.`, now)
304
+ }
305
+ : {}),
306
+ status: "active",
307
+ executionGate: { state: "enabled" },
308
+ updatedAt: now.toISOString()
309
+ });
197
310
  }
198
311
  export function archiveTask(task, now, archive) {
199
312
  if (task.status === "archived")
200
313
  return task;
201
314
  if (task.status !== "completed"
202
- && task.status !== "retired") {
203
- throw new Error(`Only a completed or retired Task can be archived: ${task.id}.`);
315
+ && task.status !== "cancelled") {
316
+ throw new Error(`Only a completed or cancelled Task can be archived: ${task.id}.`);
204
317
  }
205
318
  const timestamp = now.toISOString();
206
319
  return validateTask({
@@ -256,10 +369,22 @@ export function isTaskArchived(task) {
256
369
  export function isTaskExecutionEnabled(task) {
257
370
  return task.executionGate.state === "enabled";
258
371
  }
372
+ /**
373
+ * Whether this Task owns a managed Git workspace at all.
374
+ *
375
+ * A Task activated with an empty environment plan legitimately owns none: it
376
+ * binds no Project and adopted no directory, so there is no worktree to be
377
+ * ready and no cwd to verify. Workspace fences use this to distinguish "not
378
+ * prepared yet" from "correctly owns nothing", instead of creating a workspace
379
+ * only to satisfy the fence.
380
+ */
381
+ export function taskOwnsManagedWorkspace(task) {
382
+ return task.projectBindings.length > 0 || task.cwd !== undefined;
383
+ }
259
384
  export function stopTaskExecution(task, now) {
260
385
  validateTask(task);
261
- if (task.status !== "active") {
262
- throw new Error(`Only an active Task can be stopped: ${task.id}.`);
386
+ if (task.status !== "active" && task.status !== "draft") {
387
+ throw new Error(`Only an open Task can be stopped: ${task.id}.`);
263
388
  }
264
389
  if (task.executionGate.state === "stopped")
265
390
  return task;
@@ -271,8 +396,8 @@ export function stopTaskExecution(task, now) {
271
396
  }
272
397
  export function startTaskExecution(task, now) {
273
398
  validateTask(task);
274
- if (task.status !== "active") {
275
- throw new Error(`Only an active Task can be started: ${task.id}.`);
399
+ if (task.status !== "active" && task.status !== "draft") {
400
+ throw new Error(`Only an open Task can be started: ${task.id}.`);
276
401
  }
277
402
  if (task.executionGate.state === "enabled")
278
403
  return task;
@@ -289,7 +414,7 @@ export function validateTask(task) {
289
414
  requireText(task.title, "Task title");
290
415
  if (task.type !== undefined)
291
416
  requireSafeIdentity(task.type, "Task type");
292
- if (!["draft", "active", "completed", "retired", "archived"].includes(task.status)) {
417
+ if (!["draft", "active", "completed", "cancelled", "archived"].includes(task.status)) {
293
418
  throw new Error(`Task status is invalid: ${String(task.status)}.`);
294
419
  }
295
420
  if (task.executionGate === null
@@ -299,6 +424,14 @@ export function validateTask(task) {
299
424
  }
300
425
  requireTimestamp(task.createdAt, "Task createdAt");
301
426
  requireTimestamp(task.updatedAt, "Task updatedAt");
427
+ if (task.retirementIsolation !== undefined && task.retirementIsolation !== true) {
428
+ throw new Error("Task retirement isolation must represent explicit established evidence.");
429
+ }
430
+ if (task.completionArtifactRefs !== undefined && !Array.isArray(task.completionArtifactRefs)) {
431
+ throw new Error("Task completion artifact refs must be an array.");
432
+ }
433
+ for (const ref of task.completionArtifactRefs ?? [])
434
+ requireText(ref, "Task completion artifact ref");
302
435
  if (Date.parse(task.updatedAt) < Date.parse(task.createdAt)) {
303
436
  throw new Error("Task updatedAt cannot precede createdAt.");
304
437
  }
@@ -308,6 +441,39 @@ export function validateTask(task) {
308
441
  throw new Error(`Task workspace identity belongs to another Task: ${identity.taskId}.`);
309
442
  }
310
443
  }
444
+ if (task.activationRequest !== undefined) {
445
+ const request = validateTaskActivationRequest(task.activationRequest);
446
+ if (request.operation.targetId !== task.id) {
447
+ throw new Error(`Task activation request belongs to another Task: ${request.operation.targetId}.`);
448
+ }
449
+ }
450
+ if (task.settledActivationRequests !== undefined) {
451
+ const settled = task.settledActivationRequests;
452
+ if (!Array.isArray(settled)) {
453
+ throw new Error("Task settled activation requests are invalid.");
454
+ }
455
+ if (settled.length > MAX_SETTLED_ACTIVATION_REQUESTS) {
456
+ throw new Error("Task settled activation requests exceed the bounded limit.");
457
+ }
458
+ const seen = new Set();
459
+ for (const entry of settled) {
460
+ const request = validateTaskActivationRequest(entry);
461
+ if (request.operation.targetId !== task.id) {
462
+ throw new Error(`Task settled activation request belongs to another Task: ${request.operation.targetId}.`);
463
+ }
464
+ if (!isSettledActivationRequest(request)) {
465
+ throw new Error("Task settled activation history retains only terminal requests: "
466
+ + `${request.operation.requestId}/${request.disposition}.`);
467
+ }
468
+ // One record per id, and never a stale copy of the live slot: the slot is
469
+ // the only place a request that can still change is allowed to live.
470
+ if (seen.has(request.operation.requestId)
471
+ || request.operation.requestId === task.activationRequest?.operation.requestId) {
472
+ throw new Error(`Task activation request is duplicated: ${request.operation.requestId}.`);
473
+ }
474
+ seen.add(request.operation.requestId);
475
+ }
476
+ }
311
477
  if (task.priority !== undefined
312
478
  && !["low", "medium", "high", "urgent"].includes(task.priority)) {
313
479
  throw new Error(`Task priority is invalid: ${String(task.priority)}.`);
@@ -344,7 +510,7 @@ export function validateTask(task) {
344
510
  if (task.status === "completed" && !hasAllCompletion) {
345
511
  throw new Error("A completed Task requires completedAt, completedBy, and completionSummary.");
346
512
  }
347
- if (["draft", "active", "retired"].includes(task.status)
513
+ if (["draft", "active", "cancelled"].includes(task.status)
348
514
  && hasAnyCompletion) {
349
515
  throw new Error(`Task completion metadata is invalid for ${task.status} status.`);
350
516
  }
@@ -355,7 +521,7 @@ export function validateTask(task) {
355
521
  task.replacementTaskId
356
522
  ];
357
523
  const hasAnyRetirement = retirementFields.some((value) => value !== undefined);
358
- const retired = task.status === "retired";
524
+ const retired = task.status === "cancelled";
359
525
  const archivedRetirement = task.status === "archived" && hasAnyRetirement;
360
526
  if (retired || archivedRetirement) {
361
527
  if (task.retiredAt === undefined
@@ -0,0 +1,301 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isDeepStrictEqual } from "node:util";
3
+ import { requireIdentity, requireTimestamp } from "../domain/validation.js";
4
+ import { recordOperationEvidence, validateOperationFacts } from "../kernel/operationFacts.js";
5
+ export const TASK_ACTIVATION_IMPLEMENTATION = Object.freeze({
6
+ id: "yui:task-activation",
7
+ generation: "1"
8
+ });
9
+ export const TASK_ACTIVATION_CAPABILITY = "task.activate";
10
+ /**
11
+ * The durable activation event vocabulary.
12
+ *
13
+ * These events are the never-compacted authority for a request's terminal
14
+ * outcome, so the type strings are named constants shared by every emitter and
15
+ * every reader. A rename cannot silently desynchronise what adoption/cancel
16
+ * record from what replay enforcement reads back — which is exactly how the
17
+ * bounded display list used to lose authority once it overflowed.
18
+ */
19
+ export const TASK_ACTIVATION_EVENT = Object.freeze({
20
+ requested: "task.activation-requested",
21
+ cancelled: "task.activation-cancelled",
22
+ adopted: "task.activation-adopted",
23
+ failed: "task.activation-failed"
24
+ });
25
+ /**
26
+ * The digest that decides whether a repeated requestId is the same request.
27
+ *
28
+ * It covers only what adoption actually consumes: the Task, the start mode and
29
+ * the resource configuration. Ordinary planning progress — a Brief edit, a new
30
+ * Decision, a changed current focus — deliberately does not appear here, so
31
+ * continuing to plan never invalidates an already-issued request.
32
+ */
33
+ export function taskActivationInputDigest(taskId, input) {
34
+ return createHash("sha256").update(JSON.stringify([
35
+ taskId,
36
+ input.startMode,
37
+ input.afterPlanningRun ?? null,
38
+ canonicalEnvironmentPlan(input.environmentPlan)
39
+ ])).digest("hex");
40
+ }
41
+ export function createTaskActivationRequest(taskId, input, now) {
42
+ if (input.startMode === "after-planning-turn") {
43
+ if (input.afterPlanningRun === undefined) {
44
+ throw new Error("A deferred activation request must name its planning Turn.");
45
+ }
46
+ }
47
+ else if (input.afterPlanningRun !== undefined) {
48
+ throw new Error("An immediate activation request cannot defer to a planning Turn.");
49
+ }
50
+ const timestamp = now.toISOString();
51
+ return validateTaskActivationRequest({
52
+ schemaVersion: 1,
53
+ operation: {
54
+ requestId: input.requestId,
55
+ inputDigest: taskActivationInputDigest(taskId, input),
56
+ actorId: input.actorId,
57
+ authorityRef: input.authorityRef,
58
+ targetId: taskId,
59
+ capability: TASK_ACTIVATION_CAPABILITY,
60
+ implementation: TASK_ACTIVATION_IMPLEMENTATION,
61
+ effect: "none",
62
+ receiptRefs: [],
63
+ partialResultRefs: []
64
+ },
65
+ startMode: input.startMode,
66
+ ...(input.afterPlanningRun === undefined
67
+ ? {}
68
+ : { afterPlanningRun: input.afterPlanningRun }),
69
+ environmentPlan: canonicalEnvironmentPlan(input.environmentPlan),
70
+ disposition: "pending",
71
+ requestedAt: timestamp,
72
+ updatedAt: timestamp
73
+ });
74
+ }
75
+ /**
76
+ * Records that adoption completed. `effect` escalates to `confirmed` and never
77
+ * returns: a later launch failure is reported on the Task, not by erasing the
78
+ * fact that the environment and the active status were adopted.
79
+ */
80
+ export function adoptTaskActivationRequest(request, evidence, now) {
81
+ if (request.disposition === "adopted")
82
+ return request;
83
+ if (request.disposition !== "pending" && request.disposition !== "failed") {
84
+ throw new Error(`Activation request is not adoptable: ${request.operation.requestId}.`);
85
+ }
86
+ return validateTaskActivationRequest({
87
+ ...request,
88
+ operation: recordOperationEvidence(request.operation, {
89
+ effect: "confirmed",
90
+ ...(evidence.receiptRefs === undefined ? {} : { receiptRefs: evidence.receiptRefs })
91
+ }),
92
+ disposition: "adopted",
93
+ ...(evidence.preparationId === undefined
94
+ ? {}
95
+ : { preparationId: evidence.preparationId }),
96
+ updatedAt: now.toISOString()
97
+ });
98
+ }
99
+ /**
100
+ * Records an attempt that did not adopt. The request stays replayable and the
101
+ * Task stays a continuable Draft; `effect` rises to `possible` only when the
102
+ * caller could not prove the resources were left untouched.
103
+ */
104
+ export function failTaskActivationRequest(request, outcome, now, evidence = {}) {
105
+ if (request.disposition === "adopted") {
106
+ throw new Error(`Adopted activation cannot be reported as failed: ${request.operation.requestId}.`);
107
+ }
108
+ if (request.disposition === "cancelled")
109
+ return request;
110
+ return validateTaskActivationRequest({
111
+ ...request,
112
+ operation: recordOperationEvidence(request.operation, evidence),
113
+ disposition: "failed",
114
+ outcome: requireOutcome(outcome),
115
+ updatedAt: now.toISOString()
116
+ });
117
+ }
118
+ /**
119
+ * Records what an attempt really did, without changing its disposition.
120
+ *
121
+ * Adoption of resources and adoption of the request are separate moments, so a
122
+ * request that was cancelled or replaced after its environment was already
123
+ * adopted still has to carry that effect. This is the same monotonic evidence
124
+ * ratchet the other transitions use — it can only add refs and raise `effect`,
125
+ * never lower it and never rewrite the outcome.
126
+ */
127
+ export function recordTaskActivationEvidence(request, evidence, now) {
128
+ const operation = recordOperationEvidence(request.operation, evidence);
129
+ if (isDeepStrictEqual(operation, request.operation))
130
+ return request;
131
+ return validateTaskActivationRequest({
132
+ ...request,
133
+ operation,
134
+ updatedAt: now.toISOString()
135
+ });
136
+ }
137
+ /** An explicitly cancelled request is never replayed, even if it is retried. */
138
+ export function cancelTaskActivationRequest(request, outcome, now) {
139
+ if (request.disposition === "adopted") {
140
+ throw new Error(`Adopted activation cannot be cancelled: ${request.operation.requestId}.`);
141
+ }
142
+ if (request.disposition === "cancelled")
143
+ return request;
144
+ return validateTaskActivationRequest({
145
+ ...request,
146
+ disposition: "cancelled",
147
+ outcome: requireOutcome(outcome),
148
+ updatedAt: now.toISOString()
149
+ });
150
+ }
151
+ /**
152
+ * Re-checks a stored request against current facts immediately before adoption.
153
+ *
154
+ * Everything decided at request time is deliberately re-decided here: the Task
155
+ * may have been retired, execution may have been stopped, the request may have
156
+ * been cancelled while the planning Turn was still running. Resource-level
157
+ * rechecks stay with `prepare`/`adopt`, which own the grant and directory
158
+ * identity fences.
159
+ */
160
+ export function admitTaskActivationRequest(task, request, planningRunIsActive) {
161
+ if (request === undefined)
162
+ return undefined;
163
+ if (request.disposition === "adopted" || request.disposition === "cancelled") {
164
+ return { disposition: "settled", request };
165
+ }
166
+ if (request.operation.targetId !== task.id) {
167
+ return {
168
+ disposition: "unavailable",
169
+ request,
170
+ reason: `Activation request targets another Task: ${request.operation.targetId}.`
171
+ };
172
+ }
173
+ if (task.status !== "draft") {
174
+ return {
175
+ disposition: "unavailable",
176
+ request,
177
+ reason: `Task is no longer a Draft: ${task.id}/${task.status}.`
178
+ };
179
+ }
180
+ if (task.executionGate.state !== "enabled") {
181
+ return {
182
+ disposition: "unavailable",
183
+ request,
184
+ reason: `Task execution is stopped: ${task.id}.`
185
+ };
186
+ }
187
+ if (request.afterPlanningRun !== undefined
188
+ && planningRunIsActive(request.afterPlanningRun)) {
189
+ return {
190
+ disposition: "deferred",
191
+ request,
192
+ afterPlanningRun: request.afterPlanningRun
193
+ };
194
+ }
195
+ return { disposition: "ready", request };
196
+ }
197
+ /**
198
+ * The durable terminal outcome for a requestId, read from the activation event
199
+ * ledger.
200
+ *
201
+ * `task.activation-cancelled` and `task.activation-adopted` are never compacted
202
+ * — only runtime-observation events are pruned — so this answers "was this id
203
+ * already settled?" for the entire life of the Task, no matter how many later
204
+ * requests displaced it from the bounded display payload. A `failed` request is
205
+ * intentionally absent here: it stays replayable by contract, so replaying it is
206
+ * not a resurrection of a decided outcome.
207
+ *
208
+ * A requestId reaches at most one terminal disposition — adoption and
209
+ * cancellation exclude each other by construction — so the first matching
210
+ * terminal event is authoritative.
211
+ */
212
+ export function settledActivationFromEvents(events, requestId) {
213
+ for (const event of events) {
214
+ if (event.payload.requestId !== requestId)
215
+ continue;
216
+ if (event.type === TASK_ACTIVATION_EVENT.cancelled) {
217
+ return {
218
+ disposition: "cancelled",
219
+ ...(event.payload.reason === undefined ? {} : { outcome: event.payload.reason })
220
+ };
221
+ }
222
+ if (event.type === TASK_ACTIVATION_EVENT.adopted) {
223
+ return { disposition: "adopted" };
224
+ }
225
+ }
226
+ return undefined;
227
+ }
228
+ export function validateTaskActivationRequest(request) {
229
+ if (request.schemaVersion !== 1) {
230
+ throw new Error("Activation request must use schemaVersion 1.");
231
+ }
232
+ validateOperationFacts(request.operation);
233
+ if (request.operation.capability !== TASK_ACTIVATION_CAPABILITY) {
234
+ throw new Error("Activation request capability is invalid.");
235
+ }
236
+ if (!["immediate", "after-planning-turn"].includes(request.startMode)) {
237
+ throw new Error(`Activation start mode is invalid: ${String(request.startMode)}.`);
238
+ }
239
+ if ((request.startMode === "after-planning-turn")
240
+ !== (request.afterPlanningRun !== undefined)) {
241
+ throw new Error("Activation deferral must name exactly the planning Turn it waits for.");
242
+ }
243
+ if (request.afterPlanningRun !== undefined) {
244
+ requireIdentity(request.afterPlanningRun, "Activation planning Turn");
245
+ }
246
+ if (!["pending", "adopted", "cancelled", "failed"].includes(request.disposition)) {
247
+ throw new Error(`Activation disposition is invalid: ${String(request.disposition)}.`);
248
+ }
249
+ if (request.disposition === "adopted" && request.operation.effect !== "confirmed") {
250
+ throw new Error("An adopted activation must record a confirmed effect.");
251
+ }
252
+ if (request.preparationId !== undefined) {
253
+ requireIdentity(request.preparationId, "Activation preparation");
254
+ if (request.disposition !== "adopted") {
255
+ throw new Error("Only an adopted activation records the preparation it consumed.");
256
+ }
257
+ }
258
+ if (request.outcome !== undefined)
259
+ requireOutcome(request.outcome);
260
+ canonicalEnvironmentPlan(request.environmentPlan);
261
+ requireTimestamp(request.requestedAt, "Activation requestedAt");
262
+ requireTimestamp(request.updatedAt, "Activation updatedAt");
263
+ if (Date.parse(request.updatedAt) < Date.parse(request.requestedAt)) {
264
+ throw new Error("Activation updatedAt cannot precede requestedAt.");
265
+ }
266
+ return request;
267
+ }
268
+ /** Normalizes the plan so the digest never depends on key order. */
269
+ export function canonicalEnvironmentPlan(plan) {
270
+ const kind = plan?.kind;
271
+ if (kind === "empty")
272
+ return { kind: "empty" };
273
+ if (kind === "scratch")
274
+ return { kind: "scratch" };
275
+ if (kind === "local") {
276
+ const local = plan;
277
+ if (!["read", "write"].includes(local.access)) {
278
+ throw new Error(`Environment plan access is invalid: ${String(local.access)}.`);
279
+ }
280
+ return {
281
+ kind: "local",
282
+ resourceId: requireIdentity(local.resourceId, "Environment plan resource"),
283
+ access: local.access
284
+ };
285
+ }
286
+ throw new Error(`Environment plan kind is invalid: ${String(kind)}.`);
287
+ }
288
+ export function describeEnvironmentPlan(plan) {
289
+ return plan.kind === "local"
290
+ ? `local ${plan.resourceId} (${plan.access})`
291
+ : plan.kind;
292
+ }
293
+ function requireOutcome(value) {
294
+ if (typeof value !== "string" || value.includes("\0")) {
295
+ throw new Error("Activation outcome is invalid.");
296
+ }
297
+ const normalized = value.trim();
298
+ if (normalized.length === 0)
299
+ throw new Error("Activation outcome is required.");
300
+ return normalized.slice(0, 2000);
301
+ }