@zq-silk/yui 0.2.0 → 0.4.2

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 (208) hide show
  1. package/ARCHITECTURE.md +603 -133
  2. package/README.md +806 -31
  3. package/dist/agent/agent.js +2 -1
  4. package/dist/agent/argumentPolicy.js +3 -1
  5. package/dist/agent/launchEnvironment.js +106 -0
  6. package/dist/agent/managedRuntimeEnvironment.js +34 -0
  7. package/dist/brief/taskBrief.js +11 -1
  8. package/dist/cli/agentConfigurationPicker.js +287 -0
  9. package/dist/cli/commandCatalog.js +488 -60
  10. package/dist/cli/completion.js +146 -22
  11. package/dist/cli/helpRenderer.js +3 -1
  12. package/dist/cli/interactionCandidates.js +53 -15
  13. package/dist/cli/interactionPolicy.js +267 -30
  14. package/dist/cli/interactiveSelection.js +6 -2
  15. package/dist/cli/invocationRouter.js +5 -1
  16. package/dist/cli/operatorWizard.js +87 -0
  17. package/dist/cli/roleOptionCatalog.js +1 -0
  18. package/dist/cli/roleWizard.js +185 -21
  19. package/dist/cli/updateCommand.js +62 -19
  20. package/dist/cli/updateOrchestrator.js +539 -0
  21. package/dist/cli/updatePorts.js +1119 -0
  22. package/dist/cli/upgradeCommand.js +112 -0
  23. package/dist/cli.js +1420 -86
  24. package/dist/commands/agentCommands.js +146 -3
  25. package/dist/commands/configCommands.js +126 -0
  26. package/dist/commands/controllerCommands.js +365 -0
  27. package/dist/commands/globalRoleCommands.js +168 -126
  28. package/dist/commands/jobCommands.js +18 -8
  29. package/dist/commands/operatorCommands.js +159 -9
  30. package/dist/commands/profileCommands.js +203 -0
  31. package/dist/commands/projectCommands.js +650 -0
  32. package/dist/commands/roleConfiguration.js +85 -24
  33. package/dist/commands/roleRuntimeGuard.js +12 -0
  34. package/dist/commands/roleSkillValidation.js +47 -0
  35. package/dist/commands/taskActor.js +127 -0
  36. package/dist/commands/taskCommands.js +4201 -313
  37. package/dist/commands/taskCompletionGate.js +131 -0
  38. package/dist/commands/taskContextCommand.js +244 -30
  39. package/dist/commands/taskInputCommands.js +177 -59
  40. package/dist/commands/taskIntegrationCommands.js +303 -0
  41. package/dist/commands/taskOverviewCommand.js +363 -0
  42. package/dist/commands/taskRoleRuntimeStatus.js +125 -19
  43. package/dist/commands/textInput.js +15 -0
  44. package/dist/completion/completionInstaller.js +26 -22
  45. package/dist/config/yuiConfig.js +4 -3
  46. package/dist/context/dispatchContext.js +90 -38
  47. package/dist/context/roleSessionContext.js +119 -0
  48. package/dist/controller/claudeLifecycleHook.js +203 -0
  49. package/dist/controller/clientRuntime.js +408 -56
  50. package/dist/controller/codexLifecycleHook.js +108 -0
  51. package/dist/controller/controller.js +1089 -32
  52. package/dist/controller/domainIdentity.js +505 -0
  53. package/dist/controller/ephemeralResourceReaper.js +131 -0
  54. package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
  55. package/dist/controller/providerHookRunFence.js +127 -0
  56. package/dist/controller/resourceCleanupLinux.js +286 -0
  57. package/dist/controller/resourceInventory.js +531 -0
  58. package/dist/controller/resourceInventoryLinux.js +610 -0
  59. package/dist/controller/runtime.js +629 -10
  60. package/dist/controller/runtimeEventInbox.js +564 -0
  61. package/dist/controller/runtimeEventProcessor.js +248 -0
  62. package/dist/controller/runtimeLaunchCoordinator.js +477 -0
  63. package/dist/controller/sessionNotify.js +121 -78
  64. package/dist/coordination/deadlineScheduler.js +15 -0
  65. package/dist/coordination/mailboxScheduler.js +108 -0
  66. package/dist/coordination/workMailbox.js +329 -0
  67. package/dist/coordination/workMailboxQueue.js +86 -0
  68. package/dist/core/controllerClient.js +19 -5
  69. package/dist/core/controllerEndpoint.js +37 -0
  70. package/dist/core/controllerServer.js +218 -10
  71. package/dist/core/protocol.js +6 -2
  72. package/dist/decision/decision.js +2 -1
  73. package/dist/doctor/doctor.js +681 -32
  74. package/dist/domain/validation.js +53 -0
  75. package/dist/errors/cliError.js +5 -3
  76. package/dist/event/taskEvent.js +7 -3
  77. package/dist/execution/codexThreadNaming.js +160 -0
  78. package/dist/execution/executionGroup.js +579 -0
  79. package/dist/executor/agentAdapter.js +255 -40
  80. package/dist/executor/agentConfigurationCatalog.js +326 -0
  81. package/dist/executor/agentConfigurationProbe.js +506 -0
  82. package/dist/executor/agentExecutor.js +625 -10
  83. package/dist/executor/codexConfigConflict.js +290 -0
  84. package/dist/executor/effectiveLaunch.js +340 -0
  85. package/dist/executor/executorRegistry.js +238 -36
  86. package/dist/executor/fileRoleLaunchPlanner.js +550 -40
  87. package/dist/executor/turnCompletion.js +126 -0
  88. package/dist/input/inputRequest.js +30 -9
  89. package/dist/integration/changeSet.js +36 -0
  90. package/dist/integration/checkResult.js +24 -0
  91. package/dist/integration/gitIntegrationService.js +695 -0
  92. package/dist/integration/integrationAttempt.js +142 -0
  93. package/dist/interaction/operatorPresentation.js +96 -0
  94. package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
  95. package/dist/lifecycle/exactRunTerminalization.js +572 -0
  96. package/dist/lifecycle/providerLifecycleMapping.js +190 -0
  97. package/dist/lifecycle/taskRoleSessionReset.js +124 -0
  98. package/dist/message/message.js +23 -7
  99. package/dist/milestone/milestone.js +2 -1
  100. package/dist/operator/operatorSessionHistory.js +124 -0
  101. package/dist/output/agentConfigurationPresentation.js +43 -0
  102. package/dist/output/rolePresentation.js +34 -10
  103. package/dist/output/terminal.js +8 -0
  104. package/dist/output/timePresentation.js +55 -0
  105. package/dist/profile/agentProfile.js +128 -0
  106. package/dist/repository/gitWorkspace.js +578 -24
  107. package/dist/repository/project.js +213 -0
  108. package/dist/repository/taskWorkspaceCoordinator.js +392 -0
  109. package/dist/repository/taskWorkspacePreparer.js +1688 -191
  110. package/dist/review/reviewConfig.js +11 -0
  111. package/dist/review/reviewRound.js +399 -0
  112. package/dist/review/taskFinalReviewContract.js +90 -0
  113. package/dist/role/role.js +124 -23
  114. package/dist/run/agentRun.js +155 -12
  115. package/dist/run/runIdentity.js +82 -0
  116. package/dist/runtime/exactControlPlane.js +472 -0
  117. package/dist/runtime/index.js +8 -0
  118. package/dist/runtime/lifecycleReservation.js +38 -0
  119. package/dist/runtime/ports.js +11 -0
  120. package/dist/runtime/preallocatedNativeSession.js +13 -0
  121. package/dist/runtime/promptEnvelope.js +30 -0
  122. package/dist/runtime/runtimeBinding.js +31 -0
  123. package/dist/runtime/runtimeOwner.js +14 -0
  124. package/dist/runtime/sessionLaunchRequest.js +62 -0
  125. package/dist/runtime/sessionTitle.js +54 -0
  126. package/dist/runtime/taskRuntimeIsolation.js +643 -0
  127. package/dist/runtime/tmuxAdapters.js +315 -0
  128. package/dist/runtime/turnCompletion.js +3 -0
  129. package/dist/runtime/validation.js +23 -0
  130. package/dist/scheduler/activeRoleRunDelivery.js +342 -32
  131. package/dist/scheduler/activeTaskProgress.js +63 -0
  132. package/dist/scheduler/leaderFailure.js +2 -1
  133. package/dist/scheduler/leaderWakeupProcessor.js +307 -66
  134. package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
  135. package/dist/scheduler/operatorNotification.js +44 -2
  136. package/dist/scheduler/ports.js +28 -1
  137. package/dist/scheduler/roleRunLiveness.js +131 -25
  138. package/dist/scheduler/roleRunStall.js +951 -0
  139. package/dist/scheduler/taskExecutionProjection.js +544 -0
  140. package/dist/scheduler/wakeupQueue.js +3 -0
  141. package/dist/setup/setupCommand.js +302 -52
  142. package/dist/storage/compatibleTaskStore.js +102 -0
  143. package/dist/storage/migration/baseline.js +78 -0
  144. package/dist/storage/migration/classifier.js +51 -0
  145. package/dist/storage/migration/compatibleCodec.js +53 -0
  146. package/dist/storage/migration/engine.js +147 -0
  147. package/dist/storage/migration/index.js +33 -0
  148. package/dist/storage/migration/planner.js +154 -0
  149. package/dist/storage/migration/productionRegistry.js +486 -0
  150. package/dist/storage/migration/registry.js +169 -0
  151. package/dist/storage/migration/report.js +54 -0
  152. package/dist/storage/migration/types.js +31 -0
  153. package/dist/storage/storageSchema.js +147 -123
  154. package/dist/storage/storageVersions.js +11 -0
  155. package/dist/storage/taskStore.js +1793 -197
  156. package/dist/storage/upgrade/homeClassification.js +156 -0
  157. package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
  158. package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
  159. package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
  160. package/dist/storage/upgrade/recordVersionScan.js +176 -0
  161. package/dist/storage/upgrade/recordVersions.js +159 -0
  162. package/dist/storage/upgrade/switchProgress.js +80 -0
  163. package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
  164. package/dist/storage/upgrade/upgradeReceipt.js +161 -0
  165. package/dist/storage/upgradeCoordination.js +186 -0
  166. package/dist/storage/upgradeFence.js +366 -0
  167. package/dist/task/task.js +132 -26
  168. package/dist/task/taskRecordReference.js +66 -0
  169. package/dist/tmux/commandExecutor.js +75 -2
  170. package/dist/tmux/tmuxManager.js +747 -49
  171. package/dist/version.js +23 -0
  172. package/dist/web/assets/assetManifest.js +62 -0
  173. package/dist/web/assets/client/app.js +631 -0
  174. package/dist/web/assets/client/components.js +605 -0
  175. package/dist/web/assets/client/dom.js +14 -0
  176. package/dist/web/assets/client/format.js +28 -0
  177. package/dist/web/assets/client/i18n.js +494 -0
  178. package/dist/web/assets/client/markdown.js +114 -0
  179. package/dist/web/assets/client/theme.js +32 -0
  180. package/dist/web/assets/client/view.js +458 -0
  181. package/dist/web/assets/fontData.js +12 -0
  182. package/dist/web/assets/fonts.js +12 -0
  183. package/dist/web/assets/shell.js +114 -0
  184. package/dist/web/assets/styles/cards.js +135 -0
  185. package/dist/web/assets/styles/layout.js +47 -0
  186. package/dist/web/assets/styles/markdown.js +29 -0
  187. package/dist/web/assets/styles/responsive.js +39 -0
  188. package/dist/web/assets/styles/tokens.js +101 -0
  189. package/dist/web/assets/styles/widgets.js +147 -0
  190. package/dist/web/tmuxWebTerminal.js +158 -0
  191. package/dist/web/webServer.js +463 -0
  192. package/dist/web/webSnapshot.js +148 -0
  193. package/dist/workItem/workItem.js +642 -23
  194. package/dist/workspace/gitChangeSetCapture.js +86 -0
  195. package/dist/workspace/workItemChangeSetManager.js +445 -0
  196. package/dist/worktree/managedWorkspace.js +202 -0
  197. package/docs/task-local-identity.md +62 -0
  198. package/i18n/README.zh-CN.md +406 -31
  199. package/package.json +10 -2
  200. package/skills/yui-leader/SKILL.md +601 -39
  201. package/skills/yui-operator/SKILL.md +255 -34
  202. package/skills/yui-reviewer/SKILL.md +57 -0
  203. package/skills/yui-worker/SKILL.md +214 -17
  204. package/dist/commands/repositoryCommands.js +0 -86
  205. package/dist/operator/operatorContext.js +0 -66
  206. package/dist/repository/repository.js +0 -55
  207. package/dist/scheduler/archivedTaskRuntime.js +0 -12
  208. package/dist/worktree/roleWorkspace.js +0 -62
