@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,303 @@
1
+ import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
2
+ import { resolveProject } from "../repository/project.js";
3
+ import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
4
+ import { usageError } from "../errors/cliError.js";
5
+ import { defaultTableWidth, renderTable } from "../output/table.js";
6
+ import { createIntegrationAttempt, recordResolutionDecision, updateIntegrationAttempt } from "../integration/integrationAttempt.js";
7
+ import { GitIntegrationService } from "../integration/gitIntegrationService.js";
8
+ import { taskActor } from "./taskActor.js";
9
+ import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
10
+ export async function runTaskIntegrationCommand(args, store, home, options = {}) {
11
+ const now = options.now ?? (() => new Date());
12
+ const [command, ...rest] = args;
13
+ if (command === "start")
14
+ return start(rest, store, home, now, options.environment);
15
+ if (command === "continue") {
16
+ return continueIntegration(rest, store, home, now, options.environment);
17
+ }
18
+ if (command === "resolve") {
19
+ return resolveDecision(rest, store, now, options.environment);
20
+ }
21
+ if (command === "abort")
22
+ return abortIntegration(rest, store, now(), options.environment);
23
+ if (command === "cleanup") {
24
+ return cleanupIntegration(rest, store, home, options.environment);
25
+ }
26
+ if (command === "list")
27
+ return list(rest, store);
28
+ if (command === "show")
29
+ return show(rest, store, options.environment);
30
+ throw usageError(command === undefined
31
+ ? "Task Integration command is required."
32
+ : `Unknown command: task integration ${command}`);
33
+ }
34
+ async function cleanupIntegration(args, store, home, environment) {
35
+ if (args.length !== 1) {
36
+ throw usageError("Task Integration cleanup usage: yui task integration cleanup <task>/<integration>.");
37
+ }
38
+ const integration = requireIntegration(store, args[0], environment);
39
+ if (integration.status !== "committed" && integration.status !== "failed") {
40
+ throw usageError(`Integration is not terminal: ${integration.id}/${integration.status}.`);
41
+ }
42
+ const result = await new GitIntegrationService(home, store).cleanup(integration);
43
+ if (result === "dirty") {
44
+ throw usageError(`Integration worktree contains unresolved or uncommitted changes: ${integration.id}. Inspect it before cleanup.`);
45
+ }
46
+ return {
47
+ output: result === "removed"
48
+ ? `Cleaned Integration worktree and check logs ${integration.id}\n`
49
+ : `Integration worktree and check logs already clean: ${integration.id}\n`,
50
+ data: { integrationId: integration.id, cleanup: result }
51
+ };
52
+ }
53
+ async function start(args, store, home, now, environment) {
54
+ const usage = "Task Integration start usage: yui task integration start <task> [--project <project>] --change-set <id> [--change-set <id> ...] [--target <ref>] [--check <command> ...].";
55
+ const parsed = parseRepeatable(args, new Set(["--change-set", "--check"]), new Set(["--project", "--target"]), usage);
56
+ if (parsed.positionals.length !== 1)
57
+ throw usageError(usage);
58
+ const task = store.getTask(parsed.positionals[0]);
59
+ if (task === null)
60
+ throw usageError(`Task not found: ${parsed.positionals[0]}.`);
61
+ if (task.status !== "active") {
62
+ throw usageError(`Task is not active: ${task.id}/${task.status}.`);
63
+ }
64
+ const changeSetIds = parsed.many.get("--change-set") ?? [];
65
+ if (changeSetIds.length === 0)
66
+ throw usageError("--change-set is required.", usage);
67
+ const changeSets = changeSetIds.map((id) => {
68
+ const changeSet = store.getChangeSet(task.id, id);
69
+ if (changeSet === null)
70
+ throw usageError(`ChangeSet not found: ${id}.`);
71
+ return changeSet;
72
+ });
73
+ const reviewEvidence = new Set(store.listReviewRounds(task.id)
74
+ .flatMap(({ evidenceCommit }) => evidenceCommit === undefined ? [] : [evidenceCommit]));
75
+ const reviewSource = changeSets.find(({ headCommit }) => reviewEvidence.has(headCommit));
76
+ if (reviewSource !== undefined) {
77
+ throw usageError(`ReviewRound evidence commit cannot become an Integration source: ${reviewSource.id}.`);
78
+ }
79
+ const projectIds = [...new Set(changeSets.map(({ projectId }) => projectId))];
80
+ if (projectIds.length !== 1) {
81
+ throw usageError("An Integration may only contain ChangeSets from one Project.");
82
+ }
83
+ const requestedProject = parsed.one.get("--project");
84
+ const project = requestedProject === undefined
85
+ ? store.getProject(projectIds[0])
86
+ : resolveProject(store.listProjects(), requestedProject);
87
+ if (project === null)
88
+ throw usageError(`Project not found: ${requestedProject ?? projectIds[0]}.`);
89
+ if (project.id !== projectIds[0]) {
90
+ throw usageError(`ChangeSets belong to another Project: ${projectIds[0]}.`);
91
+ }
92
+ if (!task.projectBindings.some(({ projectId }) => projectId === project.id)) {
93
+ throw usageError(`Project does not belong to Task: ${project.id}.`);
94
+ }
95
+ const mainWorkspace = store.getTaskWorkspace(task.id);
96
+ const mainEntry = mainWorkspace === null
97
+ ? undefined
98
+ : workspaceProjectEntry(mainWorkspace, project.id);
99
+ const targetRef = parsed.one.get("--target") ?? mainEntry?.branch;
100
+ if (targetRef === undefined) {
101
+ throw usageError(`Task main worktree is not ready; reconcile the Task first: ${task.id}.`);
102
+ }
103
+ const expectedHead = (await new NodeGitWorkspace().inspect(project.path, targetRef)).baseCommit;
104
+ const integration = store.transaction((tx) => {
105
+ const created = createIntegrationAttempt({
106
+ id: tx.nextIntegrationAttemptId(task.id),
107
+ taskId: task.id,
108
+ projectId: project.id,
109
+ targetRef,
110
+ expectedHead,
111
+ changeSetIds,
112
+ checkCommands: parsed.many.get("--check") ?? []
113
+ }, now());
114
+ tx.saveIntegrationAttempt(task.id, created);
115
+ return created;
116
+ });
117
+ return runIntegration(store, home, integration, now, environment);
118
+ }
119
+ async function continueIntegration(args, store, home, now, environment) {
120
+ const usage = "Task Integration continue usage: yui task integration continue <task>/<integration>.";
121
+ const parsed = parseRepeatable(args, new Set(), new Set(), usage);
122
+ if (parsed.positionals.length !== 1)
123
+ throw usageError(usage);
124
+ const integration = requireIntegration(store, parsed.positionals[0], environment);
125
+ requireActiveIntegrationTask(store, integration);
126
+ if (integration.status !== "validating"
127
+ && (integration.status !== "blocked"
128
+ || integration.resolution?.action !== "manual-resolution")) {
129
+ throw usageError(`Integration is not ready to continue: ${integration.id}/${integration.status}.`);
130
+ }
131
+ return runIntegration(store, home, integration, now, environment);
132
+ }
133
+ async function runIntegration(store, home, integration, now, environment) {
134
+ const result = await new GitIntegrationService(home, store, undefined, now, environment)
135
+ .integrate(integration.taskId, integration.id);
136
+ const output = result.status === "committed"
137
+ ? `Integrated ${result.attempt.changeSetIds.join(", ")} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
138
+ : result.status === "blocked"
139
+ ? `Integration ${result.attempt.id} requires a Leader resolution decision in ${result.workspace.path}\n`
140
+ : `Integration ${result.attempt.id} failed; target ref was not advanced\n`;
141
+ return { output, data: result };
142
+ }
143
+ function resolveDecision(args, store, now, environment) {
144
+ const usage = "Task Integration resolve usage: yui task integration resolve <task>/<integration> --option <manual-resolution|reject> --rationale <text>.";
145
+ const parsed = parseRepeatable(args, new Set(), new Set(["--option", "--rationale"]), usage);
146
+ if (parsed.positionals.length !== 1)
147
+ throw usageError(usage);
148
+ const integration = requireIntegration(store, parsed.positionals[0], environment);
149
+ const task = requireActiveIntegrationTask(store, integration);
150
+ if (taskActor(environment, task.id) !== "leader") {
151
+ throw usageError("Only the Task Leader can resolve an Integration conflict.");
152
+ }
153
+ const selectedOption = parsed.one.get("--option");
154
+ const rationale = parsed.one.get("--rationale");
155
+ if (selectedOption === undefined || rationale === undefined)
156
+ throw usageError(usage);
157
+ const resolved = recordResolutionDecision(integration, {
158
+ action: selectedOption,
159
+ rationale
160
+ }, now());
161
+ store.saveIntegrationAttempt(resolved.taskId, resolved);
162
+ return {
163
+ output: selectedOption === "manual-resolution"
164
+ ? `Leader selected manual resolution for ${resolved.id}; resolve its candidate worktree, then run integration continue\n`
165
+ : `Leader rejected Integration ${resolved.id}\n`,
166
+ data: { integration: resolved }
167
+ };
168
+ }
169
+ function abortIntegration(args, store, now, environment) {
170
+ const usage = "Task Integration abort usage: yui task integration abort <task>/<integration> --reason <text>.";
171
+ const parsed = parseRepeatable(args, new Set(), new Set(["--reason"]), usage);
172
+ if (parsed.positionals.length !== 1)
173
+ throw usageError(usage);
174
+ const integration = requireIntegration(store, parsed.positionals[0], environment);
175
+ requireActiveIntegrationTask(store, integration);
176
+ if (integration.status !== "running" && integration.status !== "blocked") {
177
+ throw usageError(`Integration cannot be aborted from ${integration.status}: ${integration.id}.`);
178
+ }
179
+ const reason = parsed.one.get("--reason");
180
+ if (reason === undefined)
181
+ throw usageError(usage);
182
+ const aborted = updateIntegrationAttempt(integration, {
183
+ status: "failed",
184
+ checks: [
185
+ ...(integration.checks ?? []),
186
+ { name: "aborted", outcome: "failed", details: reason }
187
+ ]
188
+ }, now);
189
+ store.saveIntegrationAttempt(aborted.taskId, aborted);
190
+ return {
191
+ output: `Aborted Integration ${aborted.id}; start a new Integration Attempt to retry\n`,
192
+ data: { integration: aborted }
193
+ };
194
+ }
195
+ function requireActiveIntegrationTask(store, integration) {
196
+ const task = store.getTask(integration.taskId);
197
+ if (task === null)
198
+ throw usageError(`Task not found: ${integration.taskId}.`);
199
+ if (task.status !== "active") {
200
+ throw usageError(`Task is not active: ${task.id}/${task.status}.`);
201
+ }
202
+ return task;
203
+ }
204
+ function list(args, store) {
205
+ if (args.length !== 1)
206
+ throw usageError("Task Integration list usage: yui task integration list <task>.");
207
+ const task = store.getTask(args[0]);
208
+ if (task === null)
209
+ throw usageError(`Task not found: ${args[0]}.`);
210
+ const integrations = store.listIntegrationAttempts(task.id);
211
+ const output = integrations.length === 0
212
+ ? "No Integration Attempts found.\n"
213
+ : `${renderTable(`Integration Attempts: ${task.id}`, [
214
+ { header: "Integration", minWidth: 11, maxWidth: 24 },
215
+ { header: "Project", minWidth: 8, maxWidth: 20 },
216
+ { header: "Target", minWidth: 8, maxWidth: 30 },
217
+ { header: "Changes", minWidth: 7, maxWidth: 10 },
218
+ { header: "Status", minWidth: 7, maxWidth: 20 }
219
+ ], integrations.map((entry) => [
220
+ entry.id,
221
+ entry.projectId,
222
+ entry.targetRef,
223
+ String(entry.changeSetIds.length),
224
+ entry.status
225
+ ]), defaultTableWidth())}\n`;
226
+ return { output, data: { integrations } };
227
+ }
228
+ function show(args, store, environment) {
229
+ if (args.length !== 1)
230
+ throw usageError("Task Integration show usage: yui task integration show <task>/<integration>.");
231
+ const integration = requireIntegration(store, args[0], environment);
232
+ return {
233
+ output: `${[
234
+ `Integration Attempt: ${integration.id}`,
235
+ `Task: ${integration.taskId}`,
236
+ `Project: ${integration.projectId}`,
237
+ `Target: ${integration.targetRef}`,
238
+ `Expected head: ${integration.expectedHead}`,
239
+ `Candidate: ${integration.candidateCommit ?? "-"}`,
240
+ `ChangeSets: ${integration.changeSetIds.join(", ")}`,
241
+ `Status: ${integration.status}`,
242
+ `Conflict: ${integration.conflict?.summary ?? "-"}`,
243
+ `Resolution: ${integration.resolution?.action ?? "-"}`,
244
+ ...(integration.checks === undefined
245
+ ? ["Checks: -"]
246
+ : [
247
+ "Checks:",
248
+ ...integration.checks.flatMap((check) => [
249
+ `- ${check.outcome}: ${check.name}`,
250
+ ...(check.details === undefined ? [] : [` ${check.details}`]),
251
+ ...(check.logPath === undefined ? [] : [` Log: ${check.logPath}`])
252
+ ])
253
+ ])
254
+ ].join("\n")}\n`,
255
+ data: { integration }
256
+ };
257
+ }
258
+ function requireIntegration(store, value, environment) {
259
+ let reference;
260
+ try {
261
+ reference = resolveTaskRecordReference(value, {
262
+ kind: "integrationAttempt",
263
+ contextTaskId: environment?.YUI_TASK_ID,
264
+ label: "Integration Attempt"
265
+ });
266
+ }
267
+ catch (error) {
268
+ throw usageError(error instanceof Error ? error.message : String(error));
269
+ }
270
+ const attempt = store.getIntegrationAttempt(reference.taskId, reference.localId);
271
+ if (attempt === null) {
272
+ throw usageError(`Integration Attempt not found: ${reference.taskId}/${reference.localId}.`);
273
+ }
274
+ return attempt;
275
+ }
276
+ function parseRepeatable(args, repeatable, singular, usage) {
277
+ const positionals = [];
278
+ const many = new Map();
279
+ const one = new Map();
280
+ for (let index = 0; index < args.length; index += 1) {
281
+ const value = args[index];
282
+ if (!value.startsWith("--")) {
283
+ positionals.push(value);
284
+ continue;
285
+ }
286
+ if (!repeatable.has(value) && !singular.has(value)) {
287
+ throw usageError(`Unsupported option: ${value}.`, usage);
288
+ }
289
+ if (singular.has(value) && one.has(value)) {
290
+ throw usageError(`Option may only be specified once: ${value}.`, usage);
291
+ }
292
+ const optionValue = args[index + 1];
293
+ if (optionValue === undefined || optionValue.startsWith("--")) {
294
+ throw usageError(`${value} is required.`, usage);
295
+ }
296
+ if (repeatable.has(value))
297
+ many.set(value, [...(many.get(value) ?? []), optionValue]);
298
+ else
299
+ one.set(value, optionValue);
300
+ index += 1;
301
+ }
302
+ return { positionals, many, one };
303
+ }
@@ -0,0 +1,363 @@
1
+ import { usageError } from "../errors/cliError.js";
2
+ import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
3
+ import { formatTimestamp } from "../output/timePresentation.js";
4
+ import { defaultTableWidth, renderTable } from "../output/table.js";
5
+ import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
6
+ export function parseTaskListOptions(args) {
7
+ const allowed = new Set(["--all", "--verbose"]);
8
+ if (args.some((argument) => !allowed.has(argument))
9
+ || new Set(args).size !== args.length) {
10
+ throw usageError("Task list usage: yui task list [--all] [--verbose].");
11
+ }
12
+ return {
13
+ all: args.includes("--all"),
14
+ verbose: args.includes("--verbose")
15
+ };
16
+ }
17
+ export function buildTaskOverview(store, options) {
18
+ const tasks = store.listTasks()
19
+ .filter((task) => options.all || task.status !== "archived")
20
+ .map((task) => buildTaskOverviewEntry(task, store));
21
+ return { tasks };
22
+ }
23
+ export function renderTaskOverview(result, options, timeZone, width = defaultTableWidth()) {
24
+ if (result.tasks.length === 0)
25
+ return "No tasks found.\n";
26
+ const title = options.all ? "Tasks (all)" : "Tasks (unarchived)";
27
+ const output = renderTable(title, [
28
+ { header: "Task", minWidth: 8, maxWidth: 20 },
29
+ { header: "Title", minWidth: 10, maxWidth: 46 },
30
+ { header: "Lifecycle", minWidth: 9, maxWidth: 14 },
31
+ { header: "Leader", minWidth: 12, maxWidth: 48 },
32
+ { header: "Summary updated", minWidth: 16, maxWidth: 28 },
33
+ { header: "Work", minWidth: 8, maxWidth: 38 },
34
+ { header: "Blockers", minWidth: 9, maxWidth: 42 },
35
+ { header: "Execution", minWidth: 14, maxWidth: 34 },
36
+ { header: "Next action / owner", minWidth: 16, maxWidth: 42 }
37
+ ], result.tasks.map((task) => [
38
+ task.id,
39
+ task.title,
40
+ task.status,
41
+ leaderCell(task),
42
+ task.summaryUpdatedAt === null
43
+ ? "missing"
44
+ : formatTimestamp(task.summaryUpdatedAt, timeZone),
45
+ workCell(task.work.counts),
46
+ blockersCell(task.blockers),
47
+ `${task.execution.status} / ${task.execution.owner}`,
48
+ nextCell(task.next)
49
+ ]), width);
50
+ if (!options.verbose)
51
+ return `${output}\n`;
52
+ return `${output}\n\n${renderVerboseDetails(result.tasks, timeZone)}\n`;
53
+ }
54
+ function buildTaskOverviewEntry(task, store) {
55
+ const brief = store.getTaskBrief(task.id);
56
+ const leaderRole = store.getRole(task.id, "leader");
57
+ const workItems = store.listWorkItems(task.id);
58
+ const inputRequests = store.listInputRequests(task.id);
59
+ const openInputRequests = inputRequests.filter((request) => request.status === "open");
60
+ const agentRuns = store.listAgentRuns(task.id);
61
+ const events = store.listEvents(task.id);
62
+ const attention = collectAttention(task, agentRuns, events, store.getLeaderFailure(task.id), store.getOperatorNotification(task.id));
63
+ const blockers = collectBlockers(workItems, openInputRequests, attention);
64
+ const next = deriveNextAction(task, brief, workItems, openInputRequests, blockers, store.getPendingWakeup(task.id));
65
+ const leader = {
66
+ role: "leader",
67
+ roleStatus: leaderRole?.status ?? "missing",
68
+ summary: brief?.leaderSummary ?? null,
69
+ currentFocus: brief?.currentFocus ?? null,
70
+ updatedAt: brief?.updatedAt ?? null,
71
+ updatedBy: brief?.updatedBy ?? null,
72
+ summaryStatus: brief === null ? "missing" : "available"
73
+ };
74
+ const counts = countWorkItems(workItems);
75
+ const runtimeRuns = agentRuns
76
+ .filter((run) => run.status === "active")
77
+ .map((run) => ({
78
+ id: run.id,
79
+ roleName: run.roleName,
80
+ status: run.status,
81
+ delivery: run.deliveredAt === undefined ? "pending" : "delivered",
82
+ deliveredAt: run.deliveredAt ?? null,
83
+ createdAt: run.createdAt,
84
+ updatedAt: run.updatedAt
85
+ }));
86
+ const runtime = {
87
+ activeRuns: runtimeRuns,
88
+ activeRunCount: runtimeRuns.length,
89
+ pendingDeliveryCount: runtimeRuns.filter((run) => run.delivery === "pending").length
90
+ };
91
+ const execution = buildTaskExecutionProjection(store, task.id, task);
92
+ if (execution === null) {
93
+ throw new Error(`Task execution projection disappeared: ${task.id}.`);
94
+ }
95
+ return {
96
+ ...task,
97
+ brief,
98
+ leader,
99
+ leaderSummary: leader.summary,
100
+ currentFocus: leader.currentFocus,
101
+ summaryUpdatedAt: leader.updatedAt,
102
+ summaryUpdatedBy: leader.updatedBy,
103
+ summaryStatus: leader.summaryStatus,
104
+ work: { counts, items: workItems },
105
+ input: { open: openInputRequests, openCount: openInputRequests.length },
106
+ attention,
107
+ attentionCount: attention.length,
108
+ blockers,
109
+ next,
110
+ nextAction: next?.action ?? null,
111
+ nextOwner: next?.owner ?? null,
112
+ runtime,
113
+ execution
114
+ };
115
+ }
116
+ function collectAttention(task, runs, events, failure, notification) {
117
+ const items = [];
118
+ if (failure !== null || notification?.type === "leader-recovery-failed") {
119
+ items.push({
120
+ kind: "leader-recovery",
121
+ id: `leader-recovery:${task.id}`,
122
+ status: "failed",
123
+ owner: "operator",
124
+ summary: failure?.message
125
+ ?? (notification?.type === "leader-recovery-failed"
126
+ ? notification.message
127
+ : "Leader recovery failed."),
128
+ updatedAt: failure?.lastFailedAt
129
+ ?? (notification?.type === "leader-recovery-failed"
130
+ ? notification.updatedAt
131
+ : task.updatedAt)
132
+ });
133
+ }
134
+ const notificationStall = notification?.type === "leader-stalled"
135
+ ? notification
136
+ : null;
137
+ if (notificationStall !== null) {
138
+ items.push({
139
+ kind: "leader-stalled",
140
+ id: `leader-stall:${notificationStall.runId}:${notificationStall.progressAt}`,
141
+ status: "needs-attention",
142
+ owner: "operator",
143
+ summary: notificationStall.message,
144
+ updatedAt: notificationStall.updatedAt,
145
+ runId: notificationStall.runId,
146
+ roleName: "leader"
147
+ });
148
+ }
149
+ for (const run of runs) {
150
+ if (run.roleName !== "leader"
151
+ || run.status !== "active"
152
+ || !isRoleRunStalled(events, run.id))
153
+ continue;
154
+ if (notificationStall?.runId === run.id)
155
+ continue;
156
+ const progressAt = latestStallProgressAt(events, run.id) ?? run.updatedAt;
157
+ items.push({
158
+ kind: "leader-stalled",
159
+ id: `leader-stall:${run.id}:${progressAt}`,
160
+ status: "needs-attention",
161
+ owner: "operator",
162
+ summary: `Run ${run.id} for ${run.roleName} has no durable progress after ${progressAt}.`,
163
+ updatedAt: progressAt,
164
+ runId: run.id,
165
+ roleName: run.roleName
166
+ });
167
+ }
168
+ return items.sort((left, right) => (Date.parse(left.updatedAt) - Date.parse(right.updatedAt)
169
+ || left.id.localeCompare(right.id)));
170
+ }
171
+ function collectBlockers(workItems, openInputRequests, attention) {
172
+ const blockers = [];
173
+ const workById = new Map(workItems.map((item) => [item.id, item]));
174
+ for (const item of workItems) {
175
+ if (item.status === "failed") {
176
+ blockers.push({
177
+ kind: "work",
178
+ type: "work",
179
+ id: item.id,
180
+ status: item.status,
181
+ owner: item.assignee ?? "leader",
182
+ action: "review-failed-work",
183
+ summary: item.outcome ?? `Work Item ${item.id} failed.`
184
+ });
185
+ continue;
186
+ }
187
+ if (item.status === "awaiting_acceptance") {
188
+ blockers.push({
189
+ kind: "work",
190
+ type: "work",
191
+ id: item.id,
192
+ status: item.status,
193
+ owner: "leader",
194
+ action: "accept-work-item",
195
+ summary: `Work Item ${item.id} is awaiting Leader acceptance.`
196
+ });
197
+ continue;
198
+ }
199
+ if (item.status !== "pending")
200
+ continue;
201
+ const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"));
202
+ if (dependencies.length === 0)
203
+ continue;
204
+ blockers.push({
205
+ kind: "work",
206
+ type: "work",
207
+ id: item.id,
208
+ status: item.status,
209
+ owner: item.assignee ?? "leader",
210
+ action: "unblock-work-item",
211
+ summary: `Work Item ${item.id} is waiting on ${dependencies.join(", ")}.`
212
+ });
213
+ }
214
+ for (const request of openInputRequests) {
215
+ blockers.push({
216
+ kind: "input",
217
+ type: "input",
218
+ id: request.id,
219
+ status: request.status,
220
+ owner: "user",
221
+ action: "answer-input",
222
+ summary: request.question,
223
+ blockedRefs: request.blockedRefs
224
+ });
225
+ }
226
+ for (const item of attention) {
227
+ blockers.push({
228
+ kind: "attention",
229
+ type: "attention",
230
+ id: item.id,
231
+ status: item.status,
232
+ owner: item.owner,
233
+ action: item.kind === "leader-recovery"
234
+ ? "inspect-leader-recovery"
235
+ : "inspect-leader-stall",
236
+ summary: item.summary
237
+ });
238
+ }
239
+ return blockers;
240
+ }
241
+ function deriveNextAction(task, brief, workItems, openInputRequests, blockers, pendingWakeup) {
242
+ const input = openInputRequests[0];
243
+ if (input !== undefined) {
244
+ return {
245
+ action: "answer-input",
246
+ owner: "user",
247
+ kind: "input",
248
+ id: input.id,
249
+ summary: input.question
250
+ };
251
+ }
252
+ const attention = blockers.find((blocker) => blocker.kind === "attention");
253
+ if (attention !== undefined) {
254
+ return {
255
+ action: attention.action,
256
+ owner: attention.owner,
257
+ kind: "attention",
258
+ id: attention.id,
259
+ summary: attention.summary
260
+ };
261
+ }
262
+ const workBlocker = blockers.find((blocker) => blocker.kind === "work");
263
+ if (workBlocker !== undefined) {
264
+ return {
265
+ action: workBlocker.action,
266
+ owner: workBlocker.owner,
267
+ kind: "work",
268
+ id: workBlocker.id,
269
+ summary: workBlocker.summary
270
+ };
271
+ }
272
+ if (pendingWakeup !== null) {
273
+ return {
274
+ action: "process-leader-wakeup",
275
+ owner: "leader",
276
+ kind: "wakeup",
277
+ summary: pendingWakeup.reasons.join(", ")
278
+ };
279
+ }
280
+ const pending = workItems.find((item) => item.status === "pending");
281
+ if (pending !== undefined) {
282
+ return {
283
+ action: "start-work-item",
284
+ owner: pending.assignee ?? "leader",
285
+ kind: "work",
286
+ id: pending.id,
287
+ summary: pending.title
288
+ };
289
+ }
290
+ if (task.status === "active" && brief === null) {
291
+ return {
292
+ action: "update-leader-summary",
293
+ owner: "leader",
294
+ kind: "summary",
295
+ summary: "No persisted Task Brief is available."
296
+ };
297
+ }
298
+ return null;
299
+ }
300
+ function countWorkItems(items) {
301
+ const counts = {
302
+ total: items.length,
303
+ pending: 0,
304
+ running: 0,
305
+ awaiting_acceptance: 0,
306
+ completed: 0,
307
+ failed: 0,
308
+ retired: 0
309
+ };
310
+ for (const item of items)
311
+ counts[item.status] += 1;
312
+ return counts;
313
+ }
314
+ function leaderCell(task) {
315
+ const status = task.leader.roleStatus;
316
+ if (task.leader.summaryStatus === "missing")
317
+ return `${status} · missing summary`;
318
+ const values = [task.leader.summary, task.leader.currentFocus]
319
+ .filter((value) => value !== null && value.length > 0);
320
+ return values.length === 0
321
+ ? `${status} · summary unavailable`
322
+ : compactText(`${status} · ${values.join(" · ")}`);
323
+ }
324
+ function workCell(counts) {
325
+ if (counts.total === 0)
326
+ return "none";
327
+ const details = Object.keys(counts)
328
+ .filter((status) => status !== "total" && counts[status] > 0)
329
+ .map((status) => `${status}:${counts[status]}`);
330
+ return `${counts.total} total${details.length === 0 ? "" : ` (${details.join(", ")})`}`;
331
+ }
332
+ function blockersCell(blockers) {
333
+ if (blockers.length === 0)
334
+ return "none";
335
+ return blockers.map((blocker) => `${blocker.kind}:${blocker.id}`).join(", ");
336
+ }
337
+ function nextCell(next) {
338
+ return next === null ? "—" : `${next.action} / ${next.owner}`;
339
+ }
340
+ function compactText(value) {
341
+ const oneLine = value.replace(/\s+/g, " ").trim();
342
+ return oneLine.length <= 240 ? oneLine : `${oneLine.slice(0, 237)}...`;
343
+ }
344
+ function renderVerboseDetails(tasks, timeZone) {
345
+ return [
346
+ "Task details",
347
+ ...tasks.flatMap((task) => [
348
+ `${task.id}: ${task.title}`,
349
+ ` Description: ${task.description ?? "—"}`,
350
+ ` Current focus: ${task.currentFocus ?? "missing"}`,
351
+ ` Leader summary: ${task.leaderSummary ?? "missing"}`,
352
+ ` Summary updated by: ${task.summaryUpdatedBy ?? "unknown"}`,
353
+ ` Summary updated at: ${task.summaryUpdatedAt === null
354
+ ? "missing"
355
+ : formatTimestamp(task.summaryUpdatedAt, timeZone)}`,
356
+ ` Execution: ${task.execution.status} (${task.execution.owner}); ${task.execution.summary}`,
357
+ ` Monitoring: ${task.execution.monitoring}; attention: ${task.execution.attention.length}`,
358
+ ` Projects: ${task.projectBindings.length === 0
359
+ ? "none"
360
+ : task.projectBindings.map(({ directory, projectId, baseRef }) => (`${directory} (${projectId} @ ${baseRef})`)).join(", ")}`
361
+ ])
362
+ ].join("\n");
363
+ }