@zq-silk/yui 0.11.3 → 0.12.1
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 +23 -4
- package/README.md +44 -22
- package/dist/cli/commandCatalog.js +13 -10
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +156 -57
- package/dist/commands/configCommands.js +13 -46
- package/dist/commands/executionAuditCommands.js +3 -2
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +234 -200
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +61 -13
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +55 -7
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +3 -3
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +39 -3
- package/dist/controller/controller.js +31 -27
- package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
- package/dist/controller/runtime.js +58 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +52 -50
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +129 -45
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +69 -5
- package/dist/observability/executionAudit.js +5 -0
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +28 -0
- package/dist/runtime/runtimeProjection.js +22 -32
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +34 -199
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +42 -138
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +53 -31
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +66 -23
- package/dist/storage/sqliteStore.js +46 -23
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -58
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +7 -1
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +60 -20
- package/skills/yui-operator/SKILL.md +13 -4
- package/skills/yui-reviewer/SKILL.md +35 -11
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/lifecycle/taskRoleSessionReset.js +0 -126
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -6,11 +6,12 @@ import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
|
6
6
|
import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageError } from "../errors/cliError.js";
|
|
7
7
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
8
8
|
import { createTaskRecordRetirement, isTaskRecordRetired, taskRecordRetirement } from "../task/taskRecordRetirement.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
|
|
10
10
|
import { readCommandText } from "./textInput.js";
|
|
11
11
|
import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
|
|
12
|
-
import { createRoleSessionSet, retireTaskRoleSessionsForWorkspace,
|
|
12
|
+
import { createRoleSessionSet, retireTaskRoleSessionsForWorkspace, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
13
13
|
import { currentProviderActivation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
|
|
14
|
+
import { CONVERSATION_SWITCH_REQUESTED_EVENT, CONVERSATION_SWITCH_RESOLVED_EVENT, projectConversationSwitch, providerConversationGeneration, roleSessionDispatchModeWithConversationSwitch } from "../runtime/conversationSwitch.js";
|
|
14
15
|
import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
|
|
15
16
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
16
17
|
import { formatTimestamp } from "../output/timePresentation.js";
|
|
@@ -18,13 +19,14 @@ import { renderRoleDetails } from "../output/rolePresentation.js";
|
|
|
18
19
|
import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js";
|
|
19
20
|
import { cancelInputRequest } from "../input/inputRequest.js";
|
|
20
21
|
import { recoverExactAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
21
|
-
import { resetTaskRoleSessionGeneration } from "../lifecycle/taskRoleSessionReset.js";
|
|
22
22
|
import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
|
|
23
23
|
import { agentRunDeliveryReceiptId, createAgentRun, withAgentRunContextSnapshot } from "../run/agentRun.js";
|
|
24
24
|
import { projectRunRecovery, readRunRecoveryFacts } from "../run/recoveryProjection.js";
|
|
25
25
|
import { matchYieldReceipt } from "../run/yieldReceipt.js";
|
|
26
26
|
import { createReviewRound, createTaskReviewRound, createTaskDeltaReviewRound, attachReviewExecutionGroup, deltaRecheckBlocksAcceptance, finishReviewRound, parseReviewYieldReport, recordReviewWorkspaceDisposition, retryRunningReviewExecutionLane, retryTaskReviewRound, startReviewRound, updateReviewExecutionGroup, validateTaskReviewCandidate } from "../review/reviewRound.js";
|
|
27
27
|
import { buildDeltaRecheckDispatchContext, verifyDeltaRecheckDiff } from "../review/deltaRecheck.js";
|
|
28
|
+
import { isAcceptedTaskReviewBaseline } from "../review/reviewAcceptance.js";
|
|
29
|
+
import { projectReviewerAvailability } from "../review/reviewerAvailability.js";
|
|
28
30
|
import { buildTaskFinalReviewFindingContext, dispositionReviewFinding, planRepairGroups, reconcileReviewFindings, reconcileReviewFindingsAfterReview } from "../review/reviewFindingLedger.js";
|
|
29
31
|
import { LEADER_FINDING_DISPOSITIONS } from "../review/reviewFinding.js";
|
|
30
32
|
import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
|
|
@@ -36,7 +38,7 @@ import { mailboxHasWork as workMailboxHasWork, nextPendingBatch } from "../coord
|
|
|
36
38
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
37
39
|
import { blockingProviderContinuations, projectProviderContinuations, runOwnsBlockingProviderContinuation } from "../runtime/runtimeContinuationProjection.js";
|
|
38
40
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
39
|
-
import {
|
|
41
|
+
import { addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
|
|
40
42
|
import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
|
|
41
43
|
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
42
44
|
import { projectCompletionReadiness } from "../task/completionReadiness.js";
|
|
@@ -44,11 +46,11 @@ import { assertProjectActive, resolveProject } from "../repository/project.js";
|
|
|
44
46
|
import { acquireProjectMaintenanceLocks } from "../repository/projectMaintenanceLock.js";
|
|
45
47
|
import { currentWorkItemCandidate, currentWorkItemExecutionGroup, workItemExecutionGroupById, createWorkItem, planWorkItemExplorationStage, attachWorkItemExecutionGroup, updateWorkItemExecutionGroup, retireWorkItem, retryFailedWorkItem, submitWorkItemCandidate, updateWorkItemWriteProjects, updateWorkItemStatus } from "../workItem/workItem.js";
|
|
46
48
|
import { addExecutionLane, createExecutionGroup, queueExecutionLaneRetry, resolveExecutionGroup, restartExecutionLane, skipPendingExecutionLanes, updateExecutionLane, WORK_ITEM_EXECUTION_MODES } from "../execution/executionGroup.js";
|
|
47
|
-
import { executionStageSpendClosed, observedExecutionResourceUsage, planResourceAdmissions, projectExecutionStageResources, resolveResourceBrokerPolicy, routeExecutionStage } from "../execution/resourceBroker.js";
|
|
48
|
-
import {
|
|
49
|
+
import { DEFAULT_EXECUTION_STAGE_TOKEN_BUDGET_PER_LANE, executionStageSpendClosed, observedExecutionResourceUsage, planResourceAdmissions, projectExecutionStageResources, resolveResourceBrokerPolicy, routeExecutionStage } from "../execution/resourceBroker.js";
|
|
50
|
+
import { resolveControllerTaskConcurrency, resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
49
51
|
import { assertIndependentVerificationRoles, candidateConvergenceEvidenceSufficient, candidateConvergenceDirective, validateCandidateConvergenceResolution } from "../execution/candidateConvergence.js";
|
|
50
52
|
import { sameTaskFinalReviewContract, taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
|
|
51
|
-
import { classifyReviewRoundOutcome
|
|
53
|
+
import { classifyReviewRoundOutcome } from "../review/reviewOutcomeClassifier.js";
|
|
52
54
|
import { TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT, createTaskFinalReviewContractRebind, resolveRecordedTaskFinalReviewContract, taskFinalReviewContractRebindPayload } from "../review/taskFinalReviewContractRebind.js";
|
|
53
55
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
54
56
|
import { hasAgentConfigOptions, parseRoleOptions, patchRoleAgentBinding, roleOptionSpecs, roleProfilePatch } from "./roleConfiguration.js";
|
|
@@ -62,7 +64,7 @@ import { assertNoOpenInputRequests, isCurrentGlobalOperator, openInputRequestCou
|
|
|
62
64
|
import { runGrantCommand } from "./grantCommands.js";
|
|
63
65
|
import { runWorkflowCommand } from "./workflowCommands.js";
|
|
64
66
|
import { taskActor as resolveTaskActor, taskLeaderActionRunId } from "./taskActor.js";
|
|
65
|
-
import {
|
|
67
|
+
import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
|
|
66
68
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
67
69
|
import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
|
|
68
70
|
import { collectTaskActionability, computeActionabilityDigest, deriveLeaderRunDisposition, projectQueuedResourceLaneIdentities } from "../scheduler/actionability.js";
|
|
@@ -77,8 +79,6 @@ export class TaskFinalReviewDispatchDriftError extends CliError {
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
/** Process-local command metadata; symbols never enter JSON output or durable state. */
|
|
80
|
-
export const RESUMED_PENDING_FINAL_REVIEW = Symbol("resumed-pending-final-review");
|
|
81
|
-
export const TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW = Symbol("terminalized-leader-before-final-review");
|
|
82
82
|
function taskFinalReviewDispatchDrift(error) {
|
|
83
83
|
return new TaskFinalReviewDispatchDriftError(error instanceof Error ? error.message : String(error), error instanceof CliError ? error : undefined);
|
|
84
84
|
}
|
|
@@ -235,8 +235,8 @@ export function runTaskCommand(args, store, options = {}) {
|
|
|
235
235
|
case "update": return output(updateTaskCommand(rest, store, options));
|
|
236
236
|
case "list": return listTaskCommand(rest, store);
|
|
237
237
|
case "show": return showTaskCommand(rest, store);
|
|
238
|
-
case "context": return runTaskContextCommand(rest, store);
|
|
239
|
-
case "next-action": return runTaskNextActionCommand(rest, store);
|
|
238
|
+
case "context": return runTaskContextCommand(rest, store, options.actualTaskReviewCandidate ?? null);
|
|
239
|
+
case "next-action": return runTaskNextActionCommand(rest, store, options.actualTaskReviewCandidate ?? null);
|
|
240
240
|
case "activate": return output(activateTaskCommand(rest, store, options));
|
|
241
241
|
case "complete": return completeTaskCommand(rest, store, options);
|
|
242
242
|
case "reopen": return output(reopenTaskCommand(rest, store, options));
|
|
@@ -670,27 +670,34 @@ function showTaskCommand(args, store) {
|
|
|
670
670
|
}
|
|
671
671
|
function activateTaskCommand(args, store, options) {
|
|
672
672
|
exactPositionals(args, 1, "Task activate usage: yui task activate <task>.");
|
|
673
|
-
const
|
|
673
|
+
const activation = options.taskWorkspaceActivation;
|
|
674
674
|
const result = store.transaction((tx) => {
|
|
675
675
|
const task = requireTask(tx, args[0]);
|
|
676
676
|
if (task.status === "archived")
|
|
677
677
|
throw usageError(`Task is archived: ${task.id}.`);
|
|
678
|
+
if (task.status === "retired")
|
|
679
|
+
throw usageError(`Task is retired: ${task.id}.`);
|
|
678
680
|
if (task.status === "completed") {
|
|
679
681
|
throw usageError(`Task ${task.id} is completed; use task reopen before activating it.`);
|
|
680
682
|
}
|
|
681
|
-
if (task.status === "active")
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
683
|
+
if (task.status === "active") {
|
|
684
|
+
if (activation === undefined)
|
|
685
|
+
return { task, changed: false };
|
|
686
|
+
const workspace = tx.getTaskWorkspace(task.id);
|
|
687
|
+
if (activation.taskId !== task.id
|
|
688
|
+
|| activation.task.id !== task.id
|
|
689
|
+
|| activation.task.status !== "active"
|
|
690
|
+
|| !isDeepStrictEqual(activation.task.workspaceIdentity, task.workspaceIdentity)
|
|
691
|
+
|| activation.path !== task.cwd
|
|
692
|
+
|| workspace === null
|
|
693
|
+
|| workspace.owner.type !== "task"
|
|
694
|
+
|| workspace.owner.taskId !== task.id
|
|
695
|
+
|| workspace.root !== task.cwd) {
|
|
696
|
+
throw usageError(`Task workspace activation proof does not match ${task.id}.`);
|
|
697
|
+
}
|
|
698
|
+
return { task, changed: activation.changed };
|
|
699
|
+
}
|
|
700
|
+
throw usageError(`Task ${task.id} activation requires atomic workspace adoption through the CLI preflight.`);
|
|
694
701
|
});
|
|
695
702
|
if (result.changed) {
|
|
696
703
|
notifyMailbox(options.runtime, taskMailbox(result.task.id), result.task.id);
|
|
@@ -754,7 +761,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
754
761
|
// readiness via projectCompletionReadiness. The leader-Run check is
|
|
755
762
|
// actor-dependent and stays here; every other blocker is re-validated by
|
|
756
763
|
// the post-Review readiness fence below.
|
|
757
|
-
let terminalizedLeaderRun = false;
|
|
758
764
|
const leaderEntry = activeRuns.find(({ role }) => role.name === LEADER_ROLE);
|
|
759
765
|
if (leaderEntry !== undefined) {
|
|
760
766
|
if (actor !== "leader") {
|
|
@@ -777,15 +783,11 @@ function completeTaskCommand(args, store, options) {
|
|
|
777
783
|
if (terminal.disposition !== "applied") {
|
|
778
784
|
throw usageError(`Task Leader Run changed during completion: ${leaderEntry.run.id}/${terminal.reason}.`);
|
|
779
785
|
}
|
|
780
|
-
terminalizedLeaderRun = true;
|
|
781
786
|
}
|
|
782
787
|
// A final (Task-scoped) review is the Task delivery policy: it reviews the
|
|
783
788
|
// complete frozen Task heads, not a single WorkItem Candidate. If the
|
|
784
789
|
// review config requests a final review, create the Task ReviewRound here
|
|
785
790
|
// and return it for dispatch instead of completing the Task.
|
|
786
|
-
const pendingFinalReviewIds = new Set(tx.listReviewRounds(task.id)
|
|
787
|
-
.filter((round) => ((round.scope ?? "work-item") === "task" && round.status === "pending"))
|
|
788
|
-
.map(({ id }) => id));
|
|
789
791
|
const finalReview = prepareFinalTaskReview(tx, task, now, taskFinalContract, options);
|
|
790
792
|
if (finalReview !== null) {
|
|
791
793
|
return {
|
|
@@ -794,8 +796,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
794
796
|
runtimeCleanupTargets: [],
|
|
795
797
|
completionAdvisories: [],
|
|
796
798
|
finalReview,
|
|
797
|
-
resumedPendingFinalReview: pendingFinalReviewIds.has(finalReview.id),
|
|
798
|
-
terminalizedLeaderRun,
|
|
799
799
|
publishedTreeAuthorization: undefined
|
|
800
800
|
};
|
|
801
801
|
}
|
|
@@ -816,9 +816,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
816
816
|
tx.saveTask(completed);
|
|
817
817
|
tx.clearPendingWakeup(task.id);
|
|
818
818
|
tx.clearLeaderFailure(task.id);
|
|
819
|
-
tx.clearOperatorNotification(task.id);
|
|
820
|
-
tx.saveOperatorNotification(createTaskTerminalNotification(task.id, "completed", actor, summary, now));
|
|
821
|
-
enqueueWork(tx, { kind: "operator" }, "task-terminal", now, [taskRef(task.id)]);
|
|
822
819
|
if (publishedTreeProof !== undefined) {
|
|
823
820
|
recordTaskEvent(tx, task.id, "task.completion-published-tree-accepted", {
|
|
824
821
|
by: actor,
|
|
@@ -832,7 +829,7 @@ function completeTaskCommand(args, store, options) {
|
|
|
832
829
|
tree: publishedTreeProof.tree
|
|
833
830
|
}, now);
|
|
834
831
|
}
|
|
835
|
-
recordTaskEvent(tx, task.id, "task.completed", {
|
|
832
|
+
const terminalEvent = recordTaskEvent(tx, task.id, "task.completed", {
|
|
836
833
|
by: actor,
|
|
837
834
|
summary,
|
|
838
835
|
...(actualTaskCandidate === undefined
|
|
@@ -846,6 +843,7 @@ function completeTaskCommand(args, store, options) {
|
|
|
846
843
|
? {}
|
|
847
844
|
: { cleanupAdvisories: String(readiness.advisories.length) })
|
|
848
845
|
}, now);
|
|
846
|
+
enqueueOperatorEvent(tx, terminalEvent, "task-terminal", now);
|
|
849
847
|
// A terminal Task must never leave a Task-lane signal that can wake it.
|
|
850
848
|
// The durable records remain intact; only the derived mailbox work is
|
|
851
849
|
// discarded at this lifecycle boundary.
|
|
@@ -863,8 +861,6 @@ function completeTaskCommand(args, store, options) {
|
|
|
863
861
|
runtimeCleanupTargets,
|
|
864
862
|
completionAdvisories: readiness.advisories,
|
|
865
863
|
finalReview: undefined,
|
|
866
|
-
resumedPendingFinalReview: false,
|
|
867
|
-
terminalizedLeaderRun,
|
|
868
864
|
publishedTreeAuthorization: undefined
|
|
869
865
|
};
|
|
870
866
|
});
|
|
@@ -893,9 +889,7 @@ function completeTaskCommand(args, store, options) {
|
|
|
893
889
|
: `Final Task Review is blocked: ${result.finalReview.summary ?? result.finalReview.id}.`;
|
|
894
890
|
return output(`${status}\n`, {
|
|
895
891
|
task: result.task,
|
|
896
|
-
reviewRound: result.finalReview
|
|
897
|
-
[RESUMED_PENDING_FINAL_REVIEW]: result.resumedPendingFinalReview,
|
|
898
|
-
[TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW]: result.terminalizedLeaderRun
|
|
892
|
+
reviewRound: result.finalReview
|
|
899
893
|
});
|
|
900
894
|
}
|
|
901
895
|
const completionOutput = result.changed
|
|
@@ -935,7 +929,6 @@ function reopenTaskCommand(args, store, options) {
|
|
|
935
929
|
throw usageError(`Task is not completed: ${task.id}.`);
|
|
936
930
|
const active = reopenTask(task, now);
|
|
937
931
|
tx.saveTask(active);
|
|
938
|
-
tx.clearOperatorNotification(task.id);
|
|
939
932
|
const reopenedReason = wakeReason("task-reopened");
|
|
940
933
|
enqueueWork(tx, leaderMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
|
|
941
934
|
enqueueWork(tx, taskMailbox(task.id), reopenedReason, now, [taskRef(task.id)]);
|
|
@@ -997,7 +990,6 @@ function archiveTaskCommand(args, store, options) {
|
|
|
997
990
|
tx.saveTask(archived);
|
|
998
991
|
tx.clearPendingWakeup(task.id);
|
|
999
992
|
tx.clearLeaderFailure(task.id);
|
|
1000
|
-
tx.clearOperatorNotification(task.id);
|
|
1001
993
|
for (const role of tx.listRoles(task.id)) {
|
|
1002
994
|
tx.removeWorkMailbox(roleMailbox(task.id, role.name));
|
|
1003
995
|
}
|
|
@@ -1095,20 +1087,18 @@ function retireTaskCommand(args, store, options) {
|
|
|
1095
1087
|
}
|
|
1096
1088
|
tx.clearPendingWakeup(task.id);
|
|
1097
1089
|
tx.clearLeaderFailure(task.id);
|
|
1098
|
-
tx.clearOperatorNotification(task.id);
|
|
1099
1090
|
const retired = retireTask(task, {
|
|
1100
1091
|
by: actor,
|
|
1101
1092
|
summary,
|
|
1102
1093
|
...(replacementTaskId === undefined ? {} : { replacementTaskId })
|
|
1103
1094
|
}, now);
|
|
1104
1095
|
tx.saveTask(retired);
|
|
1105
|
-
tx
|
|
1106
|
-
enqueueWork(tx, { kind: "operator" }, "task-terminal", now, [taskRef(task.id)]);
|
|
1107
|
-
recordTaskEvent(tx, task.id, "task.retired", {
|
|
1096
|
+
const terminalEvent = recordTaskEvent(tx, task.id, "task.retired", {
|
|
1108
1097
|
by: actor,
|
|
1109
1098
|
summary,
|
|
1110
1099
|
...(replacementTaskId === undefined ? {} : { replacementTaskId })
|
|
1111
1100
|
}, now);
|
|
1101
|
+
enqueueOperatorEvent(tx, terminalEvent, "task-terminal", now);
|
|
1112
1102
|
return { task: retired, changed: true };
|
|
1113
1103
|
});
|
|
1114
1104
|
if (result.changed) {
|
|
@@ -1383,8 +1373,8 @@ function taskRoleCommand(args, store, options) {
|
|
|
1383
1373
|
return output(bindTaskRole(rest, store, options));
|
|
1384
1374
|
if (command === "unbind")
|
|
1385
1375
|
return output(unbindTaskRole(rest, store, options));
|
|
1386
|
-
if (command === "
|
|
1387
|
-
return
|
|
1376
|
+
if (command === "session")
|
|
1377
|
+
return taskRoleSessionCommand(rest, store, options);
|
|
1388
1378
|
if (command === "view")
|
|
1389
1379
|
return viewTaskRole(rest, store);
|
|
1390
1380
|
if (command === "takeover")
|
|
@@ -1395,29 +1385,78 @@ function taskRoleCommand(args, store, options) {
|
|
|
1395
1385
|
? "Task role command is required."
|
|
1396
1386
|
: `Unknown command: task role ${command}`);
|
|
1397
1387
|
}
|
|
1398
|
-
function
|
|
1399
|
-
const
|
|
1388
|
+
function taskRoleSessionCommand(args, store, options) {
|
|
1389
|
+
const [command, ...rest] = args;
|
|
1390
|
+
if (command === "switch")
|
|
1391
|
+
return switchTaskRoleSession(rest, store, options);
|
|
1392
|
+
throw usageError(command === undefined
|
|
1393
|
+
? "Task role session command is required."
|
|
1394
|
+
: `Unknown command: task role session ${command}`);
|
|
1395
|
+
}
|
|
1396
|
+
function switchTaskRoleSession(args, store, options) {
|
|
1397
|
+
const usage = "Task role session switch usage: yui task role session switch <task> <role> --reason <text>.";
|
|
1400
1398
|
const parsed = parseTail(args, new Set(["--reason"]), usage);
|
|
1401
1399
|
exactPositionals(parsed.positionals, 2, usage);
|
|
1402
|
-
const reason = requiredOption(parsed.options, "--reason");
|
|
1400
|
+
const reason = truncateEventNote(requiredOption(parsed.options, "--reason"));
|
|
1403
1401
|
const now = clock(options);
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1402
|
+
const result = store.transaction((tx) => {
|
|
1403
|
+
const task = requireTask(tx, parsed.positionals[0]);
|
|
1404
|
+
if (task.status !== "active") {
|
|
1405
|
+
throw usageError(inactiveTaskMessage(task, "switching a Provider Conversation"), usage);
|
|
1406
|
+
}
|
|
1407
|
+
const role = requireRole(tx, task.id, parsed.positionals[1]);
|
|
1408
|
+
const requestedBy = taskActor(options, task.id);
|
|
1409
|
+
const leaderRunId = requestedBy === "leader"
|
|
1410
|
+
? taskLeaderActionRunId(tx, task.id, options.environment, options.yuiHome)
|
|
1411
|
+
: undefined;
|
|
1412
|
+
if (requestedBy === "leader" && leaderRunId === undefined) {
|
|
1413
|
+
throw usageError("Leader Conversation switching requires the exact current-Turn assertion.", usage);
|
|
1414
|
+
}
|
|
1415
|
+
const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
|
|
1416
|
+
const generation = providerConversationGeneration(sessions);
|
|
1417
|
+
if (sessions === null || generation === null) {
|
|
1418
|
+
throw usageError(`Task Role has no current Provider Conversation: ${task.id}/${role.name}.`, usage);
|
|
1419
|
+
}
|
|
1420
|
+
const events = tx.listEvents(task.id);
|
|
1421
|
+
const previous = projectConversationSwitch(events, role.name, sessions);
|
|
1422
|
+
if (previous?.status === "pending"
|
|
1423
|
+
&& previous.generation === generation
|
|
1424
|
+
&& previous.requestedBy === requestedBy
|
|
1425
|
+
&& previous.reason === reason) {
|
|
1426
|
+
return { taskId: task.id, roleName: role.name, request: previous, replayed: true };
|
|
1427
|
+
}
|
|
1428
|
+
if (previous !== null && previous.resolvedAt === undefined) {
|
|
1429
|
+
tx.saveEvent(task.id, createTaskEvent(tx.nextEventId(task.id), task.id, CONVERSATION_SWITCH_RESOLVED_EVENT, {
|
|
1430
|
+
requestId: previous.requestId,
|
|
1431
|
+
roleName: role.name,
|
|
1432
|
+
generation: previous.generation,
|
|
1433
|
+
status: "obsolete"
|
|
1434
|
+
}, now));
|
|
1435
|
+
}
|
|
1436
|
+
const requestId = tx.nextEventId(task.id);
|
|
1437
|
+
const event = createTaskEvent(requestId, task.id, CONVERSATION_SWITCH_REQUESTED_EVENT, {
|
|
1438
|
+
requestId,
|
|
1439
|
+
roleName: role.name,
|
|
1440
|
+
generation,
|
|
1441
|
+
requestedBy,
|
|
1442
|
+
reason,
|
|
1443
|
+
...(leaderRunId === undefined ? {} : { leaderRunId })
|
|
1444
|
+
}, now);
|
|
1445
|
+
tx.saveEvent(task.id, event);
|
|
1446
|
+
return {
|
|
1447
|
+
taskId: task.id,
|
|
1448
|
+
roleName: role.name,
|
|
1449
|
+
request: projectConversationSwitch([...events, event], role.name, sessions),
|
|
1450
|
+
replayed: false
|
|
1451
|
+
};
|
|
1452
|
+
});
|
|
1453
|
+
options.runtime?.notifyStateChanged(result.taskId);
|
|
1454
|
+
options.runtime?.notifyMailboxChanged?.({
|
|
1455
|
+
kind: "role",
|
|
1413
1456
|
taskId: result.taskId,
|
|
1414
1457
|
roleName: result.roleName
|
|
1415
1458
|
});
|
|
1416
|
-
|
|
1417
|
-
notifyMailbox(options.runtime, result.roleName === LEADER_ROLE
|
|
1418
|
-
? { kind: "operator" }
|
|
1419
|
-
: leaderMailbox(result.taskId), result.taskId);
|
|
1420
|
-
return output(`Reset Task Role Session ${result.taskId}/${result.roleName}; runtime cleanup is pending.\n`, result);
|
|
1459
|
+
return output(`${result.replayed ? "Reused" : "Recorded"} pending Provider Conversation switch ${result.request.requestId} for ${result.taskId}/${result.roleName}. The current Conversation remains authoritative until safe replacement binding.\n`, result);
|
|
1421
1460
|
}
|
|
1422
1461
|
function addTaskRole(args, store, options) {
|
|
1423
1462
|
const usage = "Task role add usage: yui task role add <task> <name> [Role and Agent settings].";
|
|
@@ -2231,7 +2270,6 @@ function dispatchWork(args, store, options) {
|
|
|
2231
2270
|
? undefined
|
|
2232
2271
|
: (() => {
|
|
2233
2272
|
const config = tx.getConfig();
|
|
2234
|
-
const contextBudget = resolveContextBudget(config.contextBudget);
|
|
2235
2273
|
const health = resolveRuntimeHealth(config.runtimeHealth);
|
|
2236
2274
|
const maxWallClockSeconds = requestedStageMaxSeconds
|
|
2237
2275
|
?? Math.floor(health.stallWindowMs / 1_000);
|
|
@@ -2250,7 +2288,8 @@ function dispatchWork(args, store, options) {
|
|
|
2250
2288
|
? { convergence: { schemaVersion: 1 } }
|
|
2251
2289
|
: {}),
|
|
2252
2290
|
resourceBudget: {
|
|
2253
|
-
maxTokens: requestedStageMaxTokens
|
|
2291
|
+
maxTokens: requestedStageMaxTokens
|
|
2292
|
+
?? DEFAULT_EXECUTION_STAGE_TOKEN_BUDGET_PER_LANE * available,
|
|
2254
2293
|
maxToolCalls: requestedStageMaxToolCalls ?? 100 * available,
|
|
2255
2294
|
maxWallClockSeconds
|
|
2256
2295
|
},
|
|
@@ -2347,7 +2386,7 @@ function dispatchWork(args, store, options) {
|
|
|
2347
2386
|
workItemWriteProjectIds: item.writeProjectIds
|
|
2348
2387
|
});
|
|
2349
2388
|
const sessions = tx.getTaskRoleSessionSet(task.id, plan.role.name);
|
|
2350
|
-
const dispatchMode =
|
|
2389
|
+
const dispatchMode = taskRoleDispatchMode(tx, task.id, plan.role.name, sessions, effective.agentId, effective);
|
|
2351
2390
|
const laneWorkspace = laneManagedWorkspace === undefined
|
|
2352
2391
|
? undefined
|
|
2353
2392
|
: {
|
|
@@ -3521,12 +3560,6 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3521
3560
|
}
|
|
3522
3561
|
const taskFinalContract = taskFinalReviewContractForMutation(tx, task.id, options);
|
|
3523
3562
|
if (deltaRecheckRequested) {
|
|
3524
|
-
const reviewConfig = tx.getReviewConfig();
|
|
3525
|
-
if (reviewConfig === null || reviewConfig.deltaRecheck !== "enabled") {
|
|
3526
|
-
throw usageError("Delta-recheck is not enabled for this Project's review policy. "
|
|
3527
|
-
+ "Set `yui config workflow set review --role <role> --trigger final --delta-recheck enabled` "
|
|
3528
|
-
+ "or request a full Review.");
|
|
3529
|
-
}
|
|
3530
3563
|
if (taskFinalContract !== undefined) {
|
|
3531
3564
|
throw usageError("Delta-recheck is not supported with a Task-final review contract.");
|
|
3532
3565
|
}
|
|
@@ -3541,15 +3574,24 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3541
3574
|
}
|
|
3542
3575
|
const taskRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id))
|
|
3543
3576
|
.filter((entry) => (entry.scope ?? "work-item") === "task");
|
|
3577
|
+
const latestNonAcceptingDelta = deltaRecheckRequested
|
|
3578
|
+
? undefined
|
|
3579
|
+
: taskRounds.filter((entry) => (entry.reviewerRoleName === reviewerRoleName
|
|
3580
|
+
&& entry.deltaRecheck !== undefined
|
|
3581
|
+
&& deltaRecheckBlocksAcceptance(entry)
|
|
3582
|
+
&& isSameTaskReviewCandidate(entry.taskCandidate, provenance.candidate))).at(-1);
|
|
3544
3583
|
const exact = taskRounds.filter((entry) => (entry.reviewerRoleName === reviewerRoleName
|
|
3584
|
+
&& (deltaRecheckRequested
|
|
3585
|
+
? entry.deltaRecheck !== undefined
|
|
3586
|
+
: entry.deltaRecheck === undefined)
|
|
3545
3587
|
&& (taskFinalContract === undefined || sameTaskFinalReviewContract(entry.taskFinalReviewContract, taskFinalContract))
|
|
3546
3588
|
&& isSameTaskReviewCandidate(entry.taskCandidate, provenance.candidate))).at(-1);
|
|
3589
|
+
if (exact?.status === "failed") {
|
|
3590
|
+
throw usageError(`Explicit Task-final ReviewRound ${exact.id} is failed for this exact candidate; resolve it before requesting again.`);
|
|
3591
|
+
}
|
|
3547
3592
|
if (exact !== undefined
|
|
3548
|
-
&& (
|
|
3549
|
-
|
|
3550
|
-
throw usageError(`Explicit Task-final ReviewRound ${exact.id} is failed for this exact candidate; resolve it before requesting again.`);
|
|
3551
|
-
}
|
|
3552
|
-
assertNoConflictingTaskReviewRound(taskRounds, exact.id);
|
|
3593
|
+
&& (exact.status === "pending" || exact.status === "running")) {
|
|
3594
|
+
assertNoConflictingTaskReviewRound(taskRounds, exact.id, reviewerRoleName);
|
|
3553
3595
|
if (requestedLaneRoles.length === 0) {
|
|
3554
3596
|
if (exact.status === "running"
|
|
3555
3597
|
&& exact.executionGroup?.resolution === undefined
|
|
@@ -3579,9 +3621,8 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3579
3621
|
if (exact.executionGroup.lanes.length + laneRoles.filter((role) => (!exact.executionGroup.lanes.some((lane) => lane.roleName === role))).length > strategy.max) {
|
|
3580
3622
|
throw usageError(`Reviewer Lane count exceeds adaptive capacity ${strategy.max}.`);
|
|
3581
3623
|
}
|
|
3582
|
-
let appended = exact.executionGroup;
|
|
3583
3624
|
for (const laneRoleName of laneRoles) {
|
|
3584
|
-
const existingLane =
|
|
3625
|
+
const existingLane = exact.executionGroup.lanes.find(({ roleName }) => (roleName === laneRoleName));
|
|
3585
3626
|
if (existingLane !== undefined) {
|
|
3586
3627
|
if (existingLane.runId === undefined)
|
|
3587
3628
|
continue;
|
|
@@ -3601,29 +3642,32 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3601
3642
|
if (taskReviewProducerCollision(provenance, laneRoleName) !== null) {
|
|
3602
3643
|
throw usageError(`Reviewer Role must be separate from the Candidate producer: ${laneRoleName}.`);
|
|
3603
3644
|
}
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3645
|
+
const availability = projectReviewerAvailability(tx, task.id, laneRoleName);
|
|
3646
|
+
if (availability.kind === "busy")
|
|
3647
|
+
return availability;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
let appended = exact.executionGroup;
|
|
3651
|
+
for (const laneRoleName of laneRoles) {
|
|
3652
|
+
if (appended.lanes.some(({ roleName }) => roleName === laneRoleName))
|
|
3653
|
+
continue;
|
|
3654
|
+
let laneRole = tx.getRole(task.id, laneRoleName);
|
|
3655
|
+
if (laneRole === null) {
|
|
3656
|
+
laneRole = createTaskRole(tx, task, laneRoleName, undefined, now, laneRoleName);
|
|
3657
|
+
tx.saveRole(task.id, laneRole);
|
|
3614
3658
|
}
|
|
3659
|
+
appended = addExecutionLane(appended, {
|
|
3660
|
+
roleName: laneRole.name,
|
|
3661
|
+
reviewRoundId: exact.id
|
|
3662
|
+
}, now);
|
|
3615
3663
|
}
|
|
3616
3664
|
const updated = updateReviewExecutionGroup(exact, appended);
|
|
3617
3665
|
tx.saveReviewRound(task.id, updated);
|
|
3618
3666
|
return updated;
|
|
3619
3667
|
}
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
if (reviewer === null) {
|
|
3624
|
-
reviewer = createTaskRole(tx, task, reviewerRoleName, undefined, now, reviewerRoleName);
|
|
3625
|
-
tx.saveRole(task.id, reviewer);
|
|
3626
|
-
}
|
|
3668
|
+
const primaryAvailability = projectReviewerAvailability(tx, task.id, reviewerRoleName);
|
|
3669
|
+
if (primaryAvailability.kind === "busy")
|
|
3670
|
+
return primaryAvailability;
|
|
3627
3671
|
const laneRoles = requestedLaneRoles.length === 0
|
|
3628
3672
|
? [reviewerRoleName]
|
|
3629
3673
|
: requestedLaneRoles[0] === reviewerRoleName
|
|
@@ -3642,22 +3686,24 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3642
3686
|
if (tx.getGlobalRole(laneRoleName) === null && tx.getRole(task.id, laneRoleName) === null) {
|
|
3643
3687
|
throw usageError(`Global Role not found: ${laneRoleName}.`);
|
|
3644
3688
|
}
|
|
3645
|
-
|
|
3646
|
-
if (
|
|
3647
|
-
|
|
3648
|
-
|
|
3689
|
+
const availability = projectReviewerAvailability(tx, task.id, laneRoleName);
|
|
3690
|
+
if (availability.kind === "busy")
|
|
3691
|
+
return availability;
|
|
3692
|
+
if (taskReviewProducerCollision(provenance, laneRoleName) !== null) {
|
|
3693
|
+
throw usageError(`Reviewer Role must be separate from the Candidate producer: ${laneRoleName}.`);
|
|
3649
3694
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3695
|
+
}
|
|
3696
|
+
for (const laneRoleName of laneRoles) {
|
|
3697
|
+
if (tx.getRole(task.id, laneRoleName) === null) {
|
|
3698
|
+
tx.saveRole(task.id, createTaskRole(tx, task, laneRoleName, undefined, now, laneRoleName));
|
|
3652
3699
|
}
|
|
3653
|
-
assertTaskReviewRequestLane(tx, task.id, laneRole.name);
|
|
3654
3700
|
}
|
|
3655
3701
|
let deltaRecord;
|
|
3656
3702
|
if (deltaRecheckRequested) {
|
|
3657
3703
|
if (requestedLaneRoles.length > 0 || requestedStrategy !== undefined) {
|
|
3658
3704
|
throw usageError("Delta-recheck supports only the default single Reviewer Lane.");
|
|
3659
3705
|
}
|
|
3660
|
-
deltaRecord = validateDeltaRecheckRequest(tx, task.id,
|
|
3706
|
+
deltaRecord = validateDeltaRecheckRequest(tx, task.id, provenance.candidate, options.deltaRecheckPreflight);
|
|
3661
3707
|
}
|
|
3662
3708
|
let created = deltaRecord === undefined
|
|
3663
3709
|
? createTaskReviewRound(tx.nextReviewRoundId(task.id), task.id, reviewerRoleName, "leader", provenance.candidate, now, taskFinalContract)
|
|
@@ -3675,15 +3721,14 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3675
3721
|
tx.saveReviewRound(task.id, created);
|
|
3676
3722
|
// Issue 07: when a full Review is created after a non-accepting delta
|
|
3677
3723
|
// disposition, record the escalation lineage on the delta Round.
|
|
3678
|
-
if (
|
|
3679
|
-
&& deltaRecheckBlocksAcceptance(exact)
|
|
3724
|
+
if (latestNonAcceptingDelta !== undefined
|
|
3680
3725
|
&& created.deltaRecheck === undefined
|
|
3681
|
-
&&
|
|
3682
|
-
&&
|
|
3726
|
+
&& latestNonAcceptingDelta.deltaRecheck !== undefined
|
|
3727
|
+
&& latestNonAcceptingDelta.deltaRecheck.escalatedToReviewRoundId === undefined) {
|
|
3683
3728
|
tx.saveReviewRound(task.id, {
|
|
3684
|
-
...
|
|
3729
|
+
...latestNonAcceptingDelta,
|
|
3685
3730
|
deltaRecheck: {
|
|
3686
|
-
...
|
|
3731
|
+
...latestNonAcceptingDelta.deltaRecheck,
|
|
3687
3732
|
escalatedToReviewRoundId: created.id
|
|
3688
3733
|
}
|
|
3689
3734
|
});
|
|
@@ -3703,11 +3748,20 @@ function requestTaskReviewRound(args, store, options) {
|
|
|
3703
3748
|
}, now);
|
|
3704
3749
|
return created;
|
|
3705
3750
|
});
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
?
|
|
3709
|
-
|
|
3710
|
-
|
|
3751
|
+
if ("kind" in round && round.kind === "busy") {
|
|
3752
|
+
return output(`Reviewer ${round.reviewerRoleName} is busy (${round.phase}`
|
|
3753
|
+
+ `${round.activeRunId === undefined ? "" : `; Run ${round.activeRunId}`}); `
|
|
3754
|
+
+ `${round.activeReviewRoundId === undefined
|
|
3755
|
+
? ""
|
|
3756
|
+
: `active ReviewRound ${round.activeReviewRoundId}; `}`
|
|
3757
|
+
+ `retry after ${round.retryAfterSeconds}s or choose another Reviewer.\n`, { reviewRequest: round });
|
|
3758
|
+
}
|
|
3759
|
+
const requestedRound = round;
|
|
3760
|
+
return output(requestedRound.status === "pending"
|
|
3761
|
+
? requestedRound.deltaRecheck === undefined
|
|
3762
|
+
? `Task-final Review requested as ${requestedRound.id}\n`
|
|
3763
|
+
: `Task-final delta-recheck requested as ${requestedRound.id} (rechecks ${requestedRound.deltaRecheck.previousReviewRoundId})\n`
|
|
3764
|
+
: `Task-final Review is already ${requestedRound.status}: ${requestedRound.id}\n`, { reviewRound: requestedRound });
|
|
3711
3765
|
}
|
|
3712
3766
|
const TASK_FINAL_FORCE_FRESH_EVENT = "review.task-final-force-fresh-requested";
|
|
3713
3767
|
/**
|
|
@@ -3790,12 +3844,12 @@ function forceFreshTaskReviewRound(args, store, options) {
|
|
|
3790
3844
|
if (sourceIndex < 0) {
|
|
3791
3845
|
throw dataError(`Final ReviewRound is not in Task history: ${source.id}.`);
|
|
3792
3846
|
}
|
|
3793
|
-
const laterRound = taskRounds.slice(sourceIndex + 1).
|
|
3847
|
+
const laterRound = taskRounds.slice(sourceIndex + 1).find((entry) => (entry.reviewerRoleName === source.reviewerRoleName));
|
|
3794
3848
|
if (laterRound !== undefined) {
|
|
3795
3849
|
throw usageError(`A newer Task-final ReviewRound already exists after ${source.id}: `
|
|
3796
3850
|
+ `${laterRound.id}/${laterRound.status}.`);
|
|
3797
3851
|
}
|
|
3798
|
-
assertNoConflictingTaskReviewRound(taskRounds, source.id);
|
|
3852
|
+
assertNoConflictingTaskReviewRound(taskRounds, source.id, source.reviewerRoleName);
|
|
3799
3853
|
assertTaskReviewRequestLane(tx, task.id, source.reviewerRoleName);
|
|
3800
3854
|
let reviewer = tx.getRole(task.id, source.reviewerRoleName);
|
|
3801
3855
|
if (reviewer === null) {
|
|
@@ -3853,22 +3907,17 @@ export function classifyForceFreshReviewRecovery(store, round) {
|
|
|
3853
3907
|
* Review or an equivalent-and-accepted delta) so a delta never extends a
|
|
3854
3908
|
* non-accepting disposition.
|
|
3855
3909
|
*/
|
|
3856
|
-
function validateDeltaRecheckRequest(store, taskId,
|
|
3910
|
+
function validateDeltaRecheckRequest(store, taskId, candidate, preflight) {
|
|
3857
3911
|
if (preflight === undefined) {
|
|
3858
3912
|
throw usageError("Delta-recheck assessment is missing; the CLI preflight did not run. "
|
|
3859
3913
|
+ "Request a full Review or retry with a current CLI.");
|
|
3860
3914
|
}
|
|
3861
3915
|
const previous = store.getReviewRound(taskId, preflight.record.previousReviewRoundId);
|
|
3862
3916
|
if (previous === null
|
|
3863
|
-
|| (previous
|
|
3864
|
-
|
|
3865
|
-
throw usageError(`Delta-recheck previous ReviewRound is not a semantic completed Task-final Review: `
|
|
3917
|
+
|| !isAcceptedTaskReviewBaseline(store, previous)) {
|
|
3918
|
+
throw usageError(`Delta-recheck previous ReviewRound is not an accepted Task-final baseline: `
|
|
3866
3919
|
+ `${preflight.record.previousReviewRoundId}.`);
|
|
3867
3920
|
}
|
|
3868
|
-
if (previous.reviewerRoleName !== reviewerRoleName) {
|
|
3869
|
-
throw usageError(`Delta-recheck Reviewer Role must match the previous acceptance: `
|
|
3870
|
-
+ `${previous.reviewerRoleName}.`);
|
|
3871
|
-
}
|
|
3872
3921
|
if (previous.reviewBaseCommit !== preflight.record.previousBaseCommit) {
|
|
3873
3922
|
throw usageError("Delta-recheck previous base commit does not match the recorded acceptance.");
|
|
3874
3923
|
}
|
|
@@ -3876,7 +3925,7 @@ function validateDeltaRecheckRequest(store, taskId, reviewerRoleName, candidate,
|
|
|
3876
3925
|
if (previous.deltaRecheck !== undefined
|
|
3877
3926
|
&& previous.deltaRecheck.disposition !== "equivalent-and-accepted") {
|
|
3878
3927
|
throw usageError(`Delta-recheck cannot extend ${previous.id}: its disposition is `
|
|
3879
|
-
+ `${previous.deltaRecheck.disposition}.
|
|
3928
|
+
+ `${previous.deltaRecheck.disposition}. The Leader must choose the next Review action.`);
|
|
3880
3929
|
}
|
|
3881
3930
|
if (candidate.projects[0].commit === preflight.record.previousBaseCommit) {
|
|
3882
3931
|
throw usageError("Delta-recheck candidate head is unchanged; the previous acceptance already covers it.");
|
|
@@ -3986,12 +4035,12 @@ function retryFailedTaskReviewRound(args, store, options) {
|
|
|
3986
4035
|
}
|
|
3987
4036
|
// Issue 06: infra retries reuse the same semantic Round ID. Any later
|
|
3988
4037
|
// Round supersedes this one; an active Round for the same Reviewer blocks.
|
|
3989
|
-
const conflictingLater = reviewerRounds.slice(roundIndex + 1).
|
|
4038
|
+
const conflictingLater = reviewerRounds.slice(roundIndex + 1).find((entry) => (entry.reviewerRoleName === round.reviewerRoleName));
|
|
3990
4039
|
if (conflictingLater !== undefined) {
|
|
3991
4040
|
throw usageError(`A newer conflicting final ReviewRound already exists after ${round.id}: `
|
|
3992
4041
|
+ `${conflictingLater.id}/${conflictingLater.status}.`);
|
|
3993
4042
|
}
|
|
3994
|
-
assertNoConflictingTaskReviewRound(reviewerRounds, round.id);
|
|
4043
|
+
assertNoConflictingTaskReviewRound(reviewerRounds, round.id, round.reviewerRoleName);
|
|
3995
4044
|
const activeRound = reviewerRounds.find((entry) => (entry.id !== round.id
|
|
3996
4045
|
&& entry.reviewerRoleName === round.reviewerRoleName
|
|
3997
4046
|
&& (entry.status === "pending" || entry.status === "running")));
|
|
@@ -4262,7 +4311,9 @@ function settleStaleFinalReviewRun(args, store, options) {
|
|
|
4262
4311
|
if (validation.disposition !== "applied" || validation.round === null) {
|
|
4263
4312
|
throw usageError(`Review Run ${run.id} identity does not match its ReviewRound or frozen Task state changed: ${validation.reason ?? "mismatch"}.`);
|
|
4264
4313
|
}
|
|
4265
|
-
const activeReviewerRun = tx.listAgentRuns(task.id).find((entry) => (entry.purpose === "review"
|
|
4314
|
+
const activeReviewerRun = tx.listAgentRuns(task.id).find((entry) => (entry.purpose === "review"
|
|
4315
|
+
&& entry.roleName === round.reviewerRoleName
|
|
4316
|
+
&& entry.status === "active"));
|
|
4266
4317
|
const activeRoleRun = tx.getActiveAgentRun(task.id, round.reviewerRoleName);
|
|
4267
4318
|
if (activeReviewerRun !== undefined || activeRoleRun !== null) {
|
|
4268
4319
|
const active = activeReviewerRun ?? activeRoleRun;
|
|
@@ -4274,7 +4325,7 @@ function settleStaleFinalReviewRun(args, store, options) {
|
|
|
4274
4325
|
if (roundIndex < 0) {
|
|
4275
4326
|
throw dataError(`Final ReviewRound is not in Task history: ${round.id}.`);
|
|
4276
4327
|
}
|
|
4277
|
-
const laterRound = taskRounds.slice(roundIndex + 1).
|
|
4328
|
+
const laterRound = taskRounds.slice(roundIndex + 1).find((entry) => (entry.reviewerRoleName === round.reviewerRoleName));
|
|
4278
4329
|
if (laterRound !== undefined) {
|
|
4279
4330
|
throw usageError(`A later final ReviewRound already exists: ${laterRound.id}/${laterRound.status}.`);
|
|
4280
4331
|
}
|
|
@@ -4574,7 +4625,7 @@ function retryRun(args, store, options) {
|
|
|
4574
4625
|
contextSnapshotRef: contextSnapshotRef(retrySnapshot),
|
|
4575
4626
|
deltaRefIds: contextSnapshotDeltaRefIds(tx, retrySnapshot)
|
|
4576
4627
|
});
|
|
4577
|
-
const created = createAgentRun(runId, task.id, role.name,
|
|
4628
|
+
const created = createAgentRun(runId, task.id, role.name, taskRoleDispatchMode(tx, task.id, role.name, sessions, effective.agentId, effective), assignment, now, {
|
|
4578
4629
|
...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId }),
|
|
4579
4630
|
...(runningGroup === undefined ? {} : {
|
|
4580
4631
|
executionGroupId: runningGroup.id,
|
|
@@ -4751,54 +4802,50 @@ function isSameTaskReviewCandidate(left, right) {
|
|
|
4751
4802
|
return left !== undefined && isDeepStrictEqual(left, right);
|
|
4752
4803
|
}
|
|
4753
4804
|
/**
|
|
4754
|
-
*
|
|
4755
|
-
*
|
|
4756
|
-
* Round is a conflicting lane regardless of its Reviewer Role.
|
|
4805
|
+
* One Reviewer Role has one live Task-final Review lane. Different Reviewer
|
|
4806
|
+
* Roles own independent Session/Workspace slots and may review concurrently.
|
|
4757
4807
|
*/
|
|
4758
|
-
function assertNoConflictingTaskReviewRound(rounds, reusableRoundIds = []) {
|
|
4808
|
+
function assertNoConflictingTaskReviewRound(rounds, reusableRoundIds = [], reviewerRoleName) {
|
|
4759
4809
|
const reusable = new Set(typeof reusableRoundIds === "string" ? [reusableRoundIds] : reusableRoundIds);
|
|
4760
4810
|
const conflicting = rounds.find((entry) => (!reusable.has(entry.id)
|
|
4761
4811
|
&& (entry.scope ?? "work-item") === "task"
|
|
4762
|
-
&& (entry.status === "pending" || entry.status === "running")
|
|
4812
|
+
&& (entry.status === "pending" || entry.status === "running")
|
|
4813
|
+
&& (reviewerRoleName === undefined || entry.reviewerRoleName === reviewerRoleName)));
|
|
4763
4814
|
if (conflicting !== undefined) {
|
|
4764
4815
|
throw usageError(`Another active Task-final ReviewRound already exists: ${conflicting.id}/${conflicting.reviewerRoleName}.`);
|
|
4765
4816
|
}
|
|
4766
4817
|
}
|
|
4767
4818
|
/**
|
|
4768
|
-
* Queues a Task-scoped final ReviewRound
|
|
4769
|
-
*
|
|
4770
|
-
*
|
|
4819
|
+
* Queues a Task-scoped final ReviewRound only after the Reviewer lane passes
|
|
4820
|
+
* its mechanical preflight. Busy or unavailable preparation never becomes a
|
|
4821
|
+
* failed semantic ReviewRound.
|
|
4771
4822
|
*/
|
|
4772
4823
|
function queueTaskReviewRound(store, task, config, taskCandidate, options, now, requestedBy = "policy", taskFinalContract) {
|
|
4773
|
-
assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id));
|
|
4774
4824
|
const provenance = taskReviewProvenance(store, task, options, taskCandidate);
|
|
4775
4825
|
if (!isSameTaskReviewCandidate(provenance.candidate, taskCandidate)) {
|
|
4776
4826
|
throw usageError(`Task-final ReviewRound frozen Task heads changed before queueing.`);
|
|
4777
4827
|
}
|
|
4778
|
-
const pending = createTaskReviewRound(store.nextReviewRoundId(task.id), task.id, config.roleName, requestedBy, taskCandidate, now, taskFinalContract);
|
|
4779
|
-
store.saveReviewRound(task.id, pending);
|
|
4780
4828
|
const producerCollision = taskReviewProducerCollision(provenance, config.roleName);
|
|
4781
4829
|
if (producerCollision !== null) {
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4830
|
+
throw usageError(producerCollision);
|
|
4831
|
+
}
|
|
4832
|
+
const availability = projectReviewerAvailability(store, task.id, config.roleName);
|
|
4833
|
+
if (availability.kind === "busy") {
|
|
4834
|
+
throw usageError(`Reviewer ${config.roleName} is busy (${availability.phase}`
|
|
4835
|
+
+ `${availability.activeRunId === undefined ? "" : `; Run ${availability.activeRunId}`}); `
|
|
4836
|
+
+ `retry after ${availability.retryAfterSeconds}s.`);
|
|
4785
4837
|
}
|
|
4786
4838
|
let reviewer = store.getRole(task.id, config.roleName);
|
|
4787
4839
|
if (reviewer === null) {
|
|
4788
4840
|
const globalRole = store.getGlobalRole(config.roleName);
|
|
4789
4841
|
if (globalRole === null) {
|
|
4790
|
-
|
|
4791
|
-
store.saveReviewRound(task.id, failed);
|
|
4792
|
-
return failed;
|
|
4842
|
+
throw usageError(`Global Role not found: ${config.roleName}.`);
|
|
4793
4843
|
}
|
|
4794
4844
|
reviewer = createTaskRole(store, task, config.roleName, undefined, now, config.roleName);
|
|
4795
4845
|
store.saveRole(task.id, reviewer);
|
|
4796
4846
|
}
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
store.saveReviewRound(task.id, failed);
|
|
4800
|
-
return failed;
|
|
4801
|
-
}
|
|
4847
|
+
const pending = createTaskReviewRound(store.nextReviewRoundId(task.id), task.id, config.roleName, requestedBy, taskCandidate, now, taskFinalContract);
|
|
4848
|
+
store.saveReviewRound(task.id, pending);
|
|
4802
4849
|
return pending;
|
|
4803
4850
|
}
|
|
4804
4851
|
/**
|
|
@@ -4833,33 +4880,10 @@ function prepareFinalTaskReview(store, task, now, taskFinalContract, options) {
|
|
|
4833
4880
|
// review evidence. Do not create duplicate rounds on repeated completion
|
|
4834
4881
|
// attempts. A failed round remains a blocker until the Leader changes the
|
|
4835
4882
|
// candidate or otherwise resolves the failed evidence explicitly.
|
|
4836
|
-
//
|
|
4837
|
-
//
|
|
4838
|
-
//
|
|
4839
|
-
|
|
4840
|
-
&& latest.deltaRecheck !== undefined
|
|
4841
|
-
&& latest.deltaRecheck.disposition === "requires-full-review") {
|
|
4842
|
-
// Fall through to queue a full Review for the same candidate.
|
|
4843
|
-
const escalated = queueTaskReviewRound(store, task, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
|
|
4844
|
-
// Record the escalation lineage on the delta Round so the full Review
|
|
4845
|
-
// is traceable from the non-accepting delta disposition.
|
|
4846
|
-
if (latest.deltaRecheck.escalatedToReviewRoundId === undefined) {
|
|
4847
|
-
store.saveReviewRound(task.id, {
|
|
4848
|
-
...latest,
|
|
4849
|
-
deltaRecheck: {
|
|
4850
|
-
...latest.deltaRecheck,
|
|
4851
|
-
escalatedToReviewRoundId: escalated.id
|
|
4852
|
-
}
|
|
4853
|
-
});
|
|
4854
|
-
}
|
|
4855
|
-
return escalated;
|
|
4856
|
-
}
|
|
4857
|
-
else {
|
|
4858
|
-
return latest.status === "completed"
|
|
4859
|
-
&& classifyReviewRoundOutcome(latest, store)?.kind === "semantic"
|
|
4860
|
-
? null
|
|
4861
|
-
: latest;
|
|
4862
|
-
}
|
|
4883
|
+
// A non-accepting delta is durable evidence for the Leader, not an
|
|
4884
|
+
// instruction for Core to manufacture a full Review. Completion remains
|
|
4885
|
+
// blocked until the Leader explicitly chooses and obtains valid evidence.
|
|
4886
|
+
return isAcceptedTaskReviewBaseline(store, latest) ? null : latest;
|
|
4863
4887
|
}
|
|
4864
4888
|
return queueTaskReviewRound(store, task, config, taskCandidate, options, now, establishedRound?.requestedBy ?? "policy", taskFinalContract);
|
|
4865
4889
|
}
|
|
@@ -4880,7 +4904,7 @@ function resumablePendingFinalTaskReview(store, task, round, config, taskCandida
|
|
|
4880
4904
|
if (!isSameTaskReviewCandidate(round.taskCandidate, taskCandidate)) {
|
|
4881
4905
|
throw usageError(`Final ReviewRound ${round.id} freezes a candidate that is no longer the current Task candidate.`);
|
|
4882
4906
|
}
|
|
4883
|
-
assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id), round.id);
|
|
4907
|
+
assertNoConflictingTaskReviewRound(store.listReviewRounds(task.id), round.id, round.reviewerRoleName);
|
|
4884
4908
|
const reviewer = store.getRole(task.id, round.reviewerRoleName);
|
|
4885
4909
|
if (reviewer === null || reviewer.name !== round.reviewerRoleName) {
|
|
4886
4910
|
throw usageError(`Pending final ReviewRound Reviewer identity changed: ${round.id}.`);
|
|
@@ -5004,12 +5028,12 @@ function retryFailedReviewRun(previous, store, options, now) {
|
|
|
5004
5028
|
if (roundIndex < 0) {
|
|
5005
5029
|
throw dataError(`Final ReviewRound is not in Task history: ${round.id}.`);
|
|
5006
5030
|
}
|
|
5007
|
-
const laterRound = taskRounds.slice(roundIndex + 1).
|
|
5031
|
+
const laterRound = taskRounds.slice(roundIndex + 1).find((entry) => (entry.reviewerRoleName === retryReviewerRoleName));
|
|
5008
5032
|
if (laterRound !== undefined && !isSameTaskReviewCandidate(laterRound.taskCandidate, round.taskCandidate)) {
|
|
5009
5033
|
throw usageError(`A newer final Task candidate already has ReviewRound ${laterRound.id}.`);
|
|
5010
5034
|
}
|
|
5011
5035
|
const allReviewerRounds = reviewRoundsByIdentity(tx.listReviewRounds(task.id).filter((entry) => (entry.reviewerRoleName === retryReviewerRoleName)));
|
|
5012
|
-
assertNoConflictingTaskReviewRound(tx.listReviewRounds(task.id), round.id);
|
|
5036
|
+
assertNoConflictingTaskReviewRound(tx.listReviewRounds(task.id), round.id, retryReviewerRoleName);
|
|
5013
5037
|
const activeRound = allReviewerRounds.find((entry) => (entry.id !== round.id
|
|
5014
5038
|
&& (entry.status === "pending" || entry.status === "running")));
|
|
5015
5039
|
if (activeRound !== undefined) {
|
|
@@ -5150,11 +5174,11 @@ function retryFailedReviewRun(previous, store, options, now) {
|
|
|
5150
5174
|
}
|
|
5151
5175
|
/**
|
|
5152
5176
|
* Records one explicit Leader recovery decision against an exact live Run.
|
|
5153
|
-
*
|
|
5154
|
-
*
|
|
5177
|
+
* Diagnose and retry remain same-Run requests: this command never changes a
|
|
5178
|
+
* native Conversation generation.
|
|
5155
5179
|
*/
|
|
5156
5180
|
function recoverRun(args, store, options) {
|
|
5157
|
-
const usage = "Task run recover usage: yui task run recover <task>/<run> --action <diagnose|retry|
|
|
5181
|
+
const usage = "Task run recover usage: yui task run recover <task>/<run> --action <diagnose|retry|terminate> (--expected-progress-at <timestamp>|--from-next-action <fingerprint>) --provider-acceptance <accepted|rejected|ambiguous> --reason <text> [--agent-id <id>] [--adapter-id <id>] [--native-session-id <id>] [--launch-id <id>].";
|
|
5158
5182
|
const parsed = parseTail(args, new Set([
|
|
5159
5183
|
"--action",
|
|
5160
5184
|
"--expected-progress-at",
|
|
@@ -5535,7 +5559,6 @@ function yieldRun(args, store, options) {
|
|
|
5535
5559
|
kind: "yield"
|
|
5536
5560
|
}, now);
|
|
5537
5561
|
}
|
|
5538
|
-
clearMatchingLeaderStallAttention(tx, task.id, active.id);
|
|
5539
5562
|
if (active.purpose === "review") {
|
|
5540
5563
|
const round = active.reviewRoundId === undefined
|
|
5541
5564
|
? null
|
|
@@ -5814,7 +5837,6 @@ function checkpointRun(args, store, options) {
|
|
|
5814
5837
|
kind: "checkpoint"
|
|
5815
5838
|
}, now);
|
|
5816
5839
|
}
|
|
5817
|
-
clearMatchingLeaderStallAttention(tx, task.id, run.id);
|
|
5818
5840
|
return progress;
|
|
5819
5841
|
});
|
|
5820
5842
|
return `Checkpoint recorded for ${parsed.positionals[0]} (${event.id}).\n`;
|
|
@@ -5957,9 +5979,13 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
5957
5979
|
throw usageError(message);
|
|
5958
5980
|
}
|
|
5959
5981
|
if (taskScope) {
|
|
5982
|
+
const requestedReviewers = new Set(round.executionGroup?.lanes.map(({ roleName }) => roleName)
|
|
5983
|
+
?? [round.reviewerRoleName]);
|
|
5960
5984
|
const conflicting = tx.listReviewRounds(task.id).find((entry) => (entry.id !== round.id
|
|
5961
5985
|
&& (entry.scope ?? "work-item") === "task"
|
|
5962
5986
|
&& (entry.status === "pending" || entry.status === "running")
|
|
5987
|
+
&& (entry.executionGroup?.lanes.some(({ roleName }) => requestedReviewers.has(roleName))
|
|
5988
|
+
?? requestedReviewers.has(entry.reviewerRoleName))
|
|
5963
5989
|
&& !(entry.status === "running"
|
|
5964
5990
|
&& entry.reviewerRunId !== undefined
|
|
5965
5991
|
&& tx.getAgentRun(task.id, entry.reviewerRunId)?.status === "failed")));
|
|
@@ -6003,8 +6029,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
6003
6029
|
let deltaContext = "";
|
|
6004
6030
|
if (taskScope && round.deltaRecheck !== undefined) {
|
|
6005
6031
|
const previousRound = tx.getReviewRound(taskId, round.deltaRecheck.previousReviewRoundId);
|
|
6006
|
-
if (previousRound === null || !
|
|
6007
|
-
throw new TaskFinalReviewDispatchDriftError(`Delta-recheck
|
|
6032
|
+
if (previousRound === null || !isAcceptedTaskReviewBaseline(tx, previousRound)) {
|
|
6033
|
+
throw new TaskFinalReviewDispatchDriftError(`Delta-recheck accepted baseline is unavailable: ${round.deltaRecheck.previousReviewRoundId}.`);
|
|
6008
6034
|
}
|
|
6009
6035
|
const diffByProject = options.deltaRecheckDiff;
|
|
6010
6036
|
if (diffByProject === undefined) {
|
|
@@ -6048,9 +6074,9 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
6048
6074
|
"Start from the user's core outcome and the WorkItem intent. The candidate summary is a pointer, not proof: inspect the complete relevant change, callers, and proportionate checks.",
|
|
6049
6075
|
"Keep Yui Core lifecycle safety, generic Reviewer behavior, Project Policy/Knowledge, and the Task Contract separate. Follow Project Policy pointers from the dispatch context for project-specific checks.",
|
|
6050
6076
|
...(round.scope === "task" && round.deltaRecheck === undefined
|
|
6051
|
-
? ["This is
|
|
6077
|
+
? ["This is a full Task Review: inspect every bound Project at the frozen Task heads, and report only reachable, material, actionable P1/P2 findings or bounded verification gaps."]
|
|
6052
6078
|
: []),
|
|
6053
|
-
"You may freely edit source/tests, run local build or test commands, and optionally commit diagnostic evidence only inside this ReviewRound
|
|
6079
|
+
"You may freely edit source/tests, run local build or test commands, and optionally commit diagnostic evidence only inside this stable Reviewer workspace at the exact ReviewRound snapshot.",
|
|
6054
6080
|
"Do not push, integrate, mutate Task state, touch the Candidate or Worker workspace, another Task/workspace, a stable checkout, or the real Yui control-plane home.",
|
|
6055
6081
|
"Use the exact --summary-file - body to report complete findings, evidence, checks actually run, uncertainty, and recommended next actions in clear Markdown or JSON. Yui preserves the full report; no fixed wording or field list is required. If you include evidenceCommit, it must match the managed Review workspace.",
|
|
6056
6082
|
"Report reviewBaseCommit, exact checks/results, material findings, and uncertainty. Review yield completes only this Round and creates no Candidate or ChangeSet.",
|
|
@@ -6176,7 +6202,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
6176
6202
|
effective,
|
|
6177
6203
|
workspace: laneWorkspace
|
|
6178
6204
|
}, now);
|
|
6179
|
-
createdRuns.push(createAgentRun(runId, taskId, laneReviewer.name,
|
|
6205
|
+
createdRuns.push(createAgentRun(runId, taskId, laneReviewer.name, taskRoleDispatchMode(tx, taskId, laneReviewer.name, sessions, effective.agentId, effective), assignment, now, {
|
|
6180
6206
|
...(item === undefined ? {} : { workItemId: item.id }),
|
|
6181
6207
|
purpose: "review",
|
|
6182
6208
|
reviewRoundId: round.id,
|
|
@@ -6248,7 +6274,13 @@ export function failPendingReviewRound(taskId, reviewRoundId, summary, store, op
|
|
|
6248
6274
|
return round;
|
|
6249
6275
|
const terminal = finishReviewRound(round, "failed", summary, now);
|
|
6250
6276
|
tx.saveReviewRound(taskId, terminal);
|
|
6277
|
+
const event = recordTaskEventRecord(tx, taskId, "review.failed-to-start", {
|
|
6278
|
+
reviewRoundId: terminal.id,
|
|
6279
|
+
reviewerRoleName: terminal.reviewerRoleName,
|
|
6280
|
+
reason: summary
|
|
6281
|
+
}, now);
|
|
6251
6282
|
enqueueWork(tx, leaderMailbox(taskId), "review-failed", now, [
|
|
6283
|
+
eventRef(taskId, event.id),
|
|
6252
6284
|
...(round.workItemId === undefined ? [] : [workItemRef(taskId, round.workItemId)])
|
|
6253
6285
|
]);
|
|
6254
6286
|
return terminal;
|
|
@@ -6334,7 +6366,7 @@ function appendMessage(store, taskId, body, kind, author, now, context = {}) {
|
|
|
6334
6366
|
return message;
|
|
6335
6367
|
}
|
|
6336
6368
|
function recordTaskEvent(store, taskId, type, payload, now) {
|
|
6337
|
-
recordTaskEventRecord(store, taskId, type, payload, now);
|
|
6369
|
+
return recordTaskEventRecord(store, taskId, type, payload, now);
|
|
6338
6370
|
}
|
|
6339
6371
|
function leaderActionEventPayload(store, taskId, options) {
|
|
6340
6372
|
const runId = taskLeaderActionRunId(store, taskId, options.environment, options.yuiHome);
|
|
@@ -6536,6 +6568,9 @@ function assertTaskOpen(task) {
|
|
|
6536
6568
|
function taskActor(options, taskId) {
|
|
6537
6569
|
return resolveTaskActor(options.environment, taskId);
|
|
6538
6570
|
}
|
|
6571
|
+
function taskRoleDispatchMode(store, taskId, roleName, sessions, agentId, effective) {
|
|
6572
|
+
return roleSessionDispatchModeWithConversationSwitch(sessions, store.listEvents(taskId), store.getWorkMailbox({ kind: "role", taskId, roleName }), roleName, agentId, effective);
|
|
6573
|
+
}
|
|
6539
6574
|
function inactiveTaskMessage(task, action) {
|
|
6540
6575
|
if (task.status === "draft") {
|
|
6541
6576
|
return `Task ${task.id} is a Draft; activate it before ${action}.`;
|
|
@@ -6566,10 +6601,9 @@ function parseWorkStatus(value) {
|
|
|
6566
6601
|
function parseRecoveryAction(value, usage) {
|
|
6567
6602
|
if (value === "diagnose"
|
|
6568
6603
|
|| value === "retry"
|
|
6569
|
-
|| value === "replace-session"
|
|
6570
6604
|
|| value === "terminate")
|
|
6571
6605
|
return value;
|
|
6572
|
-
throw usageError("--action must be diagnose, retry,
|
|
6606
|
+
throw usageError("--action must be diagnose, retry, or terminate.", usage);
|
|
6573
6607
|
}
|
|
6574
6608
|
function parseProviderAcceptance(value, usage) {
|
|
6575
6609
|
if (value === "accepted" || value === "rejected" || value === "ambiguous")
|