@@ -1,20 +1,31 @@
1
+ import { selectedSchedulerRoles, selectedSchedulerTasks } from "./ports.js";
2
+ import { isSchedulerTaskWorkspaceReady } from "./ports.js";
3
+ import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
4
+ import { effectiveLaunchSnapshotsCompatible } from "../executor/effectiveLaunch.js";
5
+ import { RuntimeLaunchError } from "../runtime/ports.js";
6
+ import { isPreInputReadinessSupported } from "../lifecycle/canonicalLifecycleEvent.js";
7
+ import { preInputReadinessCapability } from "../lifecycle/providerLifecycleMapping.js";
1
8
  /**
2
9
  * Delivers durable Work AgentRuns before liveness reconciliation. Task command
3
10
  * handlers only record intent; this Controller path is the sole automated
4
11
  * route into the Agent terminal, through tmux receipt-backed delivery.
5
12
  */
6
- export async function processActiveRoleRunDeliveries(store, delivery, now) {
13
+ export async function processActiveRoleRunDeliveries(store, delivery, now, selection) {
7
14
  const results = [];
8
- for (const task of store.listTasks()) {
15
+ for (const task of selectedSchedulerTasks(store, selection)) {
9
16
  if (task.status !== "active")
10
17
  continue;
11
- for (const role of store.listRoles(task.id)) {
18
+ for (const role of selectedSchedulerRoles(store, task.id, selection)) {
12
19
  const run = store.getActiveAgentRun(task.id, role.name);
13
20
  // A crash after a Leader wake is durably claimed but before tmux input
14
- // is recoverable through the same receipt-backed delivery path.
15
- if (run === null || run.deliveredAt !== undefined)
21
+ // is recoverable through the same receipt-backed delivery path. The
22
+ // re-push guard keys on pushedAt (transport), not deliveredAt (provider
23
+ // acceptance): a pushed-but-unaccepted Run must never be pushed twice —
24
+ // no duplicate Enter while acceptance is still pending.
25
+ if (run === null || run.pushedAt !== undefined)
16
26
  continue;
17
- if (task.repositoryId !== undefined && task.cwd === undefined) {
27
+ const taskWorkspace = store.getTaskWorkspace(task.id);
28
+ if (!isSchedulerTaskWorkspaceReady(task, taskWorkspace)) {
18
29
  results.push({
19
30
  taskId: task.id,
20
31
  roleName: role.name,
@@ -24,66 +35,355 @@ export async function processActiveRoleRunDeliveries(store, delivery, now) {
24
35
  });
25
36
  continue;
26
37
  }
27
- const existingSession = store.getRoleSession(task.id, role.name);
28
- const receiptId = `agent-run:${run.id}`;
38
+ const existingSession = store.getRoleSession(task.id, role.name, run.effective.agentId);
39
+ const receiptId = formatAgentRunReceiptId(task.id, run.id);
40
+ const target = { kind: "role", taskId: task.id, roleName: role.name };
41
+ const claim = store.claimWorkMailbox({
42
+ target,
43
+ batchId: receiptId,
44
+ owner: "controller",
45
+ now,
46
+ executionRef: { type: "run", taskId: task.id, id: run.id }
47
+ });
48
+ if (claim.status === "empty") {
49
+ results.push({
50
+ taskId: task.id,
51
+ roleName: role.name,
52
+ runId: run.id,
53
+ status: "skipped",
54
+ reason: "mailbox-empty"
55
+ });
56
+ continue;
57
+ }
58
+ const processing = claim.processing;
59
+ if (processing.executionRef?.type !== "run"
60
+ || processing.executionRef.taskId !== task.id
61
+ || processing.executionRef.id !== run.id) {
62
+ results.push({
63
+ taskId: task.id,
64
+ roleName: role.name,
65
+ runId: run.id,
66
+ status: "skipped",
67
+ reason: "mailbox-busy"
68
+ });
69
+ continue;
70
+ }
71
+ let prepared;
72
+ let preparedSession = existingSession;
73
+ let preStartFencePersisted = false;
74
+ let deliveryAttempted = false;
29
75
  try {
30
76
  const nativeSessionId = run.mode === "resume"
31
- ? requireResumeSession(role, existingSession)
77
+ ? requireResumeSession(role, run.effective, existingSession)
32
78
  : undefined;
33
- const prepared = await delivery.prepareRoleSession({
79
+ prepared = await delivery.prepareRoleSession({
34
80
  taskId: task.id,
35
81
  roleName: role.name,
36
- agentId: role.activeAgentId,
37
- adapterId: role.adapterId,
82
+ agentId: run.effective.agentId,
83
+ adapterId: run.effective.adapterId,
84
+ effective: run.effective,
85
+ workspace: run.effective.workspace.root,
86
+ ...(run.workspace === undefined
87
+ ? {}
88
+ : { managedWorkspace: run.workspace }),
38
89
  mode: run.mode,
39
- ...(nativeSessionId === undefined ? {} : { nativeSessionId })
90
+ runId: run.id,
91
+ ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
92
+ beforeHostStart: (preflight) => {
93
+ persistPreStartFence(store, task, role, run, existingSession, preflight, now);
94
+ preparedSession = preflightSession(role, run.effective, existingSession, run.mode, preflight);
95
+ preStartFencePersisted = true;
96
+ }
97
+ });
98
+ // A fresh Codex host may already carry the exact Run prompt in its
99
+ // launch argv. Once preparation returns that transport fact, any
100
+ // later readiness or aggregate-write failure is delivery uncertainty,
101
+ // not a launch failure: preserve the Run and its reservation for the
102
+ // matching provider Hook instead of terminalizing it.
103
+ deliveryAttempted = prepared.inputSubmittedAtLaunch === true;
104
+ // Preparation may already have an exact native Session identity while
105
+ // the provider is still starting. Persist that Session + the Run fence
106
+ // before awaiting readiness so a pre-input provider Hook can validate
107
+ // the complete generation (including receipt) instead of racing an
108
+ // in-memory boundary. A delivery adapter that cannot expose a
109
+ // pre-readiness Session falls back to the existing durable Session;
110
+ // fresh runtime-discovered providers intentionally persist `null`.
111
+ if (!preStartFencePersisted) {
112
+ const preparedFenceSession = prepared.session === undefined
113
+ ? existingSession
114
+ : validateReadySession(role, run.effective, existingSession, run.mode, { prepared, session: prepared.session });
115
+ preparedSession = preparedFenceSession;
116
+ store.saveRoleRunPrepared({
117
+ task,
118
+ role,
119
+ run,
120
+ session: preparedFenceSession,
121
+ ...(prepared.launchId === undefined ? {} : { launchId: prepared.launchId }),
122
+ now
123
+ });
124
+ }
125
+ const ready = await delivery.waitUntilReady(prepared);
126
+ deliveryAttempted = deliveryAttempted
127
+ || ready.prepared.inputSubmittedAtLaunch === true;
128
+ const session = validateReadySession(role, run.effective, existingSession, run.mode, ready);
129
+ preparedSession = session;
130
+ store.saveRoleRunPrepared({
131
+ task,
132
+ role,
133
+ run,
134
+ session,
135
+ ...(ready.prepared.launchId === undefined
136
+ ? {}
137
+ : { launchId: ready.prepared.launchId }),
138
+ now
40
139
  });
41
- const existingReceipt = await delivery.findExistingReceipt?.({
42
- delivery: prepared,
43
- receiptId
44
- }) ?? null;
45
- const ready = existingReceipt ?? await delivery.waitUntilReady(prepared);
46
- const session = validateReadySession(role, existingSession, run.mode, ready);
47
- let status = "already-delivered";
48
- if (existingReceipt === null) {
49
- status = await delivery.sendOnce({
50
- delivery: ready,
51
- receiptId,
52
- text: run.input
53
- }) === "sent" ? "delivered" : "already-delivered";
140
+ if (ready.prepared.inputSubmittedAtLaunch === true) {
141
+ // The exact Run prompt was already carried by the newly-created
142
+ // Provider command. Persist transport success without writing any
143
+ // terminal bytes; only the later matching Provider Hook may mark
144
+ // the Run delivered/accepted.
145
+ store.saveRoleRunDelivery({
146
+ task,
147
+ role,
148
+ run,
149
+ session,
150
+ ...(ready.prepared.launchId === undefined
151
+ ? {}
152
+ : { launchId: ready.prepared.launchId }),
153
+ now
154
+ });
155
+ delivery.forgetPrepared?.({
156
+ taskId: task.id,
157
+ roleName: role.name,
158
+ runId: run.id,
159
+ ...(ready.prepared.launchId === undefined
160
+ ? {}
161
+ : { launchId: ready.prepared.launchId })
162
+ });
163
+ results.push({
164
+ taskId: task.id,
165
+ roleName: role.name,
166
+ runId: run.id,
167
+ status: "delivered"
168
+ });
169
+ continue;
170
+ }
171
+ // Pre-input readiness gate. For an adapter whose capability proves a
172
+ // native event fires before the first prompt (e.g. Claude SessionStart),
173
+ // a freshly-started host must not be pushed until that provider-ready
174
+ // fact has been folded. Unsupported adapters (e.g. Codex) have no
175
+ // pre-input event, so their push proceeds and acceptance is confirmed
176
+ // only by the later exact provider-accepted fold. The gate reads the
177
+ // adapter capability and the durable ready projection — never a sleep,
178
+ // screen scrape, or pane/PID inference — and fails closed for a
179
+ // supported adapter whose readiness cannot be confirmed.
180
+ if (ready.prepared.sessionStarted
181
+ && isPreInputReadinessSupported(preInputReadinessCapability(run.effective.adapterId))
182
+ && !providerReadyForPush(store, {
183
+ taskId: task.id,
184
+ roleName: role.name,
185
+ agentId: run.effective.agentId,
186
+ ...(ready.prepared.launchId === undefined ? {} : { launchId: ready.prepared.launchId }),
187
+ ...(session?.nativeSessionId === undefined
188
+ ? {}
189
+ : { nativeSessionId: session.nativeSessionId })
190
+ })) {
191
+ results.push({
192
+ taskId: task.id,
193
+ roleName: role.name,
194
+ runId: run.id,
195
+ status: "skipped",
196
+ reason: "not-ready"
197
+ });
198
+ continue;
54
199
  }
55
- store.saveRoleRunDelivery({ task, role, run, session, now });
200
+ deliveryAttempted = true;
201
+ const outcome = await delivery.sendOnce({
202
+ delivery: ready,
203
+ receiptId,
204
+ text: run.input
205
+ });
206
+ if (outcome === "busy" || outcome === "unavailable") {
207
+ results.push({
208
+ taskId: task.id,
209
+ roleName: role.name,
210
+ runId: run.id,
211
+ status: "skipped",
212
+ reason: outcome === "busy" ? "not-ready" : "runtime-unavailable",
213
+ terminalFailure: roleRunDeliveryFailure(run, processing.batchId, session, ready.prepared.launchId)
214
+ });
215
+ continue;
216
+ }
217
+ const status = outcome === "sent" ? "delivered" : "already-delivered";
218
+ store.saveRoleRunDelivery({
219
+ task,
220
+ role,
221
+ run,
222
+ session,
223
+ ...(ready.prepared.launchId === undefined
224
+ ? {}
225
+ : { launchId: ready.prepared.launchId }),
226
+ now
227
+ });
56
228
  results.push({ taskId: task.id, roleName: role.name, runId: run.id, status });
57
229
  }
58
230
  catch (error) {
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ if (error instanceof RuntimeLaunchError) {
233
+ const terminalFailure = roleRunDeliveryFailure(run, processing.batchId, existingSession, error.launchId);
234
+ if (error.retryable) {
235
+ results.push({
236
+ taskId: task.id,
237
+ roleName: role.name,
238
+ runId: run.id,
239
+ status: "skipped",
240
+ reason: "runtime-unavailable",
241
+ error: message,
242
+ terminalFailure
243
+ });
244
+ continue;
245
+ }
246
+ const persisted = store.saveRoleRunDeliveryFailure({
247
+ ...terminalFailure,
248
+ now
249
+ });
250
+ if (persisted === "failed") {
251
+ delivery.forgetPrepared?.({
252
+ taskId: task.id,
253
+ roleName: role.name,
254
+ runId: run.id,
255
+ launchId: error.launchId
256
+ });
257
+ }
258
+ results.push({
259
+ taskId: task.id,
260
+ roleName: role.name,
261
+ runId: run.id,
262
+ status: "failed",
263
+ reason: "generation-lost",
264
+ error: persisted === "state-changed"
265
+ ? "Run state changed during exact launch generation failure."
266
+ : message,
267
+ terminalized: persisted === "failed"
268
+ });
269
+ continue;
270
+ }
271
+ if (!deliveryAttempted) {
272
+ delivery.forgetPrepared?.({
273
+ taskId: task.id,
274
+ roleName: role.name,
275
+ runId: run.id,
276
+ ...(prepared?.launchId === undefined
277
+ ? {}
278
+ : { launchId: prepared.launchId })
279
+ });
280
+ const persisted = store.saveExitedRoleRun({
281
+ task,
282
+ role,
283
+ run,
284
+ session: existingSession,
285
+ summary: `Role Run could not start: ${message}`,
286
+ now
287
+ });
288
+ results.push({
289
+ taskId: task.id,
290
+ roleName: role.name,
291
+ runId: run.id,
292
+ status: "failed",
293
+ reason: "launch-failed",
294
+ error: persisted === "state-changed" ? "Run state changed during launch failure." : message,
295
+ terminalized: persisted === "failed"
296
+ });
297
+ continue;
298
+ }
59
299
  results.push({
60
300
  taskId: task.id,
61
301
  roleName: role.name,
62
302
  runId: run.id,
63
303
  status: "failed",
64
- error: error instanceof Error ? error.message : String(error)
304
+ reason: "delivery-uncertain",
305
+ error: message,
306
+ terminalFailure: roleRunDeliveryFailure(run, processing.batchId, preparedSession, prepared?.launchId)
65
307
  });
66
308
  }
67
309
  }
68
310
  }
69
311
  return results;
70
312
  }
71
- function requireResumeSession(role, session) {
313
+ function roleRunDeliveryFailure(run, mailboxBatchId, session, launchId) {
314
+ return {
315
+ taskId: run.taskId,
316
+ roleName: run.roleName,
317
+ agentId: run.effective.agentId,
318
+ adapterId: run.effective.adapterId,
319
+ runId: run.id,
320
+ mailboxBatchId,
321
+ ...(session?.nativeSessionId === undefined
322
+ ? {}
323
+ : { nativeSessionId: session.nativeSessionId }),
324
+ ...(launchId === undefined ? {} : { launchId })
325
+ };
326
+ }
327
+ function requireResumeSession(role, effective, session) {
72
328
  if (session === null || !hasText(session.nativeSessionId)) {
73
329
  throw new Error(`Role resume has no fixed native session: ${role.taskId}/${role.name}.`);
74
330
  }
331
+ if (!effectiveLaunchSnapshotsCompatible(session.effective, effective)) {
332
+ throw new Error(`Role resume effective snapshot drifted: ${role.taskId}/${role.name}.`);
333
+ }
75
334
  return session.nativeSessionId;
76
335
  }
77
- function validateReadySession(role, existing, mode, ready) {
78
- const session = ready.session;
336
+ function persistPreStartFence(store, task, role, run, existingSession, preflight, now) {
337
+ if (preflight.owner.scope !== "task"
338
+ || preflight.owner.taskId !== task.id
339
+ || preflight.owner.roleName !== role.name
340
+ || preflight.runId !== run.id
341
+ || preflight.launchId.trim().length === 0) {
342
+ throw new Error(`Pre-start launch fence changed the active Role Run: ${task.id}/${role.name}.`);
343
+ }
344
+ const session = preflightSession(role, run.effective, existingSession, run.mode, preflight);
345
+ store.saveRoleRunPrepared({
346
+ task,
347
+ role,
348
+ run,
349
+ session,
350
+ launchId: preflight.launchId,
351
+ now
352
+ });
353
+ }
354
+ function preflightSession(role, effective, existing, mode, preflight) {
355
+ const session = preflight.nativeSessionId === undefined
356
+ ? null
357
+ : {
358
+ agentId: preflight.agentId,
359
+ adapterId: preflight.adapterId,
360
+ nativeSessionId: preflight.nativeSessionId,
361
+ launchId: preflight.launchId,
362
+ status: "ready",
363
+ effective: preflight.effective
364
+ };
365
+ return validateRoleSession(role, effective, existing, mode, session);
366
+ }
367
+ function validateReadySession(role, effective, existing, mode, ready) {
368
+ return validateRoleSession(role, effective, existing, mode, ready.session);
369
+ }
370
+ function validateRoleSession(role, effective, existing, mode, session) {
79
371
  if (mode === "new" && session === null)
80
372
  return null;
81
373
  if (session === null || !hasText(session.nativeSessionId)) {
82
374
  throw new Error(`Ready Role session has no native session id: ${role.taskId}/${role.name}.`);
83
375
  }
84
- if (session.agentId !== role.activeAgentId || session.adapterId !== role.adapterId) {
376
+ if (session.agentId !== effective.agentId || session.adapterId !== effective.adapterId) {
85
377
  throw new Error(`Ready Role session identity changed: ${role.taskId}/${role.name}.`);
86
378
  }
379
+ if (!effectiveLaunchSnapshotsCompatible(session.effective, effective)) {
380
+ throw new Error(`Ready Role session effective snapshot changed: ${role.taskId}/${role.name}.`);
381
+ }
382
+ if (existing?.nativeSessionId !== undefined
383
+ && effectiveLaunchSnapshotsCompatible(existing.effective, effective)
384
+ && session.nativeSessionId !== existing.nativeSessionId) {
385
+ throw new Error(`Ready Role session changed the fixed native session id: ${role.taskId}/${role.name}.`);
386
+ }
87
387
  if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
88
388
  throw new Error(`Role resume changed the fixed native session id: ${role.taskId}/${role.name}.`);
89
389
  }
@@ -92,3 +392,13 @@ function validateReadySession(role, existing, mode, ready) {
92
392
  function hasText(value) {
93
393
  return typeof value === "string" && value.trim().length > 0;
94
394
  }
395
+ /**
396
+ * Fail-closed readiness check for a supported-readiness adapter's first push.
397
+ * When the store cannot answer (no implementation), the push is held rather than
398
+ * proceeding blind — a supported adapter must have a proven provider-ready fold.
399
+ */
400
+ function providerReadyForPush(store, input) {
401
+ if (store.isRoleGenerationProviderReady === undefined)
402
+ return false;
403
+ return store.isRoleGenerationProviderReady(input);
404
+ }
@@ -0,0 +1,63 @@
1
+ import { selectedSchedulerTasks, isSchedulerTaskWorkspaceReady } from "./ports.js";
2
+ import { queueLeaderWakeup } from "./wakeupQueue.js";
3
+ import { projectTaskExecution } from "./taskExecutionProjection.js";
4
+ /**
5
+ * Repairs an active Task that has no durable owner capable of advancing it.
6
+ * This is a low-frequency safety net; normal transitions enqueue their own
7
+ * Role mailbox signal in the same transaction.
8
+ */
9
+ export function repairOrphanedActiveTasks(store, now, selection) {
10
+ const repaired = [];
11
+ for (const task of selectedSchedulerTasks(store, selection)) {
12
+ if (task.status !== "active")
13
+ continue;
14
+ const roles = store.listRoles(task.id);
15
+ const activeRuns = roles.flatMap((role) => {
16
+ const run = store.getActiveAgentRun(task.id, role.name);
17
+ return run === null ? [] : [run];
18
+ });
19
+ const hasInFlightTurn = roles.some((role) => store.hasInFlightTurn(task.id, role.name));
20
+ const leaderTarget = { kind: "role", taskId: task.id, roleName: "leader" };
21
+ const leaderMailbox = store.getWorkMailbox(leaderTarget);
22
+ const projection = projectTaskExecution({
23
+ task,
24
+ roles,
25
+ runs: activeRuns,
26
+ pendingWakeup: store.getPendingWakeup(task.id),
27
+ leaderMailbox,
28
+ leaderFailure: store.getLeaderFailure(task.id),
29
+ operatorNotification: store.getOperatorNotification(task.id)
30
+ });
31
+ if (hasInFlightTurn
32
+ || store.hasOpenInputRequest(task.id)
33
+ || store.getLeaderFailure(task.id) !== null
34
+ || store.getOperatorNotification(task.id) !== null
35
+ || projection.status !== "needs-leader-action"
36
+ || hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
37
+ continue;
38
+ }
39
+ const taskWorkspace = store.getTaskWorkspace(task.id);
40
+ if (!isSchedulerTaskWorkspaceReady(task, taskWorkspace)) {
41
+ store.queueTaskProgress(task.id, "task-orphaned", now);
42
+ }
43
+ if (leaderMailbox?.processing !== null && leaderMailbox?.processing !== undefined) {
44
+ const recovered = store.releaseLeaderWakeupAndEnqueue === undefined
45
+ ? store.releaseWorkMailbox(leaderTarget, leaderMailbox.processing.batchId)
46
+ && (queueLeaderWakeup(store, task.id, "task-orphaned", now), true)
47
+ : store.releaseLeaderWakeupAndEnqueue(task.id, leaderMailbox.processing.batchId, "task-orphaned", now);
48
+ if (recovered) {
49
+ repaired.push(task.id);
50
+ }
51
+ continue;
52
+ }
53
+ queueLeaderWakeup(store, task.id, "task-orphaned", now);
54
+ repaired.push(task.id);
55
+ }
56
+ return repaired;
57
+ }
58
+ function hasUnclaimedLeaderWork(store, taskId, leaderMailbox) {
59
+ if (leaderMailbox === null)
60
+ return store.getPendingWakeup(taskId) !== null;
61
+ return leaderMailbox?.processing === null
62
+ && leaderMailbox.pending !== null;
63
+ }
@@ -1,7 +1,8 @@
1
+ export const CURRENT_LEADER_FAILURE_SCHEMA_VERSION = 1;
1
2
  export function recordLeaderFailure(taskId, nativeSessionId, message, now, existing) {
2
3
  const timestamp = now.toISOString();
3
4
  return {
4
- schemaVersion: 1,
5
+ schemaVersion: CURRENT_LEADER_FAILURE_SCHEMA_VERSION,
5
6
  taskId: requiredText(taskId, "Task id"),
6
7
  nativeSessionId: requiredText(nativeSessionId, "Native session id"),
7
8
  message: requiredText(message, "Leader failure message"),