@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
@@ -0,0 +1,309 @@
1
+ import { governingWorkItemCandidate, currentWorkItemExecutionGroup } from "../workItem/workItem.js";
2
+ import { MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS } from "./workItemExecution.js";
3
+ export function projectWorkItemExecution(item, turns, sessionSets = []) {
4
+ const group = currentWorkItemExecutionGroup(item);
5
+ const relevantTurns = turns.filter((turn) => turn.workItemId === item.id);
6
+ const sessionsByRole = new Map(sessionSets.map((set) => [set.owner.roleName, set]));
7
+ const lanes = group === undefined
8
+ ? []
9
+ : [...group.lanes]
10
+ .sort((left, right) => left.ordinal - right.ordinal || left.id.localeCompare(right.id))
11
+ .map((lane) => projectLane(item, group, lane, relevantTurns, sessionsByRole));
12
+ const laneCounts = Object.freeze({
13
+ running: lanes.filter(({ status }) => status === "running").length,
14
+ succeeded: lanes.filter(({ status }) => status === "succeeded").length,
15
+ needsAttention: lanes.filter(({ status }) => status === "needs-attention").length,
16
+ failed: lanes.filter(({ status }) => status === "failed").length,
17
+ unknown: lanes.filter(({ status }) => status === "unknown").length
18
+ });
19
+ const mainTurn = projectMainTurn(item, group, relevantTurns, sessionsByRole);
20
+ const synthesis = projectSynthesis(group, lanes, mainTurn);
21
+ const candidate = projectCandidate(item, group, relevantTurns, lanes, mainTurn);
22
+ return Object.freeze({
23
+ schemaVersion: 1,
24
+ shape: group === undefined ? "direct" : "replicated",
25
+ ...(group === undefined ? {} : { groupId: group.id }),
26
+ lanes: Object.freeze(lanes),
27
+ laneCounts,
28
+ synthesis,
29
+ mainTurn,
30
+ candidate,
31
+ nextAction: projectNextAction(item, lanes, synthesis, mainTurn, candidate)
32
+ });
33
+ }
34
+ function projectLane(item, group, lane, turns, sessionsByRole) {
35
+ const current = lane.currentTurnId === undefined
36
+ ? undefined
37
+ : turns.find(({ id }) => id === lane.currentTurnId);
38
+ const exact = current !== undefined
39
+ && current.taskId === item.taskId
40
+ && current.workItemId === item.id
41
+ && current.executionGroupId === group.id
42
+ && current.executionLaneId === lane.id
43
+ && current.roleName === lane.roleName;
44
+ const session = exact
45
+ ? turnSessionStatus(current, sessionsByRole.get(lane.roleName))
46
+ : "unobserved";
47
+ if (lane.disposition === "failed") {
48
+ return Object.freeze({
49
+ laneId: lane.id,
50
+ ordinal: lane.ordinal,
51
+ roleName: lane.roleName,
52
+ status: "failed",
53
+ ...(lane.currentTurnId === undefined ? {} : { currentTurnId: lane.currentTurnId }),
54
+ session,
55
+ observation: exact ? "observed" : "unobserved"
56
+ });
57
+ }
58
+ if (lane.disposition === "succeeded") {
59
+ const producerObserved = exact
60
+ && lane.successfulTurnId === current.id
61
+ && current.status === "completed"
62
+ && current.result !== undefined;
63
+ return Object.freeze({
64
+ laneId: lane.id,
65
+ ordinal: lane.ordinal,
66
+ roleName: lane.roleName,
67
+ status: producerObserved ? "succeeded" : "unknown",
68
+ ...(lane.currentTurnId === undefined ? {} : { currentTurnId: lane.currentTurnId }),
69
+ ...(lane.successfulTurnId === undefined ? {} : { successfulTurnId: lane.successfulTurnId }),
70
+ session,
71
+ observation: producerObserved ? "observed" : "unobserved"
72
+ });
73
+ }
74
+ if (!exact) {
75
+ return Object.freeze({
76
+ laneId: lane.id,
77
+ ordinal: lane.ordinal,
78
+ roleName: lane.roleName,
79
+ status: "unknown",
80
+ ...(lane.currentTurnId === undefined ? {} : { currentTurnId: lane.currentTurnId }),
81
+ session: "unobserved",
82
+ observation: "unobserved"
83
+ });
84
+ }
85
+ if (current.status === "failed") {
86
+ return Object.freeze({
87
+ laneId: lane.id,
88
+ ordinal: lane.ordinal,
89
+ roleName: lane.roleName,
90
+ status: "needs-attention",
91
+ currentTurnId: current.id,
92
+ session,
93
+ retryTurnId: current.id,
94
+ settleTurnId: current.id,
95
+ observation: "observed"
96
+ });
97
+ }
98
+ if (current.status === "active") {
99
+ return Object.freeze({
100
+ laneId: lane.id,
101
+ ordinal: lane.ordinal,
102
+ roleName: lane.roleName,
103
+ status: session === "ended" ? "needs-attention" : "running",
104
+ currentTurnId: current.id,
105
+ session,
106
+ observation: "observed"
107
+ });
108
+ }
109
+ return Object.freeze({
110
+ laneId: lane.id,
111
+ ordinal: lane.ordinal,
112
+ roleName: lane.roleName,
113
+ status: "unknown",
114
+ currentTurnId: current.id,
115
+ session,
116
+ observation: "unobserved"
117
+ });
118
+ }
119
+ function projectMainTurn(item, group, turns, sessionsByRole) {
120
+ const candidates = turns.filter((turn) => (turn.purpose === "execution"
121
+ && turn.roleName === item.assignee
122
+ && turn.executionGroupId === undefined
123
+ && turn.executionLaneId === undefined
124
+ && turn.sourceExecutionGroupId === group?.id)).sort(compareTurns);
125
+ const turn = candidates.at(-1);
126
+ if (turn === undefined) {
127
+ return Object.freeze({
128
+ status: "not-started",
129
+ ...(item.assignee === undefined ? {} : { roleName: item.assignee }),
130
+ ...(group === undefined ? {} : { sourceExecutionGroupId: group.id }),
131
+ session: "unobserved",
132
+ observation: "unobserved"
133
+ });
134
+ }
135
+ const session = turnSessionStatus(turn, sessionsByRole.get(turn.roleName));
136
+ const base = {
137
+ roleName: turn.roleName,
138
+ turnId: turn.id,
139
+ ...(turn.sourceExecutionGroupId === undefined
140
+ ? {}
141
+ : { sourceExecutionGroupId: turn.sourceExecutionGroupId }),
142
+ session
143
+ };
144
+ if (turn.status === "failed") {
145
+ return Object.freeze({
146
+ ...base,
147
+ status: "needs-attention",
148
+ retryTurnId: turn.id,
149
+ observation: "observed"
150
+ });
151
+ }
152
+ if (turn.status === "active") {
153
+ return Object.freeze({
154
+ ...base,
155
+ status: session === "ended" ? "needs-attention" : "running",
156
+ observation: "observed"
157
+ });
158
+ }
159
+ return Object.freeze({
160
+ ...base,
161
+ status: turn.result === undefined ? "unknown" : "succeeded",
162
+ observation: turn.result === undefined ? "unobserved" : "observed"
163
+ });
164
+ }
165
+ function projectSynthesis(group, lanes, mainTurn) {
166
+ if (group === undefined) {
167
+ return Object.freeze({
168
+ status: "not-applicable",
169
+ successfulLaneCount: 0,
170
+ requiredSuccessfulLaneCount: MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS
171
+ });
172
+ }
173
+ const successfulLaneCount = group.lanes.filter(({ disposition }) => disposition === "succeeded").length;
174
+ let status;
175
+ if (group.lanes.some(({ disposition }) => disposition === "open")) {
176
+ status = "blocked-by-open-lanes";
177
+ }
178
+ else if (lanes.some((lane) => (lane.status === "unknown" && group.lanes.some(({ id, disposition }) => (id === lane.laneId && disposition === "succeeded"))))) {
179
+ status = "unknown";
180
+ }
181
+ else if (successfulLaneCount < MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS) {
182
+ status = "insufficient-results";
183
+ }
184
+ else if (mainTurn.status === "not-started") {
185
+ status = "eligible";
186
+ }
187
+ else if (mainTurn.status === "running") {
188
+ status = "main-running";
189
+ }
190
+ else if (mainTurn.status === "needs-attention") {
191
+ status = "main-needs-attention";
192
+ }
193
+ else if (mainTurn.status === "succeeded") {
194
+ status = "complete";
195
+ }
196
+ else {
197
+ status = "unknown";
198
+ }
199
+ return Object.freeze({
200
+ status,
201
+ successfulLaneCount,
202
+ requiredSuccessfulLaneCount: MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS
203
+ });
204
+ }
205
+ function projectCandidate(item, group, turns, lanes, mainTurn) {
206
+ const candidate = governingWorkItemCandidate(item);
207
+ if (candidate === undefined) {
208
+ return Object.freeze({
209
+ status: "none",
210
+ successfulLaneTurns: Object.freeze([]),
211
+ observation: "observed"
212
+ });
213
+ }
214
+ if (candidate.source.type === "direct") {
215
+ const valid = group === undefined && item.assignee === undefined;
216
+ return candidateProjection(candidate, valid ? "observed" : "unknown", [], valid);
217
+ }
218
+ const sourceTurnId = candidate.source.turnId;
219
+ const sourceTurn = turns.find(({ id }) => id === sourceTurnId);
220
+ const laneTurns = group === undefined
221
+ ? []
222
+ : [...group.lanes]
223
+ .filter(({ disposition, successfulTurnId }) => (disposition === "succeeded" && successfulTurnId !== undefined))
224
+ .sort((left, right) => left.ordinal - right.ordinal || left.id.localeCompare(right.id))
225
+ .map((lane) => ({ laneId: lane.id, successfulTurnId: lane.successfulTurnId }));
226
+ const valid = sourceTurn !== undefined
227
+ && sourceTurn.id === mainTurn.turnId
228
+ && sourceTurn.status === "completed"
229
+ && sourceTurn.result !== undefined
230
+ && sourceTurn.executionGroupId === undefined
231
+ && sourceTurn.executionLaneId === undefined
232
+ && sourceTurn.sourceExecutionGroupId === group?.id
233
+ && candidate.executionLaneId === undefined
234
+ && candidate.executionGroupId === undefined
235
+ && (group === undefined || (laneTurns.length >= MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS
236
+ && lanes.filter(({ successfulTurnId }) => successfulTurnId !== undefined)
237
+ .every(({ status }) => status === "succeeded")));
238
+ return candidateProjection(candidate, valid ? "observed" : "unknown", laneTurns, valid, group?.id);
239
+ }
240
+ function candidateProjection(candidate, status, successfulLaneTurns, observed, sourceExecutionGroupId) {
241
+ return Object.freeze({
242
+ status,
243
+ candidateId: candidate.id,
244
+ sourceType: candidate.source.type,
245
+ ...(candidate.source.type === "turn" ? { mainTurnId: candidate.source.turnId } : {}),
246
+ ...(sourceExecutionGroupId === undefined ? {} : { sourceExecutionGroupId }),
247
+ successfulLaneTurns: Object.freeze(successfulLaneTurns),
248
+ observation: observed ? "observed" : "unobserved"
249
+ });
250
+ }
251
+ function projectNextAction(item, lanes, synthesis, mainTurn, candidate) {
252
+ if (["completed", "retired"].includes(item.status))
253
+ return action("none", [], []);
254
+ if (item.status === "awaiting_acceptance") {
255
+ return candidate.status === "observed"
256
+ ? action("decide-candidate", ["leader"], [candidate.candidateId])
257
+ : action("inspect-unknown", ["leader"], candidate.candidateId === undefined ? [] : [candidate.candidateId]);
258
+ }
259
+ if (lanes.some(({ status }) => status === "unknown")) {
260
+ return action("inspect-unknown", ["leader"], lanes
261
+ .filter(({ status }) => status === "unknown")
262
+ .map(({ laneId }) => laneId));
263
+ }
264
+ const recoveries = lanes.filter(({ retryTurnId }) => retryTurnId !== undefined);
265
+ if (recoveries.length > 0) {
266
+ return action("retry-or-settle-lanes", ["leader"], recoveries.map(({ retryTurnId }) => retryTurnId));
267
+ }
268
+ const activeLanes = lanes.filter(({ status }) => status === "running");
269
+ if (activeLanes.length > 0) {
270
+ return action("wait-for-lanes", activeLanes.map(({ roleName }) => roleName), activeLanes.map(({ laneId }) => laneId));
271
+ }
272
+ if (synthesis.status === "insufficient-results") {
273
+ return action("redispatch-work", ["leader"], [item.id]);
274
+ }
275
+ if (synthesis.status === "eligible") {
276
+ return action("await-main-dispatch", ["controller", ...(item.assignee === undefined ? [] : [item.assignee])], [item.id]);
277
+ }
278
+ if (mainTurn.status === "running") {
279
+ return action("wait-for-main", mainTurn.roleName === undefined ? [] : [mainTurn.roleName], mainTurn.turnId === undefined ? [] : [mainTurn.turnId]);
280
+ }
281
+ if (mainTurn.status === "needs-attention") {
282
+ return mainTurn.retryTurnId === undefined
283
+ ? action("inspect-unknown", ["leader"], mainTurn.turnId === undefined ? [] : [mainTurn.turnId])
284
+ : action("retry-main", ["leader"], [mainTurn.retryTurnId]);
285
+ }
286
+ if (mainTurn.status === "succeeded" && item.status === "running") {
287
+ return action("submit-candidate", ["leader"], [mainTurn.turnId]);
288
+ }
289
+ if (mainTurn.status === "unknown" || synthesis.status === "unknown") {
290
+ return action("inspect-unknown", ["leader"], mainTurn.turnId === undefined ? [item.id] : [mainTurn.turnId]);
291
+ }
292
+ if (item.status === "pending" || item.status === "failed") {
293
+ return action("dispatch-work", ["leader"], [item.id]);
294
+ }
295
+ return action("inspect-unknown", ["leader"], [item.id]);
296
+ }
297
+ function action(kind, owners, targetIds) {
298
+ return Object.freeze({
299
+ kind,
300
+ owners: Object.freeze([...new Set(owners)]),
301
+ targetIds: Object.freeze([...new Set(targetIds)])
302
+ });
303
+ }
304
+ function turnSessionStatus(turn, set) {
305
+ return set?.sessions[turn.effective.agentId]?.status ?? "unobserved";
306
+ }
307
+ function compareTurns(left, right) {
308
+ return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
309
+ }
@@ -0,0 +1,164 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { enqueueRoleTurnDispatch } from "../coordination/workMailboxQueue.js";
3
+ import { createTurnInput } from "../context/turnInputContract.js";
4
+ import { contextSnapshotDeltaRefIds, freezeTurnContextSnapshot } from "../context/turnContextPack.js";
5
+ import { contextSnapshotRef } from "../context/contextSnapshot.js";
6
+ import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
7
+ import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
8
+ import { createTaskEvent } from "../event/taskEvent.js";
9
+ import { createTurn } from "../turn/turn.js";
10
+ import { currentWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
11
+ import { MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS, workItemExecutionGroupSettled } from "./workItemExecution.js";
12
+ export function successfulWorkItemSynthesisProducers(store, item, group) {
13
+ if (!workItemExecutionGroupSettled(group)) {
14
+ throw new Error(`WorkItem ExecutionGroup is not settled: ${item.id}/${group.id}.`);
15
+ }
16
+ return [...group.lanes]
17
+ .sort((left, right) => left.ordinal - right.ordinal || left.id.localeCompare(right.id))
18
+ .flatMap((lane) => {
19
+ if (lane.disposition !== "succeeded")
20
+ return [];
21
+ const turn = lane.successfulTurnId === undefined
22
+ ? null
23
+ : store.getTurn(item.taskId, lane.successfulTurnId);
24
+ if (turn === null
25
+ || turn.status !== "completed"
26
+ || turn.workItemId !== item.id
27
+ || turn.executionGroupId !== group.id
28
+ || turn.executionLaneId !== lane.id
29
+ || turn.result === undefined) {
30
+ throw new Error(`Successful ExecutionLane has no exact Producer Turn result: ${group.id}/${lane.id}.`);
31
+ }
32
+ return [{
33
+ laneId: lane.id,
34
+ roleName: lane.roleName,
35
+ turnId: turn.id
36
+ }];
37
+ });
38
+ }
39
+ /**
40
+ * Reconcile every settled replicated WorkItem in one Task. The Turn row is the
41
+ * durable caller/idempotency record: at most one initial main Turn may name a
42
+ * source Group, while explicit retries append more Turns with the same source.
43
+ */
44
+ export function reconcileWorkItemMainTurns(store, taskId, now) {
45
+ const task = store.getTask(taskId);
46
+ if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
47
+ return { createdTurns: [], failedWorkItemIds: [] };
48
+ }
49
+ const createdTurns = [];
50
+ const failedWorkItemIds = [];
51
+ const items = [...store.listWorkItems(taskId)].sort((left, right) => (left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)));
52
+ for (const item of items) {
53
+ if (item.status !== "running")
54
+ continue;
55
+ const group = currentWorkItemExecutionGroup(item);
56
+ if (group === undefined || !workItemExecutionGroupSettled(group))
57
+ continue;
58
+ const producers = successfulWorkItemSynthesisProducers(store, item, group);
59
+ if (producers.length < MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS) {
60
+ const summary = `ExecutionGroup ${group.id} settled with ${producers.length} successful `
61
+ + `Producer result${producers.length === 1 ? "" : "s"}; at least `
62
+ + `${MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS} are required.`;
63
+ store.saveWorkItem(taskId, updateWorkItemStatus(item, "failed", now, summary));
64
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "work.execution-group-failed", {
65
+ workItemId: item.id,
66
+ executionGroupId: group.id,
67
+ successfulProducerCount: String(producers.length),
68
+ requiredProducerCount: String(MINIMUM_WORK_ITEM_SYNTHESIS_RESULTS)
69
+ }, now));
70
+ failedWorkItemIds.push(item.id);
71
+ continue;
72
+ }
73
+ const existing = store.listTurns(taskId).some((turn) => (turn.purpose === "execution"
74
+ && turn.workItemId === item.id
75
+ && turn.sourceExecutionGroupId === group.id));
76
+ if (existing)
77
+ continue;
78
+ if (item.assignee === undefined) {
79
+ throw new Error(`Replicated WorkItem has no main assignee: ${item.id}.`);
80
+ }
81
+ const role = store.getRole(taskId, item.assignee);
82
+ if (role === null)
83
+ throw new Error(`WorkItem main Role is missing: ${taskId}/${item.assignee}.`);
84
+ if (store.getActiveTurn(taskId, role.name) !== null)
85
+ continue;
86
+ const workspace = role.name === "leader"
87
+ ? store.getTaskWorkspace(taskId)
88
+ : store.getWorkItemWorkspace(taskId, item.id);
89
+ if (workspace === null) {
90
+ throw new Error(`WorkItem main workspace is missing: ${taskId}/${item.id}.`);
91
+ }
92
+ const visibleProjectIds = workspace.entries.map(({ projectId }) => projectId).sort();
93
+ const taskProjectIds = task.projectBindings.map(({ projectId }) => projectId).sort();
94
+ const writableProjectIds = workspace.entries
95
+ .filter(({ access }) => access === "write")
96
+ .map(({ projectId }) => projectId)
97
+ .sort();
98
+ if (!isDeepStrictEqual(visibleProjectIds, taskProjectIds)
99
+ || !isDeepStrictEqual(writableProjectIds, [...item.writeProjectIds].sort())) {
100
+ throw new Error(`WorkItem main workspace does not match its approved scope: ${item.id}.`);
101
+ }
102
+ const effective = resolveEffectiveLaunch({
103
+ role,
104
+ purpose: "execution",
105
+ workspace,
106
+ workItemWriteProjectIds: item.writeProjectIds
107
+ });
108
+ const snapshot = freezeTurnContextSnapshot(store, {
109
+ taskId,
110
+ roleName: role.name,
111
+ purpose: "execution",
112
+ workItemId: item.id,
113
+ sourceExecutionGroupId: group.id,
114
+ workspace
115
+ }, now, "controller", group.assignment.contextSnapshotRef);
116
+ const turn = createTurn(store.nextTurnId(taskId), taskId, role.name, roleAgentSessionResumeMode(store.getTaskRoleSessionSet(taskId, role.name), effective.agentId, effective), createTurnInput({
117
+ source: { type: "yui", channel: "workitem-dispatch" },
118
+ directive: synthesisDirective(group, producers),
119
+ contextSnapshotRef: contextSnapshotRef(snapshot),
120
+ deltaRefIds: contextSnapshotDeltaRefIds(store, snapshot)
121
+ }), now, {
122
+ workItemId: item.id,
123
+ sourceExecutionGroupId: group.id,
124
+ workspace,
125
+ effective
126
+ });
127
+ store.saveTurn(turn);
128
+ store.saveActiveTurn(turn);
129
+ enqueueRoleTurnDispatch(store, {
130
+ taskId,
131
+ roleName: role.name,
132
+ turnId: turn.id,
133
+ reason: "workitem-synthesis-ready",
134
+ occurredAt: now
135
+ });
136
+ store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "turn.dispatched", {
137
+ turnId: turn.id,
138
+ role: turn.roleName,
139
+ purpose: turn.purpose,
140
+ mode: turn.mode,
141
+ agent: `${turn.effective.agentId}/${turn.effective.adapterId}`,
142
+ effectiveRevision: String(turn.effective.sourceDesiredRevision),
143
+ profileAccess: turn.effective.profileAccess,
144
+ effectivePermission: turn.effective.permission.strategy,
145
+ writeProjectIds: turn.effective.writeProjectIds.join(",") || "none",
146
+ workItemId: item.id,
147
+ sourceExecutionGroupId: group.id
148
+ }, now));
149
+ createdTurns.push(turn);
150
+ }
151
+ return { createdTurns, failedWorkItemIds };
152
+ }
153
+ function synthesisDirective(group, producers) {
154
+ return [
155
+ "Synthesize every frozen successful Producer result in stable Lane order.",
156
+ "Expand each exact source Turn from the frozen Context Snapshot and consume its original result text plus Core-authored system evidence.",
157
+ "Do not rerun, retry, append, or abandon any Lane. Form the final WorkItem result from these records.",
158
+ JSON.stringify({
159
+ schemaVersion: 1,
160
+ sourceExecutionGroupId: group.id,
161
+ producers
162
+ }, null, 2)
163
+ ].join("\n\n");
164
+ }
@@ -11,10 +11,10 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
11
11
  updatedAt: now.toISOString()
