@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
package/dist/task/nextAction.js
CHANGED
|
@@ -2,8 +2,11 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { changeSetDeliverySettled, governingChangeSets } from "../integration/deliveryObligation.js";
|
|
3
3
|
import { deltaRecheckBlocksAcceptance } from "../review/reviewRound.js";
|
|
4
4
|
import { classifyReviewRoundOutcome, isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
5
|
+
import { actionableExecutionLaneRecoveries } from "../execution/executionHealth.js";
|
|
6
|
+
import { candidateConvergenceDisagreement, candidateConvergenceEvidenceSufficient, candidateConvergenceStageResultsValid } from "../execution/candidateConvergence.js";
|
|
7
|
+
import { executionStageSpendClosed, routeExecutionStage } from "../execution/resourceBroker.js";
|
|
5
8
|
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractRebind.js";
|
|
6
|
-
import { currentWorkItemCandidate, governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
9
|
+
import { currentWorkItemCandidate, currentWorkItemExecutionGroup, governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
7
10
|
const OPEN_WORK_ITEM_STATUSES = new Set(["pending", "running", "awaiting_acceptance"]);
|
|
8
11
|
export function projectNextAction(facts) {
|
|
9
12
|
const { task } = facts;
|
|
@@ -40,6 +43,45 @@ export function projectNextAction(facts) {
|
|
|
40
43
|
recommendedCommand: inconsistency.recommendedCommand
|
|
41
44
|
});
|
|
42
45
|
}
|
|
46
|
+
const laneRecovery = actionableExecutionLaneRecoveries(facts.executionGroups ?? [])
|
|
47
|
+
.find(hasExactRun);
|
|
48
|
+
if (laneRecovery !== undefined) {
|
|
49
|
+
return buildExecutionLaneRecoveryAction(facts, laneRecovery);
|
|
50
|
+
}
|
|
51
|
+
// Quick Win (EXE-03): a resume Run that failed before durable Provider
|
|
52
|
+
// acceptance must not be retried against the same native Session. The
|
|
53
|
+
// authoritative next action is to replace the Session, not to retry the
|
|
54
|
+
// same delivery. The guard only applies while the failed resume Run is the
|
|
55
|
+
// *latest* Leader Run: once a newer Run exists (the fresh-Session launch),
|
|
56
|
+
// the historical failure is stale and must not keep recommending a Session
|
|
57
|
+
// replacement.
|
|
58
|
+
const latestLeaderRun = facts.leaderRuns.at(-1);
|
|
59
|
+
const failedResumeWithoutAcceptance = latestLeaderRun !== undefined
|
|
60
|
+
&& latestLeaderRun.mode === "resume"
|
|
61
|
+
&& latestLeaderRun.status === "failed"
|
|
62
|
+
&& latestLeaderRun.deliveredAt === undefined
|
|
63
|
+
? latestLeaderRun
|
|
64
|
+
: undefined;
|
|
65
|
+
if (failedResumeWithoutAcceptance !== undefined) {
|
|
66
|
+
return buildAction(facts, {
|
|
67
|
+
kind: "replace-leader-session",
|
|
68
|
+
reason: `Leader resume Run ${failedResumeWithoutAcceptance.id} failed before Provider acceptance; `
|
|
69
|
+
+ "the native Session is proven unusable for this delivery. Replace it with a fresh Session "
|
|
70
|
+
+ "after exact cleanup/reset.",
|
|
71
|
+
refs: [ref("agent-run", failedResumeWithoutAcceptance.id)],
|
|
72
|
+
preconditions: [
|
|
73
|
+
{ fact: "Resume Run failed without durable acceptance", satisfied: true, ref: ref("agent-run", failedResumeWithoutAcceptance.id) },
|
|
74
|
+
{ fact: "Old Session is cleaned up or reset before fresh launch", satisfied: false }
|
|
75
|
+
],
|
|
76
|
+
// The failed resume Run is already terminal, so `task run recover
|
|
77
|
+
// --action replace-session` (which requires an active Run) cannot act
|
|
78
|
+
// on it. The working recovery is to reset the Role's Session
|
|
79
|
+
// generation, then clear the Leader failure so the next wake launches
|
|
80
|
+
// a fresh Session (the failed-resume guard in the wakeup processor
|
|
81
|
+
// forces mode=new for the next launch).
|
|
82
|
+
recommendedCommand: `yui task role reset ${task.id} leader --reason "resume failed before acceptance" && yui jobs retry leader-recovery:${task.id}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
43
85
|
const activeLeader = facts.activeRuns.find((run) => run.roleName === "leader");
|
|
44
86
|
if (activeLeader !== undefined) {
|
|
45
87
|
return buildAction(facts, {
|
|
@@ -99,6 +141,18 @@ export function projectNextAction(facts) {
|
|
|
99
141
|
}
|
|
100
142
|
const reviewRun = activeReviewRoundRun(activeReview, facts.activeRuns);
|
|
101
143
|
if (reviewRun === undefined) {
|
|
144
|
+
if (reviewGroupHasResourceQueue(activeReview)) {
|
|
145
|
+
return buildAction(facts, {
|
|
146
|
+
kind: "resume-review",
|
|
147
|
+
reason: `ReviewRound ${activeReview.id} has Reviewer Lanes waiting for Resource Broker capacity.`,
|
|
148
|
+
refs: [reviewRef],
|
|
149
|
+
preconditions: [
|
|
150
|
+
{ fact: "A Reviewer Lane is durably queued", satisfied: true, ref: reviewRef },
|
|
151
|
+
{ fact: "Resource capacity is available", satisfied: false }
|
|
152
|
+
],
|
|
153
|
+
recommendedCommand: `yui task work review ${task.id}/${candidateReady.id}`
|
|
154
|
+
});
|
|
155
|
+
}
|
|
102
156
|
const runRef = ref("agent-run", activeReview.reviewerRunId);
|
|
103
157
|
return buildAction(facts, {
|
|
104
158
|
kind: "repair-protocol-inconsistency",
|
|
@@ -202,6 +256,18 @@ export function projectNextAction(facts) {
|
|
|
202
256
|
recommendedCommand: `yui task review finding repair-wave ${task.id} --create`
|
|
203
257
|
});
|
|
204
258
|
}
|
|
259
|
+
const explorationStop = exhaustedExplorationReason(failedWork, facts.executionGroups);
|
|
260
|
+
if (explorationStop !== undefined) {
|
|
261
|
+
return buildAction(facts, {
|
|
262
|
+
kind: "implement-current-work-item",
|
|
263
|
+
reason: explorationStop,
|
|
264
|
+
refs: [ref("work-item", failedWork.id)],
|
|
265
|
+
preconditions: [
|
|
266
|
+
{ fact: "Work Item exploration cannot continue", satisfied: true, ref: ref("work-item", failedWork.id) }
|
|
267
|
+
],
|
|
268
|
+
recommendedCommand: `yui task work retire ${task.id}/${failedWork.id} --summary \"<reason>\"`
|
|
269
|
+
});
|
|
270
|
+
}
|
|
205
271
|
return buildAction(facts, {
|
|
206
272
|
kind: "implement-current-work-item",
|
|
207
273
|
reason: `Work Item ${failedWork.id} failed without a Review verdict; retry implementation.`,
|
|
@@ -209,7 +275,9 @@ export function projectNextAction(facts) {
|
|
|
209
275
|
preconditions: [
|
|
210
276
|
{ fact: "Work Item is failed", satisfied: true, ref: ref("work-item", failedWork.id) }
|
|
211
277
|
],
|
|
212
|
-
recommendedCommand:
|
|
278
|
+
recommendedCommand: failedWork.assignee === undefined
|
|
279
|
+
? `yui task work update ${task.id}/${failedWork.id} running`
|
|
280
|
+
: `yui task work dispatch ${task.id}/${failedWork.id}`
|
|
213
281
|
});
|
|
214
282
|
}
|
|
215
283
|
const openWork = selectOpenWorkItem(facts.workItems);
|
|
@@ -230,6 +298,9 @@ export function projectNextAction(facts) {
|
|
|
230
298
|
}
|
|
231
299
|
if (openWork?.kind === "ready") {
|
|
232
300
|
const item = openWork.item;
|
|
301
|
+
const stageAction = buildExecutionStageAction(facts, item);
|
|
302
|
+
if (stageAction !== null)
|
|
303
|
+
return stageAction;
|
|
233
304
|
const refs = [ref("work-item", item.id)];
|
|
234
305
|
return buildAction(facts, {
|
|
235
306
|
kind: "implement-current-work-item",
|
|
@@ -379,6 +450,18 @@ export function projectNextAction(facts) {
|
|
|
379
450
|
}
|
|
380
451
|
const reviewRun = activeReviewRoundRun(activeFinal, facts.activeRuns);
|
|
381
452
|
if (reviewRun === undefined) {
|
|
453
|
+
if (reviewGroupHasResourceQueue(activeFinal)) {
|
|
454
|
+
return buildAction(facts, {
|
|
455
|
+
kind: "resume-review",
|
|
456
|
+
reason: `Task-final ReviewRound ${activeFinal.id} has Reviewer Lanes waiting for Resource Broker capacity.`,
|
|
457
|
+
refs: [reviewRef],
|
|
458
|
+
preconditions: [
|
|
459
|
+
{ fact: "A Reviewer Lane is durably queued", satisfied: true, ref: reviewRef },
|
|
460
|
+
{ fact: "Resource capacity is available", satisfied: false }
|
|
461
|
+
],
|
|
462
|
+
recommendedCommand: `yui task review request ${task.id} --role ${activeFinal.reviewerRoleName}`
|
|
463
|
+
});
|
|
464
|
+
}
|
|
382
465
|
const runRef = ref("agent-run", activeFinal.reviewerRunId);
|
|
383
466
|
return buildAction(facts, {
|
|
384
467
|
kind: "repair-protocol-inconsistency",
|
|
@@ -486,6 +569,77 @@ export function projectNextAction(facts) {
|
|
|
486
569
|
recommendedCommand: `yui task complete ${task.id} --summary-file -`
|
|
487
570
|
});
|
|
488
571
|
}
|
|
572
|
+
function hasExactRun(lane) {
|
|
573
|
+
return lane.runId !== undefined;
|
|
574
|
+
}
|
|
575
|
+
function buildExecutionLaneRecoveryAction(facts, lane) {
|
|
576
|
+
const refs = [
|
|
577
|
+
ref("execution-group", lane.groupId),
|
|
578
|
+
ref("execution-lane", lane.laneId),
|
|
579
|
+
ref("agent-run", lane.runId)
|
|
580
|
+
];
|
|
581
|
+
if (lane.recovery === "retry-new-agent-run") {
|
|
582
|
+
return buildAction(facts, {
|
|
583
|
+
kind: "recover-execution-lane",
|
|
584
|
+
reason: `Execution Lane ${lane.laneId} is durably failed; retry only exact Run ${lane.runId} and retain sibling results.`,
|
|
585
|
+
refs,
|
|
586
|
+
preconditions: [
|
|
587
|
+
{ fact: "Execution Lane is failed and unresolved", satisfied: true, ref: refs[1] },
|
|
588
|
+
{ fact: "Exact failed AgentRun is retained", satisfied: true, ref: refs[2] }
|
|
589
|
+
],
|
|
590
|
+
recommendedCommand: `yui task run retry ${facts.task.id}/${lane.runId}`
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
const action = lane.recovery === "diagnose" ? "diagnose" : "terminate";
|
|
594
|
+
const recovery = facts.runRecoveries?.find(({ runId }) => runId === lane.runId);
|
|
595
|
+
const plan = recovery?.actions.find((candidate) => candidate.action === action);
|
|
596
|
+
return buildAction(facts, {
|
|
597
|
+
kind: "recover-execution-lane",
|
|
598
|
+
reason: lane.recovery === "diagnose"
|
|
599
|
+
? `Execution Lane ${lane.laneId} needs bounded diagnostics for exact Run ${lane.runId}.`
|
|
600
|
+
: `Execution Lane ${lane.laneId} has confirmed death evidence; terminate exact Run ${lane.runId}.`,
|
|
601
|
+
refs,
|
|
602
|
+
preconditions: [
|
|
603
|
+
{ fact: `Lane recovery is ${lane.recovery}`, satisfied: true, ref: refs[1] },
|
|
604
|
+
{
|
|
605
|
+
fact: `Exact Run exposes a current ${action} recovery plan`,
|
|
606
|
+
satisfied: plan !== undefined,
|
|
607
|
+
ref: refs[2]
|
|
608
|
+
}
|
|
609
|
+
],
|
|
610
|
+
...(plan === undefined ? {} : { recommendedCommand: plan.command }),
|
|
611
|
+
...(plan !== undefined && recovery?.judgmentRequired === undefined
|
|
612
|
+
? {}
|
|
613
|
+
: {
|
|
614
|
+
judgmentRequired: recovery?.judgmentRequired
|
|
615
|
+
?? `Inspect yui task run show ${facts.task.id}/${lane.runId}; its exact recovery fence is unavailable.`
|
|
616
|
+
})
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function exhaustedExplorationReason(item, executionGroups) {
|
|
620
|
+
const group = currentWorkItemExecutionGroup(item);
|
|
621
|
+
if (group?.stage === undefined || group.resolution === undefined)
|
|
622
|
+
return undefined;
|
|
623
|
+
if (group.resolution.decision === "reject") {
|
|
624
|
+
return `Work Item ${item.id} exploration was rejected and has no legal continuation; retire it explicitly.`;
|
|
625
|
+
}
|
|
626
|
+
if (group.resolution.decision === "retry"
|
|
627
|
+
&& group.stage.stage === "resolve"
|
|
628
|
+
&& group.stage.round >= group.stage.maxRounds) {
|
|
629
|
+
return `Work Item ${item.id} exhausted its exploration round budget; retire it explicitly.`;
|
|
630
|
+
}
|
|
631
|
+
const resources = executionGroups?.find(({ groupId }) => groupId === group.id)?.resources;
|
|
632
|
+
if ((group.resolution.decision === "retry" || group.resolution.decision === "blocked")
|
|
633
|
+
&& resources !== undefined
|
|
634
|
+
&& executionStageSpendClosed(resources)) {
|
|
635
|
+
return `Work Item ${item.id} cannot retry its frozen exploration resource budget; retire it explicitly or replace it with a newly authorized delivery boundary.`;
|
|
636
|
+
}
|
|
637
|
+
if ((group.resolution.decision === "retry" || group.resolution.decision === "blocked")
|
|
638
|
+
&& group.stage.stageAttempt >= group.stage.budget.maxAttempts) {
|
|
639
|
+
return `Work Item ${item.id} exhausted its ${group.stage.stage} stage attempt budget; retire it explicitly.`;
|
|
640
|
+
}
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
489
643
|
/**
|
|
490
644
|
* Stable fingerprint of the durable delivery position. It changes exactly
|
|
491
645
|
* when a delivery record changes, so the semantic-progress budget can compare
|
|
@@ -527,6 +681,156 @@ function buildAction(facts, input) {
|
|
|
527
681
|
fingerprint: createHash("sha256").update(fingerprintSource).digest("hex")
|
|
528
682
|
};
|
|
529
683
|
}
|
|
684
|
+
function buildExecutionStageAction(facts, item) {
|
|
685
|
+
const group = currentWorkItemExecutionGroup(item);
|
|
686
|
+
if (group?.stage?.resources === undefined || group.resolution !== undefined)
|
|
687
|
+
return null;
|
|
688
|
+
const projected = facts.executionGroups?.find(({ groupId }) => groupId === group.id);
|
|
689
|
+
const resources = projected?.resources;
|
|
690
|
+
if (resources === undefined)
|
|
691
|
+
return null;
|
|
692
|
+
const usableLaneIds = group.lanes.filter(({ status }) => (status === "yielded" || status === "completed")).map(({ id }) => id);
|
|
693
|
+
const stageResultsValid = candidateConvergenceStageResultsValid(group);
|
|
694
|
+
const disagreement = candidateConvergenceDisagreement(group);
|
|
695
|
+
const routing = routeExecutionStage({
|
|
696
|
+
group,
|
|
697
|
+
resources,
|
|
698
|
+
evidenceSufficient: candidateConvergenceEvidenceSufficient(item, group, usableLaneIds),
|
|
699
|
+
disagreement
|
|
700
|
+
});
|
|
701
|
+
const refs = [ref("work-item", item.id), ref("execution-group", group.id)];
|
|
702
|
+
const resolveCommand = (decision, suffix = "") => (`yui task work group resolve ${facts.task.id}/${item.id}`
|
|
703
|
+
+ ` --decision ${decision} --summary \"<stage decision>\"${suffix}`);
|
|
704
|
+
if (routing.action === "blocked") {
|
|
705
|
+
return buildAction(facts, {
|
|
706
|
+
kind: "resolve-execution-stage",
|
|
707
|
+
reason: `${routing.reason}; dispatch would preserve the same pending Lanes without starting them.`,
|
|
708
|
+
refs,
|
|
709
|
+
preconditions: [
|
|
710
|
+
{ fact: "Execution stage is unresolved", satisfied: true, ref: refs[1] },
|
|
711
|
+
{
|
|
712
|
+
fact: "Stage deadline or hard budget is exhausted",
|
|
713
|
+
satisfied: executionStageSpendClosed(resources),
|
|
714
|
+
ref: refs[1]
|
|
715
|
+
},
|
|
716
|
+
{ fact: "Acceptance-level evidence is sufficient", satisfied: false, ref: refs[1] }
|
|
717
|
+
],
|
|
718
|
+
recommendedCommand: resolveCommand("blocked"),
|
|
719
|
+
judgmentRequired: "Leader must record the resource-blocked stage, then retire or replace the delivery boundary; the frozen budget cannot be reopened by redispatch."
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
if (routing.action === "expand-parallel") {
|
|
723
|
+
return buildAction(facts, {
|
|
724
|
+
kind: "resolve-execution-stage",
|
|
725
|
+
reason: routing.reason,
|
|
726
|
+
refs,
|
|
727
|
+
preconditions: [
|
|
728
|
+
{
|
|
729
|
+
fact: "Stage quorum is open or structured results show material disagreement",
|
|
730
|
+
satisfied: !resources.quorumMet || disagreement === "high",
|
|
731
|
+
ref: refs[1]
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
fact: "Adaptive Lane capacity remains",
|
|
735
|
+
satisfied: group.strategy.mode === "adaptive"
|
|
736
|
+
&& group.lanes.length < group.strategy.max,
|
|
737
|
+
ref: refs[1]
|
|
738
|
+
}
|
|
739
|
+
],
|
|
740
|
+
recommendedCommand: `yui task work dispatch ${facts.task.id}/${item.id} --lane-role <independent-role>`,
|
|
741
|
+
...(resources.quorumMet
|
|
742
|
+
? {
|
|
743
|
+
alternatives: [{
|
|
744
|
+
kind: "deepen-sequential",
|
|
745
|
+
reason: "Resolve the current evidence and deepen sequentially when another independent Lane has lower value.",
|
|
746
|
+
recommendedCommand: resolveCommand("accept"),
|
|
747
|
+
refs
|
|
748
|
+
}]
|
|
749
|
+
}
|
|
750
|
+
: {}),
|
|
751
|
+
judgmentRequired: resources.quorumMet
|
|
752
|
+
? "Leader must choose an unused compatible Task Role for expansion or deliberately select the sequential alternative."
|
|
753
|
+
: "Leader must choose an unused compatible Task Role so the frozen stage can satisfy quorum."
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
if (routing.action === "deepen-sequential") {
|
|
757
|
+
const resolveRequestsNextRound = group.stage.stage === "resolve";
|
|
758
|
+
const decision = usableLaneIds.length === 0
|
|
759
|
+
|| !stageResultsValid
|
|
760
|
+
|| !resources.quorumMet
|
|
761
|
+
|| resolveRequestsNextRound
|
|
762
|
+
? "retry"
|
|
763
|
+
: "accept";
|
|
764
|
+
return buildAction(facts, {
|
|
765
|
+
kind: "resolve-execution-stage",
|
|
766
|
+
reason: resolveRequestsNextRound
|
|
767
|
+
? "Resolve evidence does not establish a Candidate; begin another bounded exploration round."
|
|
768
|
+
: !resources.quorumMet
|
|
769
|
+
? "The stage exhausted its Lane capacity before quorum; resolve it as a bounded retry."
|
|
770
|
+
: decision === "retry"
|
|
771
|
+
? "The stage has no structurally usable output; resolve it as a bounded retry before redispatch."
|
|
772
|
+
: routing.reason,
|
|
773
|
+
refs,
|
|
774
|
+
preconditions: [
|
|
775
|
+
{ fact: "No stage Lane remains active or queued", satisfied: true, ref: refs[1] },
|
|
776
|
+
{ fact: "Current stage has structurally usable output", satisfied: stageResultsValid, ref: refs[1] },
|
|
777
|
+
{ fact: "Stage quorum is met", satisfied: resources.quorumMet, ref: refs[1] }
|
|
778
|
+
],
|
|
779
|
+
recommendedCommand: resolveCommand(decision),
|
|
780
|
+
judgmentRequired: resolveRequestsNextRound
|
|
781
|
+
? "Leader must judge whether the frozen round budget permits another exploration round or the WorkItem should be retired."
|
|
782
|
+
: decision === "retry"
|
|
783
|
+
? "Leader must judge whether the frozen attempt budget permits one retry or the WorkItem should be retired."
|
|
784
|
+
: "Leader must select the usable stage evidence before advancing to the next bounded stage."
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (routing.action === "resolve") {
|
|
788
|
+
const earlyStop = routing.cancelPendingLaneIds.length === 0
|
|
789
|
+
? ""
|
|
790
|
+
: " --early-stop <observed-marginal-value>";
|
|
791
|
+
return buildAction(facts, {
|
|
792
|
+
kind: "resolve-execution-stage",
|
|
793
|
+
reason: routing.reason,
|
|
794
|
+
refs,
|
|
795
|
+
preconditions: [
|
|
796
|
+
{ fact: "Stage quorum is met", satisfied: resources.quorumMet, ref: refs[1] },
|
|
797
|
+
{ fact: "Acceptance-level evidence is sufficient", satisfied: true, ref: refs[1] }
|
|
798
|
+
],
|
|
799
|
+
recommendedCommand: resolveCommand("accept", earlyStop),
|
|
800
|
+
judgmentRequired: routing.cancelPendingLaneIds.length === 0
|
|
801
|
+
? "Leader must select and accept the evidence that satisfies the stage contract."
|
|
802
|
+
: "Leader must record the observed marginal value before skipping never-started pending Lanes."
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
if (resources.pendingLaneIds.length > 0 && resources.activeLaneIds.length === 0) {
|
|
806
|
+
return buildAction(facts, {
|
|
807
|
+
kind: "implement-current-work-item",
|
|
808
|
+
reason: `ExecutionGroup ${group.id} has queued Lanes and an open resource budget; retry Broker admission after the capacity wake.`,
|
|
809
|
+
refs,
|
|
810
|
+
preconditions: [
|
|
811
|
+
{ fact: "At least one Lane is durably queued", satisfied: true, ref: refs[1] },
|
|
812
|
+
{
|
|
813
|
+
fact: "Stage deadline and hard budgets remain open",
|
|
814
|
+
satisfied: !executionStageSpendClosed(resources),
|
|
815
|
+
ref: refs[1]
|
|
816
|
+
}
|
|
817
|
+
],
|
|
818
|
+
recommendedCommand: `yui task work dispatch ${facts.task.id}/${item.id}`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (resources.activeLaneIds.length > 0) {
|
|
822
|
+
return buildAction(facts, {
|
|
823
|
+
kind: "repair-protocol-inconsistency",
|
|
824
|
+
reason: `ExecutionGroup ${group.id} retains active Lanes but no delegated AgentRun is active.`,
|
|
825
|
+
refs,
|
|
826
|
+
conflicts: refs,
|
|
827
|
+
preconditions: [
|
|
828
|
+
{ fact: "Every active Lane has an active exact AgentRun", satisfied: false, ref: refs[1] }
|
|
829
|
+
]
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
530
834
|
function latestActiveWorkItemReview(rounds, item, candidate) {
|
|
531
835
|
if (candidate === undefined)
|
|
532
836
|
return undefined;
|
|
@@ -567,6 +871,12 @@ function reviewGroupAwaitingResolution(round) {
|
|
|
567
871
|
&& isPanelReviewRound(round)
|
|
568
872
|
&& round.executionGroup.lanes.every((lane) => TERMINAL_REVIEW_LANE_STATUSES.has(lane.status));
|
|
569
873
|
}
|
|
874
|
+
function reviewGroupHasResourceQueue(round) {
|
|
875
|
+
return round.executionGroup !== undefined
|
|
876
|
+
&& round.executionGroup.resolution === undefined
|
|
877
|
+
&& round.executionGroup.lanes.some((lane) => (lane.status === "pending"
|
|
878
|
+
&& lane.runId === undefined));
|
|
879
|
+
}
|
|
570
880
|
function buildResolveReviewGroupAction(facts, round) {
|
|
571
881
|
const group = round.executionGroup;
|
|
572
882
|
const refs = [
|
|
@@ -621,6 +931,8 @@ function reviewRoundConflict(round, activeRuns) {
|
|
|
621
931
|
};
|
|
622
932
|
}
|
|
623
933
|
if (activeReviewRoundRun(round, activeRuns) === undefined) {
|
|
934
|
+
if (reviewGroupHasResourceQueue(round))
|
|
935
|
+
return null;
|
|
624
936
|
const runRef = ref("agent-run", round.reviewerRunId);
|
|
625
937
|
return {
|
|
626
938
|
reason: `ReviewRound ${round.id} references Reviewer Run ${round.reviewerRunId}, but that Run is not active.`,
|
|
@@ -175,12 +175,37 @@ export function executionGroupCard(summary, t, locale) {
|
|
|
175
175
|
head.append(pills);
|
|
176
176
|
card.append(head);
|
|
177
177
|
|
|
178
|
+
if (summary.stage) {
|
|
179
|
+
const stage = node("div", "record-meta execution-stage-meta");
|
|
180
|
+
stage.append(node("span", "mono", t("detail.stage") + " · " + (summary.stage.stage || "—")));
|
|
181
|
+
stage.append(node("span", "", t("detail.round") + " · " + String(summary.stage.round)));
|
|
182
|
+
stage.append(node("span", "", t("detail.stageAttempt") + " · " + String(summary.stage.stageAttempt)));
|
|
183
|
+
stage.append(chip(t("mode." + summary.stage.mode)));
|
|
184
|
+
card.append(stage);
|
|
185
|
+
}
|
|
186
|
+
if (summary.resources) {
|
|
187
|
+
const resources = summary.resources;
|
|
188
|
+
const budget = node("div", "record-meta execution-resource-meta");
|
|
189
|
+
budget.append(node("span", "", t("detail.cost") + " · "
|
|
190
|
+
+ formatResource(resources.tokens, resources.tokensRemaining, resources.tokensObservable) + " tokens"));
|
|
191
|
+
budget.append(node("span", "", formatResource(resources.toolCalls, resources.toolCallsRemaining, resources.toolCallsObservable) + " tools"));
|
|
192
|
+
budget.append(node("span", "", resources.wallClockSeconds + "s"));
|
|
193
|
+
budget.append(node("span", "", t("detail.quorum") + " · "
|
|
194
|
+
+ resources.usableLaneCount + "/" + (summary.stage.resources?.quorum || "—")));
|
|
195
|
+
card.append(budget);
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
const lanes = node("div", "lane-list");
|
|
179
199
|
summary.laneSummaries.forEach(function (lane) {
|
|
180
200
|
const row = node("div", "lane-row");
|
|
181
201
|
row.append(statusDot(lane.status));
|
|
182
202
|
row.append(node("span", "lane-role", lane.roleName));
|
|
183
203
|
row.append(node("span", "lane-status", t("lane." + lane.status)));
|
|
204
|
+
if (lane.effective) {
|
|
205
|
+
const config = [lane.effective.adapterId, lane.effective.model, lane.effective.effort]
|
|
206
|
+
.filter(function (value) { return value; }).join(" · ");
|
|
207
|
+
if (config) row.append(chip(config));
|
|
208
|
+
}
|
|
184
209
|
if (lane.summary) {
|
|
185
210
|
row.append(node("span", "lane-summary", lane.summary));
|
|
186
211
|
}
|
|
@@ -202,6 +227,63 @@ export function executionGroupCard(summary, t, locale) {
|
|
|
202
227
|
return card;
|
|
203
228
|
}
|
|
204
229
|
|
|
230
|
+
function formatResource(used, remaining, observable) {
|
|
231
|
+
const value = String(used) + (remaining === undefined ? "" : "/" + String(used + remaining));
|
|
232
|
+
return observable === false ? value + "*" : value;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function observabilityMetricCard(observability, t) {
|
|
236
|
+
if (!observability) return null;
|
|
237
|
+
const card = node("div", "observability-metrics");
|
|
238
|
+
const cost = observability.cost || {};
|
|
239
|
+
const context = observability.context || {};
|
|
240
|
+
card.append(metricTile(t("detail.tokens"), cost.tokens + (cost.tokensObservable === false ? "*" : "")));
|
|
241
|
+
card.append(metricTile(t("detail.toolCalls"), cost.toolCalls + (cost.toolCallsObservable === false ? "*" : "")));
|
|
242
|
+
card.append(metricTile(t("detail.wallClock"), cost.wallClockSeconds + "s"));
|
|
243
|
+
card.append(metricTile(t("detail.ready"), (observability.dag?.readyIds || []).length, { hot: true }));
|
|
244
|
+
card.append(metricTile(t("detail.contextSnapshots"), context.snapshotCount));
|
|
245
|
+
card.append(metricTile(t("detail.contextPeak"), context.observedInputPeakTokens));
|
|
246
|
+
const contextMeta = node("div", "record-meta observability-context-meta");
|
|
247
|
+
contextMeta.append(node("span", "", t("detail.contextBytes") + " · "
|
|
248
|
+
+ (context.totalBytes === null ? t("detail.partial") : context.totalBytes + " B")));
|
|
249
|
+
contextMeta.append(node("span", "", t("detail.compression") + " · " + t("detail.unavailable")));
|
|
250
|
+
contextMeta.append(node("span", "", t("detail.marginalValue") + " · " + t("detail.unavailable")));
|
|
251
|
+
card.append(contextMeta);
|
|
252
|
+
return card;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function dagGraph(dag, t) {
|
|
256
|
+
if (!dag || !dag.nodes || !dag.nodes.length) return emptyRow(t);
|
|
257
|
+
const graph = node("div", "dag-graph");
|
|
258
|
+
const edgeByTarget = {};
|
|
259
|
+
(dag.edges || []).forEach(function (edge) {
|
|
260
|
+
if (!edgeByTarget[edge.to]) edgeByTarget[edge.to] = [];
|
|
261
|
+
edgeByTarget[edge.to].push(edge);
|
|
262
|
+
});
|
|
263
|
+
dag.nodes.forEach(function (item) {
|
|
264
|
+
const row = node("div", "dag-node is-" + item.projectedStatus);
|
|
265
|
+
const top = node("div", "dag-node-head");
|
|
266
|
+
top.append(statusDot(item.projectedStatus), node("strong", "dag-node-title", item.title));
|
|
267
|
+
top.append(pill(t, "dag", item.projectedStatus));
|
|
268
|
+
if ((dag.readyIds || []).indexOf(item.id) >= 0) top.append(chip(t("dag.ready"), "is-active"));
|
|
269
|
+
row.append(top);
|
|
270
|
+
const edges = edgeByTarget[item.id] || [];
|
|
271
|
+
if (edges.length) {
|
|
272
|
+
const deps = node("div", "dag-node-deps");
|
|
273
|
+
deps.append(node("small", "", t("dag.dependsOn")));
|
|
274
|
+
edges.forEach(function (edge) {
|
|
275
|
+
deps.append(chip(edge.from + " · " + t("dag.edge." + edge.status), "is-" + edge.status));
|
|
276
|
+
});
|
|
277
|
+
row.append(deps);
|
|
278
|
+
}
|
|
279
|
+
if (item.rootCauseIds && item.rootCauseIds.length) {
|
|
280
|
+
row.append(node("small", "dag-root-cause", t("dag.rootCause") + " · " + item.rootCauseIds.join(" ← ")));
|
|
281
|
+
}
|
|
282
|
+
graph.append(row);
|
|
283
|
+
});
|
|
284
|
+
return graph;
|
|
285
|
+
}
|
|
286
|
+
|
|
205
287
|
// --- WorkItem Candidates -----------------------------------------------------
|
|
206
288
|
export function candidateList(candidates, t, locale) {
|
|
207
289
|
if (!candidates || !candidates.length) return null;
|
|
@@ -473,6 +555,40 @@ export function workItemCard(item, titles, t, locale, actions, taskId) {
|
|
|
473
555
|
body.append(executionGroupCard(item.currentExecution, t, locale));
|
|
474
556
|
}
|
|
475
557
|
|
|
558
|
+
if (item.observability) {
|
|
559
|
+
const observability = item.observability;
|
|
560
|
+
if (observability.executionGroups && observability.executionGroups.length) {
|
|
561
|
+
observability.executionGroups.forEach(function (group) {
|
|
562
|
+
if (!item.currentExecution || group.groupId !== item.currentExecution.groupId) {
|
|
563
|
+
body.append(executionGroupCard(group, t, locale));
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const metrics = node("div", "record-meta work-item-observability");
|
|
568
|
+
metrics.append(node("span", "", t("detail.cost") + " · "
|
|
569
|
+
+ observability.cost.tokens + " tokens"));
|
|
570
|
+
metrics.append(node("span", "", observability.cost.toolCalls + " tools"));
|
|
571
|
+
metrics.append(node("span", "", observability.cost.wallClockSeconds + "s"));
|
|
572
|
+
metrics.append(node("span", "", t("detail.contextSnapshots") + " · "
|
|
573
|
+
+ observability.context.snapshotCount));
|
|
574
|
+
metrics.append(node("span", "", t("detail.evidence") + " · "
|
|
575
|
+
+ observability.evidenceCount));
|
|
576
|
+
if (observability.openFindingCount > 0) {
|
|
577
|
+
metrics.append(chip(t("detail.openFindings") + " · " + observability.openFindingCount, "is-danger"));
|
|
578
|
+
}
|
|
579
|
+
body.append(metrics);
|
|
580
|
+
if (observability.stages && observability.stages.length) {
|
|
581
|
+
const stages = node("div", "chip-row work-item-stages");
|
|
582
|
+
observability.stages.forEach(function (stage) {
|
|
583
|
+
const label = (stage.stage || "single")
|
|
584
|
+
+ (stage.round === undefined ? "" : " #" + stage.round)
|
|
585
|
+
+ (stage.resolution ? " · " + stage.resolution : "");
|
|
586
|
+
stages.append(chip(label, stage.groupId === observability.currentGroupId ? "is-active" : ""));
|
|
587
|
+
});
|
|
588
|
+
body.append(stages);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
476
592
|
// Review candidates submitted for this WorkItem, newest first.
|
|
477
593
|
const candidates = candidateList(item.candidates, t, locale);
|
|
478
594
|
if (candidates) body.append(candidates);
|
|
@@ -55,6 +55,24 @@ const messages = {
|
|
|
55
55
|
"detail.constraints": "Constraints",
|
|
56
56
|
"detail.decisions": "Decisions",
|
|
57
57
|
"detail.dependsOn": "Depends on",
|
|
58
|
+
"detail.dag": "Task DAG",
|
|
59
|
+
"detail.stage": "Stage",
|
|
60
|
+
"detail.round": "Round",
|
|
61
|
+
"detail.stageAttempt": "Attempt",
|
|
62
|
+
"detail.cost": "Cost",
|
|
63
|
+
"detail.tokens": "Tokens",
|
|
64
|
+
"detail.toolCalls": "Tool calls",
|
|
65
|
+
"detail.wallClock": "Wall clock",
|
|
66
|
+
"detail.ready": "Ready",
|
|
67
|
+
"detail.quorum": "Quorum",
|
|
68
|
+
"detail.contextSnapshots": "Context snapshots",
|
|
69
|
+
"detail.contextPeak": "Context peak",
|
|
70
|
+
"detail.contextBytes": "Context bytes",
|
|
71
|
+
"detail.compression": "Compression",
|
|
72
|
+
"detail.marginalValue": "Marginal value",
|
|
73
|
+
"detail.partial": "partial",
|
|
74
|
+
"detail.unavailable": "unavailable",
|
|
75
|
+
"detail.openFindings": "Open findings",
|
|
58
76
|
"detail.desired": "Desired launch",
|
|
59
77
|
"detail.desiredAgent": "Desired Agent",
|
|
60
78
|
"detail.due": "Due",
|
|
@@ -241,6 +259,7 @@ const messages = {
|
|
|
241
259
|
"exec.owner.none": "—",
|
|
242
260
|
"exec.action.advance-task": "Advance task",
|
|
243
261
|
"exec.action.wait-for-agents": "Wait for agents",
|
|
262
|
+
"exec.action.recover-execution": "Recover execution",
|
|
244
263
|
"exec.action.answer-input": "Answer input",
|
|
245
264
|
"exec.action.inspect-attention": "Inspect attention",
|
|
246
265
|
"exec.action.recover-leader": "Recover leader",
|
|
@@ -270,6 +289,20 @@ const messages = {
|
|
|
270
289
|
"lane.yielded": "Yielded",
|
|
271
290
|
"lane.completed": "Completed",
|
|
272
291
|
"lane.failed": "Failed",
|
|
292
|
+
"lane.skipped": "Skipped",
|
|
293
|
+
"dag.ready": "Ready",
|
|
294
|
+
"dag.dependsOn": "Dependencies",
|
|
295
|
+
"dag.rootCause": "Root cause",
|
|
296
|
+
"dag.blocked": "Blocked",
|
|
297
|
+
"dag.running": "Running",
|
|
298
|
+
"dag.awaiting_acceptance": "Awaiting acceptance",
|
|
299
|
+
"dag.completed": "Completed",
|
|
300
|
+
"dag.failed": "Failed",
|
|
301
|
+
"dag.retired": "Retired",
|
|
302
|
+
"dag.edge.satisfied": "satisfied",
|
|
303
|
+
"dag.edge.active": "active",
|
|
304
|
+
"dag.edge.failed-open": "failed",
|
|
305
|
+
"dag.edge.dead": "missing",
|
|
273
306
|
"resolution.accept": "Accepted",
|
|
274
307
|
"resolution.reject": "Rejected",
|
|
275
308
|
"resolution.retry": "Retry",
|
|
@@ -358,6 +391,24 @@ const messages = {
|
|
|
358
391
|
"detail.constraints": "约束",
|
|
359
392
|
"detail.decisions": "决策",
|
|
360
393
|
"detail.dependsOn": "依赖",
|
|
394
|
+
"detail.dag": "任务 DAG",
|
|
395
|
+
"detail.stage": "阶段",
|
|
396
|
+
"detail.round": "轮次",
|
|
397
|
+
"detail.stageAttempt": "阶段尝试",
|
|
398
|
+
"detail.cost": "成本",
|
|
399
|
+
"detail.tokens": "Token",
|
|
400
|
+
"detail.toolCalls": "工具调用",
|
|
401
|
+
"detail.wallClock": "墙钟时间",
|
|
402
|
+
"detail.ready": "可就绪",
|
|
403
|
+
"detail.quorum": "法定数量",
|
|
404
|
+
"detail.contextSnapshots": "上下文快照",
|
|
405
|
+
"detail.contextPeak": "上下文峰值",
|
|
406
|
+
"detail.contextBytes": "上下文字节",
|
|
407
|
+
"detail.compression": "压缩",
|
|
408
|
+
"detail.marginalValue": "边际价值",
|
|
409
|
+
"detail.partial": "部分可见",
|
|
410
|
+
"detail.unavailable": "不可用",
|
|
411
|
+
"detail.openFindings": "未解决发现",
|
|
361
412
|
"detail.desired": "期望启动配置",
|
|
362
413
|
"detail.desiredAgent": "期望 Agent",
|
|
363
414
|
"detail.due": "截止时间",
|
|
@@ -544,6 +595,7 @@ const messages = {
|
|
|
544
595
|
"exec.owner.none": "—",
|
|
545
596
|
"exec.action.advance-task": "推进任务",
|
|
546
597
|
"exec.action.wait-for-agents": "等待智能体",
|
|
598
|
+
"exec.action.recover-execution": "恢复执行",
|
|
547
599
|
"exec.action.answer-input": "回答输入",
|
|
548
600
|
"exec.action.inspect-attention": "检查注意项",
|
|
549
601
|
"exec.action.recover-leader": "恢复负责人",
|
|
@@ -573,6 +625,20 @@ const messages = {
|
|
|
573
625
|
"lane.yielded": "已交付",
|
|
574
626
|
"lane.completed": "已完成",
|
|
575
627
|
"lane.failed": "失败",
|
|
628
|
+
"lane.skipped": "已跳过",
|
|
629
|
+
"dag.ready": "可就绪",
|
|
630
|
+
"dag.dependsOn": "依赖",
|
|
631
|
+
"dag.rootCause": "根因",
|
|
632
|
+
"dag.blocked": "阻塞",
|
|
633
|
+
"dag.running": "运行中",
|
|
634
|
+
"dag.awaiting_acceptance": "等待接受",
|
|
635
|
+
"dag.completed": "已完成",
|
|
636
|
+
"dag.failed": "失败",
|
|
637
|
+
"dag.retired": "已退役",
|
|
638
|
+
"dag.edge.satisfied": "已满足",
|
|
639
|
+
"dag.edge.active": "进行中",
|
|
640
|
+
"dag.edge.failed-open": "失败传播",
|
|
641
|
+
"dag.edge.dead": "缺失",
|
|
576
642
|
"resolution.accept": "已接受",
|
|
577
643
|
"resolution.reject": "已拒绝",
|
|
578
644
|
"resolution.retry": "已重试",
|