@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.
Files changed (108) hide show
  1. package/ARCHITECTURE.md +23 -4
  2. package/README.md +44 -22
  3. package/dist/cli/commandCatalog.js +13 -10
  4. package/dist/cli/interactionPolicy.js +5 -6
  5. package/dist/cli/updateCommand.js +3 -1
  6. package/dist/cli/updateOrchestrator.js +173 -28
  7. package/dist/cli/updatePorts.js +137 -8
  8. package/dist/cli/upgradeCommand.js +19 -9
  9. package/dist/cli.js +156 -57
  10. package/dist/commands/configCommands.js +13 -46
  11. package/dist/commands/executionAuditCommands.js +3 -2
  12. package/dist/commands/jobCommands.js +4 -10
  13. package/dist/commands/taskCommands.js +234 -200
  14. package/dist/commands/taskCompletionGate.js +29 -54
  15. package/dist/commands/taskContextCommand.js +61 -13
  16. package/dist/commands/taskInputCommands.js +8 -5
  17. package/dist/commands/taskNextActionCommand.js +56 -6
  18. package/dist/commands/taskOverviewCommand.js +17 -31
  19. package/dist/commands/taskRoleRuntimeStatus.js +55 -7
  20. package/dist/commands/taskWorkspaceCommands.js +5 -2
  21. package/dist/config/configCatalog.js +3 -3
  22. package/dist/config/yuiConfig.js +3 -7
  23. package/dist/context/wakeNotification.js +20 -5
  24. package/dist/controller/agentRuntimeObserver.js +247 -51
  25. package/dist/controller/clientRuntime.js +39 -3
  26. package/dist/controller/controller.js +31 -27
  27. package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
  28. package/dist/controller/runtime.js +58 -3
  29. package/dist/controller/runtimeEventInbox.js +17 -7
  30. package/dist/controller/runtimeEventProcessor.js +12 -19
  31. package/dist/controller/runtimeHookRunFence.js +19 -4
  32. package/dist/controller/runtimeObservationHook.js +45 -0
  33. package/dist/controller/structuredProviderObservation.js +20 -3
  34. package/dist/core/controllerClient.js +20 -2
  35. package/dist/core/controllerServer.js +1 -0
  36. package/dist/execution/resourceBroker.js +5 -4
  37. package/dist/executor/agentExecutor.js +52 -50
  38. package/dist/executor/effectiveLaunch.js +8 -48
  39. package/dist/executor/fileRoleLaunchPlanner.js +129 -45
  40. package/dist/executor/workspacePreflightClassification.js +23 -2
  41. package/dist/interaction/operatorPresentation.js +33 -89
  42. package/dist/lifecycle/exactRunTerminalization.js +69 -5
  43. package/dist/observability/executionAudit.js +5 -0
  44. package/dist/observability/orchestrationMetrics.js +8 -3
  45. package/dist/profile/agentProfile.js +1 -1
  46. package/dist/release/cliHomeReleaseFence.js +123 -0
  47. package/dist/release/runtimeRelease.js +20 -0
  48. package/dist/repository/taskWorkspacePreparer.js +176 -60
  49. package/dist/resources/sqliteResourceRegistry.js +1 -1
  50. package/dist/review/deltaRecheck.js +12 -51
  51. package/dist/review/reviewAcceptance.js +26 -0
  52. package/dist/review/reviewConfig.js +0 -31
  53. package/dist/review/reviewDecision.js +113 -0
  54. package/dist/review/reviewOutcomeClassifier.js +1 -1
  55. package/dist/review/reviewRound.js +1 -1
  56. package/dist/review/reviewerAvailability.js +69 -0
  57. package/dist/run/recoveryProjection.js +45 -6
  58. package/dist/runtime/agentDriverObservation.js +24 -10
  59. package/dist/runtime/agentHost.js +159 -85
  60. package/dist/runtime/builtinAgentDrivers.js +4 -3
  61. package/dist/runtime/builtinTranscriptObserver.js +301 -64
  62. package/dist/runtime/builtinTranscriptUsage.js +9 -7
  63. package/dist/runtime/conversationSwitch.js +277 -0
  64. package/dist/runtime/index.js +2 -1
  65. package/dist/runtime/launchBroker.js +12 -0
  66. package/dist/runtime/processExitOutbox.js +88 -0
  67. package/dist/runtime/providerRuntimeIdentity.js +29 -1
  68. package/dist/runtime/runtimeHealthPolicy.js +5 -5
  69. package/dist/runtime/runtimeObservation.js +28 -0
  70. package/dist/runtime/runtimeProjection.js +22 -32
  71. package/dist/runtime/sessionTokenMetrics.js +181 -0
  72. package/dist/runtime/structuredProviderHost.js +7 -1
  73. package/dist/runtime/tmuxAdapters.js +4 -1
  74. package/dist/scheduler/activeRoleRunDelivery.js +34 -199
  75. package/dist/scheduler/activeTaskProgress.js +7 -6
  76. package/dist/scheduler/leaderWakeupProcessor.js +42 -138
  77. package/dist/scheduler/operatorEvent.js +34 -0
  78. package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
  79. package/dist/scheduler/roleRunStall.js +53 -31
  80. package/dist/scheduler/taskExecutionProjection.js +97 -76
  81. package/dist/scheduler/taskObservabilityProjection.js +10 -11
  82. package/dist/storage/migration/productionRegistry.js +379 -0
  83. package/dist/storage/sqliteSchema.js +66 -23
  84. package/dist/storage/sqliteStore.js +46 -23
  85. package/dist/storage/storeRpc.js +0 -1
  86. package/dist/storage/taskStore.js +15 -30
  87. package/dist/storage/upgrade/homeClassification.js +52 -0
  88. package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
  89. package/dist/storage/upgrade/recordVersions.js +3 -2
  90. package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
  91. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
  92. package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
  93. package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
  94. package/dist/task/completionReadiness.js +10 -6
  95. package/dist/task/nextAction.js +82 -58
  96. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  97. package/dist/web/assets/client/components.js +13 -3
  98. package/dist/web/assets/client/i18n.js +6 -2
  99. package/dist/web/webSnapshot.js +7 -1
  100. package/i18n/README.zh-CN.md +2 -2
  101. package/package.json +1 -1
  102. package/skills/yui-leader/SKILL.md +60 -20
  103. package/skills/yui-operator/SKILL.md +13 -4
  104. package/skills/yui-reviewer/SKILL.md +35 -11
  105. package/dist/context/sessionContextBudget.js +0 -71
  106. package/dist/lifecycle/contextBudgetRollover.js +0 -81
  107. package/dist/lifecycle/taskRoleSessionReset.js +0 -126
  108. package/dist/scheduler/operatorNotification.js +0 -59
