@zq-silk/yui 0.8.9 → 0.10.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/ARCHITECTURE.md +48 -46
- package/README.md +72 -42
- package/dist/cli/commandCatalog.js +16 -8
- package/dist/cli.js +27 -32
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/globalRoleCommands.js +0 -12
- package/dist/commands/sessionCommands.js +116 -0
- package/dist/commands/taskBaseCommands.js +1 -11
- package/dist/commands/taskCommands.js +123 -340
- package/dist/commands/taskCompletionGate.js +15 -12
- package/dist/commands/taskContextCommand.js +18 -11
- package/dist/commands/taskNextActionCommand.js +4 -5
- package/dist/commands/taskWorkspaceCommands.js +2 -2
- package/dist/controller/clientRuntime.js +65 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2 -49
- package/dist/doctor/doctor.js +16 -12
- package/dist/execution/executionGroup.js +0 -3
- package/dist/executor/agentAdapter.js +39 -42
- package/dist/executor/agentExecutor.js +4 -2
- package/dist/executor/codexConfigConflict.js +40 -16
- package/dist/executor/effectiveLaunch.js +33 -3
- package/dist/executor/fileRoleLaunchPlanner.js +15 -24
- package/dist/integration/deliveryObligation.js +72 -0
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/lifecycle/exactRunTerminalization.js +12 -8
- package/dist/observability/orchestrationMetrics.js +12 -26
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/taskBaseFreshness.js +5 -11
- package/dist/repository/taskWorkspaceCoordinator.js +2 -0
- package/dist/repository/taskWorkspacePreparer.js +173 -26
- package/dist/review/reviewRound.js +41 -24
- package/dist/role/role.js +0 -9
- package/dist/runtime/{firstProgressStopLoss.js → firstProgressAdvisory.js} +7 -21
- package/dist/scheduler/leaderWakeupProcessor.js +0 -25
- package/dist/setup/setupCommand.js +0 -5
- package/dist/storage/migration/productionRegistry.js +138 -0
- package/dist/storage/sqliteStore.js +2 -1
- package/dist/storage/taskStore.js +27 -27
- package/dist/storage/upgrade/upgradeOrchestrator.js +35 -7
- package/dist/task/completionReadiness.js +35 -25
- package/dist/task/nextAction.js +70 -78
- package/dist/task/task.js +12 -19
- package/dist/web/assets/client/components.js +3 -1
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/webSnapshot.js +0 -3
- package/i18n/README.zh-CN.md +33 -24
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +55 -52
- package/skills/yui-operator/SKILL.md +31 -40
- package/skills/yui-reviewer/SKILL.md +18 -12
- package/skills/yui-worker/SKILL.md +5 -3
package/dist/task/nextAction.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { changeSetDeliverySettled, governingChangeSets } from "../integration/deliveryObligation.js";
|
|
2
3
|
import { deltaRecheckBlocksAcceptance } from "../review/reviewRound.js";
|
|
3
4
|
import { classifyReviewRoundOutcome, isSemanticReviewRound } from "../review/reviewOutcomeClassifier.js";
|
|
4
5
|
import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractRebind.js";
|
|
5
|
-
import { taskDeliveryPath } from "./task.js";
|
|
6
6
|
import { currentWorkItemCandidate, governingWorkItemCandidate } from "../workItem/workItem.js";
|
|
7
7
|
const OPEN_WORK_ITEM_STATUSES = new Set(["pending", "running", "awaiting_acceptance"]);
|
|
8
8
|
export function projectNextAction(facts) {
|
|
@@ -70,7 +70,7 @@ export function projectNextAction(facts) {
|
|
|
70
70
|
const candidateRef = ref("candidate", `${candidateReady.id}/${candidate.id}`);
|
|
71
71
|
return buildAction(facts, {
|
|
72
72
|
kind: "repair-protocol-inconsistency",
|
|
73
|
-
reason: `Candidate ${candidate.id} is a
|
|
73
|
+
reason: `Candidate ${candidate.id} is a Task-main delivery with base==head (no commits); reject it and re-dispatch real work.`,
|
|
74
74
|
refs: [ref("work-item", candidateReady.id), candidateRef],
|
|
75
75
|
conflicts: [candidateRef],
|
|
76
76
|
preconditions: [
|
|
@@ -254,42 +254,38 @@ export function projectNextAction(facts) {
|
|
|
254
254
|
judgmentRequired: "Leader must choose the execution path: direct execution, a native subagent, or managed Task Role dispatch."
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
|
-
if (facts.workItems.length === 0
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
],
|
|
290
|
-
recommendedCommand: `yui task work create ${task.id} \"<objective>\"`
|
|
291
|
-
});
|
|
292
|
-
}
|
|
257
|
+
if (facts.workItems.length === 0
|
|
258
|
+
&& !taskFinalReviewRequired(facts)
|
|
259
|
+
&& !facts.reviewRounds.some((round) => ((round.scope ?? "work-item") === "task"
|
|
260
|
+
&& (round.status === "pending" || round.status === "running")))) {
|
|
261
|
+
const reviewAlternative = facts.reviewConfig === null
|
|
262
|
+
? []
|
|
263
|
+
: [{
|
|
264
|
+
kind: "request-final-review",
|
|
265
|
+
reason: "Request one independent Review of the frozen Task result when risk warrants it.",
|
|
266
|
+
recommendedCommand: `yui task review request ${task.id} --role ${facts.reviewConfig.roleName}`,
|
|
267
|
+
refs: [ref("task", task.id)]
|
|
268
|
+
}];
|
|
269
|
+
return buildAction(facts, {
|
|
270
|
+
kind: "complete-task",
|
|
271
|
+
reason: task.type === "bugfix"
|
|
272
|
+
? `Task ${task.id} is a Leader-owned bugfix; implement and verify it on Task main without manufacturing a WorkItem.`
|
|
273
|
+
: task.type === "feature"
|
|
274
|
+
? `Feature ${task.id} has no independent delivery units; the Leader may implement it on Task main or create WorkItems only if separate ownership is genuinely useful.`
|
|
275
|
+
: `Task ${task.id} has no independent delivery units; the Leader decides whether to own it on Task main or create WorkItems only if separate ownership is genuinely useful.`,
|
|
276
|
+
refs: [ref("task", task.id)],
|
|
277
|
+
preconditions: [
|
|
278
|
+
{ fact: "Task is active", satisfied: task.status === "active", ref: ref("task", task.id) },
|
|
279
|
+
{ fact: "Task main is clean, committed, and verified", satisfied: false }
|
|
280
|
+
],
|
|
281
|
+
recommendedCommand: `yui task complete ${task.id} --summary-file -`,
|
|
282
|
+
...(reviewAlternative.length === 0 ? {} : { alternatives: reviewAlternative }),
|
|
283
|
+
judgmentRequired: task.type === "bugfix"
|
|
284
|
+
? "Leader must judge whether the bugfix risk warrants one optional final Review."
|
|
285
|
+
: task.type === "feature"
|
|
286
|
+
? "Leader must judge whether this feature is small enough to own directly or needs independently owned WorkItems, and whether the final result warrants Review."
|
|
287
|
+
: "Leader must choose the smallest useful topology from the Project-defined Task intent, then decide whether the frozen result warrants Review."
|
|
288
|
+
});
|
|
293
289
|
}
|
|
294
290
|
const uncaptured = facts.workItems.find((item) => needsChangeSetCapture(facts, item));
|
|
295
291
|
if (uncaptured !== undefined) {
|
|
@@ -304,8 +300,8 @@ export function projectNextAction(facts) {
|
|
|
304
300
|
recommendedCommand: `yui task work capture ${task.id}/${uncaptured.id}`
|
|
305
301
|
});
|
|
306
302
|
}
|
|
307
|
-
const unintegrated = facts.changeSets
|
|
308
|
-
.find((changeSet) => !
|
|
303
|
+
const unintegrated = governingChangeSets(facts.workItems, facts.changeSets)
|
|
304
|
+
.find((changeSet) => !changeSetDeliverySettled(changeSet, facts.integrations, facts.integrationQueueEntries));
|
|
309
305
|
if (unintegrated !== undefined) {
|
|
310
306
|
return buildAction(facts, {
|
|
311
307
|
kind: "integrate-change-set",
|
|
@@ -318,11 +314,13 @@ export function projectNextAction(facts) {
|
|
|
318
314
|
recommendedCommand: `yui task integration start ${task.id} --project ${unintegrated.projectId} --change-set ${unintegrated.id}`
|
|
319
315
|
});
|
|
320
316
|
}
|
|
317
|
+
const finalReviewRequired = taskFinalReviewRequired(facts);
|
|
321
318
|
const failedFinal = latestTaskFinalReview(facts.reviewRounds);
|
|
322
319
|
const failedFinalOutcome = failedFinal === undefined
|
|
323
320
|
? null
|
|
324
321
|
: classifyReviewRoundOutcome(failedFinal, nextActionReviewOutcomeEvidence(facts));
|
|
325
|
-
if (
|
|
322
|
+
if (finalReviewRequired
|
|
323
|
+
&& failedFinal !== undefined && failedFinalOutcome?.kind === "non-semantic") {
|
|
326
324
|
return buildAction(facts, {
|
|
327
325
|
kind: "resume-review",
|
|
328
326
|
reason: `Task-final Review ${failedFinal.id} ended before a semantic review was proven.`,
|
|
@@ -333,7 +331,8 @@ export function projectNextAction(facts) {
|
|
|
333
331
|
recommendedCommand: `yui task review force-fresh ${task.id}/${failedFinal.id}`
|
|
334
332
|
});
|
|
335
333
|
}
|
|
336
|
-
if (
|
|
334
|
+
if (finalReviewRequired
|
|
335
|
+
&& failedFinal !== undefined && failedFinalOutcome?.kind === "ambiguous") {
|
|
337
336
|
return buildAction(facts, {
|
|
338
337
|
kind: "repair-protocol-inconsistency",
|
|
339
338
|
reason: `Task-final Review ${failedFinal.id} has ambiguous semantic and infrastructure evidence: ${failedFinalOutcome.reason}`,
|
|
@@ -344,7 +343,8 @@ export function projectNextAction(facts) {
|
|
|
344
343
|
]
|
|
345
344
|
});
|
|
346
345
|
}
|
|
347
|
-
if (
|
|
346
|
+
if (finalReviewRequired
|
|
347
|
+
&& failedFinal !== undefined
|
|
348
348
|
&& failedFinalOutcome?.kind === "semantic"
|
|
349
349
|
&& ((failedFinal.checks ?? []).some(({ outcome }) => outcome === "failed")
|
|
350
350
|
|| deltaRecheckBlocksAcceptance(failedFinal))) {
|
|
@@ -423,33 +423,29 @@ export function projectNextAction(facts) {
|
|
|
423
423
|
recommendedCommand: `yui task review retry ${task.id}/${activeFinal.id}`
|
|
424
424
|
});
|
|
425
425
|
}
|
|
426
|
-
const finalReviewRequired = taskFinalReviewRequired(facts);
|
|
427
426
|
if (task.projectBindings.length > 0
|
|
428
427
|
&& finalReviewRequired
|
|
429
428
|
&& !hasValidFinalReview(facts)) {
|
|
430
429
|
const reviewerRole = taskFinalReviewRole(facts);
|
|
431
|
-
const directWithoutWorkItems = taskDeliveryPath(task) === "direct"
|
|
432
|
-
&& facts.workItems.length === 0;
|
|
433
430
|
return buildAction(facts, {
|
|
434
431
|
kind: "request-final-review",
|
|
435
|
-
reason:
|
|
436
|
-
? "This
|
|
437
|
-
: "All
|
|
432
|
+
reason: facts.workItems.length === 0
|
|
433
|
+
? "This Task already owns a final-Review obligation; completion must prepare or resume a Review of its frozen Task head."
|
|
434
|
+
: "All WorkItems are integrated but no valid Task-final Review attests the frozen Task result.",
|
|
438
435
|
refs: [ref("task", task.id)],
|
|
439
|
-
preconditions:
|
|
436
|
+
preconditions: facts.workItems.length === 0
|
|
440
437
|
? [{ fact: "Valid established Task-final Review at the direct head", satisfied: false }]
|
|
441
438
|
: [
|
|
442
439
|
{ fact: "All Work Items are terminal", satisfied: true },
|
|
443
|
-
{ fact: "Every ChangeSet is
|
|
440
|
+
{ fact: "Every governing ChangeSet is settled", satisfied: true },
|
|
444
441
|
{ fact: "Valid Task-final Review at the integrated head", satisfied: false }
|
|
445
442
|
],
|
|
446
|
-
recommendedCommand:
|
|
443
|
+
recommendedCommand: facts.workItems.length === 0
|
|
447
444
|
? `yui task complete ${task.id} --summary-file -`
|
|
448
445
|
: `yui task review request ${task.id} --role ${reviewerRole ?? "<reviewer-role>"}`
|
|
449
446
|
});
|
|
450
447
|
}
|
|
451
|
-
const finalReviewOptional =
|
|
452
|
-
&& !finalReviewRequired
|
|
448
|
+
const finalReviewOptional = !finalReviewRequired
|
|
453
449
|
&& !hasValidFinalReview(facts);
|
|
454
450
|
const finalReviewAlternative = finalReviewOptional && facts.reviewConfig !== null
|
|
455
451
|
? [{
|
|
@@ -461,32 +457,31 @@ export function projectNextAction(facts) {
|
|
|
461
457
|
: [];
|
|
462
458
|
return buildAction(facts, {
|
|
463
459
|
kind: "complete-task",
|
|
464
|
-
reason:
|
|
465
|
-
? "The
|
|
466
|
-
: "
|
|
460
|
+
reason: facts.workItems.length === 0
|
|
461
|
+
? "The Leader-owned Task result and its established obligations are ready; complete it without creating successor work."
|
|
462
|
+
: "Every independent delivery unit is integrated; complete the Task instead of creating successor work.",
|
|
467
463
|
refs: [ref("task", task.id)],
|
|
468
464
|
preconditions: [
|
|
469
465
|
{ fact: "All Work Items are terminal", satisfied: true },
|
|
470
466
|
...(task.projectBindings.length === 0
|
|
471
467
|
? []
|
|
472
|
-
:
|
|
473
|
-
? [{
|
|
474
|
-
fact: "Valid established Task-final Review at the direct head",
|
|
475
|
-
satisfied: hasValidFinalReview(facts)
|
|
476
|
-
}]
|
|
468
|
+
: facts.workItems.length === 0
|
|
469
|
+
? [{ fact: "Task main is clean, committed, and verified", satisfied: false }]
|
|
477
470
|
: [
|
|
478
|
-
{ fact: "Every ChangeSet is
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
471
|
+
{ fact: "Every governing ChangeSet is settled", satisfied: true },
|
|
472
|
+
...(finalReviewRequired
|
|
473
|
+
? [{
|
|
474
|
+
fact: "Valid Task-final Review at the integrated head",
|
|
475
|
+
satisfied: hasValidFinalReview(facts)
|
|
476
|
+
}]
|
|
477
|
+
: [])
|
|
483
478
|
])
|
|
484
479
|
],
|
|
485
480
|
...(finalReviewAlternative.length === 0 ? {} : { alternatives: finalReviewAlternative }),
|
|
486
481
|
...(!finalReviewOptional
|
|
487
482
|
? {}
|
|
488
483
|
: {
|
|
489
|
-
judgmentRequired: "Leader must decide whether the
|
|
484
|
+
judgmentRequired: "Leader must decide whether the frozen Task result is safe to complete or needs one optional Task-final Review."
|
|
490
485
|
}),
|
|
491
486
|
recommendedCommand: `yui task complete ${task.id} --summary-file -`
|
|
492
487
|
});
|
|
@@ -502,6 +497,7 @@ export function durableStateFingerprint(facts) {
|
|
|
502
497
|
...facts.workItems.map((item) => `work:${item.id}:${item.status}:${item.revision}:${item.updatedAt}`),
|
|
503
498
|
...facts.changeSets.map((changeSet) => `change-set:${changeSet.id}:${changeSet.headCommit}`),
|
|
504
499
|
...facts.integrations.map((attempt) => `integration:${attempt.id}:${attempt.status}:${attempt.updatedAt}`),
|
|
500
|
+
...facts.integrationQueueEntries.map((entry) => `integration-queue:${entry.id}:${entry.status}:${entry.updatedAt}`),
|
|
505
501
|
...facts.reviewRounds.map((round) => `review:${round.id}:${round.status}:${round.endedAt ?? ""}`),
|
|
506
502
|
...facts.taskFinalReviewContractEvents.map((event) => `task-final-review-event:${event.id}:${event.createdAt}`)
|
|
507
503
|
];
|
|
@@ -684,10 +680,7 @@ function taskFinalReviewContractResolution(facts) {
|
|
|
684
680
|
return resolveRecordedTaskFinalReviewContract(facts.task.id, facts.workItems, facts.reviewRounds, facts.taskFinalReviewContractEvents);
|
|
685
681
|
}
|
|
686
682
|
function taskFinalReviewRequired(facts) {
|
|
687
|
-
return taskFinalReviewContract(facts) !== undefined
|
|
688
|
-
|| latestTaskFinalReview(facts.reviewRounds) !== undefined
|
|
689
|
-
|| (taskDeliveryPath(facts.task) === "integrated"
|
|
690
|
-
&& facts.reviewConfig?.trigger === "final");
|
|
683
|
+
return taskFinalReviewContract(facts) !== undefined;
|
|
691
684
|
}
|
|
692
685
|
function taskFinalReviewRole(facts) {
|
|
693
686
|
return taskFinalReviewContract(facts)?.reviewerRoleName
|
|
@@ -713,18 +706,15 @@ function latestTaskFinalReview(rounds) {
|
|
|
713
706
|
.reverse()
|
|
714
707
|
.find((round) => (round.scope ?? "work-item") === "task");
|
|
715
708
|
}
|
|
716
|
-
function hasCommittedIntegration(integrations, changeSetId) {
|
|
717
|
-
return integrations.some((attempt) => attempt.status === "committed" && attempt.changeSetIds.includes(changeSetId));
|
|
718
|
-
}
|
|
719
709
|
function needsChangeSetCapture(facts, item) {
|
|
720
710
|
if (item.status !== "completed")
|
|
721
711
|
return false;
|
|
722
|
-
if (facts.changeSets
|
|
712
|
+
if (governingChangeSets([item], facts.changeSets).length > 0)
|
|
723
713
|
return false;
|
|
724
714
|
const candidate = item.candidates.at(-1);
|
|
725
715
|
if (candidate === undefined)
|
|
726
716
|
return false;
|
|
727
|
-
// A metadata-only
|
|
717
|
+
// A metadata-only Task-main Candidate has no WorkItem Develop
|
|
728
718
|
// workspace to capture; its boundary is the Task-main head itself.
|
|
729
719
|
if (candidate.workspace === undefined
|
|
730
720
|
&& candidate.gitSnapshot === undefined
|
|
@@ -750,7 +740,7 @@ function hasValidFinalReview(facts) {
|
|
|
750
740
|
.filter((changeSet) => attempt.changeSetIds.includes(changeSet.id))
|
|
751
741
|
.map((changeSet) => changeSet.headCommit)));
|
|
752
742
|
if (integratedHeads.size === 0)
|
|
753
|
-
return
|
|
743
|
+
return facts.workItems.length === 0;
|
|
754
744
|
for (const head of integratedHeads) {
|
|
755
745
|
if (!reviewedCommits.has(head))
|
|
756
746
|
return false;
|
|
@@ -816,6 +806,8 @@ function detectProtocolInconsistency(facts) {
|
|
|
816
806
|
for (const round of facts.reviewRounds) {
|
|
817
807
|
if (round.status !== "pending" && round.status !== "running")
|
|
818
808
|
continue;
|
|
809
|
+
if (round.workItemId === undefined)
|
|
810
|
+
continue;
|
|
819
811
|
const item = workItemById.get(round.workItemId);
|
|
820
812
|
if (item !== undefined && item.status === "retired") {
|
|
821
813
|
return {
|
package/dist/task/task.js
CHANGED
|
@@ -2,7 +2,7 @@ import { validateTaskWorkspaceIdentity } from "../repository/taskWorkspaceIdenti
|
|
|
2
2
|
export function createTask(id, title, now, metadata = {}) {
|
|
3
3
|
const timestamp = now.toISOString();
|
|
4
4
|
return {
|
|
5
|
-
schemaVersion:
|
|
5
|
+
schemaVersion: 5,
|
|
6
6
|
id: requireSafeIdentity(id, "Task id"),
|
|
7
7
|
title: requireText(title, "Task title"),
|
|
8
8
|
...cloneMetadata(metadata),
|
|
@@ -141,6 +141,7 @@ export function updateTaskMetadata(task, metadata, now) {
|
|
|
141
141
|
const updated = { ...task, updatedAt: now.toISOString() };
|
|
142
142
|
if (metadata.title !== undefined)
|
|
143
143
|
updated.title = requireText(metadata.title, "Task title");
|
|
144
|
+
applyOptional(updated, "type", metadata.type);
|
|
144
145
|
applyOptional(updated, "description", metadata.description);
|
|
145
146
|
applyOptional(updated, "priority", metadata.priority);
|
|
146
147
|
applyOptional(updated, "tags", metadata.tags === undefined || metadata.tags === null
|
|
@@ -152,8 +153,6 @@ export function updateTaskMetadata(task, metadata, now) {
|
|
|
152
153
|
}
|
|
153
154
|
if (metadata.cwd !== undefined)
|
|
154
155
|
updated.cwd = requireText(metadata.cwd, "Task workspace");
|
|
155
|
-
if (metadata.requireIntegration === true)
|
|
156
|
-
updated.requireIntegration = true;
|
|
157
156
|
return updated;
|
|
158
157
|
}
|
|
159
158
|
function applyOptional(task, key, value) {
|
|
@@ -171,10 +170,12 @@ export function isTaskArchived(task) {
|
|
|
171
170
|
return task.status === "archived";
|
|
172
171
|
}
|
|
173
172
|
export function validateTask(task) {
|
|
174
|
-
if (task.schemaVersion !==
|
|
175
|
-
throw new Error("Task must use schemaVersion
|
|
173
|
+
if (task.schemaVersion !== 5)
|
|
174
|
+
throw new Error("Task must use schemaVersion 5.");
|
|
176
175
|
requireSafeIdentity(task.id, "Task id");
|
|
177
176
|
requireText(task.title, "Task title");
|
|
177
|
+
if (task.type !== undefined)
|
|
178
|
+
requireSafeIdentity(task.type, "Task type");
|
|
178
179
|
if (!["draft", "active", "completed", "retired", "archived"].includes(task.status)) {
|
|
179
180
|
throw new Error(`Task status is invalid: ${String(task.status)}.`);
|
|
180
181
|
}
|
|
@@ -206,8 +207,10 @@ export function validateTask(task) {
|
|
|
206
207
|
normalizeProjectBindings(task.projectBindings);
|
|
207
208
|
if (task.cwd !== undefined)
|
|
208
209
|
requireText(task.cwd, "Task workspace");
|
|
209
|
-
if (task.
|
|
210
|
-
|
|
210
|
+
if (task.legacyDeliveryPath !== undefined
|
|
211
|
+
&& task.legacyDeliveryPath !== "direct"
|
|
212
|
+
&& task.legacyDeliveryPath !== "integrated") {
|
|
213
|
+
throw new Error("Task legacy delivery path is invalid.");
|
|
211
214
|
}
|
|
212
215
|
const completionFields = [task.completedAt, task.completedBy, task.completionSummary];
|
|
213
216
|
const hasAnyCompletion = completionFields.some((value) => value !== undefined);
|
|
@@ -283,13 +286,13 @@ export function validateTask(task) {
|
|
|
283
286
|
}
|
|
284
287
|
function cloneMetadata(metadata) {
|
|
285
288
|
const cloned = {
|
|
289
|
+
...(metadata.type === undefined ? {} : { type: requireSafeIdentity(metadata.type, "Task type") }),
|
|
286
290
|
...(metadata.description === undefined ? {} : { description: metadata.description }),
|
|
287
291
|
...(metadata.priority === undefined ? {} : { priority: metadata.priority }),
|
|
288
292
|
...(metadata.tags === undefined ? {} : { tags: [...metadata.tags] }),
|
|
289
293
|
...(metadata.dueAt === undefined ? {} : { dueAt: metadata.dueAt }),
|
|
290
294
|
projectBindings: normalizeProjectBindings(metadata.projectBindings ?? []),
|
|
291
|
-
...(metadata.cwd === undefined ? {} : { cwd: requireText(metadata.cwd, "Task workspace") })
|
|
292
|
-
...(metadata.requireIntegration === true ? { requireIntegration: true } : {})
|
|
295
|
+
...(metadata.cwd === undefined ? {} : { cwd: requireText(metadata.cwd, "Task workspace") })
|
|
293
296
|
};
|
|
294
297
|
return cloned;
|
|
295
298
|
}
|
|
@@ -319,16 +322,6 @@ export function taskProjectBinding(task, projectId) {
|
|
|
319
322
|
export function taskHasProjects(task) {
|
|
320
323
|
return task.projectBindings.length > 0;
|
|
321
324
|
}
|
|
322
|
-
/**
|
|
323
|
-
* Delivery is a projection of the existing Task contract, not a second source
|
|
324
|
-
* of truth. Keeping it derived preserves every stored Task schema while giving
|
|
325
|
-
* callers one product-level name for the three supported paths.
|
|
326
|
-
*/
|
|
327
|
-
export function taskDeliveryPath(task) {
|
|
328
|
-
if (task.projectBindings.length === 0)
|
|
329
|
-
return "no-project";
|
|
330
|
-
return task.requireIntegration === true ? "integrated" : "direct";
|
|
331
|
-
}
|
|
332
325
|
export function taskProjectIds(task) {
|
|
333
326
|
return task.projectBindings.map(({ projectId }) => projectId);
|
|
334
327
|
}
|
|
@@ -577,7 +577,9 @@ export function reviewCard(round, t, locale) {
|
|
|
577
577
|
card.append(head);
|
|
578
578
|
const meta = node("div", "record-meta");
|
|
579
579
|
meta.append(node("span", "meta-name", round.reviewerRoleName));
|
|
580
|
-
|
|
580
|
+
if (round.workItemId && round.candidateId) {
|
|
581
|
+
meta.append(node("span", "mono", round.workItemId + " · " + round.candidateId));
|
|
582
|
+
}
|
|
581
583
|
if (round.createdAt) meta.append(node("time", "", formatDateTime(round.createdAt, locale)));
|
|
582
584
|
meta.append(node("span", "", t("detail.reviewBase") + " · " + round.reviewBaseCommit));
|
|
583
585
|
if (round.workspace && round.workspace.root) {
|
|
@@ -106,6 +106,9 @@ const messages = {
|
|
|
106
106
|
"detail.writeProjects": "Writable Projects",
|
|
107
107
|
"disposition.abandoned": "Abandoned",
|
|
108
108
|
"disposition.integrated": "Integrated",
|
|
109
|
+
"disposition.preserved": "Preserved",
|
|
110
|
+
"disposition.reassigned": "Continued by a newer review",
|
|
111
|
+
"disposition.removed": "Removed",
|
|
109
112
|
"empty.brief": "No task brief has been recorded.",
|
|
110
113
|
"empty.none": "No items",
|
|
111
114
|
"empty.runs": "No runs yet.",
|
|
@@ -406,6 +409,9 @@ const messages = {
|
|
|
406
409
|
"detail.writeProjects": "可写项目",
|
|
407
410
|
"disposition.abandoned": "已废弃",
|
|
408
411
|
"disposition.integrated": "已集成",
|
|
412
|
+
"disposition.preserved": "已保留",
|
|
413
|
+
"disposition.reassigned": "已由后续评审继续使用",
|
|
414
|
+
"disposition.removed": "已移除",
|
|
409
415
|
"empty.brief": "尚未记录任务简报。",
|
|
410
416
|
"empty.none": "暂无条目",
|
|
411
417
|
"empty.runs": "暂无运行记录。",
|
package/dist/web/webSnapshot.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { taskDeliveryPath } from "../task/task.js";
|
|
2
1
|
import { isRoleRunStalled, latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
|
|
3
2
|
import { buildTaskExecutionProjection } from "../scheduler/taskExecutionProjection.js";
|
|
4
3
|
import { summarizeExecutionGroup } from "../execution/executionGroup.js";
|
|
@@ -41,7 +40,6 @@ export function buildWebDashboardSnapshot(store, now = new Date()) {
|
|
|
41
40
|
});
|
|
42
41
|
return {
|
|
43
42
|
...task,
|
|
44
|
-
deliveryPath: taskDeliveryPath(task),
|
|
45
43
|
...(names.length === 0 ? {} : { projectNames: names }),
|
|
46
44
|
workItems: countWorkItems(reader.listWorkItems(task.id)),
|
|
47
45
|
roleCount: reader.listRoles(task.id).length,
|
|
@@ -110,7 +108,6 @@ export function buildWebTaskDetail(store, taskId, now = new Date()) {
|
|
|
110
108
|
return {
|
|
111
109
|
task: {
|
|
112
110
|
...task,
|
|
113
|
-
deliveryPath: taskDeliveryPath(task),
|
|
114
111
|
...(projectNames.length === 0 ? {} : { projectNames })
|
|
115
112
|
},
|
|
116
113
|
execution: buildTaskExecutionProjection(reader, taskId),
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -106,8 +106,8 @@ yui project add app /absolute/workspace/app \
|
|
|
106
106
|
yui project update app --alias app-cli --development develop
|
|
107
107
|
yui project list
|
|
108
108
|
|
|
109
|
-
yui task create "修复 CSV 转义" --project app --
|
|
110
|
-
yui task create "交付 CSV 导出" --project app --
|
|
109
|
+
yui task create "修复 CSV 转义" --project app --type bugfix
|
|
110
|
+
yui task create "交付 CSV 导出" --project app --type feature
|
|
111
111
|
yui task update <task-id> --priority high --tags release,csv --due-at 2026-08-01T00:00:00Z
|
|
112
112
|
yui task update <task-id> --clear-priority --clear-tags --clear-due-at
|
|
113
113
|
yui task show <task-id>
|
|
@@ -115,15 +115,14 @@ yui task context <task-id>
|
|
|
115
115
|
yui task activate <task-id>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
Project
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
旧的 `--require-integration` 等价于 `--delivery integrated`,且 integrated 不可降级。
|
|
118
|
+
Task type 描述需求意图,不选择执行协议。软件 Project 通常使用 `bugfix` 或
|
|
119
|
+
`feature`:bugfix 由 Leader 在 Task main 独立、快速完成;如果范围扩大到需要
|
|
120
|
+
独立 owner,应先改为 feature 再创建 WorkItem。feature 由 Leader 判断是自己直接
|
|
121
|
+
交付,还是拆成由不同 Worker 独立负责、可并行推进的较大
|
|
122
|
+
WorkItem。实现步骤、测试、review finding 和局部修复都不是 WorkItem。只有当一项
|
|
123
|
+
需求本身具有独立 owner 和可验收结果时才创建 WorkItem。只有当前 governing
|
|
124
|
+
Candidate 的 ChangeSet 是交付义务:它们必须通过 committed Integration 汇总回
|
|
125
|
+
Task main,或由 Leader 在队列中显式 supersede;旧 Candidate 和 ChangeSet 只保留为审计证据。
|
|
127
126
|
|
|
128
127
|
面向用户的时间默认按北京时间(`Asia/Shanghai`)显示;持久化记录和
|
|
129
128
|
`--json` 数据仍使用 UTC/RFC 3339。可通过以下命令查看或修改 IANA 时区:
|
|
@@ -142,9 +141,8 @@ yui config show
|
|
|
142
141
|
yui config workflow clear review
|
|
143
142
|
```
|
|
144
143
|
|
|
145
|
-
对带 Project 的软件交付,可使用 `--trigger final
|
|
146
|
-
|
|
147
|
-
一次 Task 级 ReviewRound:
|
|
144
|
+
对带 Project 的软件交付,可使用 `--trigger final` 提供默认 Reviewer Role;
|
|
145
|
+
是否需要独立的 Task-final Review 仍由 Leader 根据风险判断:
|
|
148
146
|
|
|
149
147
|
```sh
|
|
150
148
|
yui config workflow set review --role reviewer --trigger final
|
|
@@ -161,11 +159,13 @@ AgentRun 不创建新 WorkItem,也不会递归触发审查。审查以自然
|
|
|
161
159
|
唤醒 Leader;Leader 决定验收、reject 后在原 Role 与原 Session 中修复、
|
|
162
160
|
再次审查,或通过 InputRequest 询问用户。审查失败会保留为可见证据并
|
|
163
161
|
唤醒 Leader,但不会取代 Leader 的最终判断。
|
|
164
|
-
`final` 不为每个 WorkItem 创建完整 ReviewRound
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
162
|
+
`final` 不为每个 WorkItem 创建完整 ReviewRound,也不决定 Task 拓扑。Leader
|
|
163
|
+
显式请求 Task 级 Review;不可变 Task contract 也可以强制要求。Task-final Round
|
|
164
|
+
直接冻结 Task main,不需要虚构 WorkItem/Candidate,因此没有 WorkItem 的小任务也能
|
|
165
|
+
review。冻结头变化时创建新的语义 Round;同一 Reviewer 的兼容原生 Session 可以在
|
|
166
|
+
稳定 workspace 中继续,而每个 Run 仍严格绑定自己的 Round 和冻结头。旧报告保留为
|
|
167
|
+
证据。Reviewer 按 Project Policy/Knowledge 检查整个 Task,并只报告有直接证据的
|
|
168
|
+
可达、重要、可行动问题或有限验证缺口。
|
|
169
169
|
所有候选、ReviewRound 和 Leader 决策都集中在原 WorkItem 下;reject
|
|
170
170
|
后的下一轮会复用原执行 Role、Session 与 workspace,并追加新候选。
|
|
171
171
|
|
|
@@ -476,6 +476,7 @@ Task Role 使用以下显式入口:
|
|
|
476
476
|
|
|
477
477
|
```sh
|
|
478
478
|
yui session enter <global-role>
|
|
479
|
+
yui session stop --all
|
|
479
480
|
yui task role view <task-id> <role>
|
|
480
481
|
yui task role takeover <task-id> <role>
|
|
481
482
|
yui task role release <task-id> <role>
|
|
@@ -483,12 +484,20 @@ yui task role release <task-id> <role>
|
|
|
483
484
|
|
|
484
485
|
`view` 始终只读。`takeover` 要求存在 active managed Run 且没有未决 Turn;它先以持久 CAS 把唯一 writer authority 转给人工 holder,再把相同 epoch 同步给 Agent Host,最后开放 PTY 输入网关。人工输入仍由 Host 转换为结构化 Provider Turn,而不是直接注入 Provider 终端。detach 会自动归还 authority;`release` 即使没有 active Run 也可执行,用于幂等修复中断或未完全同步的接管。Global Operator 与 global Role 继续使用原生交互式 CLI,不属于受管理 Task Provider 协议。
|
|
485
486
|
|
|
487
|
+
当新版本需要离线迁移 Home 时,应等待当前 Turn/Run 完成,然后从普通 shell
|
|
488
|
+
执行 `yui session stop --all`,再重新执行 `yui update`。停止命令会先整体预检:
|
|
489
|
+
只要仍有 Session 正在运行或存在未决生命周期工作,就不会开始停止;全部空闲
|
|
490
|
+
时会先阻止新的 Leader 调度,停止并等待 Controller 完全退出,重新检查运行时
|
|
491
|
+
事实后再停止 Task Role 和 global Role Session。成功后 Controller 保持停止,
|
|
492
|
+
应紧接着执行 `yui update`。如果当前安装版本还没有这条命令,应手动退出提示中
|
|
493
|
+
列出的全部 managed Session;新的 staged CLI 不能写入尚待迁移的旧 Home。
|
|
494
|
+
|
|
486
495
|
tmux 会在 pane 创建时固定其历史容量。配置该限制之前创建的 Role 会保留原容量;Yui 会在 Terminal attach 和 Web 中提示用户退出并重新进入一次,从而创建具有 100,000 行历史的新 pane。
|
|
487
496
|
|
|
488
|
-
每个 Role
|
|
489
|
-
Agent binding 独立保存 native session
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
每个 Role(包括 Operator)可绑定多个 Agent,但任一时刻只有一个 active Agent,
|
|
498
|
+
并为每个 Agent binding 独立保存 native session。同一种 adapter 可以有多个
|
|
499
|
+
binding,用于不同账号、模型、profile 或环境来源;这些 binding 是预先保存、
|
|
500
|
+
可随时切换的配置,而不是并行 writer。Operator 可为
|
|
492
501
|
每个 binding 保留多条历史对话。`operator new` 与 `operator resume`
|
|
493
502
|
复用唯一的 Operator tmux pane;存在运行中进程时,Yui 会先确认再停止
|
|
494
503
|
并切换。跨 Agent 切换默认复用已保存的 model/effort,只有用户明确选择
|
|
@@ -509,7 +518,7 @@ state、receipt 与 pane fence。Yui 不会解析 prompt glyph、进度文本、
|
|
|
509
518
|
或其他 Agent 终端输出来推断 ready 或 success。`captureRole()` 只用于显式的人类
|
|
510
519
|
transcript 查看,不具备生命周期权威。
|
|
511
520
|
|
|
512
|
-
稳定的 Role 上下文也属于启动元数据,而不是 bootstrap turn。Yui 通过 Agent 原生的 system/developer instruction 通道传入 Role 策略和 `systemPrompt`。Task execution Run 按角色接收通用 Leader 或 Worker Skill,review Run 则按持久 Run purpose 接收通用 Reviewer Skill;这些都只是 Yui 自己拥有的可移植编排规则。Project Skills 始终是 Project 中正常版本化的文件,由 Agent 通过自身项目机制发现、选择并按需加载;Yui 不扫描、不解析、不复制,也不注入 Project Skills。Codex developer instructions 只携带 Yui 自有 Role Skill
|
|
521
|
+
稳定的 Role 上下文也属于启动元数据,而不是 bootstrap turn。Yui 通过 Agent 原生的 system/developer instruction 通道传入 Role 策略和 `systemPrompt`。Task execution Run 按角色接收通用 Leader 或 Worker Skill,review Run 则按持久 Run purpose 接收通用 Reviewer Skill;这些都只是 Yui 自己拥有的可移植编排规则。Project Skills 始终是 Project 中正常版本化的文件,由 Agent 通过自身项目机制发现、选择并按需加载;Yui 不扫描、不解析、不复制,也不注入 Project Skills。Codex developer instructions 只携带 Yui 自有 Role Skill 的精简绝对路径,并作为本次 invocation 的覆盖值传入;已有的用户、profile、Project 和 system 配置不会使 Session 拒绝启动,Yui 也不会修改原配置文件。优先级高于 invocation 的 managed `developer_instructions` 仍会成为边界明确的启动阻塞,因为 Codex 不允许本次启动参数覆盖它。交互式 Codex Session 的结构化 `notify` 遵循同一规则:Doctor 会把普通覆盖来源作为上下文报告,并拒绝最终生效的 managed 冲突;Managed Run 不占用 `notify`,只使用 Agent Driver Hook。`skills.config` 只负责启停已发现 Skill,Yui 不会误用它。Claude 从 Yui 管理的私有 `0600` context 文件读取同一份 Yui Role Skill 内容,不再把大段或敏感文本放进 argv;重试和 resume 会复用按 purpose 区分的稳定路径。非 Operator 的 global Role 保持中性,不会注入 Task 编排 Skill。因此 Operator 会停在空白的原生 composer,用户输入仍是第一条 user message;Leader wake、Worker 和 Reviewer Run assignment 仍是邮箱投递的真实工作消息。不具备原生指令通道的 adapter 必须拒绝这类上下文,不能静默降级为首轮 user prompt。
|
|
513
522
|
|
|
514
523
|
## Controller 与失败处理
|
|
515
524
|
|