@zq-silk/yui 0.14.0 → 0.14.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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. package/dist/task/repairWave.js +0 -150
@@ -5,10 +5,10 @@ import { usageError } from "../errors/cliError.js";
5
5
  import { defaultTableWidth, renderTable } from "../output/table.js";
6
6
  import { createIntegrationAttempt, recordResolutionDecision, supersedeIntegration, updateIntegrationAttempt } from "../integration/integrationAttempt.js";
7
7
  import { GitIntegrationService } from "../integration/gitIntegrationService.js";
8
+ import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
8
9
  import { runTaskIntegrationQueueCommand } from "./taskIntegrationQueueCommands.js";
9
10
  import { taskLocalActor } from "./taskActor.js";
10
11
  import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
11
- import { runDeliveryGuardPreflight, withGuardWarnings } from "./deliveryGuardPreflight.js";
12
12
  export async function runTaskIntegrationCommand(args, store, home, options = {}) {
13
13
  const now = options.now ?? (() => new Date());
14
14
  const [command, ...rest] = args;
@@ -44,7 +44,7 @@ async function cleanupIntegration(args, store, home, environment) {
44
44
  throw usageError("Task Integration cleanup usage: yui task integration cleanup <task>/<integration>.");
45
45
  }
46
46
  const integration = requireIntegration(store, args[0], environment);
47
- taskLocalActor(store, environment, integration.taskId, home);
47
+ taskLocalActor(store, environment, integration.taskId);
48
48
  if (integration.status !== "committed"
49
49
  && integration.status !== "superseded"
50
50
  && integration.status !== "failed") {
@@ -75,51 +75,51 @@ async function cleanupIntegration(args, store, home, environment) {
75
75
  };
76
76
  }
77
77
  async function start(args, store, home, now, options) {
78
- const usage = "Task Integration start usage: yui task integration start <task> [--project <project>] --change-set <id> [--change-set <id> ...] [--target <ref>] [--check <command> ...].";
79
- const parsed = parseRepeatable(args, new Set(["--change-set", "--check"]), new Set(["--project", "--target"]), usage);
78
+ const usage = "Task Integration start usage: yui task integration start <task> --work-item <id> --strategy <ff|cherry-pick|merge|manual> [--project <project>] [--target <ref>] [--check <command> ...].";
79
+ const parsed = parseRepeatable(args, new Set(["--check"]), new Set(["--work-item", "--strategy", "--project", "--target"]), usage);
80
80
  if (parsed.positionals.length !== 1)
81
81
  throw usageError(usage);
82
- const task = store.getTask(parsed.positionals[0]);
82
+ let task = store.getTask(parsed.positionals[0]);
83
83
  if (task === null)
84
84
  throw usageError(`Task not found: ${parsed.positionals[0]}.`);
85
85
  if (task.status !== "active") {
86
86
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
87
87
  }
88
- taskLocalActor(store, options.environment, task.id, home);
89
- const changeSetIds = parsed.many.get("--change-set") ?? [];
90
- if (changeSetIds.length === 0)
91
- throw usageError("--change-set is required.", usage);
92
- const changeSets = changeSetIds.map((id) => {
93
- const changeSet = store.getChangeSet(task.id, id);
94
- if (changeSet === null)
95
- throw usageError(`ChangeSet not found: ${id}.`);
96
- return changeSet;
97
- });
98
- // Only diagnostic evidence commits (a reviewer's own commit on top of the
99
- // frozen base) are barred from becoming an Integration source. A clean
100
- // review attests the frozen base itself (evidenceCommit === reviewBaseCommit),
101
- // which is the candidate's own head and a legitimate source.
102
- const diagnosticEvidence = new Set(store.listReviewRounds(task.id)
103
- .flatMap(({ evidenceCommit, reviewBaseCommit }) => evidenceCommit !== undefined && evidenceCommit !== reviewBaseCommit
104
- ? [evidenceCommit]
105
- : []));
106
- const reviewSource = changeSets.find(({ headCommit }) => diagnosticEvidence.has(headCommit));
107
- if (reviewSource !== undefined) {
108
- throw usageError(`ReviewRound evidence commit cannot become an Integration source: ${reviewSource.id}.`);
88
+ taskLocalActor(store, options.environment, task.id);
89
+ await new FileTaskWorkspacePreparer(home, store, undefined, now)
90
+ .prepareTaskWorkspace(task.id);
91
+ task = store.getTask(task.id);
92
+ if (task === null || task.status !== "active") {
93
+ throw usageError(`Task is no longer active: ${parsed.positionals[0]}.`);
94
+ }
95
+ const workItemId = parsed.one.get("--work-item");
96
+ const strategy = parsed.one.get("--strategy");
97
+ if (workItemId === undefined || strategy === undefined
98
+ || !["ff", "cherry-pick", "merge", "manual"].includes(strategy)) {
99
+ throw usageError(usage);
109
100
  }
110
- const projectIds = [...new Set(changeSets.map(({ projectId }) => projectId))];
111
- if (projectIds.length !== 1) {
112
- throw usageError("An Integration may only contain ChangeSets from one Project.");
101
+ const workItem = store.getWorkItem(task.id, workItemId);
102
+ if (workItem === null)
103
+ throw usageError(`WorkItem not found: ${workItemId}.`);
104
+ const candidate = workItem.candidates.at(-1);
105
+ if (candidate?.gitSnapshot === undefined || candidate.workspace === undefined) {
106
+ throw usageError(`WorkItem has no committed result snapshot: ${workItem.id}.`);
113
107
  }
108
+ const projectIds = candidate.workspace.entries
109
+ .filter(({ access }) => access === "write")
110
+ .map(({ projectId }) => projectId);
114
111
  const requestedProject = parsed.one.get("--project");
112
+ if (requestedProject === undefined && projectIds.length !== 1) {
113
+ throw usageError("Select --project when a WorkItem result contains multiple Projects.");
114
+ }
115
115
  const project = requestedProject === undefined
116
116
  ? store.getProject(projectIds[0])
117
117
  : resolveProject(store.listProjects(), requestedProject);
118
118
  if (project === null)
119
119
  throw usageError(`Project not found: ${requestedProject ?? projectIds[0]}.`);
120
120
  assertProjectActive(project, "start an Integration");
121
- if (project.id !== projectIds[0]) {
122
- throw usageError(`ChangeSets belong to another Project: ${projectIds[0]}.`);
121
+ if (!projectIds.includes(project.id)) {
122
+ throw usageError(`WorkItem result does not contain Project: ${project.id}.`);
123
123
  }
124
124
  if (!task.projectBindings.some(({ projectId }) => projectId === project.id)) {
125
125
  throw usageError(`Project does not belong to Task: ${project.id}.`);
@@ -132,35 +132,33 @@ async function start(args, store, home, now, options) {
132
132
  if (targetRef === undefined) {
133
133
  throw usageError(`Task main worktree is not ready; reconcile the Task first: ${task.id}.`);
134
134
  }
135
- const expectedHead = (await new NodeGitWorkspace().inspect(project.path, targetRef)).baseCommit;
136
- // The duplicate/budget preflight runs inside the same transaction as the
137
- // attempt insert: on the single-writer SQLite backend the check and the
138
- // record creation are atomic, so a concurrent Leader cannot sneak a
139
- // duplicate Integration between the guard and the write.
135
+ const expectedHead = (await new NodeGitWorkspace().inspect(mainEntry.path, targetRef)).baseCommit;
136
+ const snapshotEntry = candidate.workspace.entries.find(({ projectId }) => projectId === project.id);
137
+ const resultCommit = candidate.gitSnapshot.projects.find(({ projectId }) => projectId === project.id)?.commit;
138
+ if (snapshotEntry === undefined || resultCommit === undefined) {
139
+ throw usageError(`WorkItem result Project snapshot is incomplete: ${workItem.id}/${project.id}.`);
140
+ }
140
141
  const integration = store.transaction((tx) => {
141
- taskLocalActor(tx, options.environment, task.id, home);
142
- const guard = runDeliveryGuardPreflight(tx, task.id, {
143
- kind: "integration-start",
144
- projectId: project.id,
145
- changeSetIds
146
- }, { environment: options.environment, budget: true });
142
+ taskLocalActor(tx, options.environment, task.id);
147
143
  const created = createIntegrationAttempt({
148
144
  id: tx.nextIntegrationAttemptId(task.id),
149
145
  taskId: task.id,
150
146
  projectId: project.id,
151
147
  targetRef,
152
- expectedHead,
153
- changeSetIds,
148
+ beforeCommit: expectedHead,
149
+ source: {
150
+ kind: "work-item",
151
+ workItemId: workItem.id,
152
+ startCommit: snapshotEntry.baseCommit,
153
+ resultCommit,
154
+ strategy
155
+ },
154
156
  checkCommands: parsed.many.get("--check") ?? []
155
157
  }, now());
156
158
  tx.saveIntegrationAttempt(task.id, created);
157
- return { attempt: created, guard };
159
+ return created;
158
160
  });
159
- const result = await runIntegration(store, home, integration.attempt, now, options);
160
- return {
161
- ...result,
162
- output: withGuardWarnings(integration.guard, result.output)
163
- };
161
+ return runIntegration(store, home, integration, now, options);
164
162
  }
165
163
  async function continueIntegration(args, store, home, now, options) {
166
164
  const usage = "Task Integration continue usage: yui task integration continue <task>/<integration>.";
@@ -169,7 +167,7 @@ async function continueIntegration(args, store, home, now, options) {
169
167
  throw usageError(usage);
170
168
  const integration = requireIntegration(store, parsed.positionals[0], options.environment);
171
169
  requireActiveIntegrationTask(store, integration);
172
- taskLocalActor(store, options.environment, integration.taskId, home);
170
+ taskLocalActor(store, options.environment, integration.taskId);
173
171
  if (integration.status !== "validating"
174
172
  && integration.status !== "running"
175
173
  && (integration.status !== "blocked"
@@ -181,7 +179,7 @@ async function continueIntegration(args, store, home, now, options) {
181
179
  async function runIntegration(store, home, integration, now, options) {
182
180
  const result = await new GitIntegrationService(home, store, undefined, now, options.environment, undefined, options.jobPort).integrate(integration.taskId, integration.id);
183
181
  const output = result.status === "committed"
184
- ? `Integrated ${result.attempt.changeSetIds.join(", ")} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
182
+ ? `Integrated ${integrationSourceLabel(result.attempt)} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
185
183
  : result.status === "blocked"
186
184
  ? `Integration ${result.attempt.id} requires a Task Agent resolution decision in ${result.workspace.path}\n`
187
185
  : result.status === "checks-running"
@@ -201,7 +199,7 @@ function resolveDecision(args, store, now, environment, home) {
201
199
  const resolved = store.transaction((tx) => {
202
200
  const integration = requireIntegration(tx, parsed.positionals[0], environment);
203
201
  const task = requireActiveIntegrationTask(tx, integration);
204
- const actor = taskLocalActor(tx, environment, task.id, home);
202
+ const actor = taskLocalActor(tx, environment, task.id);
205
203
  const updated = recordResolutionDecision(integration, {
206
204
  action: selectedOption,
207
205
  rationale
@@ -223,7 +221,7 @@ async function abortIntegration(args, store, now, options, home) {
223
221
  throw usageError(usage);
224
222
  const integration = requireIntegration(store, parsed.positionals[0], options.environment);
225
223
  requireActiveIntegrationTask(store, integration);
226
- taskLocalActor(store, options.environment, integration.taskId, home);
224
+ taskLocalActor(store, options.environment, integration.taskId);
227
225
  if (integration.status !== "running" && integration.status !== "blocked") {
228
226
  throw usageError(`Integration cannot be aborted from ${integration.status}: ${integration.id}.`);
229
227
  }
@@ -245,7 +243,7 @@ async function abortIntegration(args, store, now, options, home) {
245
243
  if (current.status !== "running" && current.status !== "blocked") {
246
244
  throw usageError(`Integration cannot be aborted from ${current.status}: ${current.id}.`);
247
245
  }
248
- taskLocalActor(tx, options.environment, current.taskId, home);
246
+ taskLocalActor(tx, options.environment, current.taskId);
249
247
  const aborted = updateIntegrationAttempt(current, {
250
248
  status: "failed",
251
249
  checks: [
@@ -275,7 +273,7 @@ function supersedeIntegrationCommand(args, store, now, environment, home) {
275
273
  throw usageError(usage);
276
274
  // Superseding a committed Integration rewrites delivery-baseline evidence
277
275
  // and audit history, so it remains an explicit Task-control decision.
278
- taskLocalActor(store, environment, integration.taskId, home);
276
+ taskLocalActor(store, environment, integration.taskId);
279
277
  // A queue-backed committed Attempt cannot be superseded: the queue entry
280
278
  // would remain in its current status while its Attempt becomes "superseded",
281
279
  // leaving contradictory terminal records that never converge. This covers
@@ -295,7 +293,7 @@ function supersedeIntegrationCommand(args, store, now, environment, home) {
295
293
  if (current.status !== "committed") {
296
294
  throw usageError(`Integration cannot be superseded from ${current.status}: ${current.id}.`);
297
295
  }
298
- taskLocalActor(tx, environment, current.taskId, home);
296
+ taskLocalActor(tx, environment, current.taskId);
299
297
  const superseded = supersedeIntegration(current, reason, now);
300
298
  tx.saveIntegrationAttempt(superseded.taskId, superseded);
301
299
  return {
@@ -326,13 +324,13 @@ function list(args, store) {
326
324
  { header: "Integration", minWidth: 11, maxWidth: 24 },
327
325
  { header: "Project", minWidth: 8, maxWidth: 20 },
328
326
  { header: "Target", minWidth: 8, maxWidth: 30 },
329
- { header: "Changes", minWidth: 7, maxWidth: 10 },
327
+ { header: "Source", minWidth: 10, maxWidth: 28 },
330
328
  { header: "Status", minWidth: 7, maxWidth: 20 }
331
329
  ], integrations.map((entry) => [
332
330
  entry.id,
333
331
  entry.projectId,
334
332
  entry.targetRef,
335
- String(entry.changeSetIds.length),
333
+ integrationSourceLabel(entry),
336
334
  entry.status
337
335
  ]), defaultTableWidth())}\n`;
338
336
  return { output, data: { integrations } };
@@ -347,11 +345,13 @@ function show(args, store, environment) {
347
345
  `Task: ${integration.taskId}`,
348
346
  `Project: ${integration.projectId}`,
349
347
  `Target: ${integration.targetRef}`,
350
- `Expected head: ${integration.expectedHead}`,
348
+ `Before commit: ${integration.beforeCommit}`,
351
349
  `Candidate: ${integration.candidateCommit ?? "-"}`,
350
+ `After commit: ${integration.afterCommit ?? "-"}`,
352
351
  `Job: ${integration.jobId ?? "-"}`,
353
- `ChangeSets: ${integration.changeSetIds.join(", ")}`,
352
+ `Source: ${integrationSourceLabel(integration)}`,
354
353
  `Status: ${integration.status}`,
354
+ `Summary: ${integration.summary ?? "-"}`,
355
355
  `Conflict: ${integration.conflict?.summary ?? "-"}`,
356
356
  `Resolution: ${integration.resolution?.action ?? "-"}`,
357
357
  ...(integration.checks === undefined
@@ -368,6 +368,14 @@ function show(args, store, environment) {
368
368
  data: { integration }
369
369
  };
370
370
  }
371
+ function integrationSourceLabel(integration) {
372
+ if (integration.source.kind === "work-item") {
373
+ return `work-item:${integration.source.workItemId}@${integration.source.resultCommit.slice(0, 12)}`;
374
+ }
375
+ return integration.source.kind === "upstream"
376
+ ? `upstream:${integration.source.branch}@${integration.source.remoteCommit.slice(0, 12)}`
377
+ : `historical:${integration.source.changeSetIds.join(",")}`;
378
+ }
371
379
  function requireIntegration(store, value, environment) {
372
380
  let reference;
373
381
  try {
@@ -222,5 +222,5 @@ function requireQueueEntry(store, value, environment) {
222
222
  return entry;
223
223
  }
224
224
  function requireTaskControlActor(store, environment, taskId, home) {
225
- return taskLocalActor(store, environment, taskId, home);
225
+ return taskLocalActor(store, environment, taskId);
226
226
  }
@@ -1,18 +1,17 @@
1
1
  import { taskNotFound, usageError } from "../errors/cliError.js";
2
2
  import { projectNextAction } from "../task/nextAction.js";
3
3
  import { projectCompletionReadiness } from "../task/completionReadiness.js";
4
- import { extractReviewFindings, planRepairWave } from "../task/repairWave.js";
5
4
  import { projectTaskOrchestration } from "../observability/orchestrationMetrics.js";
6
5
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
7
6
  import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
8
7
  import { projectReviewDecision } from "../review/reviewDecision.js";
8
+ import { projectTaskRemoteDeliveryFromStore, renderTaskRemoteDelivery } from "./taskRemoteDeliveryCommand.js";
9
9
  /**
10
10
  * Issue 07 (Leader convergence): read-only `yui task next-action <task>`.
11
11
  * Folds the existing durable records into exactly one protocol-level next
12
12
  * action with exact refs, preconditions, recommended command, alternatives, and
13
13
  * the judgment that remains owned by the Leader.
14
- * The command never mutates state; when the action is `route-review-findings`
15
- * it also prints the minimal repair wave for the failing Review.
14
+ * The command never mutates state and never interprets Agent result text.
16
15
  *
17
16
  * Issue 06: when the recommended action is `complete-task`, the output also
18
17
  * carries the full `completionReadiness` blocker list so the Leader sees every
@@ -44,7 +43,6 @@ export function runTaskNextActionCommand(args, store, currentTaskReviewCandidate
44
43
  executionGroups: execution.executionGroups
45
44
  };
46
45
  const action = projectNextAction(actionFacts);
47
- const repairWave = repairWaveFor(action, actionFacts);
48
46
  // Issue 12: surface pending Knowledge promotion proposals for the Task's
49
47
  // bound Projects as a non-blocking advisory. The proposals are workflow
50
48
  // state, not completion blockers: an Operator reviews them separately.
@@ -70,6 +68,7 @@ export function runTaskNextActionCommand(args, store, currentTaskReviewCandidate
70
68
  }
71
69
  const events = reader.listEvents(taskId);
72
70
  const task = reader.getTask(taskId);
71
+ const remoteDelivery = projectTaskRemoteDeliveryFromStore(reader, task, currentTaskReviewCandidate);
73
72
  const operationalTurns = operationalTaskRecords(reader.listTurns(taskId), events, "turn");
74
73
  const reviewRounds = reader.listReviewRounds(taskId);
75
74
  const reviewDecision = projectReviewDecision({
@@ -88,23 +87,21 @@ export function runTaskNextActionCommand(args, store, currentTaskReviewCandidate
88
87
  workItems: reader.listWorkItems(taskId),
89
88
  changeSets: reader.listChangeSets(taskId),
90
89
  reviewRounds,
91
- reviewFindings: reader.listReviewFindings(taskId),
92
90
  integrations: reader.listIntegrationAttempts(taskId),
93
91
  durableJobs: reader.listDurableJobs(taskId),
94
92
  publications: reader.listPublicationReferences(taskId),
95
- decisions: reader.listDecisions(taskId),
96
93
  events,
97
94
  managedWorkspaces: reader.listManagedWorkspaces(taskId)
98
95
  });
99
96
  return {
100
97
  taskType: facts.task.type ?? null,
101
98
  action,
102
- repairWave,
103
99
  completionReadiness,
104
100
  knowledgeProposals,
105
101
  reviewDecision,
106
102
  execution: nextActionExecutionView(execution),
107
- orchestration
103
+ orchestration,
104
+ remoteDelivery
108
105
  };
109
106
  });
110
107
  if (asJson) {
@@ -116,28 +113,15 @@ export function runTaskNextActionCommand(args, store, currentTaskReviewCandidate
116
113
  }
117
114
  return {
118
115
  kind: "output",
119
- output: renderNextAction(data.action, data.taskType, data.repairWave, data.completionReadiness, data.knowledgeProposals, data.reviewDecision, data.execution, data.orchestration.advisories),
116
+ output: renderNextAction(data.action, data.taskType, data.completionReadiness, data.knowledgeProposals, data.reviewDecision, data.execution, data.orchestration.advisories, data.remoteDelivery),
120
117
  data
121
118
  };
122
119
  }
123
- function repairWaveFor(action, facts) {
124
- if (action.kind !== "route-review-findings")
125
- return null;
126
- const reviewRef = action.refs.find((ref) => ref.kind === "review-round");
127
- if (reviewRef === undefined)
128
- return null;
129
- const round = facts.reviewRounds.find((candidate) => candidate.id === reviewRef.id);
130
- if (round === undefined)
131
- return null;
132
- const findings = extractReviewFindings(round);
133
- if (findings.length === 0)
134
- return null;
135
- return planRepairWave(round.id, findings);
136
- }
137
- function renderNextAction(action, taskType, repairWave, completionReadiness, knowledgeProposals, reviewDecision, execution, orchestrationAdvisories) {
120
+ function renderNextAction(action, taskType, completionReadiness, knowledgeProposals, reviewDecision, execution, orchestrationAdvisories, remoteDelivery) {
138
121
  const lines = [
139
122
  `Task: ${action.taskId}`,
140
123
  `Type: ${taskType ?? "unspecified"}`,
124
+ renderTaskRemoteDelivery(remoteDelivery).trimEnd(),
141
125
  `Next action: ${action.kind}`,
142
126
  `Reason: ${action.reason}`,
143
127
  `Execution view: ${execution.status}; owner/action=${execution.owner}/${execution.action}; reason=${execution.reason}; ${execution.summary}`,
@@ -202,11 +186,6 @@ function renderNextAction(action, taskType, repairWave, completionReadiness, kno
202
186
  + ` — fix before archive: ${advisory.fix}`));
203
187
  }
204
188
  }
205
- if (repairWave !== null) {
206
- lines.push(`Repair wave (${repairWave.openFindingCount} open finding(s), ${repairWave.groups.length} group(s)):`, ...repairWave.groups.map((group) => ` ${group.id}: findings ${group.findingIds.join(", ")}`
207
- + ` — paths ${group.paths.length === 0 ? "none" : group.paths.join(", ")}`
208
- + ` — ${group.reason}`));
209
- }
210
189
  if (knowledgeProposals.length > 0) {
211
190
  lines.push(`Knowledge proposals (non-blocking): ${knowledgeProposals.length} pending`, ...knowledgeProposals.map((proposal) => ` ${proposal.projectId}/${proposal.proposalId}: ${proposal.title}`
212
191
  + ` — review: yui project knowledge proposals list ${proposal.projectId}`));
@@ -6,6 +6,7 @@ import { defaultTableWidth, renderTable } from "../output/table.js";
6
6
  import { projectTaskExecutionFromFacts } from "../scheduler/taskExecutionProjection.js";
7
7
  import { resolveRuntimeHealth } from "../config/yuiConfig.js";
8
8
  import { projectNextAction } from "../task/nextAction.js";
9
+ import { projectTaskRemoteDeliveryFromStore } from "./taskRemoteDeliveryCommand.js";
9
10
  export function parseTaskListOptions(args) {
10
11
  const allowed = new Set(["--all", "--verbose"]);
11
12
  if (args.some((argument) => !allowed.has(argument))
@@ -37,6 +38,7 @@ export function renderTaskOverview(result, options, timeZone, width = defaultTab
37
38
  { header: "Work", minWidth: 8, maxWidth: 38 },
38
39
  { header: "Blockers", minWidth: 9, maxWidth: 42 },
39
40
  { header: "Execution", minWidth: 14, maxWidth: 34 },
41
+ { header: "Delivery", minWidth: 12, maxWidth: 28 },
40
42
  { header: "Next action / owner", minWidth: 16, maxWidth: 42 }
41
43
  ], result.tasks.map((task) => [
42
44
  task.id,
@@ -49,6 +51,7 @@ export function renderTaskOverview(result, options, timeZone, width = defaultTab
49
51
  workCell(task.work.counts),
50
52
  blockersCell(task.blockers),
51
53
  `${task.execution.status} / ${task.execution.owner}`,
54
+ deliveryCell(task.remoteDelivery),
52
55
  nextCell(task.next)
53
56
  ]), width);
54
57
  if (!options.verbose)
@@ -137,6 +140,10 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
137
140
  currentTaskReviewCandidate: null,
138
141
  executionGroups: execution.executionGroups
139
142
  }));
143
+ const persistedCandidate = task.status === "active" || task.status === "retired"
144
+ ? taskRemoteDeliveryCandidate(task)
145
+ : null;
146
+ const remoteDelivery = projectTaskRemoteDeliveryFromStore(store, task, persistedCandidate);
140
147
  return {
141
148
  ...task,
142
149
  brief,
@@ -155,7 +162,8 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
155
162
  nextAction: next?.action ?? null,
156
163
  nextOwner: next?.owner ?? null,
157
164
  runtime,
158
- execution
165
+ execution,
166
+ remoteDelivery
159
167
  };
160
168
  }
161
169
  function collectAttention(task, turns, events, failure) {
@@ -220,8 +228,7 @@ function collectBlockers(workItems, openInputRequests, attention) {
220
228
  }
221
229
  if (item.status !== "pending")
222
230
  continue;
223
- const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"
224
- && workById.get(dependency)?.status !== "retired"));
231
+ const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"));
225
232
  if (dependencies.length === 0)
226
233
  continue;
227
234
  blockers.push({
@@ -326,6 +333,20 @@ function blockersCell(blockers) {
326
333
  function nextCell(next) {
327
334
  return next === null ? "—" : `${next.action} / ${next.owner}`;
328
335
  }
336
+ function deliveryCell(delivery) {
337
+ if (delivery.status === "none")
338
+ return "none";
339
+ return `${delivery.status} · ${delivery.mergedProjectCount}/${delivery.codeProjectCount} merged`
340
+ + (delivery.allMerged
341
+ ? ` · ${delivery.verifiedProjectCount}/${delivery.codeProjectCount} verified`
342
+ : "");
343
+ }
344
+ function taskRemoteDeliveryCandidate(task) {
345
+ const projects = task.projectBindings.flatMap(({ projectId, currentCommit }) => (currentCommit === undefined ? [] : [{ projectId, commit: currentCommit }]));
346
+ return projects.length === task.projectBindings.length
347
+ ? { projects }
348
+ : null;
349
+ }
329
350
  function compactText(value) {
330
351
  const oneLine = value.replace(/\s+/g, " ").trim();
331
352
  return oneLine.length <= 240 ? oneLine : `${oneLine.slice(0, 237)}...`;
@@ -343,6 +364,7 @@ function renderVerboseDetails(tasks, timeZone) {
343
364
  ? "missing"
344
365
  : formatTimestamp(task.summaryUpdatedAt, timeZone)}`,
345
366
  ` Execution: ${task.execution.status} (${task.execution.owner}); ${task.execution.summary}`,
367
+ ` Remote delivery: ${deliveryCell(task.remoteDelivery)}; source=${task.remoteDelivery.source}${task.remoteDelivery.provisional ? " (provisional)" : ""}`,
346
368
  ` Monitoring: ${task.execution.monitoring}; attention: ${task.execution.attention.length}`,
347
369
  ` DAG: ${task.execution.observability.dag.nodes.length} nodes, ${task.execution.observability.dag.edges.length} edges; ready=${task.execution.observability.dag.readyIds.join(", ") || "none"}; blocked=${task.execution.observability.dag.blockedIds.join(", ") || "none"}`,
348
370
  ` Cost: tokens=${task.execution.observability.cost.tokens}${task.execution.observability.cost.tokensObservable ? "" : " (partial)"}; tools=${task.execution.observability.cost.toolCalls}${task.execution.observability.cost.toolCallsObservable ? "" : " (partial)"}; wall=${task.execution.observability.cost.wallClockSeconds}s; retries=${task.execution.observability.cost.retryCount}`,