@zq-silk/yui 0.10.0 → 0.11.0
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.
- package/README.md +53 -0
- package/dist/cli/commandCatalog.js +28 -7
- package/dist/cli.js +50 -47
- package/dist/commands/projectCommands.js +69 -6
- package/dist/commands/taskCommands.js +639 -89
- package/dist/commands/taskContextCommand.js +78 -27
- package/dist/commands/taskNextActionCommand.js +13 -2
- package/dist/commands/taskOverviewCommand.js +21 -5
- package/dist/commands/taskUpstreamCommands.js +136 -0
- package/dist/context/runContextPack.js +184 -17
- package/dist/controller/agentRuntimeObserver.js +31 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +7 -13
- package/dist/execution/candidateConvergence.js +623 -0
- package/dist/execution/executionGroup.js +255 -13
- package/dist/execution/executionHealth.js +324 -0
- package/dist/execution/resourceBroker.js +425 -0
- package/dist/executor/fileRoleLaunchPlanner.js +6 -9
- package/dist/executor/workspacePreflightClassification.js +117 -0
- package/dist/lifecycle/exactRunTerminalization.js +13 -2
- package/dist/lifecycle/taskRoleSessionReset.js +4 -2
- package/dist/repository/taskBaseFreshness.js +26 -1
- package/dist/repository/taskWorkspacePreparer.js +17 -1
- package/dist/review/reviewRound.js +27 -6
- package/dist/run/agentRun.js +2 -2
- package/dist/run/recoveryProjection.js +15 -0
- package/dist/runtime/runtimeContinuationProjection.js +7 -0
- package/dist/runtime/tmuxAdapters.js +8 -2
- package/dist/scheduler/actionability.js +169 -3
- package/dist/scheduler/activeTaskProgress.js +15 -10
- package/dist/scheduler/leaderWakeupProcessor.js +17 -1
- package/dist/scheduler/taskExecutionProjection.js +105 -8
- package/dist/scheduler/taskObservabilityProjection.js +282 -0
- package/dist/storage/migration/productionRegistry.js +14 -0
- package/dist/storage/sqliteStore.js +12 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/task/completionReadiness.js +1 -1
- package/dist/task/nextAction.js +314 -2
- package/dist/web/assets/client/components.js +116 -0
- package/dist/web/assets/client/i18n.js +66 -0
- package/dist/web/assets/client/view.js +15 -0
- package/dist/web/assets/styles/cards.js +23 -0
- package/dist/web/assets/styles/responsive.js +2 -0
- package/dist/web/webSnapshot.js +8 -2
- package/dist/workItem/workItem.js +262 -5
- package/i18n/README.zh-CN.md +42 -0
- package/package.json +1 -1
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
messageCard,
|
|
15
15
|
metaItem,
|
|
16
16
|
metricTile,
|
|
17
|
+
observabilityMetricCard,
|
|
18
|
+
dagGraph,
|
|
17
19
|
overviewRow,
|
|
18
20
|
pagedList,
|
|
19
21
|
pathMetaItem,
|
|
@@ -290,6 +292,9 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
|
|
|
290
292
|
// the attention/blocker facts behind it.
|
|
291
293
|
const band = executionBand(data.execution, t, locale);
|
|
292
294
|
if (band) summaryBody.append(band);
|
|
295
|
+
const observability = data.observability || (data.execution && data.execution.observability);
|
|
296
|
+
const metrics = observabilityMetricCard(observability, t);
|
|
297
|
+
if (metrics) summaryBody.append(metrics);
|
|
293
298
|
|
|
294
299
|
if (task.completionSummary) {
|
|
295
300
|
const conclusion = node("div", "conclusion");
|
|
@@ -367,6 +372,16 @@ export function renderTaskDetail(detail, data, t, locale, actions) {
|
|
|
367
372
|
));
|
|
368
373
|
}
|
|
369
374
|
|
|
375
|
+
if (observability && observability.dag) {
|
|
376
|
+
const dagBody = node("div", "section-body");
|
|
377
|
+
dagBody.append(dagGraph(observability.dag, t));
|
|
378
|
+
scaffold.append(anchorSection(
|
|
379
|
+
"detail-dag",
|
|
380
|
+
sectionHead(t("detail.dag"), { count: observability.dag.nodes.length }),
|
|
381
|
+
dagBody
|
|
382
|
+
));
|
|
383
|
+
}
|
|
384
|
+
|
|
370
385
|
// 3. Focus (anchor #detail-focus) — brief + technical approach
|
|
371
386
|
const focusBody = node("div", "section-body");
|
|
372
387
|
if (data.brief) {
|
|
@@ -159,6 +159,29 @@ details.work-item-card>summary.record-head~*{margin-top:0}
|
|
|
159
159
|
.lane-role{font-family:var(--font-body);font-weight:700;font-size:12px;color:var(--text)}
|
|
160
160
|
.lane-status{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
|
161
161
|
.lane-summary{flex:1;min-width:0;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
162
|
+
.execution-stage-meta,.execution-resource-meta,.work-item-observability{gap:10px;flex-wrap:wrap}
|
|
163
|
+
.work-item-stages{margin-top:6px}
|
|
164
|
+
.execution-stage-meta .chip,.execution-resource-meta .chip{margin-left:auto}
|
|
165
|
+
.observability-metrics{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px;margin-top:12px}
|
|
166
|
+
.observability-metrics .metric{min-height:52px;padding:8px 10px}
|
|
167
|
+
.observability-metrics .metric-value{font-size:18px}
|
|
168
|
+
.observability-context-meta{gap:10px;flex-wrap:wrap;margin-top:7px}
|
|
169
|
+
.dag-graph{display:grid;gap:8px}
|
|
170
|
+
.dag-node{display:grid;gap:6px;padding:9px 11px;background:var(--bg-2);border:1px solid var(--border);border-left:3px solid var(--faint);border-radius:var(--radius);box-shadow:var(--shadow-card)}
|
|
171
|
+
.dag-node.is-ready{border-left-color:var(--accent)}
|
|
172
|
+
.dag-node.is-running{border-left-color:var(--active)}
|
|
173
|
+
.dag-node.is-blocked,.dag-node.is-failed,.dag-node.is-awaiting_acceptance{border-left-color:var(--danger)}
|
|
174
|
+
.dag-node.is-completed{border-left-color:var(--success)}
|
|
175
|
+
.dag-node.is-retired{border-left-color:var(--faint)}
|
|
176
|
+
.dag-node-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
177
|
+
.dag-node-head .status-dot{margin-top:0}
|
|
178
|
+
.dag-node-title{flex:1;min-width:0;font-size:12px;color:var(--text)}
|
|
179
|
+
.dag-node-deps{display:flex;align-items:center;gap:6px;flex-wrap:wrap;color:var(--muted);font-family:var(--font-mono);font-size:9.5px}
|
|
180
|
+
.dag-node-deps small{margin-right:2px;text-transform:uppercase;letter-spacing:.08em}
|
|
181
|
+
.dag-root-cause{color:var(--warning);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.03em}
|
|
182
|
+
.chip.is-satisfied{color:var(--success);background:var(--success-soft);border-color:transparent}
|
|
183
|
+
.chip.is-active{color:var(--accent);background:var(--accent-soft);border-color:var(--accent-line)}
|
|
184
|
+
.chip.is-failed-open,.chip.is-dead{color:var(--danger);background:var(--danger-soft);border-color:transparent}
|
|
162
185
|
.exec-resolution{display:flex;gap:9px;align-items:baseline;flex-wrap:wrap;padding:7px 10px;border-radius:var(--radius);background:var(--accent-soft);border:1px solid var(--accent-line)}
|
|
163
186
|
.exec-resolution-decision{font-family:var(--font-mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--accent)}
|
|
164
187
|
.exec-resolution-summary{flex:1;min-width:0;font-size:12px;color:var(--text)}
|
|
@@ -21,6 +21,7 @@ export const RESPONSIVE_STYLES = `
|
|
|
21
21
|
body.detail-active .detail-back{display:grid}
|
|
22
22
|
.detail{overflow:visible}
|
|
23
23
|
.overview-duo{grid-template-columns:1fr}
|
|
24
|
+
.observability-metrics{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
24
25
|
/* Terminal panel overlays full-screen instead of occupying a grid column */
|
|
25
26
|
.terminal-panel{position:fixed;inset:0;z-index:70;grid-column:auto;height:100vh;border-left:0}
|
|
26
27
|
}
|
|
@@ -34,6 +35,7 @@ export const RESPONSIVE_STYLES = `
|
|
|
34
35
|
.topbar{flex-wrap:nowrap;gap:10px}
|
|
35
36
|
.detail-tabs{mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent);-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 28px),transparent)}
|
|
36
37
|
.record-cols{grid-template-columns:1fr}
|
|
38
|
+
.observability-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
37
39
|
}
|
|
38
40
|
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
|
|
39
41
|
`;
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -33,7 +33,7 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
33
33
|
const needsAttentionCount = reader.listAgentRuns(task.id)
|
|
34
34
|
.filter((run) => run.status === "active" && isRoleRunStalled(events, run.id))
|
|
35
35
|
.length;
|
|
36
|
-
const execution = buildTaskExecutionProjection(reader, task.id);
|
|
36
|
+
const execution = buildTaskExecutionProjection(reader, task.id, task, now);
|
|
37
37
|
const names = task.projectBindings.flatMap(({ projectId }) => {
|
|
38
38
|
const name = projectNames.get(projectId);
|
|
39
39
|
return name === undefined ? [] : [name];
|
|
@@ -105,18 +105,24 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
105
105
|
&& effectiveLaunch.sourceDesiredRevision !== role.launchRevision
|
|
106
106
|
};
|
|
107
107
|
});
|
|
108
|
+
const execution = buildTaskExecutionProjection(reader, taskId, task, now);
|
|
109
|
+
if (execution === null)
|
|
110
|
+
return null;
|
|
111
|
+
const workItemObservability = new Map(execution.observability.workItems.map((item) => [item.workItemId, item]));
|
|
108
112
|
return {
|
|
109
113
|
task: {
|
|
110
114
|
...task,
|
|
111
115
|
...(projectNames.length === 0 ? {} : { projectNames })
|
|
112
116
|
},
|
|
113
|
-
execution
|
|
117
|
+
execution,
|
|
118
|
+
observability: execution.observability,
|
|
114
119
|
brief: reader.getTaskBrief(taskId),
|
|
115
120
|
roles,
|
|
116
121
|
workItems: reader.listWorkItems(taskId).map((item) => {
|
|
117
122
|
const group = currentWorkItemExecutionGroup(item);
|
|
118
123
|
return {
|
|
119
124
|
...item,
|
|
125
|
+
observability: workItemObservability.get(item.id),
|
|
120
126
|
...(group === undefined ? {} : { currentExecution: summarizeExecutionGroup(group) })
|
|
121
127
|
};
|
|
122
128
|
}),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
|
|
1
|
+
import { normalizedUniqueIdentities, normalizedUniqueText, requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
|
|
2
|
+
import { contextContentDigest, validateContextSnapshotRef } from "../context/contextSnapshot.js";
|
|
2
3
|
import { validateReviewConfig } from "../review/reviewConfig.js";
|
|
3
4
|
import { taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
4
5
|
import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
|
|
5
6
|
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
6
|
-
import { assertExecutionGroupTransition, validateExecutionGroup } from "../execution/executionGroup.js";
|
|
7
|
+
import { assertExecutionGroupTransition, validateExecutionGroup, WORK_ITEM_EXPLORATION_STAGES } from "../execution/executionGroup.js";
|
|
7
8
|
const TERMINAL_STATUSES = [
|
|
8
9
|
"completed",
|
|
9
10
|
"failed",
|
|
@@ -12,7 +13,7 @@ const TERMINAL_STATUSES = [
|
|
|
12
13
|
export function createWorkItem(id, taskId, input, now) {
|
|
13
14
|
const timestamp = now.toISOString();
|
|
14
15
|
return validateWorkItem({
|
|
15
|
-
schemaVersion:
|
|
16
|
+
schemaVersion: 12,
|
|
16
17
|
id: requireIdentity(id, "Work Item id"),
|
|
17
18
|
taskId: requireIdentity(taskId, "Task id"),
|
|
18
19
|
title: requireText(input.title, "Work item title"),
|
|
@@ -247,8 +248,8 @@ export function recordWorkItemWorkspaceDisposition(workItem, disposition, now) {
|
|
|
247
248
|
});
|
|
248
249
|
}
|
|
249
250
|
export function validateWorkItem(workItem) {
|
|
250
|
-
if (workItem.schemaVersion !==
|
|
251
|
-
throw new Error("WorkItem must use schemaVersion
|
|
251
|
+
if (workItem.schemaVersion !== 12)
|
|
252
|
+
throw new Error("WorkItem must use schemaVersion 12.");
|
|
252
253
|
validateTaskRecordReference({ taskId: workItem.taskId, localId: workItem.id }, "workItem");
|
|
253
254
|
requireIdentity(workItem.taskId, "Task id");
|
|
254
255
|
requireText(workItem.title, "Work item title");
|
|
@@ -283,6 +284,7 @@ export function validateWorkItem(workItem) {
|
|
|
283
284
|
}
|
|
284
285
|
groupIds.add(group.id);
|
|
285
286
|
}
|
|
287
|
+
validateWorkItemExplorationHistory(workItem.executionGroups);
|
|
286
288
|
if (workItem.currentExecutionGroupId !== undefined) {
|
|
287
289
|
requireIdentity(workItem.currentExecutionGroupId, "ExecutionGroup id");
|
|
288
290
|
if (!groupIds.has(workItem.currentExecutionGroupId)) {
|
|
@@ -309,6 +311,16 @@ export function validateWorkItem(workItem) {
|
|
|
309
311
|
if (candidate.workItemRevision > workItem.revision) {
|
|
310
312
|
throw new Error("Work Item candidate revision cannot exceed the Work Item revision.");
|
|
311
313
|
}
|
|
314
|
+
if (candidate.executionGroupId !== undefined) {
|
|
315
|
+
const group = workItem.executionGroups.find(({ id }) => id === candidate.executionGroupId);
|
|
316
|
+
if (group?.stage !== undefined) {
|
|
317
|
+
if (group.stage.stage !== "resolve"
|
|
318
|
+
|| group.resolution?.decision !== "accept"
|
|
319
|
+
|| !group.resolution.selectedLaneIds.includes(candidate.executionLaneId)) {
|
|
320
|
+
throw new Error("An exploration Candidate must come from the accepted Resolve stage.");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
312
324
|
});
|
|
313
325
|
const currentCandidate = currentWorkItemCandidate(workItem);
|
|
314
326
|
if (workItem.status === "awaiting_acceptance" && currentCandidate === undefined) {
|
|
@@ -351,6 +363,130 @@ export function validateWorkItem(workItem) {
|
|
|
351
363
|
}
|
|
352
364
|
return workItem;
|
|
353
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Project the next legal exploration stage from immutable WorkItem Group
|
|
368
|
+
* history. Retry repeats the current stage; retry at Resolve begins the next
|
|
369
|
+
* round. Reject has no continuation, while blocked follows the same bounded
|
|
370
|
+
* retry path after the Leader settles any required InputRequest.
|
|
371
|
+
*/
|
|
372
|
+
export function planWorkItemExplorationStage(workItem, input) {
|
|
373
|
+
validateWorkItem(workItem);
|
|
374
|
+
const snapshot = validateContextSnapshotRef(input.contextSnapshotRef);
|
|
375
|
+
const capacity = executionStrategyCapacity(input.strategy);
|
|
376
|
+
const current = currentWorkItemExecutionGroup(workItem);
|
|
377
|
+
if (workItem.executionGroups.length === 0) {
|
|
378
|
+
if (workItem.status !== "pending" && workItem.status !== "running") {
|
|
379
|
+
throw new Error(`Work Item cannot begin exploration from ${workItem.status}: ${workItem.id}.`);
|
|
380
|
+
}
|
|
381
|
+
if (input.mode === undefined) {
|
|
382
|
+
throw new Error("The first exploration stage requires a mode.");
|
|
383
|
+
}
|
|
384
|
+
if (input.convergence === undefined) {
|
|
385
|
+
throw new Error("The first exploration stage requires a candidate convergence policy.");
|
|
386
|
+
}
|
|
387
|
+
const resourceBudget = requireNewStageResourceBudget(input.resourceBudget);
|
|
388
|
+
const resources = requireNewStageResources(input.resources);
|
|
389
|
+
return {
|
|
390
|
+
schemaVersion: 1,
|
|
391
|
+
mode: input.mode,
|
|
392
|
+
stage: "plan",
|
|
393
|
+
round: 1,
|
|
394
|
+
stageAttempt: 1,
|
|
395
|
+
maxRounds: requirePositiveInteger(input.maxRounds ?? 1, "Exploration max rounds"),
|
|
396
|
+
budget: {
|
|
397
|
+
maxLanes: capacity,
|
|
398
|
+
maxAttempts: requirePositiveInteger(input.maxAttempts ?? 1, "Exploration stage max attempts"),
|
|
399
|
+
...resourceBudget
|
|
400
|
+
},
|
|
401
|
+
resources,
|
|
402
|
+
contextSnapshotRef: snapshot,
|
|
403
|
+
parentResults: [],
|
|
404
|
+
...(input.convergence === undefined ? {} : { convergence: input.convergence })
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (current === undefined || current.stage === undefined) {
|
|
408
|
+
throw new Error("A Work Item cannot mix single and exploration ExecutionGroups.");
|
|
409
|
+
}
|
|
410
|
+
if (current.resolution === undefined) {
|
|
411
|
+
throw new Error(`Execution stage is still active: ${current.id}.`);
|
|
412
|
+
}
|
|
413
|
+
if (input.mode !== undefined && input.mode !== current.stage.mode) {
|
|
414
|
+
throw new Error("Work Item exploration mode is immutable.");
|
|
415
|
+
}
|
|
416
|
+
if (input.maxRounds !== undefined && input.maxRounds !== current.stage.maxRounds) {
|
|
417
|
+
throw new Error("Work Item exploration maxRounds is immutable.");
|
|
418
|
+
}
|
|
419
|
+
const parentResults = selectedParentResults(current);
|
|
420
|
+
const base = {
|
|
421
|
+
schemaVersion: 1,
|
|
422
|
+
mode: current.stage.mode,
|
|
423
|
+
maxRounds: current.stage.maxRounds,
|
|
424
|
+
contextSnapshotRef: snapshot,
|
|
425
|
+
parentResults,
|
|
426
|
+
...(current.stage.convergence === undefined
|
|
427
|
+
? {}
|
|
428
|
+
: { convergence: current.stage.convergence })
|
|
429
|
+
};
|
|
430
|
+
if (current.resolution.decision === "reject") {
|
|
431
|
+
throw new Error(`Rejected exploration has no continuation: ${current.id}.`);
|
|
432
|
+
}
|
|
433
|
+
if (current.resolution.decision === "accept") {
|
|
434
|
+
if (current.stage.stage === "resolve") {
|
|
435
|
+
throw new Error(`Accepted Resolve stage already completed exploration: ${current.id}.`);
|
|
436
|
+
}
|
|
437
|
+
const resourceBudget = requireNewStageResourceBudget(input.resourceBudget);
|
|
438
|
+
const resources = requireNewStageResources(input.resources);
|
|
439
|
+
return {
|
|
440
|
+
...base,
|
|
441
|
+
stage: nextExplorationStage(current.stage.stage),
|
|
442
|
+
round: current.stage.round,
|
|
443
|
+
stageAttempt: 1,
|
|
444
|
+
budget: {
|
|
445
|
+
maxLanes: capacity,
|
|
446
|
+
maxAttempts: requirePositiveInteger(input.maxAttempts ?? 1, "Exploration stage max attempts"),
|
|
447
|
+
...resourceBudget
|
|
448
|
+
},
|
|
449
|
+
resources
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (current.stage.stage === "resolve" && current.resolution.decision === "retry") {
|
|
453
|
+
if (current.stage.round >= current.stage.maxRounds) {
|
|
454
|
+
throw new Error(`Work Item exploration round budget is exhausted: ${workItem.id}.`);
|
|
455
|
+
}
|
|
456
|
+
const resourceBudget = requireNewStageResourceBudget(input.resourceBudget);
|
|
457
|
+
const resources = requireNewStageResources(input.resources);
|
|
458
|
+
return {
|
|
459
|
+
...base,
|
|
460
|
+
stage: "plan",
|
|
461
|
+
round: current.stage.round + 1,
|
|
462
|
+
stageAttempt: 1,
|
|
463
|
+
budget: {
|
|
464
|
+
maxLanes: capacity,
|
|
465
|
+
maxAttempts: requirePositiveInteger(input.maxAttempts ?? 1, "Exploration stage max attempts"),
|
|
466
|
+
...resourceBudget
|
|
467
|
+
},
|
|
468
|
+
resources
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (current.stage.stageAttempt >= current.stage.budget.maxAttempts) {
|
|
472
|
+
throw new Error(`Work Item exploration stage attempt budget is exhausted: ${workItem.id}.`);
|
|
473
|
+
}
|
|
474
|
+
if (capacity !== current.stage.budget.maxLanes) {
|
|
475
|
+
throw new Error("A retried exploration stage must keep its frozen Lane budget.");
|
|
476
|
+
}
|
|
477
|
+
if (input.maxAttempts !== undefined
|
|
478
|
+
&& input.maxAttempts !== current.stage.budget.maxAttempts) {
|
|
479
|
+
throw new Error("A retried exploration stage must keep its frozen attempt budget.");
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
...base,
|
|
483
|
+
stage: current.stage.stage,
|
|
484
|
+
round: current.stage.round,
|
|
485
|
+
stageAttempt: current.stage.stageAttempt + 1,
|
|
486
|
+
budget: current.stage.budget,
|
|
487
|
+
...(current.stage.resources === undefined ? {} : { resources: current.stage.resources })
|
|
488
|
+
};
|
|
489
|
+
}
|
|
354
490
|
/** Resolve the WorkItem's current execution iteration. */
|
|
355
491
|
export function currentWorkItemExecutionGroup(workItem) {
|
|
356
492
|
return workItem.currentExecutionGroupId === undefined
|
|
@@ -361,6 +497,127 @@ export function currentWorkItemExecutionGroup(workItem) {
|
|
|
361
497
|
export function workItemExecutionGroupById(workItem, executionGroupId) {
|
|
362
498
|
return workItem.executionGroups.find(({ id }) => id === executionGroupId);
|
|
363
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* True when a Run failure belongs to the WorkItem's current unresolved Lane.
|
|
502
|
+
* Such a failure is Lane-bounded: the WorkItem remains running so the Leader
|
|
503
|
+
* can reuse completed siblings and retry only this failed attempt.
|
|
504
|
+
*/
|
|
505
|
+
export function workItemOwnsUnresolvedExecutionLane(workItem, executionGroupId, executionLaneId) {
|
|
506
|
+
if (executionGroupId === undefined
|
|
507
|
+
|| executionLaneId === undefined
|
|
508
|
+
|| workItem.currentExecutionGroupId !== executionGroupId)
|
|
509
|
+
return false;
|
|
510
|
+
const group = workItem.executionGroups.find(({ id }) => id === executionGroupId);
|
|
511
|
+
return group !== undefined
|
|
512
|
+
&& group.resolution === undefined
|
|
513
|
+
&& group.lanes.some(({ id, status }) => (id === executionLaneId && status === "failed"));
|
|
514
|
+
}
|
|
515
|
+
function validateWorkItemExplorationHistory(groups) {
|
|
516
|
+
const staged = groups.filter(({ stage }) => stage !== undefined);
|
|
517
|
+
if (staged.length === 0)
|
|
518
|
+
return;
|
|
519
|
+
if (staged.length !== groups.length) {
|
|
520
|
+
throw new Error("A Work Item cannot mix single and exploration ExecutionGroups.");
|
|
521
|
+
}
|
|
522
|
+
const first = groups[0].stage;
|
|
523
|
+
if (first.stage !== "plan"
|
|
524
|
+
|| first.round !== 1
|
|
525
|
+
|| first.stageAttempt !== 1
|
|
526
|
+
|| first.parentResults.length !== 0) {
|
|
527
|
+
throw new Error("Work Item exploration must begin at Plan round 1 attempt 1.");
|
|
528
|
+
}
|
|
529
|
+
for (let index = 1; index < groups.length; index += 1) {
|
|
530
|
+
const previous = groups[index - 1];
|
|
531
|
+
const current = groups[index];
|
|
532
|
+
const before = previous.stage;
|
|
533
|
+
const after = current.stage;
|
|
534
|
+
if (previous.resolution === undefined) {
|
|
535
|
+
throw new Error(`Exploration stage must resolve before its successor: ${previous.id}.`);
|
|
536
|
+
}
|
|
537
|
+
if (previous.resolution.decision === "reject") {
|
|
538
|
+
throw new Error(`Rejected exploration cannot have a successor: ${previous.id}.`);
|
|
539
|
+
}
|
|
540
|
+
if (after.mode !== before.mode
|
|
541
|
+
|| after.maxRounds !== before.maxRounds
|
|
542
|
+
|| JSON.stringify(after.convergence) !== JSON.stringify(before.convergence)) {
|
|
543
|
+
throw new Error("Work Item exploration mode, maxRounds and convergence policy are immutable.");
|
|
544
|
+
}
|
|
545
|
+
const parents = selectedParentResults(previous);
|
|
546
|
+
if (JSON.stringify(after.parentResults) !== JSON.stringify(parents)) {
|
|
547
|
+
throw new Error(`Exploration parentResults do not match ${previous.id}.`);
|
|
548
|
+
}
|
|
549
|
+
if (previous.resolution.decision === "accept") {
|
|
550
|
+
if (before.stage === "resolve"
|
|
551
|
+
|| after.stage !== nextExplorationStage(before.stage)
|
|
552
|
+
|| after.round !== before.round
|
|
553
|
+
|| after.stageAttempt !== 1) {
|
|
554
|
+
throw new Error(`Exploration stage transition is invalid: ${previous.id}/${current.id}.`);
|
|
555
|
+
}
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (before.stage === "resolve" && previous.resolution.decision === "retry") {
|
|
559
|
+
if (before.round >= before.maxRounds
|
|
560
|
+
|| after.stage !== "plan"
|
|
561
|
+
|| after.round !== before.round + 1
|
|
562
|
+
|| after.stageAttempt !== 1) {
|
|
563
|
+
throw new Error(`Exploration round transition is invalid: ${previous.id}/${current.id}.`);
|
|
564
|
+
}
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (after.stage !== before.stage
|
|
568
|
+
|| after.round !== before.round
|
|
569
|
+
|| after.stageAttempt !== before.stageAttempt + 1
|
|
570
|
+
|| after.stageAttempt > before.budget.maxAttempts
|
|
571
|
+
|| JSON.stringify(after.budget) !== JSON.stringify(before.budget)
|
|
572
|
+
|| JSON.stringify(after.resources) !== JSON.stringify(before.resources)) {
|
|
573
|
+
throw new Error(`Exploration retry transition is invalid: ${previous.id}/${current.id}.`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function requireNewStageResourceBudget(value) {
|
|
578
|
+
if (value?.maxTokens === undefined
|
|
579
|
+
|| value.maxToolCalls === undefined
|
|
580
|
+
|| value.maxWallClockSeconds === undefined) {
|
|
581
|
+
throw new Error("A new exploration stage requires token, tool-call and wall-clock budgets.");
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
maxTokens: requirePositiveInteger(value.maxTokens, "Exploration stage max tokens"),
|
|
585
|
+
maxToolCalls: requirePositiveInteger(value.maxToolCalls, "Exploration stage max tool calls"),
|
|
586
|
+
maxWallClockSeconds: requirePositiveInteger(value.maxWallClockSeconds, "Exploration stage max wall-clock seconds")
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function requireNewStageResources(value) {
|
|
590
|
+
if (value === undefined) {
|
|
591
|
+
throw new Error("A new exploration stage requires resource completion policy.");
|
|
592
|
+
}
|
|
593
|
+
return value;
|
|
594
|
+
}
|
|
595
|
+
function selectedParentResults(group) {
|
|
596
|
+
if (group.resolution === undefined) {
|
|
597
|
+
throw new Error(`ExecutionGroup is unresolved: ${group.id}.`);
|
|
598
|
+
}
|
|
599
|
+
return group.resolution.selectedLaneIds.map((laneId) => {
|
|
600
|
+
const lane = group.lanes.find(({ id }) => id === laneId);
|
|
601
|
+
if (lane?.result === undefined) {
|
|
602
|
+
throw new Error(`Selected ExecutionLane result is missing: ${group.id}/${laneId}.`);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
executionGroupId: group.id,
|
|
606
|
+
executionLaneId: lane.id,
|
|
607
|
+
resultDigest: contextContentDigest(lane.result)
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function nextExplorationStage(stage) {
|
|
612
|
+
const index = WORK_ITEM_EXPLORATION_STAGES.indexOf(stage);
|
|
613
|
+
const next = WORK_ITEM_EXPLORATION_STAGES[index + 1];
|
|
614
|
+
if (next === undefined)
|
|
615
|
+
throw new Error("Resolve is the final exploration stage.");
|
|
616
|
+
return next;
|
|
617
|
+
}
|
|
618
|
+
function executionStrategyCapacity(strategy) {
|
|
619
|
+
return requirePositiveInteger(strategy.mode === "fixed" ? strategy.count : strategy.max, "Execution strategy capacity");
|
|
620
|
+
}
|
|
364
621
|
function validateWorkItemExecutionGroup(group, taskId, workItemId) {
|
|
365
622
|
validateExecutionGroup(group);
|
|
366
623
|
if (group.taskId !== taskId || group.purpose !== "execution") {
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -288,6 +288,48 @@ yui task work isolate <task-id>/<work-item-id>
|
|
|
288
288
|
yui task work dispatch <task-id>/<work-item-id> --input "完成实现并运行聚焦测试"
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
派发默认仍为 `single`。Leader 可在全新 WorkItem 上显式开启有界多路探索;每个
|
|
292
|
+
接受的阶段按 `Plan → Generate → Compare → Synthesize → Verify → Resolve`
|
|
293
|
+
推进,只有接受后的 Resolve 阶段会物化既有的单一 Candidate:
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
yui task work dispatch <task-id>/<work-item-id> \
|
|
297
|
+
--mode parallel-diverse --max-rounds 2 --stage-max-attempts 2 \
|
|
298
|
+
--strategy fixed:2 --lane-role critic \
|
|
299
|
+
--stage-max-tokens 240000 --stage-max-tool-calls 200 \
|
|
300
|
+
--stage-max-seconds 1800 --stage-quorum 2
|
|
301
|
+
yui task work group resolve <task-id>/<work-item-id> \
|
|
302
|
+
--decision accept --summary "Plan 证据充分"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
每个阶段都是新的不可变 ExecutionGroup;其 ContextSnapshot 与选中的父 Lane
|
|
306
|
+
结果都以持久引用衔接。`retry` 在阶段尝试预算内重做当前阶段,而 Resolve 上的
|
|
307
|
+
`retry` 才进入下一轮,并受最大轮次约束。
|
|
308
|
+
|
|
309
|
+
每个新阶段还会冻结一份 Resource Broker 契约:token、工具调用和墙钟预算,
|
|
310
|
+
quorum 与 deadline,straggler 窗口,以及继续增加 Lane 所需的最低边际价值。
|
|
311
|
+
省略这些参数时复用现有 context budget 与 runtime-health 时间窗;同一阶段的 retry
|
|
312
|
+
累计原有花费并共享绝对 deadline。执行、Lane retry 和 Reviewer panel 准入统一核算
|
|
313
|
+
Home、Task、WorkItem、Group、Provider、Agent 和模型层级的活动 Lane;容量不足的
|
|
314
|
+
Lane 会耐久保留为 pending,不会把整个 Group 判失败。容量释放或 deadline 到达会沿
|
|
315
|
+
既有 actionability 路径唤醒 Leader,重跑同一 dispatch 即按冻结输入继续。释放的
|
|
316
|
+
容量优先留给最早且当前可准入的等待 Lane;受 Provider 或 Agent 限制的队首
|
|
317
|
+
不会阻塞独立资源域继续推进。Provider 限流仍沿用既有的原地重试窗口,不会扩散成
|
|
318
|
+
兄弟 Lane 失败。
|
|
319
|
+
|
|
320
|
+
Leader 可在接受 Group 时增加 `--early-stop <0-100>`。只有 quorum 已满足且 T5 的
|
|
321
|
+
Verify/Resolve 证据证明验收充分时,Yui 才允许提前终止;它只跳过从未启动的 Lane,
|
|
322
|
+
运行中的 straggler 会被报告并保留,不会因为省费被自动杀死。证据不足时,预算或
|
|
323
|
+
deadline 耗尽会把阶段留给 Leader 决议,绝不会把薄证据转换成成功。
|
|
324
|
+
|
|
325
|
+
新建探索历史还会冻结结构化候选收敛契约。Yui 将当前阶段的精确 JSON 形状追加到
|
|
326
|
+
每个 Lane assignment,并在 Leader 推进前校验入选报告:Compare 必须显式划分
|
|
327
|
+
重复簇,并以直接来源、可执行检查或冻结产物支持每条入选路线;Synthesize 对调研
|
|
328
|
+
使用 claim/evidence 表、对架构使用决策矩阵、对代码只选择一个冻结 Git snapshot。
|
|
329
|
+
Verify 必须使用与入选 Synthesize 作者不同的 Role。只有逐条验收证据完整时才能得到
|
|
330
|
+
`passed` 并由 Resolve 接受 Candidate;显式 gap 只能形成 `next-round`,再通过有界的
|
|
331
|
+
Resolve `retry` 继续。票数和衍生分析可以作为报告上下文,但不能替代直接证据。
|
|
332
|
+
|
|
291
333
|
每个 Agent binding 只有一套 adapter-specific 权限枚举配置:`default` 遵循
|
|
292
334
|
provider 默认行为;`bypass` 编译 provider 支持的 bypass flag;`configured`
|
|
293
335
|
保留其中显式设置的原生选项。Codex 选项是 `sandbox` 和 `approval`;Claude 选项是
|