@zq-silk/yui 0.15.7 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +157 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1188 -710
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +529 -479
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,298 +1,107 @@
1
- import { createTurnInput } from "../context/turnInputContract.js";
2
1
  import { roleSessionMayContinue } from "../executor/effectiveLaunch.js";
3
2
  import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
4
- import { createTurn } from "../turn/turn.js";
5
3
  import { isSchedulerTaskWorkspaceReady } from "./ports.js";
6
- import { formatProviderDeliveryFailure, providerDeliveryFailureFacts, serializeAgentErrorRaw, innermostCauseName } from "../runtime/agentError.js";
4
+ import { taskOwnsManagedWorkspace } from "../task/task.js";
5
+ import { hasImmediateWakeReason } from "./wakeReason.js";
7
6
  export const LEADER_WAKE_AGGREGATION_MS = 60_000;
8
- export const LEADER_WAKE_FORCE_MS = 10 * 60_000;
9
- /**
10
- * Materializes durable Leader intent as a Turn. Provider lifecycle and
11
- * Turn submission are handled by processActiveRoleTurnDeliveries, so every
12
- * Role follows one launch, serialization, and failure path.
7
+ /** Deliver a durable mailbox notification, not an implicit execution assignment.
8
+ * Acceptance settles only its claimed batch; no Agent report is required.
13
9
  */
