@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
@@ -1,426 +0,0 @@
1
- import { isDeepStrictEqual } from "node:util";
2
- import { requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
3
- import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
4
- import { validateExecutionGroup } from "./executionGroup.js";
5
- export const RESOURCE_BROKER_POLICY_SCHEMA_VERSION = 1;
6
- /** Compatibility/display default; observed tokens never close execution spend. */
7
- export const DEFAULT_EXECUTION_STAGE_TOKEN_BUDGET_PER_LANE = 120_000;
8
- /** One shared hard-stop predicate for admission, recovery, and Leader routing. */
9
- export function executionStageSpendClosed(resources) {
10
- return resources.deadlineReached
11
- || resources.exhaustedBudgets.includes("tool-calls")
12
- || resources.exhaustedBudgets.includes("wall-clock");
13
- }
14
- const DEFAULT_ACTIVE_LANES = 4;
15
- const DEFAULT_ACTIVE_LANES_PER_TASK = 2;
16
- const DEFAULT_ACTIVE_LANES_PER_WORK_ITEM = 2;
17
- const DEFAULT_ACTIVE_LANES_PER_GROUP = 2;
18
- const DEFAULT_ACTIVE_LANES_PER_PROVIDER = 4;
19
- const DEFAULT_ACTIVE_LANES_PER_AGENT = 2;
20
- const DEFAULT_ACTIVE_LANES_PER_MODEL = 2;
21
- const DEFAULT_QUEUED_LANES_PER_GROUP = 4;
22
- export function resolveResourceBrokerPolicy(configured) {
23
- const value = configured ?? {};
24
- return Object.freeze({
25
- schemaVersion: RESOURCE_BROKER_POLICY_SCHEMA_VERSION,
26
- maxActiveLanes: positive(value.maxActiveLanes, DEFAULT_ACTIVE_LANES, "maxActiveLanes"),
27
- maxActiveLanesPerTask: positive(value.maxActiveLanesPerTask, DEFAULT_ACTIVE_LANES_PER_TASK, "maxActiveLanesPerTask"),
28
- maxActiveLanesPerWorkItem: positive(value.maxActiveLanesPerWorkItem, DEFAULT_ACTIVE_LANES_PER_WORK_ITEM, "maxActiveLanesPerWorkItem"),
29
- maxActiveLanesPerGroup: positive(value.maxActiveLanesPerGroup, DEFAULT_ACTIVE_LANES_PER_GROUP, "maxActiveLanesPerGroup"),
30
- maxActiveLanesPerProvider: positive(value.maxActiveLanesPerProvider, DEFAULT_ACTIVE_LANES_PER_PROVIDER, "maxActiveLanesPerProvider"),
31
- maxActiveLanesPerAgent: positive(value.maxActiveLanesPerAgent, DEFAULT_ACTIVE_LANES_PER_AGENT, "maxActiveLanesPerAgent"),
32
- maxActiveLanesPerModel: positive(value.maxActiveLanesPerModel, DEFAULT_ACTIVE_LANES_PER_MODEL, "maxActiveLanesPerModel"),
33
- maxQueuedLanesPerGroup: positive(value.maxQueuedLanesPerGroup, DEFAULT_QUEUED_LANES_PER_GROUP, "maxQueuedLanesPerGroup")
34
- });
35
- }
36
- /**
37
- * Deterministic admission over the caller's stable request order. Capacity
38
- * pressure queues a Lane; it never terminalizes that Lane or its siblings.
39
- */
40
- export function planResourceAdmissions(input) {
41
- const policy = resolveResourceBrokerPolicy(input.policy);
42
- const active = input.active.map(validateLaneIdentity);
43
- const queued = input.queued.map(validateLaneIdentity);
44
- const decisions = [];
45
- for (const raw of input.requests) {
46
- const request = validateLaneIdentity(raw);
47
- const directLimits = activeLimits(policy, active, request);
48
- const activeLaneKeys = new Set(active.map(resourceLaneKey));
49
- const queuedBefore = [
50
- ...queued,
51
- ...decisions.filter(({ decision }) => decision === "queued").map(({ request }) => request)
52
- ].filter((candidate) => !activeLaneKeys.has(resourceLaneKey(candidate)));
53
- const fairReservations = directLimits.length === 0
54
- ? reservableOlderQueuedLanes(policy, active, queuedBefore, request)
55
- : [];
56
- const fairLimits = activeLimits(policy, [...active, ...fairReservations], request);
57
- const limitedBy = fairLimits.length === 0
58
- ? directLimits
59
- : directLimits.length === 0 && fairReservations.length > 0
60
- ? [...fairLimits, "fair-queue"]
61
- : fairLimits;
62
- if (limitedBy.length === 0) {
63
- active.push(request);
64
- decisions.push(Object.freeze({
65
- request,
66
- decision: "admitted",
67
- limitedBy: [],
68
- reason: "capacity is available at every resource scope"
69
- }));
70
- continue;
71
- }
72
- const groupQueued = queuedBefore.filter(({ taskId, executionGroupId }) => (taskId === request.taskId && executionGroupId === request.executionGroupId)).length;
73
- if (groupQueued >= policy.maxQueuedLanesPerGroup) {
74
- decisions.push(Object.freeze({
75
- request,
76
- decision: "blocked",
77
- limitedBy: [...limitedBy, "group-queue"],
78
- reason: `resource capacity is unavailable and the Group queue is full (${groupQueued}/${policy.maxQueuedLanesPerGroup})`
79
- }));
80
- continue;
81
- }
82
- decisions.push(Object.freeze({
83
- request,
84
- decision: "queued",
85
- limitedBy,
86
- reason: `resource backpressure: ${limitedBy.join(", ")}`
87
- }));
88
- }
89
- return decisions;
90
- }
91
- /**
92
- * Reserve only older queued Lanes that could start now. This gives released
93
- * capacity to the oldest compatible waiter without letting a Provider- or
94
- * Agent-blocked head prevent independent work from using other scopes.
95
- */
96
- function reservableOlderQueuedLanes(policy, active, queued, request) {
97
- const projected = [...active];
98
- const reserved = [];
99
- const seen = new Set();
100
- for (const candidate of [...queued].sort(compareResourceQueueOrder)) {
101
- const key = resourceLaneKey(candidate);
102
- if (seen.has(key))
103
- continue;
104
- seen.add(key);
105
- if (sameResourceLane(candidate, request)
106
- || compareResourceQueueOrder(candidate, request) >= 0
107
- || activeLimits(policy, projected, candidate).length > 0)
108
- continue;
109
- projected.push(candidate);
110
- reserved.push(candidate);
111
- }
112
- return reserved;
113
- }
114
- function compareResourceQueueOrder(left, right) {
115
- return left.requestedAt.localeCompare(right.requestedAt)
116
- || left.taskId.localeCompare(right.taskId, undefined, { numeric: true })
117
- || left.executionGroupId.localeCompare(right.executionGroupId, undefined, { numeric: true })
118
- || left.executionLaneId.localeCompare(right.executionLaneId, undefined, { numeric: true });
119
- }
120
- function sameResourceLane(left, right) {
121
- return resourceLaneKey(left) === resourceLaneKey(right);
122
- }
123
- function resourceLaneKey(value) {
124
- return `${value.taskId}\0${value.executionGroupId}\0${value.executionLaneId}`;
125
- }
126
- /** Current stage spend/completion projection used by CLI, Web, and routing. */
127
- export function projectExecutionStageResources(input) {
128
- const group = validateExecutionGroup(input.group);
129
- const stageGroups = executionStageRetryLineage(group, input.stageGroups);
130
- const stage = group.stage;
131
- const resources = stage?.resources;
132
- const tokens = nonNegative(input.usage.tokens, "Execution token usage");
133
- const toolCalls = nonNegative(input.usage.toolCalls, "Execution tool-call usage");
134
- const nowMs = input.now.getTime();
135
- if (!Number.isFinite(nowMs))
136
- throw new Error("Execution resource projection time is invalid.");
137
- const stageStartedAt = stageGroups
138
- .map(({ createdAt }) => createdAt)
139
- .sort()[0] ?? group.createdAt;
140
- const wallClockSeconds = Math.max(0, Math.floor((nowMs - Date.parse(stageStartedAt)) / 1_000));
141
- const usable = group.lanes.filter(({ status }) => isUsable(status))
142
- .sort((left, right) => (left.endedAt ?? left.updatedAt).localeCompare(right.endedAt ?? right.updatedAt));
143
- const activeLaneIds = group.lanes.filter(({ status }) => status === "running").map(({ id }) => id);
144
- const pendingLaneIds = group.lanes.filter(({ status }) => status === "pending").map(({ id }) => id);
145
- const skippedLaneIds = group.lanes.filter(({ status }) => status === "skipped").map(({ id }) => id);
146
- const quorum = resources?.quorum ?? group.lanes.length;
147
- const quorumMet = usable.length >= quorum;
148
- const quorumReachedAt = quorumMet
149
- ? (usable[quorum - 1].endedAt ?? usable[quorum - 1].updatedAt)
150
- : undefined;
151
- const tokensObservable = input.usage.tokensObservable ?? true;
152
- const toolCallsObservable = input.usage.toolCallsObservable ?? true;
153
- const exhaustedBudgets = [];
154
- if (toolCallsObservable && stage?.budget.maxToolCalls !== undefined
155
- && toolCalls >= stage.budget.maxToolCalls)
156
- exhaustedBudgets.push("tool-calls");
157
- if (stage?.budget.maxWallClockSeconds !== undefined
158
- && wallClockSeconds >= stage.budget.maxWallClockSeconds)
159
- exhaustedBudgets.push("wall-clock");
160
- const deadlineReached = resources === undefined
161
- ? false
162
- : nowMs >= Date.parse(resources.deadlineAt);
163
- const stragglerLaneIds = resources === undefined || quorumReachedAt === undefined
164
- ? []
165
- : nowMs - Date.parse(quorumReachedAt) < resources.stragglerAfterSeconds * 1_000
166
- ? []
167
- : [...activeLaneIds];
168
- return Object.freeze({
169
- tokens,
170
- toolCalls,
171
- wallClockSeconds,
172
- ...(stage?.budget.maxTokens === undefined
173
- ? {}
174
- : { tokensRemaining: Math.max(0, stage.budget.maxTokens - tokens) }),
175
- ...(stage?.budget.maxToolCalls === undefined
176
- ? {}
177
- : { toolCallsRemaining: Math.max(0, stage.budget.maxToolCalls - toolCalls) }),
178
- ...(stage?.budget.maxWallClockSeconds === undefined
179
- ? {}
180
- : {
181
- wallClockSecondsRemaining: Math.max(0, stage.budget.maxWallClockSeconds - wallClockSeconds)
182
- }),
183
- tokensObservable,
184
- toolCallsObservable,
185
- usableLaneCount: usable.length,
186
- activeLaneIds,
187
- pendingLaneIds,
188
- skippedLaneIds,
189
- quorumMet,
190
- ...(quorumReachedAt === undefined ? {} : { quorumReachedAt }),
191
- deadlineReached,
192
- exhaustedBudgets,
193
- stragglerLaneIds
194
- });
195
- }
196
- /**
197
- * Parallel-vs-sequential decision support. Evidence sufficiency is an explicit
198
- * semantic input: budget pressure can block or defer work, but can never turn
199
- * insufficient evidence into a successful early stop.
200
- */
201
- export function routeExecutionStage(input) {
202
- const group = validateExecutionGroup(input.group);
203
- const stage = group.stage;
204
- const policy = stage?.resources;
205
- const marginal = input.marginalValuePercent === undefined
206
- ? undefined
207
- : percentage(input.marginalValuePercent, "Execution marginal value");
208
- const budgetPressure = executionStageSpendClosed(input.resources);
209
- const marginalLow = marginal !== undefined
210
- && policy !== undefined
211
- && marginal < policy.minimumMarginalValuePercent;
212
- const earlyTerminationAllowed = input.evidenceSufficient && input.resources.quorumMet;
213
- const stopSpending = earlyTerminationAllowed && (budgetPressure || marginalLow);
214
- const cancelPendingLaneIds = stopSpending ? input.resources.pendingLaneIds : [];
215
- const retainActiveLaneIds = stopSpending ? input.resources.activeLaneIds : [];
216
- if (earlyTerminationAllowed) {
217
- if (input.resources.activeLaneIds.length > 0) {
218
- return Object.freeze({
219
- action: "wait",
220
- earlyTerminationAllowed,
221
- cancelPendingLaneIds,
222
- retainActiveLaneIds,
223
- reason: stopSpending
224
- ? "quorum and sufficient evidence allow pending work to stop, but active stragglers are retained"
225
- : "evidence is sufficient, but active Lanes remain inside the stage policy"
226
- });
227
- }
228
- if (!stopSpending && input.resources.pendingLaneIds.length > 0) {
229
- return Object.freeze({
230
- action: "wait",
231
- earlyTerminationAllowed,
232
- cancelPendingLaneIds: [],
233
- retainActiveLaneIds: [],
234
- reason: "evidence is sufficient, but pending Lanes remain inside the configured marginal-value policy"
235
- });
236
- }
237
- return Object.freeze({
238
- action: "resolve",
239
- earlyTerminationAllowed,
240
- cancelPendingLaneIds,
241
- retainActiveLaneIds,
242
- reason: stopSpending
243
- ? "quorum and sufficient evidence make further pending spend uneconomic"
244
- : "quorum and sufficient evidence are complete"
245
- });
246
- }
247
- if (input.resources.activeLaneIds.length > 0) {
248
- return Object.freeze({
249
- action: "wait",
250
- earlyTerminationAllowed: false,
251
- cancelPendingLaneIds: [],
252
- retainActiveLaneIds: [],
253
- reason: budgetPressure
254
- ? "evidence is insufficient; active work is retained despite budget pressure"
255
- : "evidence is insufficient and active Lanes may still add evidence"
256
- });
257
- }
258
- if (budgetPressure) {
259
- return Object.freeze({
260
- action: "blocked",
261
- earlyTerminationAllowed: false,
262
- cancelPendingLaneIds: [],
263
- retainActiveLaneIds: [],
264
- reason: "the stage budget or deadline is exhausted before evidence sufficiency"
265
- });
266
- }
267
- if (input.resources.pendingLaneIds.length > 0) {
268
- return Object.freeze({
269
- action: "wait",
270
- earlyTerminationAllowed: false,
271
- cancelPendingLaneIds: [],
272
- retainActiveLaneIds: [],
273
- reason: "evidence is insufficient and scheduled Lanes are waiting for resource capacity"
274
- });
275
- }
276
- const capacity = group.strategy.mode === "fixed" ? group.strategy.count : group.strategy.max;
277
- if (group.strategy.mode === "adaptive"
278
- && group.lanes.length < capacity
279
- && (!input.resources.quorumMet || input.disagreement === "high")) {
280
- return Object.freeze({
281
- action: "expand-parallel",
282
- earlyTerminationAllowed: false,
283
- cancelPendingLaneIds: [],
284
- retainActiveLaneIds: [],
285
- reason: input.resources.quorumMet
286
- ? "material disagreement and available Lane capacity favor another independent route"
287
- : "stage quorum is open and available Lane capacity requires another independent route"
288
- });
289
- }
290
- return Object.freeze({
291
- action: "deepen-sequential",
292
- earlyTerminationAllowed: false,
293
- cancelPendingLaneIds: [],
294
- retainActiveLaneIds: [],
295
- reason: "available evidence favors the next bounded stage over more parallel fan-out"
296
- });
297
- }
298
- /** Fold exact runtime observations into the spend dimensions Yui can prove. */
299
- export function observedExecutionResourceUsage(input) {
300
- const group = validateExecutionGroup(input.group);
301
- const stageGroups = executionStageRetryLineage(group, input.stageGroups);
302
- const groupIds = new Set(stageGroups.map(({ id }) => id));
303
- const turnIds = new Set([
304
- ...stageGroups.flatMap((candidate) => candidate.lanes
305
- .flatMap(({ turnId }) => turnId === undefined ? [] : [turnId])),
306
- ...(input.turns ?? []).filter((turn) => (turn.taskId === group.taskId
307
- && turn.purpose === "execution"
308
- && turn.executionGroupId !== undefined
309
- && groupIds.has(turn.executionGroupId))).map(({ id }) => id)
310
- ]);
311
- const observations = input.events.map(runtimeObservationFromTaskEvent)
312
- .filter((value) => (value !== null
313
- && value.fence.taskId === group.taskId
314
- && value.fence.turnId !== undefined
315
- && turnIds.has(value.fence.turnId)));
316
- let tokens = 0;
317
- let tokensObservable = true;
318
- for (const turnId of turnIds) {
319
- const usage = observations.filter(({ fence, payload }) => (fence.turnId === turnId && payload.usage !== undefined)).sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
320
- || (left.sequence ?? 0) - (right.sequence ?? 0)
321
- || (left.ordinal ?? 0) - (right.ordinal ?? 0)));
322
- if (usage.length === 0) {
323
- tokensObservable = false;
324
- continue;
325
- }
326
- const requestSnapshots = new Map(usage
327
- .filter(({ payload }) => payload.usage.semantics === "request-context")
328
- .map((observation) => [observation.semanticKey, observation.payload.usage]));
329
- if (requestSnapshots.size > 0) {
330
- tokens += [...requestSnapshots.values()]
331
- .reduce((sum, value) => sum + value.inputTokens + value.outputTokens, 0);
332
- continue;
333
- }
334
- const cumulative = usage
335
- .filter(({ payload }) => payload.usage.semantics === "cumulative-session")
336
- .map(({ payload }) => payload.usage.inputTokens + payload.usage.outputTokens);
337
- if (cumulative.length >= 2 && cumulative[0] === 0) {
338
- tokens += Math.max(...cumulative);
339
- continue;
340
- }
341
- if (cumulative.length >= 2) {
342
- tokens += Math.max(0, Math.max(...cumulative) - cumulative[0]);
343
- }
344
- if (cumulative.length > 0 || usage.some(({ payload }) => (payload.usage.semantics === "remaining-context")))
345
- tokensObservable = false;
346
- }
347
- const toolOperationIds = new Set(observations.flatMap((observation) => (observation.kind === "operation.started" && observation.payload.operation === "tool"
348
- ? [`${observation.fence.turnId}\0${observation.payload.operationId ?? observation.semanticKey}`]
349
- : [])));
350
- const toolCallsObservable = observations.some(({ kind, payload }) => (kind === "operation.started" && payload.operation === "tool"));
351
- return Object.freeze({
352
- tokens,
353
- toolCalls: toolOperationIds.size,
354
- tokensObservable,
355
- toolCallsObservable
356
- });
357
- }
358
- function activeLimits(policy, active, request) {
359
- const checks = [
360
- ["home", policy.maxActiveLanes, () => true],
361
- ["task", policy.maxActiveLanesPerTask, (value) => value.taskId === request.taskId],
362
- ["work-item", policy.maxActiveLanesPerWorkItem, (value) => (request.workItemId !== undefined && value.taskId === request.taskId
363
- && value.workItemId === request.workItemId)],
364
- ["group", policy.maxActiveLanesPerGroup, (value) => (value.taskId === request.taskId && value.executionGroupId === request.executionGroupId)],
365
- ["provider", policy.maxActiveLanesPerProvider, (value) => value.providerId === request.providerId],
366
- ["agent", policy.maxActiveLanesPerAgent, (value) => value.agentId === request.agentId],
367
- ["model", policy.maxActiveLanesPerModel, (value) => modelKey(value) === modelKey(request)]
368
- ];
369
- return checks.filter(([, limit, matches]) => active.filter(matches).length >= limit)
370
- .map(([scope]) => scope);
371
- }
372
- function executionStageRetryLineage(group, candidates) {
373
- const stage = group.stage;
374
- if (stage === undefined || candidates === undefined)
375
- return [group];
376
- const lineage = candidates
377
- .map(validateExecutionGroup)
378
- .filter((candidate) => (candidate.taskId === group.taskId
379
- && candidate.purpose === "execution"
380
- && candidate.stage?.mode === stage.mode
381
- && candidate.stage.stage === stage.stage
382
- && candidate.stage.round === stage.round
383
- && candidate.stage.stageAttempt <= stage.stageAttempt
384
- && isDeepStrictEqual(candidate.stage.budget, stage.budget)
385
- && isDeepStrictEqual(candidate.stage.resources, stage.resources)));
386
- if (!lineage.some(({ id }) => id === group.id))
387
- lineage.push(group);
388
- return lineage;
389
- }
390
- function validateLaneIdentity(value) {
391
- requireIdentity(value.taskId, "Resource Lane Task id");
392
- if (value.workItemId !== undefined)
393
- requireIdentity(value.workItemId, "Resource Lane WorkItem id");
394
- requireIdentity(value.executionGroupId, "Resource Lane ExecutionGroup id");
395
- requireIdentity(value.executionLaneId, "Resource Lane id");
396
- requireIdentity(value.providerId, "Resource Lane Provider id");
397
- requireIdentity(value.agentId, "Resource Lane Agent id");
398
- if (value.model !== undefined)
399
- requireText(value.model, "Resource Lane model");
400
- requireTimestamp(value.requestedAt, "Resource Lane request time");
401
- return Object.freeze({ ...value });
402
- }
403
- function modelKey(value) {
404
- return `${value.providerId}\0${value.model ?? "default"}`;
405
- }
406
- function positive(value, fallback, label) {
407
- const resolved = value ?? fallback;
408
- if (!Number.isSafeInteger(resolved) || resolved < 1) {
409
- throw new TypeError(`${label} must be a positive integer.`);
410
- }
411
- return resolved;
412
- }
413
- function nonNegative(value, label) {
414
- if (!Number.isSafeInteger(value) || value < 0)
415
- throw new Error(`${label} must be a non-negative integer.`);
416
- return value;
417
- }
418
- function percentage(value, label) {
419
- if (!Number.isSafeInteger(value) || value < 0 || value > 100) {
420
- throw new Error(`${label} must be an integer from 0 to 100.`);
421
- }
422
- return value;
423
- }
424
- function isUsable(status) {
425
- return status === "completed";
426
- }
@@ -1,16 +0,0 @@
1
- export function withYuiTurnEnvironment(launch, yuiHome, role, turn, nativeSessionId, nativeSessionRoot) {
2
- return {
3
- ...launch,
4
- env: {
5
- ...launch.env,
6
- YUI_HOME: yuiHome,
7
- YUI_TASK_ID: turn.taskId,
8
- YUI_ROLE: role.name,
9
- YUI_AGENT_ID: role.activeAgentId ?? role.agent ?? "",
10
- YUI_TURN_ID: turn.id,
11
- YUI_WORKSPACE: role.workspace,
12
- ...(nativeSessionRoot === undefined ? {} : { YUI_NATIVE_SESSION_ROOT: nativeSessionRoot }),
13
- ...(nativeSessionId === undefined ? {} : { YUI_NATIVE_SESSION_ID: nativeSessionId })
14
- }
15
- };
16
- }
@@ -1,240 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { requireIdentity, requireText, requireTimestamp, normalizedUniqueText } from "../domain/validation.js";
3
- import { validateTaskRecordReference } from "../task/taskRecordReference.js";
4
- /** Dispositions that still block Task completion under `enforce`. */
5
- export const BLOCKING_FINDING_DISPOSITIONS = [
6
- "open",
7
- "fixed-pending-review"
8
- ];
9
- /** Dispositions a Task-control Agent may set explicitly. */
10
- export const TASK_CONTROL_FINDING_DISPOSITIONS = [
11
- "fixed-pending-review",
12
- "verified-fixed",
13
- "accepted-risk",
14
- "not-actionable",
15
- "superseded"
16
- ];
17
- /** Normalizes the severity vocabularies reviewers may use to P1/P2/P3. */
18
- export function normalizeReviewFindingSeverity(value) {
19
- if (typeof value !== "string")
20
- throw new Error("Review finding severity is required.");
21
- const normalized = value.trim().toLowerCase();
22
- if (normalized === "p1" || normalized === "critical" || normalized === "blocker")
23
- return "p1";
24
- if (normalized === "p2" || normalized === "high" || normalized === "major")
25
- return "p2";
26
- if (normalized === "p3"
27
- || normalized === "medium"
28
- || normalized === "moderate"
29
- || normalized === "low"
30
- || normalized === "minor"
31
- || normalized === "nit"
32
- || normalized === "info"
33
- || normalized === "style") {
34
- return "p3";
35
- }
36
- throw new Error(`Review finding severity is invalid: ${String(value)}.`);
37
- }
38
- /**
39
- * Derives the stable ledger key for one reported finding. The key is
40
- * deterministic for the same invariant/path/symbol so a repeated finding in a
41
- * later Round updates the existing record instead of creating a duplicate.
42
- */
43
- export function reviewFindingStableKey(input) {
44
- const invariant = input.invariant.trim().toLowerCase();
45
- const primaryPath = (input.primaryPath ?? "").trim().toLowerCase();
46
- const primarySymbol = (input.primarySymbol ?? "").trim().toLowerCase();
47
- const basis = [
48
- invariant,
49
- primaryPath,
50
- primarySymbol
51
- ].join("|");
52
- // Reports without path/symbol provenance cannot safely collapse different
53
- // titles under one invariant. Use the title as the fallback identity instead
54
- // of merging every such finding into a single ledger record.
55
- const material = primaryPath.length === 0 && primarySymbol.length === 0
56
- ? `${invariant}|${input.title.trim().toLowerCase()}`
57
- : basis;
58
- return `rf-${createHash("sha256").update(material).digest("hex").slice(0, 16)}`;
59
- }
60
- export function createReviewFinding(id, taskId, input, now) {
61
- const timestamp = now.toISOString();
62
- return validateReviewFinding({
63
- schemaVersion: 1,
64
- id: requireIdentity(id, "ReviewFinding id"),
65
- taskId: requireIdentity(taskId, "Task id"),
66
- ...(input.sourceId === undefined ? {} : { sourceId: requireIdentity(input.sourceId, "ReviewFinding source id") }),
67
- stableKey: requireIdentity(input.stableKey, "ReviewFinding stable key"),
68
- severity: input.severity,
69
- invariant: requireText(input.invariant, "ReviewFinding invariant"),
70
- title: requireText(input.title, "ReviewFinding title"),
71
- affectedPaths: normalizedUniqueText(input.affectedPaths ?? [], "ReviewFinding affected path"),
72
- affectedSymbols: normalizedUniqueText(input.affectedSymbols ?? [], "ReviewFinding affected symbol"),
73
- evidence: normalizedUniqueText(input.evidence ?? [], "ReviewFinding evidence"),
74
- firstReviewRoundId: requireIdentity(input.reviewRoundId, "ReviewFinding first ReviewRound"),
75
- lastReviewRoundId: requireIdentity(input.reviewRoundId, "ReviewFinding last ReviewRound"),
76
- disposition: "open",
77
- createdAt: timestamp,
78
- updatedAt: timestamp
79
- });
80
- }
81
- /**
82
- * Applies one Leader disposition. Terminal dispositions
83
- * (verified-fixed/accepted-risk/not-actionable/superseded) are explicit Leader
84
- * decisions and stay terminal; `fixed-pending-review` may still regress to
85
- * `open` when a later Round reproduces the finding.
86
- */
87
- export function disposeReviewFinding(finding, input) {
88
- validateReviewFinding(finding);
89
- if (!TASK_CONTROL_FINDING_DISPOSITIONS.includes(input.disposition)) {
90
- throw new Error(`ReviewFinding disposition is not a Leader decision: ${input.disposition}.`);
91
- }
92
- if (finding.disposition !== "open" && finding.disposition !== "fixed-pending-review") {
93
- throw new Error(`ReviewFinding ${finding.id} is already terminal: ${finding.disposition}.`);
94
- }
95
- if (input.disposition === "superseded" && input.supersededBy === undefined) {
96
- throw new Error(`ReviewFinding ${finding.id} supersession requires a successor stable key.`);
97
- }
98
- const repair = input.repair === undefined
99
- ? finding.repair
100
- : validateReviewFindingRepair(input.repair);
101
- return validateReviewFinding({
102
- ...finding,
103
- disposition: input.disposition,
104
- ...(repair === undefined ? {} : { repair }),
105
- dispositionBy: requireIdentity(input.by, "ReviewFinding disposition actor"),
106
- ...(input.note === undefined ? {} : { dispositionNote: requireText(input.note, "ReviewFinding disposition note") }),
107
- ...(input.supersededBy === undefined ? {} : { supersededBy: requireIdentity(input.supersededBy, "ReviewFinding successor stable key") }),
108
- mergeRequired: false,
109
- updatedAt: input.now.toISOString()
110
- });
111
- }
112
- /**
113
- * Reconciles a re-detection of this finding in a later Round. A regression
114
- * reopens a `fixed-pending-review` finding; a `verified-fixed` finding that
115
- * reproduces also reopens (the fix did not hold). Explicit Leader decisions
116
- * (accepted-risk/not-actionable/superseded) stay terminal but still get their
117
- * evidence and last-Round pointer refreshed.
118
- */
119
- export function redetectReviewFinding(finding, input) {
120
- validateReviewFinding(finding);
121
- const evidence = normalizedUniqueText([...finding.evidence, ...(input.evidence ?? [])], "ReviewFinding evidence");
122
- const reopen = finding.disposition === "fixed-pending-review"
123
- || finding.disposition === "verified-fixed";
124
- return validateReviewFinding({
125
- ...finding,
126
- lastReviewRoundId: requireIdentity(input.reviewRoundId, "ReviewFinding last ReviewRound"),
127
- evidence,
128
- ...(reopen ? { disposition: "open", repair: undefined } : {}),
129
- ...(reopen ? { mergeRequired: finding.mergeRequired } : {}),
130
- updatedAt: input.now.toISOString()
131
- });
132
- }
133
- /** Refreshes last-Round/evidence for a terminal finding without reopening it. */
134
- export function touchReviewFinding(finding, input) {
135
- validateReviewFinding(finding);
136
- const evidence = normalizedUniqueText([...finding.evidence, ...(input.evidence ?? [])], "ReviewFinding evidence");
137
- if (finding.lastReviewRoundId === input.reviewRoundId
138
- && evidence.length === finding.evidence.length) {
139
- return finding;
140
- }
141
- return validateReviewFinding({
142
- ...finding,
143
- lastReviewRoundId: requireIdentity(input.reviewRoundId, "ReviewFinding last ReviewRound"),
144
- evidence,
145
- updatedAt: input.now.toISOString()
146
- });
147
- }
148
- /**
149
- * Issue 06: a Reviewer reports a previously-open finding as resolved in a
150
- * later semantic Round. The claim is only accepted when the ledger already
151
- * has repair refs; otherwise the old finding remains open (a changed candidate
152
- * without repair evidence must not advance disposition). With repair refs,
153
- * this transitions `open` to `fixed-pending-review` (the fix claim is recorded
154
- * but the Leader still owns the final disposition) and refreshes
155
- * evidence/last-Round for `fixed-pending-review`. Explicit Leader decisions
156
- * (accepted-risk/not-actionable/superseded) and `verified-fixed` are untouched.
157
- */
158
- export function resolveReviewFinding(finding, input) {
159
- validateReviewFinding(finding);
160
- const evidence = normalizedUniqueText([...finding.evidence, ...(input.evidence ?? [])], "ReviewFinding evidence");
161
- const transition = finding.disposition === "open" && finding.repair !== undefined;
162
- if (!transition
163
- && finding.lastReviewRoundId === input.reviewRoundId
164
- && evidence.length === finding.evidence.length) {
165
- return finding;
166
- }
167
- return validateReviewFinding({
168
- ...finding,
169
- lastReviewRoundId: requireIdentity(input.reviewRoundId, "ReviewFinding last ReviewRound"),
170
- evidence,
171
- ...(transition ? { disposition: "fixed-pending-review" } : {}),
172
- updatedAt: input.now.toISOString()
173
- });
174
- }
175
- export function isReviewFindingBlocking(finding) {
176
- return BLOCKING_FINDING_DISPOSITIONS.includes(finding.disposition)
177
- && (finding.severity === "p1" || finding.severity === "p2");
178
- }
179
- export function validateReviewFinding(finding) {
180
- if (finding.schemaVersion !== 1) {
181
- throw new Error("ReviewFinding must use schemaVersion 1.");
182
- }
183
- validateTaskRecordReference({ taskId: finding.taskId, localId: finding.id }, "reviewFinding");
184
- requireIdentity(finding.stableKey, "ReviewFinding stable key");
185
- if (finding.severity !== "p1" && finding.severity !== "p2" && finding.severity !== "p3") {
186
- throw new Error(`ReviewFinding severity is invalid: ${String(finding.severity)}.`);
187
- }
188
- requireText(finding.invariant, "ReviewFinding invariant");
189
- requireText(finding.title, "ReviewFinding title");
190
- normalizedUniqueText(finding.affectedPaths, "ReviewFinding affected path");
191
- normalizedUniqueText(finding.affectedSymbols, "ReviewFinding affected symbol");
192
- normalizedUniqueText(finding.evidence, "ReviewFinding evidence");
193
- validateTaskRecordReference({ taskId: finding.taskId, localId: finding.firstReviewRoundId }, "reviewRound");
194
- validateTaskRecordReference({ taskId: finding.taskId, localId: finding.lastReviewRoundId }, "reviewRound");
195
- if (![
196
- "open",
197
- "fixed-pending-review",
198
- "verified-fixed",
199
- "accepted-risk",
200
- "not-actionable",
201
- "superseded"
202
- ].includes(finding.disposition)) {
203
- throw new Error(`ReviewFinding disposition is invalid: ${String(finding.disposition)}.`);
204
- }
205
- if (finding.repair !== undefined)
206
- validateReviewFindingRepair(finding.repair);
207
- if (finding.dispositionBy !== undefined) {
208
- requireIdentity(finding.dispositionBy, "ReviewFinding disposition actor");
209
- }
210
- if (finding.dispositionNote !== undefined) {
211
- requireText(finding.dispositionNote, "ReviewFinding disposition note");
212
- }
213
- if (finding.supersededBy !== undefined) {
214
- requireIdentity(finding.supersededBy, "ReviewFinding successor stable key");
215
- if (finding.disposition !== "superseded") {
216
- throw new Error(`ReviewFinding ${finding.id} carries a successor but is not superseded.`);
217
- }
218
- }
219
- if (finding.disposition === "superseded" && finding.supersededBy === undefined) {
220
- throw new Error(`ReviewFinding ${finding.id} is superseded without a successor.`);
221
- }
222
- requireTimestamp(finding.createdAt, "ReviewFinding createdAt");
223
- requireTimestamp(finding.updatedAt, "ReviewFinding updatedAt");
224
- return finding;
225
- }
226
- function validateReviewFindingRepair(repair) {
227
- if (repair.workItemId !== undefined) {
228
- requireIdentity(repair.workItemId, "ReviewFinding repair WorkItem");
229
- }
230
- if (repair.commit !== undefined) {
231
- const commit = requireText(repair.commit, "ReviewFinding repair commit").toLowerCase();
232
- if (!/^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u.test(commit)) {
233
- throw new Error("ReviewFinding repair commit is invalid.");
234
- }
235
- }
236
- if (repair.verification !== undefined) {
237
- requireText(repair.verification, "ReviewFinding repair verification");
238
- }
239
- return repair;
240
- }