package/dist/cli.js CHANGED
@@ -37,7 +37,7 @@ import { runResourcesCommand } from "./commands/resourcesCommands.js";
37
37
  import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
38
38
  import { runProjectCommand } from "./commands/projectCommands.js";
39
39
  import { runProfileCommand } from "./commands/profileCommands.js";
40
- import { dispatchPreparedReviewRound, failPendingReviewRound, RESUMED_PENDING_FINAL_REVIEW, TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW, TaskFinalReviewDispatchDriftError, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
40
+ import { dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
41
41
  import { taskActor } from "./commands/taskActor.js";
42
42
  import { isCurrentGlobalOperator } from "./commands/taskInputCommands.js";
43
43
  import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
@@ -49,6 +49,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
49
49
  import { createUpdatePorts } from "./cli/updatePorts.js";
50
50
  import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
51
51
  import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
52
+ import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
52
53
  import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
53
54
  import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
54
55
  import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
@@ -68,7 +69,7 @@ import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliE
68
69
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
69
70
  import { AGENT_HOST_CONTROL_PROTOCOL, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
70
71
  import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
71
- import { FileTaskWorkspacePreparer, ReviewRoundWorkspaceEvidenceError } from "./repository/taskWorkspacePreparer.js";
72
+ import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
72
73
  import { inspectStorageSchema } from "./storage/storageSchema.js";
73
74
  import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
74
75
  import { resolveYuiHome } from "./storage/taskStore.js";
@@ -83,13 +84,14 @@ import { AgentConfigurationCatalogService } from "./executor/agentConfigurationC
83
84
  import { TmuxWebTerminalService } from "./web/tmuxWebTerminal.js";
84
85
  import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
85
86
  import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
87
+ import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
86
88
  import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR, assertCompatibleControlPlanePreflight, assertExactControlPlanePreflight, assertExactTaskRuntimeEnvironment, assertExactTaskRuntimeState, exactControlPlaneDigest, extractExactControlArgument, createExactControlPlaneDescriptor, parseExactControlPlaneDescriptor } from "./runtime/exactControlPlane.js";
87
89
  import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
88
90
  import { builtinAgentDriverRegistry } from "./runtime/builtinAgentDrivers.js";
89
91
  import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
90
92
  import { prepareTaskFinalReviewContractRebindProof, resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractRebind.js";
91
93
  import { assessDeltaRecheck } from "./review/deltaRecheck.js";
92
- import { deltaRecheckEnabled } from "./review/reviewConfig.js";
94
+ import { isAcceptedTaskReviewBaseline } from "./review/reviewAcceptance.js";
93
95
  import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
94
96
  import { currentWorkItemExecutionGroup, workItemExecutionGroupById } from "./workItem/workItem.js";
95
97
  const VERSION = YUI_VERSION;
@@ -107,13 +109,47 @@ void main().catch((error) => {
107
109
  process.exitCode = error.exitCode;
108
110
  return;
109
111
  }
110
- const message = error instanceof Error ? error.message : String(error);
112
+ const message = runtimeFailureMessage(error);
111
113
  process.stderr.write(`${jsonOutput
112
114
  ? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message, details: {} })
113
115
  : `RUNTIME_ERROR: ${message}`}\n`);
114
116
  process.exitCode = 5;
115
117
  });
118
+ function runtimeFailureMessage(error) {
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ if (!(error instanceof SqliteSchemaMigrationError))
121
+ return message;
122
+ try {
123
+ return `${message}\n${describeCliHomeInvocation({
124
+ home: resolveYuiHome(process.env),
125
+ packageRoot: fileURLToPath(new URL("../", import.meta.url)),
126
+ entryPath: fileURLToPath(import.meta.url)
127
+ })}`;
128
+ }
129
+ catch {
130
+ return message;
131
+ }
132
+ }
116
133
  export async function main() {
134
+ const home = resolveYuiHome(process.env);
135
+ const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
136
+ const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
137
+ || process.env.YUI_SESSION_SCOPE === "global"
138
+ || process.env[YUI_CONTROL_PLANE_DESCRIPTOR] !== undefined
139
+ || process.env[YUI_TASK_RUNTIME_DESCRIPTOR] !== undefined;
140
+ const homeFreeInvocation = args.length === 0
141
+ || (args[0] === "version" && args.length === 1)
142
+ || routedForFence?.kind === "help"
143
+ || routedForFence?.kind === "path-error"
144
+ || routedForFence?.kind === "incomplete";
145
+ if (managedInvocation || !homeFreeInvocation) {
146
+ assertCliHomeReleaseFence({
147
+ home,
148
+ packageRoot: fileURLToPath(new URL("../", import.meta.url)),
149
+ entryPath: fileURLToPath(import.meta.url),
150
+ args
151
+ });
152
+ }
117
153
  const delegatedDriver = explicitReleaseActivationDriver();
118
154
  if (delegatedDriver !== null) {
119
155
  const delegated = spawnSync(process.execPath, [delegatedDriver, ...process.argv.slice(2)], {
@@ -160,7 +196,6 @@ export async function main() {
160
196
  process.exitCode = runUpdateCommand();
161
197
  return;
162
198
  }
163
- const home = resolveYuiHome(process.env);
164
199
  if (args[0] === "release") {
165
200
  const subcommand = args[1];
166
201
  if (subcommand === "install" && args.length === 3) {
@@ -266,19 +301,44 @@ export async function main() {
266
301
  if (args[0] === "upgrade") {
267
302
  // Mirror doctor/controller: needs a Home but self-manages the schema check,
268
303
  // because upgrade must run against a non-current Home.
269
- const result = await runUpgradeCommand(args.slice(1), home, process.env.YUI_UPDATE_EXTERNALLY_QUIESCED === "1"
270
- ? { controllerLifecycle: "externally-quiesced" }
271
- : {});
272
- // Public execute upgrades leave the Home operational even when no
273
- // Controller existed before the command. Dry-run and the staged updater's
274
- // externally-quiesced preflight must remain read-only/lifecycle-neutral.
275
- if (args.length === 1
276
- && result.exitCode === 0
277
- && process.env.YUI_UPDATE_EXTERNALLY_QUIESCED !== "1") {
278
- await ensureFileTaskController(home, { environment: process.env });
304
+ const externallyQuiesced = process.env.YUI_UPDATE_EXTERNALLY_QUIESCED === "1";
305
+ const ownsHandover = args.length === 1 && !externallyQuiesced;
306
+ const externalFenceOwner = externallyQuiesced
307
+ ? process.env.YUI_UPDATE_HANDOVER_OWNER_PID
308
+ : undefined;
309
+ const externalUpgradeFenceOwnerPid = externalFenceOwner === undefined
310
+ ? undefined
311
+ : Number(externalFenceOwner);
312
+ if (externalUpgradeFenceOwnerPid !== undefined
313
+ && (!Number.isSafeInteger(externalUpgradeFenceOwnerPid)
314
+ || externalUpgradeFenceOwnerPid < 1)) {
315
+ throw runtimeError("Update storage-fence owner PID is invalid.");
316
+ }
317
+ const handover = ownsHandover ? acquireHandoverLock(home) : undefined;
318
+ try {
319
+ const result = await runUpgradeCommand(args.slice(1), home, externallyQuiesced
320
+ ? {
321
+ controllerLifecycle: "externally-quiesced",
322
+ ...(externalUpgradeFenceOwnerPid === undefined
323
+ ? {}
324
+ : { externalUpgradeFenceOwnerPid })
325
+ }
326
+ : {});
327
+ // Public execute upgrades leave the Home operational even when no
328
+ // Controller existed before the command. Dry-run and the staged updater's
329
+ // externally-quiesced preflight must remain read-only/lifecycle-neutral.
330
+ if (ownsHandover && result.exitCode === 0) {
331
+ await ensureFileTaskController(home, {
332
+ environment: process.env,
333
+ handoverOwnerPid: process.pid
334
+ });
335
+ }
336
+ process.exitCode = result.exitCode;
337
+ emit(result.output, false, result.data);
338
+ }
339
+ finally {
340
+ handover?.release();
279
341
  }
280
- process.exitCode = result.exitCode;
281
- emit(result.output, false, result.data);
282
342
  return;
283
343
  }
284
344
  if (args[0] === "internal") {
@@ -486,9 +546,21 @@ export async function main() {
486
546
  }
487
547
  validateCompatibleFileTaskStore(home);
488
548
  const controllerMethod = method;
549
+ const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
550
+ const updateHandoverOwnerPid = updateHandoverOwner === undefined
551
+ ? undefined
552
+ : Number(updateHandoverOwner);
553
+ if (updateHandoverOwnerPid !== undefined
554
+ && (!Number.isSafeInteger(updateHandoverOwnerPid) || updateHandoverOwnerPid < 1)) {
555
+ throw runtimeError("Update Controller handover owner PID is invalid.");
556
+ }
557
+ const controllerOptions = {
558
+ environment: process.env,
559
+ ...(updateHandoverOwnerPid === undefined ? {} : { handoverOwnerPid: updateHandoverOwnerPid })
560
+ };
489
561
  const result = controllerMethod === "restart"
490
- ? await restartFileTaskController(home, { environment: process.env })
491
- : await stopFileTaskController(home, { environment: process.env });
562
+ ? await restartFileTaskController(home, controllerOptions)
563
+ : await stopFileTaskController(home, controllerOptions);
492
564
  // The update lifecycle needs the authenticated replacement PID returned by
493
565
  // restart/readiness. Keep stop's long-standing text envelope, while
494
566
  // exposing restart's structured result alongside its human output.
@@ -1123,14 +1195,16 @@ export async function main() {
1123
1195
  const laneSnapshotPreflight = executionLaneGitSnapshot === undefined
1124
1196
  ? undefined
1125
1197
  : executionLaneGitSnapshot;
1126
- // RFC Phase 3: prepare the Task workspace before activation so a
1127
- // preparation failure keeps the Task in Draft instead of leaving it
1128
- // Active without a workspace.
1129
- if (resolved[1] === "activate") {
1198
+ // Physical preparation may precede the durable write, but Task status,
1199
+ // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1200
+ // one transaction. A failed attempt therefore leaves the Task Draft and
1201
+ // owning no writable workspace.
1202
+ let taskWorkspaceActivation;
1203
+ if (resolved[1] === "activate" && resolved.length === 3) {
1130
1204
  const taskId = resolved[2];
1131
1205
  const task = taskId === undefined ? null : store.getTask(taskId);
1132
1206
  if (task !== null && task.status === "draft") {
1133
- await workspacePreparer.prepareTaskWorkspace(task.id);
1207
+ taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id);
1134
1208
  }
1135
1209
  }
1136
1210
  const result = runTaskCommand(resolved.slice(1), store, {
@@ -1153,6 +1227,7 @@ export async function main() {
1153
1227
  ? {}
1154
1228
  : { candidateWorkspace: candidateMaterialization.workspace ?? null }),
1155
1229
  ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1230
+ ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1156
1231
  ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1157
1232
  ...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
1158
1233
  ...(actualTaskReviewCandidate === undefined
@@ -1219,31 +1294,54 @@ export async function main() {
1219
1294
  ...(storedRound?.deltaRecheck === undefined
1220
1295
  || deltaRecheckPreflight === undefined
1221
1296
  ? {}
1222
- : { deltaRecheckDiff: deltaRecheckPreflight.diffByProject })
1297
+ : {
1298
+ deltaRecheckDiff: deltaRecheckPreflight.diffByProject
1299
+ })
1223
1300
  });
1224
1301
  reviewOutput = run === null
1225
1302
  ? `Review ${requestedRound.id} retained pending by Resource Broker\n`
1226
1303
  : `Review queued as ${requestedRound.id} (${run.id})\n`;
1227
1304
  reviewData = {
1305
+ reviewRequest: run === null
1306
+ ? {
1307
+ kind: "busy",
1308
+ reviewerRoleName: requestedRound.reviewerRoleName,
1309
+ phase: "resource-broker",
1310
+ activeReviewRoundId: requestedRound.id,
1311
+ retryable: true,
1312
+ retryAfterSeconds: 5
1313
+ }
1314
+ : {
1315
+ kind: "started",
1316
+ reviewerRoleName: requestedRound.reviewerRoleName,
1317
+ reviewRoundId: requestedRound.id,
1318
+ runId: run.id
1319
+ },
1228
1320
  reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
1229
1321
  ...(run === null ? {} : { reviewRun: run }),
1230
1322
  workspace
1231
1323
  };
1232
1324
  }
1233
1325
  catch (error) {
1234
- const currentRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
1235
- if (requestedRound.resumedPendingFinalReview
1236
- && !requestedRound.terminalizedLeaderRun
1237
- && (error instanceof ReviewRoundWorkspaceEvidenceError
1238
- || error instanceof TaskFinalReviewDispatchDriftError)
1239
- && (currentRound?.scope ?? "work-item") === "task") {
1240
- throw error;
1241
- }
1242
1326
  const message = error instanceof Error ? error.message : String(error);
1243
1327
  await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
1244
- const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, { runtime, environment: process.env, yuiHome: home });
1245
- reviewOutput = `Review could not start: ${message}\n`;
1246
- reviewData = { reviewRound: failed };
1328
+ const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
1329
+ runtime,
1330
+ environment: process.env,
1331
+ yuiHome: home
1332
+ });
1333
+ reviewOutput = `Review could not start: ${message}\n`
1334
+ + "The failed ReviewRound was retained for Leader routing.\n";
1335
+ reviewData = {
1336
+ reviewRequest: {
1337
+ kind: "unavailable",
1338
+ reviewerRoleName: requestedRound.reviewerRoleName,
1339
+ reviewRoundId: failed.id,
1340
+ reason: message,
1341
+ retryable: true
1342
+ },
1343
+ reviewRound: failed
1344
+ };
1247
1345
  }
1248
1346
  }
1249
1347
  if (resolved[1] === "project" && resolved[2] === "add") {
@@ -1906,6 +2004,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
1906
2004
  if (args[0] !== "task")
1907
2005
  return undefined;
1908
2006
  let taskId;
2007
+ let decisionSupportRead = false;
1909
2008
  if (args[1] === "complete" && args[2] !== undefined) {
1910
2009
  const task = store.getTask(args[2]);
1911
2010
  if (task === null || task.status !== "active" || task.projectBindings.length === 0) {
@@ -1963,9 +2062,21 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
1963
2062
  taskId = reference.taskId;
1964
2063
  }
1965
2064
  }
2065
+ else if ((args[1] === "context" || args[1] === "next-action")
2066
+ && args[2] !== undefined) {
2067
+ taskId = store.getTask(args[2])?.id;
2068
+ decisionSupportRead = true;
2069
+ }
1966
2070
  if (taskId === undefined || store.getTask(taskId)?.status !== "active")
1967
2071
  return undefined;
1968
- return snapshotActualTaskReviewCandidate(taskId, store, preparer);
2072
+ try {
2073
+ return await snapshotActualTaskReviewCandidate(taskId, store, preparer);
2074
+ }
2075
+ catch (error) {
2076
+ if (decisionSupportRead && error instanceof CliError)
2077
+ return undefined;
2078
+ throw error;
2079
+ }
1969
2080
  }
1970
2081
  async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
1971
2082
  const task = store.getTask(taskId);
@@ -2002,9 +2113,8 @@ async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
2002
2113
  /**
2003
2114
  * Issue 07: computes the delta-recheck assessment for `task review request
2004
2115
  * --delta-recheck`. Runs only for that exact command; every other command
2005
- * returns undefined. The assessment's deterministic gates (contiguous base,
2006
- * attempt thresholds, evidence-file overlap) fail closed here; the Reviewer
2007
- * still owns the semantic equivalence decision.
2116
+ * returns undefined. Technical evidence boundaries fail closed here; semantic
2117
+ * risk remains a Leader and Project-policy decision.
2008
2118
  */
2009
2119
  async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReviewCandidate) {
2010
2120
  if (args[0] !== "task" || args[1] !== "review" || args[2] !== "request") {
@@ -2018,23 +2128,13 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
2018
2128
  const task = store.getTask(taskId);
2019
2129
  if (task === null || task.status !== "active")
2020
2130
  return undefined;
2021
- const config = store.getReviewConfig();
2022
- if (!deltaRecheckEnabled(config)) {
2023
- throw usageError("Delta-recheck is not enabled for this Project's review policy. "
2024
- + "Set `yui config workflow set review --role <role> --trigger final --delta-recheck enabled` "
2025
- + "or request a full Review.");
2026
- }
2027
- const reviewConfig = config;
2028
2131
  if (actualTaskReviewCandidate === undefined)
2029
2132
  return undefined;
2030
2133
  // The previous Round is the latest completed Task-final Round that accepted
2031
2134
  // a head (a full Review or an equivalent-and-accepted delta). A
2032
2135
  // non-accepted delta cannot be the base for a new delta.
2033
2136
  const previous = [...store.listReviewRounds(task.id)]
2034
- .filter((round) => ((round.scope ?? "work-item") === "task"
2035
- && round.status === "completed"
2036
- && (round.deltaRecheck === undefined
2037
- || round.deltaRecheck.disposition === "equivalent-and-accepted")))
2137
+ .filter((round) => isAcceptedTaskReviewBaseline(store, round))
2038
2138
  .sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))
2039
2139
  .at(-1);
2040
2140
  if (previous === undefined) {
@@ -2052,11 +2152,10 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
2052
2152
  repositoryPaths,
2053
2153
  previousRound: previous,
2054
2154
  candidate: actualTaskReviewCandidate,
2055
- git: new NodeGitWorkspace(),
2056
- config: reviewConfig
2155
+ git: new NodeGitWorkspace()
2057
2156
  });
2058
2157
  if (assessment.kind === "ineligible") {
2059
- throw usageError(`Delta-recheck is not allowed: ${assessment.reason} Request a full Review instead.`);
2158
+ throw usageError(`Delta-recheck is technically unavailable: ${assessment.reason}`);
2060
2159
  }
2061
2160
  return assessment.preflight;
2062
2161
  }
@@ -2115,13 +2214,13 @@ function reviewRoundFromCommandData(data) {
2115
2214
  const value = round;
2116
2215
  return typeof value.id === "string"
2117
2216
  && typeof value.taskId === "string"
2217
+ && typeof value.reviewerRoleName === "string"
2118
2218
  && typeof value.status === "string"
2119
2219
  ? {
2120
2220
  id: value.id,
2121
2221
  taskId: value.taskId,
2122
- status: value.status,
2123
- resumedPendingFinalReview: data[RESUMED_PENDING_FINAL_REVIEW] === true,
2124
- terminalizedLeaderRun: data[TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW] === true
2222
+ reviewerRoleName: value.reviewerRoleName,
2223
+ status: value.status
2125
2224
  }
2126
2225
  : undefined;
2127
2226
  }
@@ -1,11 +1,11 @@
1
1
  import { mkdirSync, realpathSync } from "node:fs";
2
2
  import { isAbsolute, relative, resolve } from "node:path";
3
3
  import { usageError } from "../errors/cliError.js";
4
- import { DEFAULT_CONTEXT_HARD_TOKENS, DEFAULT_CONTEXT_SOFT_TOKENS, DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
4
+ import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
5
5
  import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
6
6
  import { resolveTimeZone } from "../output/timePresentation.js";
7
7
  import { defaultTableWidth, renderTable } from "../output/table.js";
8
- import { DEFAULT_DELTA_RECHECK_MAX_CHANGED_FILES, DEFAULT_DELTA_RECHECK_MAX_CHANGED_LINES, REVIEW_FINDING_LEDGER_MODES, REVIEW_DELTA_RECHECK_MODES, REVIEW_TRIGGERS } from "../review/reviewConfig.js";
8
+ import { REVIEW_FINDING_LEDGER_MODES, REVIEW_TRIGGERS } from "../review/reviewConfig.js";
9
9
  /**
10
10
  * One uniform key model for every durable setting. Each catalog domain uses
11
11
  * the same show/set/clear contract; strategy settings such as `review` and
@@ -23,8 +23,7 @@ const RESOURCES_GC_AUTO_QUARANTINE_SET_USAGE = "Resources config set usage: yui
23
23
  const LEADER_NEXT_ACTION_SET_USAGE = `Workflow config set usage: yui config workflow set leader-next-action <${LEADER_NEXT_ACTION_MODES.join("|")}>.`;
24
24
  const CONTEXT_BUDGET_SET_USAGE = "Workflow config set usage: yui config workflow set context-budget [--soft-tokens <n>] [--hard-tokens <n>].";
25
25
  const REVIEW_SET_USAGE = "Workflow config set usage: yui config workflow set review --role <global-role> --trigger <always|leader|final> "
26
- + "[--finding-ledger <shadow|enforce>] [--delta-recheck <enabled|disabled>] "
27
- + "[--delta-recheck-max-lines <n>] [--delta-recheck-max-files <n>].";
26
+ + "[--finding-ledger <shadow|enforce>].";
28
27
  export function runConfigCommand(domain, args, store) {
29
28
  const keys = configDefinitionsForDomain(domain).map(({ key }) => key);
30
29
  const usage = `${capitalize(domain)} config usage: yui config ${domain} show | yui config ${domain} set <key> <value...> | yui config ${domain} clear <key>.`;
@@ -97,12 +96,7 @@ export function effectiveConfigData(config, domain) {
97
96
  : {
98
97
  roleName: config.review.roleName,
99
98
  trigger: config.review.trigger,
100
- findingLedger: config.review.findingLedger ?? "shadow",
101
- deltaRecheck: config.review.deltaRecheck ?? "disabled",
102
- deltaRecheckMaxChangedLines: config.review.deltaRecheckMaxChangedLines
103
- ?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_LINES,
104
- deltaRecheckMaxChangedFiles: config.review.deltaRecheckMaxChangedFiles
105
- ?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_FILES
99
+ findingLedger: config.review.findingLedger ?? "shadow"
106
100
  },
107
101
  completionInstallations: config.completionInstallations ?? {}
108
102
  };
@@ -175,14 +169,6 @@ function parseReconciliationIntervalSeconds(value) {
175
169
  function parseBooleanConfigValue(value) {
176
170
  return value === "true" ? true : value === "false" ? false : value;
177
171
  }
178
- function parsePositiveIntegerOption(value, usage) {
179
- if (value === undefined)
180
- return undefined;
181
- if (!/^\d+$/.test(value) || Number(value) < 1) {
182
- throw usageError(usage);
183
- }
184
- return Number(value);
185
- }
186
172
  const CONFIG_KEY_HANDLERS = [
187
173
  {
188
174
  key: "default-agent",
@@ -313,10 +299,10 @@ const CONFIG_KEY_HANDLERS = [
313
299
  },
314
300
  {
315
301
  key: "context-budget",
316
- showLabel: "Context budget",
302
+ showLabel: "Legacy context budget (inactive)",
317
303
  showValue: (config) => {
318
304
  const budget = resolveContextBudget(config.contextBudget);
319
- return `soft ${budget.softTokens} / hard ${budget.hardTokens} tokens`;
305
+ return `inactive; legacy soft ${budget.softTokens} / hard ${budget.hardTokens} tokens`;
320
306
  },
321
307
  set(args, store) {
322
308
  if (args.length !== 2 && args.length !== 4)
@@ -343,14 +329,14 @@ const CONFIG_KEY_HANDLERS = [
343
329
  };
344
330
  const resolved = resolveContextBudget({ ...current, ...next });
345
331
  saveConfigKey(store, (config) => ({ ...config, contextBudget: resolved }));
346
- return `Context budget set to soft ${resolved.softTokens} / hard ${resolved.hardTokens} tokens\n`;
332
+ return `Legacy context budget stored as soft ${resolved.softTokens} / hard ${resolved.hardTokens} tokens (inactive)\n`;
347
333
  },
348
334
  clear(store) {
349
335
  saveConfigKey(store, (config) => {
350
336
  const { contextBudget: _removed, ...rest } = config;
351
337
  return rest;
352
338
  });
353
- return `Context budget reset to soft ${DEFAULT_CONTEXT_SOFT_TOKENS} / hard ${DEFAULT_CONTEXT_HARD_TOKENS} tokens\n`;
339
+ return "Legacy context budget cleared; Session Token metrics remain read-only\n";
354
340
  }
355
341
  },
356
342
  {
@@ -469,7 +455,7 @@ const CONFIG_KEY_HANDLERS = [
469
455
  const { runtimeHealth: _removed, ...rest } = config;
470
456
  return rest;
471
457
  });
472
- return "Runtime health thresholds reset to quiet 300s / diagnostic 600s / stall 1800s\n";
458
+ return "Runtime health thresholds reset to quiet 300s / diagnostic 900s / stall 1800s\n";
473
459
  }
474
460
  },
475
461
  {
@@ -696,7 +682,7 @@ const CONFIG_KEY_HANDLERS = [
696
682
  showValue: (config) => (config.review === undefined
697
683
  ? "disabled"
698
684
  : `${config.review.roleName} (${config.review.trigger}; finding ledger: ${config.review.findingLedger ?? "shadow"}`
699
- + `${config.review.deltaRecheck === "enabled" ? "; delta recheck: enabled" : ""})`),
685
+ + ")"),
700
686
  set(args, store) {
701
687
  if (args.length < 4 || args.length % 2 !== 0)
702
688
  throw usageError(REVIEW_SET_USAGE);
@@ -704,8 +690,7 @@ const CONFIG_KEY_HANDLERS = [
704
690
  for (let index = 0; index < args.length; index += 2) {
705
691
  const name = args[index];
706
692
  const value = args[index + 1];
707
- if (!["--role", "--trigger", "--finding-ledger", "--delta-recheck",
708
- "--delta-recheck-max-lines", "--delta-recheck-max-files"].includes(name)
693
+ if (!["--role", "--trigger", "--finding-ledger"].includes(name)
709
694
  || value === undefined
710
695
  || options.has(name)) {
711
696
  throw usageError(REVIEW_SET_USAGE);
@@ -731,33 +716,15 @@ const CONFIG_KEY_HANDLERS = [
731
716
  }
732
717
  findingLedger = rawLedgerMode;
733
718
  }
734
- const rawDeltaRecheck = options.get("--delta-recheck")?.trim();
735
- let deltaRecheck;
736
- if (rawDeltaRecheck !== undefined) {
737
- if (!REVIEW_DELTA_RECHECK_MODES.includes(rawDeltaRecheck)) {
738
- throw usageError(REVIEW_SET_USAGE);
739
- }
740
- deltaRecheck = rawDeltaRecheck;
741
- }
742
- const deltaRecheckMaxChangedLines = parsePositiveIntegerOption(options.get("--delta-recheck-max-lines"), REVIEW_SET_USAGE);
743
- const deltaRecheckMaxChangedFiles = parsePositiveIntegerOption(options.get("--delta-recheck-max-files"), REVIEW_SET_USAGE);
744
719
  saveConfigKey(store, (config) => ({
745
720
  ...config,
746
721
  review: {
747
722
  roleName,
748
723
  trigger,
749
- ...(findingLedger === undefined ? {} : { findingLedger }),
750
- ...(deltaRecheck === undefined ? {} : { deltaRecheck }),
751
- ...(deltaRecheckMaxChangedLines === undefined
752
- ? {}
753
- : { deltaRecheckMaxChangedLines }),
754
- ...(deltaRecheckMaxChangedFiles === undefined
755
- ? {}
756
- : { deltaRecheckMaxChangedFiles })
724
+ ...(findingLedger === undefined ? {} : { findingLedger })
757
725
  }
758
726
  }));
759
- return `Review set to ${roleName} (${trigger}; finding ledger: ${findingLedger ?? "shadow"}; `
760
- + `delta recheck: ${deltaRecheck ?? "disabled"})\n`;
727
+ return `Review set to ${roleName} (${trigger}; finding ledger: ${findingLedger ?? "shadow"})\n`;
761
728
  },
762
729
  clear(store) {
763
730
  saveConfigKey(store, (config) => {
@@ -139,7 +139,8 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
139
139
  }
140
140
  if (report.sessions.status === "ok" && report.sessions.data !== undefined) {
141
141
  const sessions = report.sessions.data;
142
- lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`, `Terminal by Run relation: ${sessions.terminalByRunRelation.postRunYielded} post-run-yielded`
142
+ lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · Conversation switches ${sessions.conversationSwitches}`
143
+ + ` · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`, `Terminal by Run relation: ${sessions.terminalByRunRelation.postRunYielded} post-run-yielded`
143
144
  + ` · ${sessions.terminalByRunRelation.runFailed} run-failed`
144
145
  + ` · ${sessions.terminalByRunRelation.activeRun} active-run`
145
146
  + ` · ${sessions.terminalByRunRelation.noRun} no-run`);
@@ -155,7 +156,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
155
156
  `Delta-rechecks: ${reviews.deltaRechecks.total} total · `
156
157
  + `${reviews.deltaRechecks.equivalentAndAccepted} accepted · `
157
158
  + `${reviews.deltaRechecks.finding} finding · `
158
- + `${reviews.deltaRechecks.requiresFullReview} escalated to full Review`
159
+ + `${reviews.deltaRechecks.requiresFullReview} requiring Leader decision`
159
160
  ]));
160
161
  }
161
162
  else {
@@ -56,18 +56,14 @@ function collectJobs(store) {
56
56
  });
57
57
  }
58
58
  const failure = store.getLeaderFailure(task.id);
59
- const notification = store.getOperatorNotification(task.id);
60
- const recoveryNotification = notification?.type === "leader-recovery-failed"
61
- ? notification
62
- : null;
63
- if (failure !== null || recoveryNotification !== null) {
59
+ if (failure !== null) {
64
60
  jobs.push({
65
61
  id: `${RECOVERY_PREFIX}${task.id}`,
66
62
  taskId: task.id,
67
63
  kind: "leader-recovery",
68
64
  status: "failed",
69
- detail: failure?.message ?? recoveryNotification?.message ?? "Leader recovery failed.",
70
- updatedAt: failure?.lastFailedAt ?? recoveryNotification?.updatedAt ?? task.updatedAt
65
+ detail: failure.message,
66
+ updatedAt: failure.lastFailedAt
71
67
  });
72
68
  }
73
69
  }
@@ -93,12 +89,10 @@ function retryJob(args, store, options) {
93
89
  if (task.status !== "active") {
94
90
  throw usageError(`Leader recovery can only be retried for an active Task: ${task.id}.`);
95
91
  }
96
- if (tx.getLeaderFailure(task.id) === null
97
- && tx.getOperatorNotification(task.id)?.type !== "leader-recovery-failed") {
92
+ if (tx.getLeaderFailure(task.id) === null) {
98
93
  throw usageError(`Job not found: ${id}.`);
99
94
  }
100
95
  tx.clearLeaderFailure(task.id);
101
- tx.clearOperatorNotification(task.id);
102
96
  enqueueWork(tx, { kind: "role", taskId: task.id, roleName: "leader" }, "recovery-retry", now, [{ type: "task", id: task.id }]);
103
97
  });
104
98
  const target = { kind: "role", taskId, roleName: "leader" };