12
12
  };
13
13
  return owner.scope === "global"
14
- ? { ...base, schemaVersion: 4 }
14
+ ? { ...base, schemaVersion: 5 }
15
15
  : {
16
16
  ...base,
17
- schemaVersion: 11,
17
+ schemaVersion: 12,
18
18
  providerBinding: null
19
19
  };
20
20
  }
@@ -46,7 +46,7 @@ export function recordRoleAgentSession(set, input, now) {
46
46
  const nativeSessionId = requireText(input.nativeSessionId, "Native session id");
47
47
  if (set.owner.scope === "task") {
48
48
  const historical = (set.history ?? []).find((entry) => (entry.nativeSessionId === nativeSessionId
49
- || (input.launchId !== undefined && entry.launchId === input.launchId)));
49
+ || (input.runtimeGenerationId !== undefined && entry.runtimeGenerationId === input.runtimeGenerationId)));
50
50
  if (historical !== undefined) {
51
51
  throw new Error("A new Task Role Session cannot reuse a historical native identity.");
52
52
  }
@@ -78,13 +78,13 @@ export function recordRoleAgentSession(set, input, now) {
78
78
  const timestamp = now.toISOString();
79
79
  const continuing = existing?.nativeSessionId === nativeSessionId ? existing : undefined;
80
80
  const session = {
81
- schemaVersion: 4,
81
+ schemaVersion: 5,
82
82
  agentId,
83
83
  adapterId,
84
84
  nativeSessionId,
85
- ...(input.launchId === undefined
86
- ? continuing?.launchId === undefined ? {} : { launchId: continuing.launchId }
87
- : { launchId: requireText(input.launchId, "Launch id") }),
85
+ ...(input.runtimeGenerationId === undefined
86
+ ? continuing?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: continuing.runtimeGenerationId }
87
+ : { runtimeGenerationId: requireText(input.runtimeGenerationId, "Runtime generation id") }),
88
88
  ...optionalSessionText("title", input.title ?? continuing?.title),
89
89
  ...optionalSessionText("preview", input.preview ?? continuing?.preview),
90
90
  policy: input.policy,
@@ -207,7 +207,7 @@ export function retireConfirmedAbsentInactiveTaskRolePlaceholders(set, now) {
207
207
  const isNeverStartedInactiveClaude = agentId !== set.activeAgentId
208
208
  && session.adapterId === "claude"
209
209
  && session.status === "active"
210
- && session.launchId === undefined
210
+ && session.runtimeGenerationId === undefined
211
211
  && session.recentCompletedTurnIds.length === 0;
212
212
  if (!isNeverStartedInactiveClaude)
213
213
  return [agentId, session];
@@ -327,7 +327,7 @@ export function detachRoleAgentSessionHost(set, now) {
327
327
  if (active === undefined || active.status === "ended")
328
328
  return set;
329
329
  const timestamp = requireDate(now, "Role Host detach timestamp");
330
- const { launchId: _launchId, endReason: _endReason, ...session } = active;
330
+ const { runtimeGenerationId: _runtimeGenerationId, endReason: _endReason, ...session } = active;
331
331
  let updated = validateRoleSessionSet({
332
332
  ...set,
333
333
  sessions: {
@@ -431,7 +431,7 @@ export function validateRoleSessionSet(set) {
431
431
  validateRoleAgentSession(session, agentId);
432
432
  }
433
433
  if (set.owner.scope === "global") {
434
- if (set.schemaVersion !== 4) {
434
+ if (set.schemaVersion !== 5) {
435
435
  throw new Error("Global Role session set schema version is invalid.");
436
436
  }
437
437
  if (Object.hasOwn(set, "providerBinding")) {
@@ -449,7 +449,7 @@ export function validateRoleSessionSet(set) {
449
449
  }
450
450
  }
451
451
  else {
452
- if (set.schemaVersion !== 11) {
452
+ if (set.schemaVersion !== 12) {
453
453
  throw new Error("Task Role session set schema version is invalid.");
454
454
  }
455
455
  if (!Object.hasOwn(set, "providerBinding")) {
@@ -495,7 +495,7 @@ export function validateRoleSessionSet(set) {
495
495
  return set;
496
496
  }
497
497
  export function validateRoleAgentSession(session, expectedAgentId = session.agentId) {
498
- if (session.schemaVersion !== 4) {
498
+ if (session.schemaVersion !== 5) {
499
499
  throw new Error(`Role Agent session schema version is invalid: ${expectedAgentId}.`);
500
500
  }
501
501
  const agentId = requireSafeIdentity(session.agentId, "Agent id");
@@ -510,15 +510,15 @@ export function validateRoleAgentSession(session, expectedAgentId = session.agen
510
510
  // A restored opaque host may have no provider-native identity; retain its
511
511
  // launch fence so it can be inspected or stopped without inventing identity.
512
512
  if (session.nativeSessionId === undefined) {
513
- if (session.launchId === undefined) {
514
- throw new Error("Role Agent session requires a native Session or launch id.");
513
+ if (session.runtimeGenerationId === undefined) {
514
+ throw new Error("Role Agent session requires a native Session or runtime generation id.");
515
515
  }
516
516
  }
517
517
  else {
518
518
  requireText(session.nativeSessionId, "Native session id");
519
519
  }
520
- if (session.launchId !== undefined)
521
- requireSafeIdentity(session.launchId, "Launch id");
520
+ if (session.runtimeGenerationId !== undefined)
521
+ requireSafeIdentity(session.runtimeGenerationId, "Runtime generation id");
522
522
  if (session.title !== undefined
523
523
  && optionalSessionText("title", session.title).title !== session.title) {
524
524
  throw new Error("Role Agent session title is invalid.");
@@ -550,10 +550,10 @@ function taskRoleSessionIdentity(session) {
550
550
  if (session.nativeSessionId !== undefined) {
551
551
  return `${session.agentId}\0native\0${session.nativeSessionId}`;
552
552
  }
553
- if (session.launchId === undefined) {
554
- throw new Error("Opaque Task Role session requires a launch identity.");
553
+ if (session.runtimeGenerationId === undefined) {
554
+ throw new Error("Opaque Task Role session requires a runtime generation identity.");
555
555
  }
556
- return `${session.agentId}\0launch\0${session.launchId}`;
556
+ return `${session.agentId}\0runtime-generation\0${session.runtimeGenerationId}`;
557
557
  }
558
558
  function optionalSessionText(field, value) {
559
559
  if (value === undefined)
@@ -92,12 +92,12 @@ export class ExecutorRegistry {
92
92
  const request = input.mode === "new"
93
93
  ? createSessionLaunchRequest({
94
94
  ...common,
95
- launchId: deliveryBase.deliveryId,
95
+ runtimeGenerationId: deliveryBase.deliveryId,
96
96
  mode: "new"
97
97
  })
98
98
  : createSessionLaunchRequest({
99
99
  ...common,
100
- launchId: deliveryBase.deliveryId,
100
+ runtimeGenerationId: deliveryBase.deliveryId,
101
101
  mode: "resume",
102
102
  nativeSessionId: input.nativeSessionId
103
103
  });
@@ -118,7 +118,7 @@ export class ExecutorRegistry {
118
118
  }
119
119
  const delivery = {
120
120
  ...deliveryBase,
121
- ...(binding === undefined ? {} : { launchId: binding.launchId }),
121
+ ...(binding === undefined ? {} : { runtimeGenerationId: binding.runtimeGenerationId }),
122
122
  sessionStarted,
123
123
  session,
124
124
  };
@@ -145,27 +145,6 @@ export class ExecutorRegistry {
145
145
  if (turnId === undefined) {
146
146
  throw new Error("Runtime prompt delivery requires a Task-local Turn id.");
147
147
  }
148
- // A reused native process retains the stable descriptor path from its
149
- // original control plane. Publish only the current-control source after
150
- // the Turn/Session fence is durable and immediately before Provider input;
151
- // the reused Hook self-refreshes its own source before the volatile
152
- // fence instead of the Controller scanning history to keep it fresh.
153
- if (prepared.binding.hostCreated === false
154
- && this.planner.refreshTaskRuntimeDescriptor !== undefined) {
155
- if (!hasText(prepared.binding.nativeSessionId)) {
156
- throw new Error("Runtime prompt delivery requires a native Session id.");
157
- }
158
- this.planner.refreshTaskRuntimeDescriptor({
159
- taskId: input.delivery.prepared.taskId,
160
- roleName: input.delivery.prepared.roleName,
161
- turnId,
162
- launchId: prepared.binding.launchId,
163
- nativeSessionId: prepared.binding.nativeSessionId,
164
- agentId: prepared.binding.agentId,
165
- adapterId: prepared.binding.adapterId,
166
- workspace: prepared.workspace
167
- });
168
- }
169
148
  const outcome = await this.runtimePorts.promptPush.tryPush({
170
149
  binding: prepared.binding,
171
150
  envelope: createPromptEnvelope({
@@ -195,7 +174,7 @@ export class ExecutorRegistry {
195
174
  return "unavailable";
196
175
  const outcome = await this.runtimePorts.promptPush.trySteer({
197
176
  owner: { scope: "task", taskId: input.taskId, roleName: input.roleName },
198
- launchId: input.launchId,
177
+ runtimeGenerationId: input.runtimeGenerationId,
199
178
  agentId: input.agentId,
200
179
  adapterId: input.adapterId,
201
180
  nativeSessionId: input.nativeSessionId,
@@ -223,8 +202,8 @@ export class ExecutorRegistry {
223
202
  || delivery.roleName !== input.roleName
224
203
  || (input.turnId !== undefined
225
204
  && delivery.turnId !== input.turnId)
226
- || (input.launchId !== undefined
227
- && delivery.launchId !== input.launchId)) {
205
+ || (input.runtimeGenerationId !== undefined
206
+ && delivery.runtimeGenerationId !== input.runtimeGenerationId)) {
228
207
  continue;
229
208
  }
230
209
  this.#prepared.delete(deliveryId);
@@ -287,7 +266,7 @@ export class ExecutorRegistry {
287
266
  ...(input.nativeSessionId === undefined
288
267
  ? {}
289
268
  : { nativeSessionId: input.nativeSessionId }),
290
- ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
269
+ ...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
291
270
  ...(input.progressAt === undefined ? {} : { progressAt: input.progressAt })
292
271
  }));
293
272
  if (this.runtimePorts?.roleResourceInventory !== undefined