14
10
  export async function processLeaderWakeups(store, delivery, now, selection) {
15
11
  const results = [];
16
- const wakeups = selection === undefined || selection.full
17
- ? store.listPendingWakeups().filter((wakeup) => !selection?.blockedTaskIds?.has(wakeup.taskId))
18
- : [...selection.taskIds].flatMap((taskId) => {
19
- if (selection.blockedTaskIds?.has(taskId))
20
- return [];
21
- const wakeup = store.getPendingWakeup(taskId);
22
- return wakeup === null ? [] : [wakeup];
23
- });
12
+ const wakeups = store.listPendingWakeups().filter((wake) => !selection?.blockedTaskIds?.has(wake.taskId)
13
+ && (selection === undefined || selection.full || selection.taskIds.has(wake.taskId)));
24
14
  for (const wakeup of wakeups) {
25
15
  const task = store.getTask(wakeup.taskId);
26
16
  const role = store.getRole(wakeup.taskId, "leader");
27
- if (task === null
28
- || task.status !== "active"
29
- || task.executionGate.state !== "enabled"
30
- || role === null) {
31
- results.push({ taskId: wakeup.taskId, status: "skipped", reason: "unavailable" });
17
+ const base = { taskId: wakeup.taskId };
18
+ if (task == null || !["active", "draft"].includes(task.status) || task.executionGate.state !== "enabled" || role === null) {
19
+ results.push({ ...base, status: "skipped", reason: "unavailable" });
32
20
  continue;
33
21
  }
34
- if (!isSchedulerTaskWorkspaceReady(task, store.getTaskWorkspace(task.id))) {
35
- results.push({ taskId: task.id, status: "skipped", reason: "workspace-not-ready" });
22
+ if (!isSchedulerTaskWorkspaceReady(task, store.getTaskWorkspace(task.id), task.status === "draft" ? "planning" : "execution")) {
23
+ results.push({ ...base, status: "skipped", reason: "workspace-not-ready" });
36
24
  continue;
37
25
  }
38
- if (store.hasOpenInputRequest(task.id)) {
39
- results.push({ taskId: task.id, status: "skipped", reason: "waiting-input" });
26
+ if (!hasImmediateWakeReason(wakeup.reasons)
27
+ && now.getTime() - Date.parse(wakeup.firstRequestedAt) < LEADER_WAKE_AGGREGATION_MS) {
28
+ results.push({ ...base, status: "skipped", reason: "aggregating" });
40
29
  continue;
41
30
  }
42
- const waitedMs = Math.max(0, now.getTime() - Date.parse(wakeup.firstRequestedAt));
43
- if (waitedMs < LEADER_WAKE_AGGREGATION_MS) {
44
- results.push({ taskId: task.id, status: "skipped", reason: "aggregating" });
31
+ if (task.status === "draft" && store.prepareDraftPlanning?.(task.id, now)) {
32
+ results.push({ ...base, status: "skipped", reason: "not-ready" });
45
33
  continue;
46
34
  }
47
- const active = store.getActiveTurn(task.id, role.name);
48
- const leaderTarget = { kind: "role", taskId: task.id, roleName: role.name };
49
- const claimedSteer = store.getWorkMailbox(leaderTarget)?.processing;
50
- if (claimedSteer?.owner.startsWith("leader-steer:") === true
51
- && (active === null || claimedSteer.owner !== `leader-steer:${active.id}`)) {
52
- store.releaseWorkMailbox(leaderTarget, claimedSteer.batchId);
53
- results.push({ taskId: task.id, status: "skipped", reason: "state-changed" });
35
+ const sessions = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
36
+ const provider = sessions?.providerBinding;
37
+ if (provider?.authority.owner === "human" || provider?.authority.owner === "unknown") {
38
+ results.push({ ...base, status: "skipped", reason: "busy" });
54
39
  continue;
55
40
  }
56
- if (active !== null) {
57
- if (waitedMs >= LEADER_WAKE_FORCE_MS && active !== null) {
58
- results.push(await forceLeaderSteer(store, delivery, task.id, role.name, active, now));
59
- continue;
60
- }
61
- results.push({ taskId: task.id, status: "skipped", reason: "busy" });
41
+ // Claim inspection also settles a previously accepted receipt after a
42
+ // Controller restart, before checking whether that native execution is busy.
43
+ const notification = store.claimLeaderNotification(task.id, now);
44
+ if (notification === null) {
45
+ results.push({ ...base, status: "skipped", reason: "state-changed" });
46
+ continue;
47
+ }
48
+ if (notification.disposition !== "submit") {
49
+ results.push({ ...base, status: "skipped", reason: "not-ready",
50
+ ...(notification.disposition === "unknown" ? {
51
+ error: `Notification acceptance is unknown; the fixed wake is retained and will not be replayed. `
52
+ + `After establishing native quiescence, release only this claim with yui task wake resolve ${task.id} ${notification.wakeId} --reason <evidence>.`
53
+ } : {}) });
62
54
  continue;
63
55
  }
56
+ let attempted = false;
64
57
  try {
65
- const reopening = wakeup.reasons.includes("task-reopened");
66
- const existingSession = store.getRoleSession(task.id, role.name, reopening ? undefined : role.effective.agentId);
67
- const compatible = existingSession !== null
68
- && roleSessionMayContinue(existingSession.effective, role.effective);
69
- if (hasNativeSession(existingSession)
70
- && existingSession.status === "active"
71
- && !compatible
72
- && !reopening) {
73
- throw new Error(`Leader Session is incompatible with desired effective launch: ${task.id}/${role.name}.`);
58
+ const session = store.getRoleSession(task.id, role.name, role.effective.agentId);
59
+ const effective = session?.status === "active" ? session.effective : role.effective;
60
+ if (session?.status === "active" && !roleSessionMayContinue(session.effective, role.effective)) {
61
+ throw new Error("Leader Session configuration changed; explicitly apply it before notification delivery.");
74
62
  }
75
- const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
76
- const mode = reopening && !compatible
77
- ? "new"
78
- : sessionSet === null
79
- ? hasNativeSession(existingSession) && existingSession.status === "active" && compatible
80
- ? "resume"
81
- : "new"
82
- : roleAgentSessionResumeMode(sessionSet, role.effective.agentId, role.effective);
83
- const turnId = store.peekNextTurnId(task.id);
84
- const envelope = store.getTaskWakeEnvelope?.(task.id) ?? null;
85
- const contextSnapshot = store.freezeLeaderContextSnapshot?.(task.id, role.name, now);
86
- const input = createTurnInput({
87
- source: leaderWakeInputSource(wakeup.reasons),
88
- directive: [
89
- `Wake reasons: ${wakeup.reasons.join(", ")}.`,
90
- ...(envelope === null ? [] : [envelope.text.trim()]),
91
- `Load exact context for ${task.id}/${turnId}.`,
92
- "Read every referenced completed Worker or Reviewer Turn in full before deciding its disposition.",
93
- "Before ending this Turn, judge the affected WorkItems and Task; persist any lifecycle or result changes you establish.",
94
- "The final Turn response is evidence only and never updates WorkItem or Task state by itself."
95
- ].join("\n"),
96
- ...(contextSnapshot === undefined ? {} : { contextSnapshotRef: contextSnapshot.ref }),
97
- deltaRefIds: contextSnapshot?.deltaRefIds ?? []
63
+ const mode = sessions === null ? "new"
64
+ : roleAgentSessionResumeMode(sessions, effective.agentId, effective);
65
+ const prepared = await delivery.prepareRoleSession({
66
+ taskId: task.id, roleName: role.name, agentId: effective.agentId,
67
+ adapterId: effective.adapterId, effective, workspace: effective.workspace.root,
68
+ ...(role.managedWorkspace === undefined ? {} : { managedWorkspace: role.managedWorkspace }),
69
+ ...(task.status === "draft" || !taskOwnsManagedWorkspace(task)
70
+ ? { workspaceFree: true } : {}),
71
+ mode, ...(mode === "resume" && session?.nativeSessionId !== undefined
72
+ ? { nativeSessionId: session.nativeSessionId } : {})
98
73
  });
99
- const turn = createTurn(turnId, task.id, role.name, mode, input, now, {
100
- ...(role.managedWorkspace === undefined ? {} : { workspace: role.managedWorkspace }),
101
- effective: role.effective
74
+ const ready = await delivery.waitUntilReady(prepared);
75
+ attempted = true;
76
+ const outcome = await delivery.sendOnce({
77
+ delivery: ready, receiptId: notification.attemptId, notificationId: notification.wakeId,
78
+ text: [
79
+ `Yui Task notification: task=${task.id} wake=${notification.wakeId}.`,
80
+ `Read current context: yui task context ${task.id} --json.`,
81
+ `Read the fixed notification window: yui task wake show ${task.id} ${notification.wakeId}.`,
82
+ "Read referenced result Messages in full before deciding their disposition.",
83
+ "Follow the Leader Skill to advance outstanding Task work from these durable facts, or record the exact decision/authority needed.",
84
+ "This is a notification, not an execution assignment. No separate final report is required.",
85
+ "Reading context is not an acknowledgement that its requirements have been implemented."
86
+ ].join("\n")
102
87
  });
103
- const claim = store.saveLeaderDispatch({
104
- task,
105
- role,
106
- turn,
107
- session: reopening && !compatible ? null : existingSession,
108
- wakeup,
109
- ...(envelope === null ? {} : {
110
- wakeId: envelope.wakeId,
111
- wakeFromCursor: envelope.fromCursor
112
- }),
113
- now
114
- });
115
- results.push(claim === "claimed"
116
- ? { taskId: task.id, turnId: turn.id, status: "dispatched" }
117
- : { taskId: task.id, status: "skipped", reason: claim });
88
+ const accepted = outcome.status === "sent" || outcome.status === "already-sent";
89
+ const disposition = accepted ? "accepted"
90
+ : outcome.status === "delivery-unknown" ? "unknown"
91
+ : outcome.status === "rejected" ? "rejected"
92
+ : outcome.status === "pending" ? null : "deferred";
93
+ if (disposition !== null)
94
+ store.settleLeaderNotification(task.id, notification.attemptId, disposition, now, outcome.failure?.detail);
95
+ results.push({ ...base, status: accepted ? "dispatched" : "skipped", reason: accepted ? undefined : "not-ready" });
118
96
  }
119
97
  catch (error) {
120
- results.push({
121
- taskId: task.id,
122
- status: "failed",
123
- reason: "not-ready",
124
- error: error instanceof Error ? error.message : String(error)
125
- });
98
+ const detail = error instanceof Error ? error.message : String(error);
99
+ store.settleLeaderNotification(task.id, notification.attemptId, attempted ? "unknown" : "deferred", now, detail);
100
+ results.push({ ...base, status: "failed", reason: "not-ready", error: detail });
126
101
  }
127
- }
128
- return results;
129
- }
130
- function leaderWakeInputSource(reasons) {
131
- if (reasons.length > 0 && reasons.every((reason) => reason === "user-message")) {
132
- return { type: "yui", channel: "user-message" };
133
- }
134
- if (reasons.length > 0 && reasons.every((reason) => reason.startsWith("input-answered:"))) {
135
- return { type: "yui", channel: "input-response" };
136
- }
137
- return { type: "yui", channel: "leader-wakeup" };
138
- }
139
- async function forceLeaderSteer(store, delivery, taskId, roleName, active, now) {
140
- const target = { kind: "role", taskId, roleName };
141
- const mailbox = store.getWorkMailbox(target);
142
- const pending = mailbox?.pending;
143
- const existing = mailbox?.processing;
144
- if (existing === null || existing === undefined) {
145
- if (pending === null || pending === undefined) {
146
- return { taskId, turnId: active.id, status: "skipped", reason: "state-changed" };
102
+ finally {
103
+ delivery.forgetPrepared?.({ taskId: task.id, roleName: role.name });
147
104
  }
148
105
  }
149
- const owner = `leader-steer:${active.id}`;
150
- if (existing?.owner === owner) {
151
- // A previous pass already claimed this input. If it could not settle the
152
- // claim, delivery may have happened even when the Host has since restarted.
153
- // Do not turn a mailbox read into an automatic Provider retry.
154
- return {
155
- taskId, turnId: active.id, status: "skipped", reason: "not-ready",
156
- error: "The existing Leader steer attempt is unresolved; its mailbox claim and error evidence are preserved."
157
- };
158
- }
159
- const batchId = existing?.batchId
160
- ?? `leader-steer:${encodeURIComponent(taskId)}:${encodeURIComponent(active.id)}:${pending.fromSequence}-${pending.toSequence}`;
161
- const claim = store.claimWorkMailbox({ target, batchId, owner, now });
162
- if (claim.status === "empty") {
163
- return { taskId, turnId: active.id, status: "skipped", reason: "state-changed" };
164
- }
165
- const processing = claim.processing;
166
- if (processing.batchId !== batchId || processing.owner !== owner) {
167
- return { taskId, turnId: active.id, status: "skipped", reason: "busy" };
168
- }
169
- const receiptId = `turn-input:${taskId}/${active.id}/${batchId}`;
170
- let inputMayBeAccepted = false;
171
- let inputAccepted = false;
172
- let deliveryReturned = false;
173
- let runtimeGenerationId;
174
- try {
175
- const sessions = store.getTaskRoleSessionSet?.(taskId, roleName) ?? null;
176
- const session = sessions?.sessions[active.effective.agentId];
177
- const binding = sessions?.providerBinding;
178
- const providerTurn = binding?.turn;
179
- const authority = binding?.authority;
180
- if (session?.runtimeGenerationId === undefined || session.nativeSessionId === undefined
181
- || providerTurn === null || providerTurn === undefined
182
- || providerTurn.turnId !== active.id || providerTurn.nativeTurnId === undefined
183
- || providerTurn.status !== "accepted"
184
- || authority?.owner !== "controller" || authority.holderId === undefined) {
185
- store.releaseWorkMailbox(target, batchId);
186
- return { taskId, status: "failed", reason: "not-ready", error: "Active Leader Turn has no steerable Provider fence." };
187
- }
188
- const batch = processing.batch;
189
- const envelope = store.getTaskWakeEnvelope?.(taskId) ?? null;
190
- const referencedTurnIds = envelope?.referencedTurnIds ?? [];
191
- const displayedTurnIds = referencedTurnIds.slice(0, 4);
192
- const directive = [
193
- `Aggregated Leader events: ${batch.reasons.join(", ")}.`,
194
- `The first event arrived at ${batch.firstQueuedAt} and has waited at least 10 minutes while this Leader Turn remained active.`,
195
- ...displayedTurnIds.map((turnId) => `Read the complete result: yui task turn show ${taskId}/${turnId}.`),
196
- ...(referencedTurnIds.length > displayedTurnIds.length
197
- ? [
198
- `${referencedTurnIds.length - displayedTurnIds.length} additional result Turns are listed in yui task wake show ${taskId} ${envelope.wakeId}.`
199
- ]
200
- : []),
201
- "Process these events now and update durable Task or WorkItem facts when needed.",
202
- "After the events are handled, continue the work you were doing before this interruption.",
203
- `Load the current exact context for ${taskId}/${active.id}; the event batch may have grown while this input was delivered.`
204
- ].join("\n");
205
- const input = createTurnInput({
206
- source: { type: "yui", channel: "leader-forced-wakeup" },
207
- directive,
208
- deltaRefIds: []
209
- });
210
- runtimeGenerationId = session.runtimeGenerationId;
211
- inputMayBeAccepted = true;
212
- const outcome = await delivery.steerOnce({
213
- taskId,
214
- roleName,
215
- agentId: active.effective.agentId,
216
- adapterId: active.effective.adapterId,
217
- runtimeGenerationId: session.runtimeGenerationId,
218
- nativeSessionId: session.nativeSessionId,
219
- nativeTurnId: providerTurn.nativeTurnId,
220
- authority: {
221
- epoch: authority.epoch,
222
- owner: "controller",
223
- holderId: authority.holderId
224
- },
225
- receiptId,
226
- text: directive
227
- });
228
- deliveryReturned = true;
229
- inputAccepted = outcome.status === "sent" || outcome.status === "already-sent"
230
- || outcome.failure?.inputDisposition === "accepted";
231
- inputMayBeAccepted = outcome.status === "delivery-unknown" || inputAccepted;
232
- if (outcome.status !== "sent" && outcome.status !== "already-sent") {
233
- if (!inputMayBeAccepted)
234
- store.releaseWorkMailbox(target, batchId);
235
- const failure = outcome.failure;
236
- if (failure !== undefined || outcome.status === "rejected" || outcome.status === "delivery-unknown") {
237
- store.recordAgentError?.({
238
- taskId, roleName, turnId: active.id,
239
- source: "host",
240
- phase: failure?.phase ?? "turn-submit",
241
- message: failure === undefined ? `Leader steer ${outcome.status}.` : formatProviderDeliveryFailure(failure),
242
- raw: failure?.raw ?? serializeAgentErrorRaw(failure ?? outcome.status),
243
- inputDisposition: failure?.inputDisposition
244
- ?? (outcome.status === "delivery-unknown" ? "unknown" : "not-accepted"),
245
- ...providerDeliveryFailureFacts(failure),
246
- attemptId: receiptId
247
- }, now);
248
- }
249
- return {
250
- taskId,
251
- turnId: active.id,
252
- status: outcome.status === "busy" ? "skipped" : "failed",
253
- reason: outcome.status === "busy" ? "busy" : "not-ready",
254
- // Report the Host's cause when it supplied one; the bare status word
255
- // alone cannot distinguish a rejection from a lost transport.
256
- error: outcome.failure === undefined
257
- ? outcome.status
258
- : `${outcome.status}: ${formatProviderDeliveryFailure(outcome.failure)}`
259
- };
260
- }
261
- const saved = store.saveLeaderSteer({ taskId, turnId: active.id, batchId, input, now });
262
- if (saved !== "claimed")
263
- store.releaseWorkMailbox(target, batchId);
264
- return saved === "claimed"
265
- ? { taskId, turnId: active.id, status: "steered" }
266
- : { taskId, turnId: active.id, status: "skipped", reason: saved };
267
- }
268
- catch (error) {
269
- // A transport exception (or failure after delivery) is not evidence that
270
- // the input was unsubmitted. Preserve its exact mailbox attempt.
271
- if (!inputMayBeAccepted)
272
- store.releaseWorkMailbox(target, batchId);
273
- store.recordAgentError?.({
274
- taskId, roleName, turnId: active.id,
275
- source: inputMayBeAccepted && !deliveryReturned ? "host" : "yui",
276
- phase: "turn-submit",
277
- message: error instanceof Error ? error.message : String(error),
278
- raw: serializeAgentErrorRaw(error),
279
- inputDisposition: inputAccepted ? "accepted" : inputMayBeAccepted ? "unknown" : "not-accepted",
280
- ...(error instanceof Error ? { errorName: error.name } : {}),
281
- causeName: innermostCauseName(error),
282
- expectedRuntimeGenerationId: runtimeGenerationId,
283
- attemptId: receiptId
284
- }, now);
285
- return {
286
- taskId,
287
- turnId: active.id,
288
- status: "failed",
289
- reason: "not-ready",
290
- error: error instanceof Error ? error.message : String(error)
291
- };
292
- }
293
- }
294
- function hasNativeSession(session) {
295
- return session !== null
296
- && typeof session.nativeSessionId === "string"
297
- && session.nativeSessionId.trim().length > 0;
106
+ return results;
298
107
  }
@@ -54,7 +54,7 @@ export async function processOperatorInputNotifications(store, delivery, selecti
54
54
  return attentions.map((attention) => skipped(attention, reason));
55
55
  }
56
56
  if (outcome === "sent")
57
- store.markOperatorTurnStarted(now);
57
+ store.markOperatorRunStarted(now);
58
58
  store.completeWorkMailbox(targetMailbox, processing.batchId);
59
59
  return attentions.map((attention) => ({
60
60
  ...attentionIdentity(attention),
@@ -1,7 +1,38 @@
1
1
  import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
2
- export function isSchedulerTaskWorkspaceReady(task, workspace) {
2
+ import { taskOwnsManagedWorkspace } from "../task/task.js";
3
+ /**
4
+ * Whether the Task's workspace state admits a launch.
5
+ *
6
+ * A Task that owns no managed workspace — activated with an empty environment
7
+ * plan, binding no Project — is admitted with no workspace record at all. The
8
+ * scheduler must not wait for a worktree that was deliberately never created.
9
+ * Every Task that does own one still needs the full ownership proof.
10
+ *
11
+ * A Draft planning conversation is a third case: the Task may already bind a
12
+ * Project, yet activation has not run, so no worktree exists or is owed. It is
13
+ * admitted with no workspace at all, and precisely because it has none it must
14
+ * never be handed one implicitly — workspace preparation keeps the strictly
15
+ * active selection, so a planning Draft never enters it.
16
+ */
17
+ export function isSchedulerTaskWorkspaceReady(task, workspace, purpose) {
18
+ if (isSchedulerPlanningDraft(task, purpose)) {
19
+ return workspace === null || workspace === undefined;
20
+ }
21
+ if (!taskOwnsManagedWorkspace(task)) {
22
+ return workspace === null || workspace === undefined;
23
+ }
3
24
  return isTaskOwnedWorkspace(workspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })));
4
25
  }
26
+ /**
27
+ * A Draft running a planning Turn: durable planning before activation.
28
+ *
29
+ * This is the single predicate every scheduler phase uses to tell that third
30
+ * case apart, so admitting planning never widens to "a Draft is active". The
31
+ * purpose comes from the durable Turn, never from a caller argument.
32
+ */
33
+ export function isSchedulerPlanningDraft(task, purpose) {
34
+ return purpose === "planning" && task.status === "draft";
35
+ }
5
36
  /** Resolves Tasks without a global scan for a dirty reconciliation pass. */
6
37
  export function selectedSchedulerTasks(store, selection) {
7
38
  if (selection === undefined || selection.full) {
@@ -19,20 +50,44 @@ export function selectedSchedulerTasks(store, selection) {
19
50
  * the durable active index, so terminal history never enters Role, delivery,
20
51
  * workspace, or liveness projections. Dirty passes keep their exact-key
21
52
  * semantics and simply discard a Task that is no longer active.
53
+ *
54
+ * With `includePlanningDrafts`, a Draft that already carries an active planning
55
+ * Turn is resolved too. Planning is durable work owed to a Role, so the phases
56
+ * that keep an admitted Turn converging — delivery, liveness, stall — must be
57
+ * able to see it, or a Draft planning Turn could be dispatched and then never
58
+ * re-delivered, resumed, or terminalized. The Draft is admitted only on the
59
+ * evidence of its own durable planning Turn: `listPlanningDraftTaskIds` reads
60
+ * the Turn, never the Task status alone, so an ordinary Draft with no planning
61
+ * Turn stays invisible exactly as before. Phases that act on execution facts
62
+ * (workspace preparation, integration, completion) keep the default and
63
+ * therefore keep seeing active Tasks only.
22
64
  */
23
- export function selectedActiveSchedulerTasks(store, selection) {
65
+ export function selectedActiveSchedulerTasks(store, selection, options) {
66
+ const planningDrafts = options?.includePlanningDrafts === true;
67
+ const admits = (task) => {
68
+ if (task === null || task.executionGate.state !== "enabled")
69
+ return false;
70
+ if (task.status === "active")
71
+ return true;
72
+ return planningDrafts && hasActivePlanningRun(store, task);
73
+ };
24
74
  if (selection === undefined || selection.full) {
25
75
  const indexedTaskIds = store.listActiveTaskIds?.();
26
76
  if (indexedTaskIds === undefined) {
27
- return store.listTasks().filter((task) => (task.status === "active"
28
- && task.executionGate.state === "enabled"
29
- && !selection?.blockedTaskIds?.has(task.id)));
77
+ return store.listTasks().filter((task) => (admits(task) && !selection?.blockedTaskIds?.has(task.id)));
30
78
  }
31
- return [...indexedTaskIds].flatMap((taskId) => {
32
- if (selection?.blockedTaskIds?.has(taskId))
79
+ // The active index is bounded and authoritative for active Tasks; planning
80
+ // Drafts come from their own bounded index rather than a full-history scan.
81
+ const taskIds = planningDrafts
82
+ ? [...indexedTaskIds, ...(store.listPlanningDraftTaskIds?.() ?? [])]
83
+ : [...indexedTaskIds];
84
+ const seen = new Set();
85
+ return taskIds.flatMap((taskId) => {
86
+ if (seen.has(taskId) || selection?.blockedTaskIds?.has(taskId))
33
87
  return [];
88
+ seen.add(taskId);
34
89
  const task = store.getTask(taskId);
35
- return task?.status === "active" && task.executionGate.state === "enabled" ? [task] : [];
90
+ return admits(task) ? [task] : [];
36
91
  });
37
92
  }
38
93
  const taskIds = selection.taskIds;
@@ -40,7 +95,23 @@ export function selectedActiveSchedulerTasks(store, selection) {
40
95
  if (selection.blockedTaskIds?.has(taskId))
41
96
  return [];
42
97
  const task = store.getTask(taskId);
43
- return task?.status === "active" && task.executionGate.state === "enabled" ? [task] : [];
98
+ return admits(task) ? [task] : [];
99
+ });
100
+ }
101
+ /**
102
+ * Whether a Draft carries an active planning Turn on any of its Roles. Read
103
+ * from the durable active-Turn pointers, so a Draft is never admitted on its
104
+ * status alone.
105
+ */
106
+ function hasActivePlanningRun(store, task) {
107
+ if (task.status !== "draft")
108
+ return false;
109
+ const roles = store.listRoles?.(task.id) ?? [];
110
+ return roles.some((role) => {
111
+ const run = store.getActiveRun?.(task.id, role.name) ?? null;
112
+ return run !== null
113
+ && run.status === "active"
114
+ && isSchedulerPlanningDraft(task, run.purpose);
44
115
  });
45
116
  }
46
117
  /** Resolves either every Role in a selected Task or only explicit Role keys. */
@@ -1,19 +1,24 @@
1
1
  import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
2
2
  import { formatTaskRecordReference } from "../task/taskRecordReference.js";
3
- import { currentRoleTurnProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleTurnStall.js";
3
+ import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
4
+ import { currentRoleRunProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleRunStall.js";
4
5
  /**
5
6
  * Lightweight liveness only. Pane/process loss is runtime-health evidence,
6
- * not an application-level outcome. The Turn stays active so native
7
+ * not an application-level outcome. The AgentRun stays active so native
7
8
  * child work or another observer can still contribute facts, while the stall
8
9
  * path raises bounded attention independently.
9
10
  * Process liveness is only a recovery signal. An absent pane/Host never proves
10
- * that the native Session or Turn ended; recover the same generation and
11
- * native identity when possible, otherwise preserve the active Turn.
11
+ * that the native Session or AgentRun ended; recover the same Session and
12
+ * native identity when possible, otherwise preserve the active AgentRun.
12
13
  */
13
- export async function reconcileExitedRoleTurns(store, delivery, now, selection, excludedTurnRefs = new Set(), liveStatuses, resourceEvidence, targetedInventory = selection !== undefined && !selection.full) {
14
+ export async function reconcileExitedRoleRuns(store, delivery, now, selection, excludedRunRefs = new Set(), liveStatuses, resourceEvidence, targetedInventory = selection !== undefined && !selection.full) {
14
15
  const failed = [];
15
- const candidates = selectedActiveSchedulerTasks(store, selection).flatMap((task) => (selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
16
- const run = store.getActiveTurn(task.id, role.name);
16
+ // A Draft planning Turn is an admitted Turn with a real Host, so liveness must
17
+ // resolve it too; otherwise a lost planning host would never be reaped.
18
+ const candidates = selectedActiveSchedulerTasks(store, selection, {
19
+ includePlanningDrafts: true
20
+ }).flatMap((task) => (selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
21
+ const run = store.getActiveRun(task.id, role.name);
17
22
  if (run === null)
18
23
  return [];
19
24
  const session = store.getRoleSession(task.id, role.name, run.effective.agentId);
@@ -27,9 +32,8 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
27
32
  roleName: role.name,
28
33
  agentId: run.effective.agentId,
29
34
  adapterId: run.effective.adapterId,
30
- turnId: run.id,
35
+ runId: run.id,
31
36
  progressAt: run.createdAt,
32
- ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
33
37
  ...(session?.nativeSessionId === undefined
34
38
  ? {}
35
39
  : { nativeSessionId: session.nativeSessionId })
@@ -38,9 +42,9 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
38
42
  })));
39
43
  if (candidates.length === 0)
40
44
  return failed;
41
- const eligible = candidates.filter(({ task, run }) => (!excludedTurnRefs.has(formatTaskRecordReference(task.id, run.id, "turn"))));
45
+ const eligible = candidates.filter(({ task, run }) => (!excludedRunRefs.has(formatTaskRecordReference(task.id, run.id, "run"))));
42
46
  // Full reconciliation builds one complete provider inventory for every
43
- // active Turn, including delivery-uncertain and completion-pending Turns.
47
+ // active AgentRun, including delivery-uncertain and completion-pending AgentRuns.
44
48
  // The stall phase reuses that snapshot so one full pass never probes the
45
49
  // same pane twice. When targetedInventory is true, a dirty pass instead
46
50
  // uses exact probes below; stall reconciliation intentionally does not run
@@ -57,14 +61,13 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
57
61
  ? [{
58
62
  taskId: task.id,
59
63
  roleName: role.name,
60
- turnId: run.id,
64
+ runId: run.id,
61
65
  agentId: run.effective.agentId,
62
66
  adapterId: run.effective.adapterId,
63
- progressAt: currentRoleTurnProgressAt(store, task.id, role.name, run).progressAt,
67
+ progressAt: currentRoleRunProgressAt(store, task.id, role.name, run).progressAt,
64
68
  ...(session?.nativeSessionId === undefined
65
69
  ? {}
66
70
  : { nativeSessionId: session.nativeSessionId }),
67
- ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId })
68
71
  }]
69
72
  : []))
70
73
  : [], targetedInventory);
@@ -76,8 +79,8 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
76
79
  for (const [key, resource] of batchSnapshot.resources) {
77
80
  const candidate = candidates.find(({ task, role }) => (`${task.id}\0${role.name}` === key));
78
81
  if (candidate !== undefined) {
79
- // Keep only the exact Turn key. A task/role or bare-Turn fallback can
80
- // bridge an asynchronous sample from a prior generation.
82
+ // Keep only the exact AgentRun key. A task/role or bare-AgentRun fallback can
83
+ // bridge an asynchronous sample from a prior Session.
81
84
  resourceEvidence.set(`${key}\0${candidate.run.id}`, resource);
82
85
  }
83
86
  }
@@ -93,8 +96,7 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
93
96
  store.saveRoleHostExitObservation?.({
94
97
  taskId: task.id,
95
98
  roleName: role.name,
96
- turnId: run.id,
97
- ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
99
+ runId: run.id,
98
100
  ...(session?.nativeSessionId === undefined
99
101
  ? {}
100
102
  : { nativeSessionId: session.nativeSessionId }),
@@ -110,7 +112,7 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
110
112
  delivery.forgetPrepared?.({
111
113
  taskId: task.id,
112
114
  roleName: role.name,
113
- turnId: run.id
115
+ runId: run.id
114
116
  });
115
117
  const recovered = await delivery.prepareRoleSession({
116
118
  taskId: task.id,
@@ -121,25 +123,21 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
121
123
  workspace: run.effective.workspace.root,
122
124
  ...(run.workspace === undefined ? {} : { managedWorkspace: run.workspace }),
123
125
  mode: "resume",
124
- turnId: run.id,
126
+ runId: run.id,
125
127
  nativeSessionId: session.nativeSessionId,
126
- ...(session.runtimeGenerationId === undefined
127
- ? {}
128
- : { hostActivationId: session.runtimeGenerationId })
129
128
  });
130
- store.saveRoleTurnPrepared({
129
+ store.saveRoleRunPrepared({
131
130
  task,
132
131
  role,
133
- turn: run,
132
+ run: run,
134
133
  session: recovered.session ?? session,
135
- ...(recovered.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: recovered.runtimeGenerationId }),
136
134
  now
137
135
  });
138
136
  liveStatuses?.set(`${task.id}\0${role.name}`, "present");
139
137
  }
140
138
  catch {
141
139
  // Absence plus an inconclusive local recovery attempt is still not a
142
- // native Session death proof. Keep the exact Turn active for the next
140
+ // native Session death proof. Keep the exact AgentRun active for the next
143
141
  // bounded recovery pass and expose the blocked axis separately.
144
142
  store.queueTaskProgress(task.id, "host-missing-native-resume-pending", now);
145
143
  }
@@ -196,9 +194,7 @@ function exactBatchInventory(batch, candidates) {
196
194
  return { statuses, resources, hostExits };
197
195
  }
198
196
  function isResourceCandidate(task, run, now) {
199
- if (task.status !== "active"
200
- || task.executionGate.state !== "enabled"
201
- || run.status !== "active") {
197
+ if (!runPurposeAdmitsTaskState(run.purpose, task) || run.status !== "active") {
202
198
  return false;
203
199
  }
204
200
  const createdAt = Date.parse(run.createdAt);