@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
@@ -0,0 +1,544 @@
1
+ import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
2
+ import { summarizeExecutionGroup } from "../execution/executionGroup.js";
3
+ import { isRoleRunStalled, latestStallProgressAt } from "./roleRunStall.js";
4
+ /**
5
+ * Build one consistent Task-first projection from the existing durable
6
+ * aggregate. This function only reads; it never starts a Controller, queues a
7
+ * wake, writes a Message, or mutates any record.
8
+ */
9
+ export function buildTaskExecutionProjection(store, taskId, taskOverride) {
10
+ const task = store.getTask?.(taskId) ?? taskOverride ?? null;
11
+ if (task === null)
12
+ return null;
13
+ const roles = store.listRoles?.(taskId) ?? [];
14
+ const runs = store.listAgentRuns?.(taskId) ?? [];
15
+ const leaderMailbox = store.getWorkMailbox?.({
16
+ kind: "role",
17
+ taskId,
18
+ roleName: "leader"
19
+ }) ?? null;
20
+ const roleSessions = roles.flatMap((role) => {
21
+ const session = store.getRoleSession?.(taskId, role.name, role.activeAgentId);
22
+ return session === null || session === undefined
23
+ ? []
24
+ : [{ roleName: role.name, ...session }];
25
+ });
26
+ return projectTaskExecution({
27
+ task,
28
+ roles,
29
+ runs,
30
+ executionGroups: store.listWorkItems === undefined && store.listReviewRounds === undefined
31
+ ? []
32
+ : collectExecutionGroups(store.listWorkItems?.(taskId) ?? [], store.listReviewRounds?.(taskId) ?? []),
33
+ workItems: store.listWorkItems?.(taskId) ?? [],
34
+ inputRequests: store.listInputRequests?.(taskId) ?? [],
35
+ ...(store.listReviewRounds === undefined
36
+ ? {}
37
+ : { reviewRounds: store.listReviewRounds(taskId) }),
38
+ ...(store.listChangeSets === undefined
39
+ ? {}
40
+ : { changeSets: store.listChangeSets(taskId) }),
41
+ ...(store.listIntegrationAttempts === undefined
42
+ ? {}
43
+ : { integrations: store.listIntegrationAttempts(taskId) }),
44
+ ...(store.listEvents === undefined ? {} : { events: store.listEvents(taskId) }),
45
+ ...(store.getTaskBrief === undefined ? {} : { brief: store.getTaskBrief(taskId) }),
46
+ pendingWakeup: store.getPendingWakeup?.(taskId) ?? null,
47
+ leaderMailbox,
48
+ leaderFailure: store.getLeaderFailure?.(taskId) ?? null,
49
+ operatorNotification: store.getOperatorNotification?.(taskId) ?? null,
50
+ roleSessions
51
+ });
52
+ }
53
+ /** Alias kept intentionally small for scheduler callers and external read models. */
54
+ export const deriveTaskExecutionProjection = projectTaskExecution;
55
+ export function projectTaskExecution(facts) {
56
+ const { task, roles, runs, workItems = [], inputRequests = [], reviewRounds = [], integrations = [], events = [], pendingWakeup = null, leaderMailbox = null, leaderFailure = null, operatorNotification = null, roleSessions = [], executionGroups = [] } = facts;
57
+ const groupSummaries = executionGroups.map((group) => summarizeExecutionGroup(group));
58
+ const render = (input) => projection({
59
+ ...input,
60
+ executionGroups: groupSummaries
61
+ });
62
+ const activeRuns = runs.filter((run) => run.status === "active");
63
+ const activeRunViews = activeRuns.map((run) => ({
64
+ id: run.id,
65
+ roleName: run.roleName,
66
+ purpose: run.purpose,
67
+ delivered: run.deliveredAt !== undefined,
68
+ status: run.status,
69
+ ...(run.executionGroupId === undefined ? {} : { executionGroupId: run.executionGroupId }),
70
+ ...(run.executionLaneId === undefined ? {} : { executionLaneId: run.executionLaneId })
71
+ }));
72
+ const monitoring = task.status === "completed"
73
+ || task.status === "retired"
74
+ || task.status === "archived"
75
+ ? "stopped"
76
+ : "active";
77
+ if (monitoring === "stopped") {
78
+ const stoppedStatus = task.status;
79
+ return render({
80
+ task,
81
+ status: stoppedStatus,
82
+ owner: "none",
83
+ action: "none",
84
+ summary: `Task ${task.id} is ${task.status}; execution monitoring is stopped.`,
85
+ reason: "task-terminal",
86
+ monitoring,
87
+ failClosed: false,
88
+ activeRuns: activeRunViews,
89
+ activeExecutorCount: 0,
90
+ attention: [],
91
+ blockers: [],
92
+ pendingWakeup
93
+ });
94
+ }
95
+ const attention = collectAttention({
96
+ task,
97
+ roles,
98
+ activeRuns,
99
+ events,
100
+ leaderFailure,
101
+ operatorNotification,
102
+ roleSessions,
103
+ inputRequests
104
+ });
105
+ const openInputs = inputRequests.filter((request) => request.status === "open");
106
+ const blockers = collectBlockers(workItems, reviewRounds, integrations, openInputs, task);
107
+ const activeExecutors = activeRuns.filter((run) => isExecutionCarrier(run));
108
+ const activeWorkers = activeExecutors.filter((run) => run.roleName !== "leader");
109
+ const healthyExecutionCarriers = activeWorkers.filter((run) => (run.deliveredAt !== undefined
110
+ && !attention.some((item) => item.runId === run.id)));
111
+ const activeLeader = activeRuns.find((run) => run.roleName === "leader");
112
+ const pendingDelivery = activeRuns.some((run) => run.deliveredAt === undefined);
113
+ const hasPendingLeaderWork = pendingWakeup !== null
114
+ || leaderMailbox?.pending !== null && leaderMailbox?.pending !== undefined;
115
+ const recoveryPending = isRecoveryPending(pendingWakeup, leaderMailbox, leaderFailure, operatorNotification);
116
+ const failedWork = workItems.some((item) => item.status === "failed");
117
+ const candidateReady = workItems.some((item) => item.status === "awaiting_acceptance");
118
+ const blockedIntegration = integrations.some((attempt) => attempt.status === "blocked");
119
+ const unresolvedIntegration = integrations.some((attempt) => (attempt.status === "running"
120
+ || attempt.status === "validating"
121
+ || attempt.status === "blocked"));
122
+ const hasLeaderMismatch = attention.some((item) => item.kind === "identity-mismatch");
123
+ if (attention.length > 0) {
124
+ const first = attention[0];
125
+ const progressingWithAttention = first.kind === "checkpoint-overdue"
126
+ && healthyExecutionCarriers.length > 0;
127
+ return render({
128
+ task,
129
+ status: progressingWithAttention ? "progressing-with-attention" : "attention",
130
+ owner: first.owner,
131
+ action: "inspect-attention",
132
+ summary: progressingWithAttention
133
+ ? `${healthyExecutionCarriers.length} healthy execution carrier(s) remain while ${first.summary}`
134
+ : first.summary,
135
+ reason: progressingWithAttention ? "progressing-with-attention" : first.kind,
136
+ monitoring,
137
+ failClosed: hasLeaderMismatch || attention.some((item) => item.failClosed === true),
138
+ activeRuns: activeRunViews,
139
+ activeExecutorCount: activeExecutors.length,
140
+ attention,
141
+ blockers,
142
+ pendingWakeup
143
+ });
144
+ }
145
+ if (openInputs.length > 0) {
146
+ return render({
147
+ task,
148
+ status: "waiting-user",
149
+ owner: "user",
150
+ action: "answer-input",
151
+ summary: openInputs[0].question,
152
+ reason: "open-input-request",
153
+ monitoring,
154
+ failClosed: false,
155
+ activeRuns: activeRunViews,
156
+ activeExecutorCount: activeExecutors.length,
157
+ attention,
158
+ blockers,
159
+ pendingWakeup
160
+ });
161
+ }
162
+ if (blockedIntegration || failedWork || hasLeaderMismatch) {
163
+ return render({
164
+ task,
165
+ status: "blocked",
166
+ owner: "leader",
167
+ action: "resolve-blocker",
168
+ summary: blockers[0]?.summary ?? `Task ${task.id} has a blocked durable record.`,
169
+ reason: blockedIntegration ? "integration-blocked" : failedWork ? "work-failed" : "identity-mismatch",
170
+ monitoring,
171
+ failClosed: hasLeaderMismatch,
172
+ activeRuns: activeRunViews,
173
+ activeExecutorCount: activeExecutors.length,
174
+ attention,
175
+ blockers,
176
+ pendingWakeup
177
+ });
178
+ }
179
+ if (recoveryPending) {
180
+ return render({
181
+ task,
182
+ status: "recovering",
183
+ owner: leaderFailure !== null ? "operator" : "leader",
184
+ action: leaderFailure !== null ? "inspect-attention" : "recover-leader",
185
+ summary: leaderFailure?.message
186
+ ?? `Task ${task.id} has a durable recovery wake pending.`,
187
+ reason: leaderFailure === null ? "recovery-pending" : "leader-recovery-failed",
188
+ monitoring,
189
+ failClosed: leaderFailure !== null,
190
+ activeRuns: activeRunViews,
191
+ activeExecutorCount: activeExecutors.length,
192
+ attention,
193
+ blockers,
194
+ pendingWakeup
195
+ });
196
+ }
197
+ if (pendingDelivery) {
198
+ return render({
199
+ task,
200
+ status: "recovering",
201
+ owner: "leader",
202
+ action: "recover-leader",
203
+ summary: "An active Run is awaiting provider acceptance; delivery remains fail-closed.",
204
+ reason: "delivery-pending",
205
+ monitoring,
206
+ failClosed: false,
207
+ activeRuns: activeRunViews,
208
+ activeExecutorCount: activeExecutors.length,
209
+ attention,
210
+ blockers,
211
+ pendingWakeup
212
+ });
213
+ }
214
+ if (activeWorkers.length > 0 && activeLeader === undefined) {
215
+ return render({
216
+ task,
217
+ status: "waiting-on-agents",
218
+ owner: roleOwner(activeWorkers[0].roleName),
219
+ action: "wait-for-agents",
220
+ summary: `${activeWorkers.length} delegated execution Run(s) are active; Leader is waiting for their result.`,
221
+ reason: "delegated-work-active",
222
+ monitoring,
223
+ failClosed: false,
224
+ activeRuns: activeRunViews,
225
+ activeExecutorCount: activeExecutors.length,
226
+ attention,
227
+ blockers,
228
+ pendingWakeup
229
+ });
230
+ }
231
+ if (activeLeader !== undefined) {
232
+ return render({
233
+ task,
234
+ status: activeWorkers.length > 0 ? "waiting-on-agents" : "working",
235
+ owner: "leader",
236
+ action: activeWorkers.length > 0 ? "wait-for-agents" : "advance-task",
237
+ summary: activeWorkers.length > 0
238
+ ? `Leader Run is active while ${activeWorkers.length} delegated Run(s) continue.`
239
+ : "Leader Run is actively advancing the Task.",
240
+ reason: activeWorkers.length > 0 ? "leader-and-delegates-active" : "leader-run-active",
241
+ monitoring,
242
+ failClosed: false,
243
+ activeRuns: activeRunViews,
244
+ activeExecutorCount: activeExecutors.length,
245
+ attention,
246
+ blockers,
247
+ pendingWakeup
248
+ });
249
+ }
250
+ if (activeExecutors.length > 0) {
251
+ return render({
252
+ task,
253
+ status: "waiting-on-agents",
254
+ owner: roleOwner(activeExecutors[0].roleName),
255
+ action: "wait-for-agents",
256
+ summary: `${activeExecutors.length} delegated execution Run(s) are active.`,
257
+ reason: "delegated-work-active",
258
+ monitoring,
259
+ failClosed: false,
260
+ activeRuns: activeRunViews,
261
+ activeExecutorCount: activeExecutors.length,
262
+ attention,
263
+ blockers,
264
+ pendingWakeup
265
+ });
266
+ }
267
+ if (candidateReady || unresolvedIntegration || hasPendingLeaderWork) {
268
+ return render({
269
+ task,
270
+ status: "needs-leader-action",
271
+ owner: "leader",
272
+ action: "advance-task",
273
+ summary: candidateReady
274
+ ? "A WorkItem Candidate is awaiting Leader action."
275
+ : unresolvedIntegration
276
+ ? "An Integration or validation record needs Leader action."
277
+ : "A durable Leader wake is pending.",
278
+ reason: candidateReady
279
+ ? "candidate-ready"
280
+ : unresolvedIntegration ? "integration-pending" : "leader-wake-pending",
281
+ monitoring,
282
+ failClosed: false,
283
+ activeRuns: activeRunViews,
284
+ activeExecutorCount: activeExecutors.length,
285
+ attention,
286
+ blockers,
287
+ pendingWakeup
288
+ });
289
+ }
290
+ return render({
291
+ task,
292
+ status: "needs-leader-action",
293
+ owner: "leader",
294
+ action: "advance-task",
295
+ summary: `Active Task ${task.id} has no current executor or open input.`,
296
+ reason: "no-executor",
297
+ monitoring,
298
+ failClosed: false,
299
+ activeRuns: activeRunViews,
300
+ activeExecutorCount: activeExecutors.length,
301
+ attention,
302
+ blockers,
303
+ pendingWakeup
304
+ });
305
+ }
306
+ function projection(input) {
307
+ return {
308
+ taskId: input.task.id,
309
+ taskStatus: input.task.status,
310
+ status: input.status,
311
+ owner: input.owner,
312
+ action: input.action,
313
+ summary: input.summary,
314
+ reason: input.reason,
315
+ monitoring: input.monitoring,
316
+ failClosed: input.failClosed,
317
+ activeExecutorCount: input.activeExecutorCount,
318
+ activeRuns: input.activeRuns,
319
+ executionGroups: input.executionGroups ?? [],
320
+ attention: input.attention,
321
+ blockers: input.blockers,
322
+ pendingWakeup: input.pendingWakeup,
323
+ next: { owner: input.owner, action: input.action }
324
+ };
325
+ }
326
+ function collectExecutionGroups(workItems, reviewRounds) {
327
+ const groups = [
328
+ ...workItems.flatMap((item) => {
329
+ const group = currentWorkItemExecutionGroup(item);
330
+ return group === undefined ? [] : [group];
331
+ }),
332
+ ...reviewRounds.flatMap((round) => round.executionGroup === undefined ? [] : [round.executionGroup])
333
+ ];
334
+ const seen = new Set();
335
+ return groups.filter((group) => {
336
+ if (seen.has(group.id))
337
+ return false;
338
+ seen.add(group.id);
339
+ return true;
340
+ });
341
+ }
342
+ function collectAttention(input) {
343
+ const result = [];
344
+ const { task, activeRuns, events, leaderFailure, operatorNotification } = input;
345
+ if (leaderFailure !== null || operatorNotification?.type === "leader-recovery-failed") {
346
+ result.push({
347
+ kind: "leader-recovery",
348
+ id: `leader-recovery:${task.id}`,
349
+ owner: "operator",
350
+ summary: leaderFailure?.message
351
+ ?? (operatorNotification?.type === "leader-recovery-failed"
352
+ ? operatorNotification.message
353
+ : "Leader recovery failed."),
354
+ ...(leaderFailure === null ? {} : { failClosed: true })
355
+ });
356
+ }
357
+ if (operatorNotification?.type === "leader-stalled") {
358
+ result.push({
359
+ kind: "leader-stalled",
360
+ id: `leader-stall:${operatorNotification.runId}:${operatorNotification.progressAt}`,
361
+ owner: "operator",
362
+ summary: operatorNotification.message,
363
+ runId: operatorNotification.runId,
364
+ roleName: "leader"
365
+ });
366
+ }
367
+ for (const run of activeRuns) {
368
+ const role = input.roles.find((candidate) => candidate.name === run.roleName);
369
+ const session = input.roleSessions.find((candidate) => candidate.roleName === run.roleName);
370
+ if (role !== undefined
371
+ && ((role.activeAgentId !== undefined && role.activeAgentId !== run.effective?.agentId)
372
+ || (role.adapterId !== undefined && role.adapterId !== run.effective?.adapterId))) {
373
+ result.push({
374
+ kind: "identity-mismatch",
375
+ id: `identity:${run.id}`,
376
+ owner: "leader",
377
+ summary: `Run ${run.id} does not match the current ${run.roleName} Agent/adapter fence.`,
378
+ runId: run.id,
379
+ roleName: run.roleName,
380
+ failClosed: true
381
+ });
382
+ }
383
+ if (session !== undefined
384
+ && (session.agentId !== run.effective?.agentId || session.adapterId !== run.effective?.adapterId)) {
385
+ result.push({
386
+ kind: "identity-mismatch",
387
+ id: `session:${run.id}`,
388
+ owner: "leader",
389
+ summary: `Run ${run.id} has a provider/session identity mismatch; monitoring fails closed.`,
390
+ runId: run.id,
391
+ roleName: run.roleName,
392
+ failClosed: true
393
+ });
394
+ }
395
+ if (isRoleRunStalled(events, run.id)) {
396
+ if (run.roleName === "leader") {
397
+ const progressAt = latestStallProgressAt(events, run.id) ?? "unknown";
398
+ result.push({
399
+ kind: "leader-stalled",
400
+ id: `leader-stall:${run.id}:${progressAt}`,
401
+ owner: "operator",
402
+ summary: `Leader Run ${run.id} has an unresolved no-progress attention.`,
403
+ runId: run.id,
404
+ roleName: run.roleName
405
+ });
406
+ }
407
+ else {
408
+ result.push({
409
+ kind: "checkpoint-overdue",
410
+ id: `checkpoint-overdue:${run.id}`,
411
+ owner: "leader",
412
+ summary: `Delegated Run ${run.id} has an unresolved checkpoint-overdue signal.`,
413
+ runId: run.id,
414
+ roleName: run.roleName
415
+ });
416
+ }
417
+ }
418
+ }
419
+ for (const request of input.inputRequests.filter(({ status }) => status === "open")) {
420
+ const requester = request.requester;
421
+ const run = requester === undefined
422
+ ? undefined
423
+ : activeRuns.find(({ id }) => id === requester.runId);
424
+ const role = requester === undefined
425
+ ? undefined
426
+ : input.roles.find(({ name }) => name === requester.roleName);
427
+ const session = requester === undefined
428
+ ? undefined
429
+ : input.roleSessions.find(({ roleName }) => roleName === requester.roleName);
430
+ if (run === undefined
431
+ || run.roleName !== "leader"
432
+ || requester === undefined
433
+ || run.effective?.agentId !== requester.agentId
434
+ || (role?.adapterId !== undefined && role.adapterId !== run.effective?.adapterId)
435
+ || (requester.nativeSessionId !== undefined
436
+ && session?.nativeSessionId !== requester.nativeSessionId)) {
437
+ result.push({
438
+ kind: "identity-mismatch",
439
+ id: `input:${request.id}`,
440
+ owner: "leader",
441
+ summary: `InputRequest ${request.id} does not match the active Leader Run/session; it is held fail-closed.`,
442
+ ...(requester?.runId === undefined ? {} : { runId: requester.runId }),
443
+ roleName: "leader",
444
+ failClosed: true
445
+ });
446
+ }
447
+ }
448
+ return uniqueAttention(result);
449
+ }
450
+ function collectBlockers(workItems, reviewRounds, integrations, openInputs, task) {
451
+ const blockers = [];
452
+ const byId = new Map(workItems.map((item) => [item.id, item]));
453
+ for (const item of workItems) {
454
+ if (item.status === "failed" || item.status === "awaiting_acceptance") {
455
+ blockers.push({
456
+ kind: "work",
457
+ id: item.id,
458
+ owner: "leader",
459
+ summary: item.outcome ?? `WorkItem ${item.id} is ${item.status}.`
460
+ });
461
+ }
462
+ if (item.status === "pending" && (item.dependsOn ?? []).some((id) => byId.get(id)?.status !== "completed")) {
463
+ blockers.push({
464
+ kind: "work",
465
+ id: item.id,
466
+ owner: "leader",
467
+ summary: `WorkItem ${item.id} is waiting on a dependency.`
468
+ });
469
+ }
470
+ }
471
+ for (const round of reviewRounds.filter(({ status }) => status === "failed")) {
472
+ blockers.push({
473
+ kind: "review",
474
+ id: round.id,
475
+ owner: "leader",
476
+ summary: round.summary ?? `ReviewRound ${round.id} failed.`
477
+ });
478
+ }
479
+ for (const attempt of integrations.filter(({ status }) => status === "blocked" || status === "failed")) {
480
+ blockers.push({
481
+ kind: "integration",
482
+ id: attempt.id,
483
+ owner: "leader",
484
+ summary: attempt.conflict?.summary ?? `Integration ${attempt.id} is ${attempt.status}.`
485
+ });
486
+ }
487
+ for (const request of openInputs) {
488
+ blockers.push({
489
+ kind: "input",
490
+ id: request.id,
491
+ owner: "user",
492
+ summary: request.question
493
+ });
494
+ }
495
+ if (task.status === "active" && task.cwd === undefined) {
496
+ blockers.push({
497
+ kind: "identity",
498
+ id: `workspace:${task.id}`,
499
+ owner: "leader",
500
+ summary: "Project-backed Task workspace is not ready."
501
+ });
502
+ }
503
+ return blockers;
504
+ }
505
+ function isExecutionCarrier(run) {
506
+ return run.purpose === "review"
507
+ || run.roleName === "leader"
508
+ // A scheduler-only projection may not have the WorkItem fold available
509
+ // (for example during a recovery read). An active non-Leader Role is still
510
+ // an execution carrier; omitting it would falsely wake the Leader.
511
+ || run.workItemId !== undefined
512
+ || run.roleName !== "leader";
513
+ }
514
+ function roleOwner(roleName) {
515
+ const normalized = roleName.toLowerCase();
516
+ if (normalized === "leader")
517
+ return "leader";
518
+ if (normalized.includes("review"))
519
+ return "reviewer";
520
+ if (normalized.includes("test"))
521
+ return "tester";
522
+ return "worker";
523
+ }
524
+ function isRecoveryPending(wakeup, mailbox, failure, notification) {
525
+ if (failure !== null || notification?.type === "leader-recovery-failed")
526
+ return false;
527
+ const reasons = [
528
+ ...(wakeup?.reasons ?? []),
529
+ ...(mailbox?.pending?.reasons ?? []),
530
+ ...(mailbox?.processing?.batch.reasons ?? [])
531
+ ];
532
+ return reasons.some((reason) => /(?:recover|stalled|failed|uncertain|orphan)/iu.test(reason));
533
+ }
534
+ function uniqueAttention(items) {
535
+ const result = [];
536
+ const seen = new Set();
537
+ for (const item of items) {
538
+ if (seen.has(item.id))
539
+ continue;
540
+ seen.add(item.id);
541
+ result.push(item);
542
+ }
543
+ return result;
544
+ }
@@ -1,5 +1,8 @@
1
1
  import { mergePendingWakeup } from "./pendingWakeup.js";
2
2
  export function queueLeaderWakeup(store, taskId, reason, now) {
3
+ if (store.enqueueLeaderWakeup !== undefined) {
4
+ return store.enqueueLeaderWakeup(taskId, reason, now);
5
+ }
3
6
  const pending = mergePendingWakeup(taskId, reason, now, store.getPendingWakeup(taskId));
4
7
  store.savePendingWakeup(pending);
5
8
  return pending;