@zq-silk/yui 0.12.2 → 0.12.4

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.
@@ -7,6 +7,7 @@ import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageErr
7
7
  import { createTaskEvent } from "../event/taskEvent.js";
8
8
  import { createTaskRecordRetirement, isTaskRecordRetired, taskRecordRetirement } from "../task/taskRecordRetirement.js";
9
9
  import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
10
+ import { routeRoleEvent } from "../scheduler/operatorEvent.js";
10
11
  import { readCommandText } from "./textInput.js";
11
12
  import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
12
13
  import { createRoleSessionSet, retireTaskRoleSessionsForWorkspace, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
@@ -18,11 +19,12 @@ import { formatTimestamp } from "../output/timePresentation.js";
18
19
  import { renderRoleDetails } from "../output/rolePresentation.js";
19
20
  import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js";
20
21
  import { cancelInputRequest } from "../input/inputRequest.js";
21
- import { recoverExactAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
22
+ import { recoverExactAgentRun, retireExactActiveAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
22
23
  import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
23
24
  import { agentRunDeliveryReceiptId, createAgentRun, withAgentRunContextSnapshot } from "../run/agentRun.js";
24
25
  import { projectRunRecovery, readRunRecoveryFacts } from "../run/recoveryProjection.js";
25
26
  import { matchYieldReceipt } from "../run/yieldReceipt.js";
27
+ import { createRejectedYieldAttempt, rejectedYieldAttemptEventPayload, rejectedYieldAttemptFromTaskEvent, RUN_YIELD_REJECTED_EVENT } from "../run/rejectedYieldAttempt.js";
26
28
  import { createReviewRound, createTaskReviewRound, createTaskDeltaReviewRound, attachReviewExecutionGroup, deltaRecheckBlocksAcceptance, finishReviewRound, parseReviewYieldReport, recordReviewWorkspaceDisposition, retryRunningReviewExecutionLane, retryTaskReviewRound, startReviewRound, updateReviewExecutionGroup, validateTaskReviewCandidate } from "../review/reviewRound.js";
27
29
  import { buildDeltaRecheckDispatchContext, verifyDeltaRecheckDiff } from "../review/deltaRecheck.js";
28
30
  import { isAcceptedTaskReviewBaseline } from "../review/reviewAcceptance.js";
@@ -63,7 +65,7 @@ import { inspectTaskRoleRuntimeStatuses, renderTaskRoleRuntimeStatus, taskRoleAc
63
65
  import { assertNoOpenInputRequests, isCurrentGlobalOperator, openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
64
66
  import { runGrantCommand } from "./grantCommands.js";
65
67
  import { runWorkflowCommand } from "./workflowCommands.js";
66
- import { taskActor as resolveTaskActor, taskLeaderActionRunId } from "./taskActor.js";
68
+ import { taskLocalActor as resolveTaskLocalActor, taskLeaderActionRunId } from "./taskActor.js";
67
69
  import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
68
70
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
69
71
  import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
@@ -164,7 +166,7 @@ export function parseTaskCompletionRequest(args, summaryOverride) {
164
166
  */
165
167
  export function preflightTaskCompletion(taskId, store, options = {}, request = {}) {
166
168
  const task = requireTask(store, taskId);
167
- const actor = taskActor(options, task.id);
169
+ const actor = taskActor(store, options, task.id);
168
170
  if (task.status === "completed") {
169
171
  return { task, actor, completed: true, activeTaskReview: false };
170
172
  }
@@ -379,7 +381,7 @@ function taskProjectCommand(args, store, options) {
379
381
  const updated = store.transaction((tx) => {
380
382
  const task = requireTask(tx, parsed.positionals[0]);
381
383
  assertTaskOpen(task);
382
- if (task.status === "active" && taskActor(options, task.id) !== "leader") {
384
+ if (task.status === "active" && taskActor(tx, options, task.id) !== "leader") {
383
385
  throw usageError("Only the Task Leader may add a Project to an active Task.");
384
386
  }
385
387
  const project = resolveProject(tx.listProjects(), parsed.positionals[1]);
@@ -444,6 +446,7 @@ function updateTaskCommand(args, store, options) {
444
446
  const current = requireTask(tx, parsed.positionals[0]);
445
447
  if (current.status === "archived")
446
448
  throw usageError(`Task is archived: ${current.id}.`);
449
+ taskActor(tx, options, current.id);
447
450
  const updated = updateTaskMetadata(current, {
448
451
  ...(parsed.options.has("--title") ? { title: requiredOption(parsed.options, "--title") } : {}),
449
452
  ...(parsed.options.has("--type")
@@ -952,7 +955,7 @@ function archiveTaskCommand(args, store, options) {
952
955
  const now = clock(options);
953
956
  const result = store.transaction((tx) => {
954
957
  const task = requireTask(tx, request.taskId);
955
- const actor = taskActor(options, task.id);
958
+ const actor = taskActor(tx, options, task.id);
956
959
  if (task.status === "archived")
957
960
  return { task, changed: false };
958
961
  if (task.status !== "completed"
@@ -1016,7 +1019,7 @@ function retireTaskCommand(args, store, options) {
1016
1019
  const now = clock(options);
1017
1020
  const result = store.transaction((tx) => {
1018
1021
  const task = requireTask(tx, taskId);
1019
- const actor = taskActor(options, task.id);
1022
+ const actor = taskActor(tx, options, task.id);
1020
1023
  if (task.status === "retired") {
1021
1024
  const same = task.retirementSummary === summary
1022
1025
  && task.replacementTaskId === replacementTaskId;
@@ -1144,7 +1147,7 @@ export function parseTaskArchiveArguments(args) {
1144
1147
  export function validateTaskArchiveRequest(args, store, options = {}) {
1145
1148
  const request = parseTaskArchiveArguments(args);
1146
1149
  const task = requireTask(store, request.taskId);
1147
- const actor = taskActor(options, task.id);
1150
+ const actor = taskActor(store, options, task.id);
1148
1151
  if (actor === "leader") {
1149
1152
  throw usageError("Only the global Operator may archive a Task from a managed Session.");
1150
1153
  }
@@ -1201,7 +1204,7 @@ function taskMessageCommand(args, store, options) {
1201
1204
  const result = store.transaction((tx) => {
1202
1205
  const task = requireTask(tx, parsed.positionals[0]);
1203
1206
  assertTaskOpen(task);
1204
- const actor = taskActor(options, task.id);
1207
+ const actor = taskActor(tx, options, task.id);
1205
1208
  const message = actor === "leader"
1206
1209
  ? appendMessage(tx, task.id, body, "role-result", { type: "role", roleName: LEADER_ROLE }, now)
1207
1210
  : actor === "operator"
@@ -1296,10 +1299,7 @@ function retireMessage(args, store, options) {
1296
1299
  const result = store.transaction((tx) => {
1297
1300
  const task = requireTask(tx, reference.taskId);
1298
1301
  assertTaskOpen(task);
1299
- const actor = taskActor(options, task.id);
1300
- if (actor === "leader") {
1301
- throw usageError("Only the user or global Operator may retire a Task Message.");
1302
- }
1302
+ const actor = taskActor(tx, options, task.id);
1303
1303
  const message = tx.listMessages(task.id).find(({ id }) => id === reference.localId);
1304
1304
  if (message === undefined) {
1305
1305
  throw dataError(`Task Message not found: ${task.id}/${reference.localId}.`);
@@ -1405,7 +1405,7 @@ function switchTaskRoleSession(args, store, options) {
1405
1405
  throw usageError(inactiveTaskMessage(task, "switching a Provider Conversation"), usage);
1406
1406
  }
1407
1407
  const role = requireRole(tx, task.id, parsed.positionals[1]);
1408
- const requestedBy = taskActor(options, task.id);
1408
+ const requestedBy = taskActor(tx, options, task.id);
1409
1409
  const leaderRunId = requestedBy === "leader"
1410
1410
  ? taskLeaderActionRunId(tx, task.id, options.environment, options.yuiHome)
1411
1411
  : undefined;
@@ -1476,6 +1476,7 @@ function addTaskRole(args, store, options) {
1476
1476
  const result = store.transaction((tx) => {
1477
1477
  const task = requireTask(tx, taskId);
1478
1478
  assertTaskOpen(task);
1479
+ taskActor(tx, options, task.id);
1479
1480
  assertRoleRuntimeMutationAllowed(tx, {
1480
1481
  scope: "task",
1481
1482
  taskId: task.id,
@@ -1594,6 +1595,7 @@ function updateTaskRole(args, store, options) {
1594
1595
  const updated = store.transaction((tx) => {
1595
1596
  const task = requireTask(tx, taskId);
1596
1597
  assertTaskOpen(task);
1598
+ taskActor(tx, options, task.id);
1597
1599
  const role = requireRole(tx, task.id, roleName);
1598
1600
  const changesLaunchContext = hasRoleLaunchContextOptions(parsed) || parsed.has("--profile");
1599
1601
  const changesAgentConfig = hasAgentConfigOptions(parsed);
@@ -1640,6 +1642,7 @@ function removeTaskRole(args, store, options) {
1640
1642
  const removed = store.transaction((tx) => {
1641
1643
  const task = requireTask(tx, args[0]);
1642
1644
  assertTaskOpen(task);
1645
+ taskActor(tx, options, task.id);
1643
1646
  const role = requireRole(tx, task.id, args[1]);
1644
1647
  if (role.name === LEADER_ROLE)
1645
1648
  throw usageError("The Task Leader role cannot be removed.");
@@ -1669,6 +1672,7 @@ function bindTaskRole(args, store, options) {
1669
1672
  const result = store.transaction((tx) => {
1670
1673
  const task = requireTask(tx, args[0]);
1671
1674
  assertTaskOpen(task);
1675
+ taskActor(tx, options, task.id);
1672
1676
  const role = requireRole(tx, task.id, args[1]);
1673
1677
  assertRoleRuntimeMutationAllowed(tx, {
1674
1678
  scope: "task",
@@ -1722,6 +1726,8 @@ function unbindTaskRole(args, store, options) {
1722
1726
  const now = clock(options);
1723
1727
  const result = store.transaction((tx) => {
1724
1728
  const task = requireTask(tx, args[0]);
1729
+ assertTaskOpen(task);
1730
+ taskActor(tx, options, task.id);
1725
1731
  const role = requireRole(tx, task.id, args[1]);
1726
1732
  try {
1727
1733
  const unbound = unbindRoleAgent(role, tx.getTaskRoleSessionSet(task.id, role.name), args[2], now);
@@ -1772,6 +1778,7 @@ function transferTaskRoleAuthority(args, store, options, action) {
1772
1778
  if (task.status !== "active") {
1773
1779
  throw usageError(inactiveTaskMessage(task, `${action} Provider authority`));
1774
1780
  }
1781
+ taskActor(tx, options, task.id);
1775
1782
  const role = requireRole(tx, task.id, args[1]);
1776
1783
  const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1777
1784
  const session = sessions?.sessions[role.activeAgentId];
@@ -1887,6 +1894,7 @@ function createWork(args, store, options) {
1887
1894
  const item = store.transaction((tx) => {
1888
1895
  const task = requireTask(tx, parsed.positionals[0]);
1889
1896
  assertTaskOpen(task);
1897
+ taskActor(tx, options, task.id);
1890
1898
  for (const dependencyId of parsed.after) {
1891
1899
  const dependency = tx.getWorkItem(task.id, dependencyId);
1892
1900
  if (dependency === null)
@@ -1946,7 +1954,7 @@ function updateWorkScope(args, store, options) {
1946
1954
  const updated = store.transaction((tx) => {
1947
1955
  const item = requireWorkItem(tx, parsed.positionals[0], options);
1948
1956
  const task = requireTask(tx, item.taskId);
1949
- if (taskActor(options, task.id) !== "leader") {
1957
+ if (taskActor(tx, options, task.id) !== "leader") {
1950
1958
  throw usageError("Only the Task Leader may change a Work Item Project scope.");
1951
1959
  }
1952
1960
  if (tx.getActiveAgentRun(task.id, item.assignee ?? "") !== null) {
@@ -1996,7 +2004,7 @@ function updateWork(args, store, options) {
1996
2004
  const task = requireTask(tx, current.taskId);
1997
2005
  assertTaskOpen(task);
1998
2006
  if (current.assignee === undefined) {
1999
- if (taskActor(options, task.id) !== "leader") {
2007
+ if (taskActor(tx, options, task.id) !== "leader") {
2000
2008
  throw usageError(`Only the Task Leader may update unassigned Work Item execution: ${current.id}.`);
2001
2009
  }
2002
2010
  if (status === "running") {
@@ -2143,6 +2151,7 @@ function dispatchWork(args, store, options) {
2143
2151
  if (task.status !== "active") {
2144
2152
  throw usageError(inactiveTaskMessage(task, "dispatch"));
2145
2153
  }
2154
+ taskActor(tx, options, task.id);
2146
2155
  const currentGroup = currentWorkItemExecutionGroup(item);
2147
2156
  const existingGroup = currentGroup?.resolution === undefined
2148
2157
  ? currentGroup
@@ -2190,7 +2199,7 @@ function dispatchWork(args, store, options) {
2190
2199
  + `The Task Leader must run "yui task work update ${item.id} running", `
2191
2200
  + "then execute it directly or create native subagents in the Leader Session.");
2192
2201
  }
2193
- if (expanding && taskActor(options, task.id) !== "leader") {
2202
+ if (expanding && taskActor(tx, options, task.id) !== "leader") {
2194
2203
  throw usageError("Only the Task Leader may expand a running ExecutionGroup.");
2195
2204
  }
2196
2205
  assertWorkItemDependenciesCompleted(tx, item);
@@ -2572,7 +2581,7 @@ function resolveWorkExecutionGroup(args, store, options) {
2572
2581
  const task = requireTask(tx, item.taskId);
2573
2582
  if (task.status !== "active")
2574
2583
  throw usageError(inactiveTaskMessage(task, "resolving an ExecutionGroup"));
2575
- if (taskActor(options, task.id) !== "leader") {
2584
+ if (taskActor(tx, options, task.id) !== "leader") {
2576
2585
  throw usageError("Only the Task Leader may resolve an ExecutionGroup.");
2577
2586
  }
2578
2587
  let group = currentWorkItemExecutionGroup(item);
@@ -2806,7 +2815,7 @@ function acceptWork(args, store, options) {
2806
2815
  if (task.status !== "active") {
2807
2816
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2808
2817
  }
2809
- if (taskActor(options, task.id) !== "leader") {
2818
+ if (taskActor(tx, options, task.id) !== "leader") {
2810
2819
  throw usageError("Only the Task Leader may accept a Work Item.");
2811
2820
  }
2812
2821
  if (item.status !== "awaiting_acceptance") {
@@ -2929,7 +2938,7 @@ function rejectWork(args, store, options) {
2929
2938
  if (task.status !== "active") {
2930
2939
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2931
2940
  }
2932
- if (taskActor(options, task.id) !== "leader") {
2941
+ if (taskActor(tx, options, task.id) !== "leader") {
2933
2942
  throw usageError("Only the Task Leader may reject a Work Item.");
2934
2943
  }
2935
2944
  if (item.status !== "awaiting_acceptance") {
@@ -2966,7 +2975,7 @@ function retireWork(args, store, options) {
2966
2975
  if (task.status !== "active") {
2967
2976
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
2968
2977
  }
2969
- const actor = taskActor(options, task.id);
2978
+ const actor = taskActor(tx, options, task.id);
2970
2979
  if (replacementWorkItemId !== undefined) {
2971
2980
  const replacement = requireWorkItem(tx, replacementWorkItemId, options);
2972
2981
  if (replacement.taskId !== task.id) {
@@ -3088,7 +3097,7 @@ function reviewWork(args, store, options) {
3088
3097
  if (task.status !== "active") {
3089
3098
  throw usageError(`Task is not active: ${task.id}/${task.status}.`);
3090
3099
  }
3091
- if (taskActor(options, task.id) !== "leader") {
3100
+ if (taskActor(tx, options, task.id) !== "leader") {
3092
3101
  throw usageError("Only the Task Leader may request a Work Item review.");
3093
3102
  }
3094
3103
  if (item.status !== "awaiting_acceptance") {
@@ -3281,7 +3290,7 @@ function resolveReviewExecutionGroup(args, store, options) {
3281
3290
  const task = requireTask(tx, round.taskId);
3282
3291
  if (task.status !== "active")
3283
3292
  throw usageError(inactiveTaskMessage(task, "resolving a Review ExecutionGroup"));
3284
- if (taskActor(options, task.id) !== "leader") {
3293
+ if (taskActor(tx, options, task.id) !== "leader") {
3285
3294
  throw usageError("Only the Task Leader may resolve a Reviewer ExecutionGroup.");
3286
3295
  }
3287
3296
  const group = round.executionGroup;
@@ -3414,7 +3423,7 @@ function disposeReviewFindingCommand(args, store, options) {
3414
3423
  const task = requireTask(tx, reference.taskId);
3415
3424
  if (task.status !== "active")
3416
3425
  throw usageError(inactiveTaskMessage(task, "dispositioning a review finding"));
3417
- if (taskActor(options, task.id) !== "leader") {
3426
+ if (taskActor(tx, options, task.id) !== "leader") {
3418
3427
  throw usageError("Only the Task Leader may disposition a review finding.");
3419
3428
  }
3420
3429
  const command = {
@@ -3451,7 +3460,7 @@ function planReviewRepairWave(args, store, options) {
3451
3460
  if (currentTask.status !== "active") {
3452
3461
  throw usageError(inactiveTaskMessage(currentTask, "creating a review repair wave"));
3453
3462
  }
3454
- if (taskActor(options, currentTask.id) !== "leader") {
3463
+ if (taskActor(tx, options, currentTask.id) !== "leader") {
3455
3464
  throw usageError("Only the Task Leader may create a review repair wave.");
3456
3465
  }
3457
3466
  const openItems = tx.listWorkItems(currentTask.id)
@@ -3552,7 +3561,7 @@ function requestTaskReviewRound(args, store, options) {
3552
3561
  const task = requireTask(tx, parsed.positionals[0]);
3553
3562
  if (task.status !== "active")
3554
3563
  throw usageError(`Task is not active: ${task.id}.`);
3555
- if (taskActor(options, task.id) !== "leader") {
3564
+ if (taskActor(tx, options, task.id) !== "leader") {
3556
3565
  throw usageError("Only the Task Leader may request a Task-final Review.");
3557
3566
  }
3558
3567
  if (task.projectBindings.length === 0) {
@@ -3783,7 +3792,7 @@ function forceFreshTaskReviewRound(args, store, options) {
3783
3792
  const task = requireTask(tx, reference.taskId);
3784
3793
  if (task.status !== "active")
3785
3794
  throw usageError(`Task is not active: ${task.id}.`);
3786
- if (taskActor(options, task.id) !== "leader") {
3795
+ if (taskActor(tx, options, task.id) !== "leader") {
3787
3796
  throw usageError("Only the Task Leader may force a fresh Task-final ReviewRound.");
3788
3797
  }
3789
3798
  if ((source.scope ?? "work-item") !== "task") {
@@ -3995,7 +4004,7 @@ function retryFailedTaskReviewRound(args, store, options) {
3995
4004
  const task = requireTask(tx, reference.taskId);
3996
4005
  if (task.status !== "active")
3997
4006
  throw usageError(`Task is not active: ${task.id}.`);
3998
- if (taskActor(options, task.id) !== "leader") {
4007
+ if (taskActor(tx, options, task.id) !== "leader") {
3999
4008
  throw usageError("Only the Task Leader may retry a failed Task-final ReviewRound.");
4000
4009
  }
4001
4010
  if ((round.scope ?? "work-item") !== "task") {
@@ -4117,8 +4126,16 @@ function taskRunCommand(args, store, options) {
4117
4126
  : `Unknown command: task run ${command}`);
4118
4127
  }
4119
4128
  function retireRun(args, store, options) {
4120
- const usage = "Task run retire usage: yui task run retire <task>/<run> --reason <text>.";
4121
- const parsed = parseTail(args, new Set(["--reason"]), usage);
4129
+ const usage = "Task run retire usage: yui task run retire <task>/<run> --reason <text> [--expected-progress-at <timestamp>] [--agent-id <id>] [--adapter-id <id>] [--native-session-id <id>] [--launch-id <id>].";
4130
+ const parsed = parseTail(args, new Set([
4131
+ "--reason",
4132
+ "--expected-progress-at",
4133
+ "--progress-at",
4134
+ "--agent-id",
4135
+ "--adapter-id",
4136
+ "--native-session-id",
4137
+ "--launch-id"
4138
+ ]), usage);
4122
4139
  exactPositionals(parsed.positionals, 1, usage);
4123
4140
  const reason = requiredOption(parsed.options, "--reason");
4124
4141
  const reference = taskRecordReference(parsed.positionals[0], "agentRun", "Agent Run reference", options);
@@ -4126,10 +4143,7 @@ function retireRun(args, store, options) {
4126
4143
  const result = store.transaction((tx) => {
4127
4144
  const task = requireTask(tx, reference.taskId);
4128
4145
  assertTaskOpen(task);
4129
- const actor = taskActor(options, task.id);
4130
- if (actor === "leader") {
4131
- throw usageError("Only the user or global Operator may retire an Agent Run.");
4132
- }
4146
+ const actor = taskActor(tx, options, task.id);
4133
4147
  let run = tx.getAgentRun(task.id, reference.localId);
4134
4148
  if (run === null)
4135
4149
  throw dataError(`Agent Run not found: ${task.id}/${reference.localId}.`);
@@ -4138,19 +4152,66 @@ function retireRun(args, store, options) {
4138
4152
  return { task, run, changed: false };
4139
4153
  }
4140
4154
  if (run.status === "active") {
4141
- const terminal = terminalizeExactTaskRun(tx, {
4155
+ if (actor === "leader"
4156
+ && taskLeaderActionRunId(tx, task.id, options.environment, options.yuiHome) === run.id) {
4157
+ throw usageError("A Task Leader cannot retire its own current authority Run.", usage);
4158
+ }
4159
+ const expectedProgressAt = requiredOption(parsed.options, parsed.options.has("--expected-progress-at")
4160
+ ? "--expected-progress-at"
4161
+ : "--progress-at");
4162
+ if (parsed.options.has("--expected-progress-at") && parsed.options.has("--progress-at")) {
4163
+ throw usageError("--expected-progress-at and --progress-at are mutually exclusive.", usage);
4164
+ }
4165
+ const agentId = requiredOption(parsed.options, "--agent-id");
4166
+ const adapterId = requiredOption(parsed.options, "--adapter-id");
4167
+ const nativeSessionId = parsed.options.get("--native-session-id");
4168
+ const launchId = parsed.options.get("--launch-id");
4169
+ const sessions = tx.getTaskRoleSessionSet(task.id, run.roleName);
4170
+ const session = sessions?.sessions[run.effective.agentId];
4171
+ if (session?.nativeSessionId !== undefined && nativeSessionId === undefined) {
4172
+ throw usageError("--native-session-id is required for this active Run.", usage);
4173
+ }
4174
+ if (session?.nativeSessionId === undefined && launchId === undefined) {
4175
+ throw usageError("--launch-id is required for an opaque active Run.", usage);
4176
+ }
4177
+ const terminal = retireExactActiveAgentRun(tx, {
4142
4178
  taskId: task.id,
4143
4179
  roleName: run.roleName,
4144
- agentId: run.effective.agentId,
4145
4180
  runId: run.id,
4146
- receiptId: agentRunDeliveryReceiptId(run),
4147
- outcome: { status: "failed", summary: `Agent Run retired: ${reason}` }
4181
+ agentId,
4182
+ adapterId,
4183
+ ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
4184
+ ...(launchId === undefined ? {} : { launchId }),
4185
+ expectedProgressAt,
4186
+ reason: `Agent Run retired: ${reason}`
4148
4187
  }, now);
4149
4188
  if (terminal.disposition !== "applied" || terminal.run === null) {
4150
- throw usageError(`Agent Run changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
4189
+ throw usageError(terminal.disposition === "blocked"
4190
+ ? `Agent Run retirement is blocked: ${run.id}/${terminal.reason ?? "unsafe"}.`
4191
+ : `Agent Run changed during retirement: ${run.id}/${terminal.reason ?? "obsolete"}.`);
4151
4192
  }
4152
4193
  run = terminal.run;
4153
4194
  }
4195
+ recordTaskEvent(tx, task.id, "run.retired", {
4196
+ runId: run.id,
4197
+ reason,
4198
+ ...(parsed.options.get("--expected-progress-at") === undefined
4199
+ && parsed.options.get("--progress-at") === undefined
4200
+ ? {}
4201
+ : {
4202
+ expectedProgressAt: parsed.options.get("--expected-progress-at")
4203
+ ?? parsed.options.get("--progress-at")
4204
+ }),
4205
+ ...(parsed.options.get("--native-session-id") === undefined
4206
+ ? {}
4207
+ : { nativeSessionId: parsed.options.get("--native-session-id") }),
4208
+ ...(parsed.options.get("--launch-id") === undefined
4209
+ ? {}
4210
+ : { launchId: parsed.options.get("--launch-id") }),
4211
+ ...(actor === "leader"
4212
+ ? leaderActionEventPayload(tx, task.id, options)
4213
+ : { retiredBy: actor })
4214
+ }, now);
4154
4215
  tx.saveEvent(task.id, createTaskRecordRetirement({
4155
4216
  eventId: tx.nextEventId(task.id),
4156
4217
  taskId: task.id,
@@ -4289,7 +4350,7 @@ function settleStaleFinalReviewRun(args, store, options) {
4289
4350
  const task = requireTask(tx, run.taskId);
4290
4351
  if (task.status !== "active")
4291
4352
  throw usageError(`Task is not active: ${task.id}.`);
4292
- if (taskActor(options, task.id) !== "leader") {
4353
+ if (taskActor(tx, options, task.id) !== "leader") {
4293
4354
  throw usageError("Only the Task Leader may settle a stale final review Run.");
4294
4355
  }
4295
4356
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
@@ -4978,7 +5039,7 @@ function retryFailedReviewRun(previous, store, options, now) {
4978
5039
  throw usageError(`Review Run ${run.id} still owns an unsettled Provider continuation; `
4979
5040
  + "reconcile it before retrying the Lane.");
4980
5041
  }
4981
- if (taskActor(options, task.id) !== "leader") {
5042
+ if (taskActor(tx, options, task.id) !== "leader") {
4982
5043
  throw usageError("Only the Task Leader may retry a failed final review Run.");
4983
5044
  }
4984
5045
  const round = tx.getReviewRound(task.id, run.reviewRoundId);
@@ -5205,7 +5266,7 @@ function recoverRun(args, store, options) {
5205
5266
  const task = requireTask(tx, active.taskId);
5206
5267
  if (task.status !== "active")
5207
5268
  throw usageError(inactiveTaskMessage(task, "recovering a run"));
5208
- if (taskActor(options, task.id) !== "leader") {
5269
+ if (taskActor(tx, options, task.id) !== "leader") {
5209
5270
  throw usageError("Only the Task Leader may control exact Agent Run recovery.");
5210
5271
  }
5211
5272
  const roleName = parsed.options.get("--role") ?? active.roleName;
@@ -5308,18 +5369,27 @@ function showRun(args, store, options) {
5308
5369
  const retirement = tx.listEvents(run.taskId)
5309
5370
  .map(taskRecordRetirement)
5310
5371
  .find((entry) => entry?.recordKind === "agent-run" && entry.recordId === run.id) ?? null;
5311
- return { run, recovery: projectRunRecovery(facts), retirement };
5372
+ const allRejectedYieldAttempts = tx.listEvents(run.taskId)
5373
+ .map(rejectedYieldAttemptFromTaskEvent)
5374
+ .filter((attempt) => (attempt !== null && attempt.runId === run.id));
5375
+ return {
5376
+ run,
5377
+ recovery: projectRunRecovery(facts),
5378
+ retirement,
5379
+ rejectedYieldAttemptCount: allRejectedYieldAttempts.length,
5380
+ rejectedYieldAttempts: allRejectedYieldAttempts.slice(-16)
5381
+ };
5312
5382
  });
5313
5383
  if (asJson) {
5314
5384
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
5315
5385
  }
5316
5386
  return {
5317
5387
  kind: "output",
5318
- output: renderRunShow(data.run, data.recovery, data.retirement),
5388
+ output: renderRunShow(data.run, data.recovery, data.retirement, data.rejectedYieldAttemptCount, data.rejectedYieldAttempts),
5319
5389
  data
5320
5390
  };
5321
5391
  }
5322
- function renderRunShow(run, recovery, retirement) {
5392
+ function renderRunShow(run, recovery, retirement, rejectedYieldAttemptCount, rejectedYieldAttempts) {
5323
5393
  const lines = [
5324
5394
  `Run: ${run.id}`,
5325
5395
  `Task: ${run.taskId}`,
@@ -5339,7 +5409,19 @@ function renderRunShow(run, recovery, retirement) {
5339
5409
  : [`Provider accepted (durable): ${run.deliveredAt}`]),
5340
5410
  ...(run.summary === undefined || run.summary.trim().length === 0
5341
5411
  ? []
5342
- : [`Summary: ${run.summary}`])
5412
+ : [`Summary: ${run.summary}`]),
5413
+ ...(rejectedYieldAttemptCount === 0
5414
+ ? []
5415
+ : [
5416
+ `Rejected yield attempts: ${rejectedYieldAttempts.length} shown of ${rejectedYieldAttemptCount} (unaccepted diagnostic evidence only)`,
5417
+ ...rejectedYieldAttempts.slice(-5).flatMap((attempt) => [
5418
+ ` ${attempt.attemptedAt}: ${attempt.rejectionReason} (${attempt.attemptDigest.slice(0, 16)})`,
5419
+ ` Reviewer output: ${truncateRunShowText(attempt.summary)}`,
5420
+ ` Checks: ${attempt.checks.length === 0
5421
+ ? "none"
5422
+ : attempt.checks.map(({ name, outcome }) => `${name}:${outcome}`).join(", ")}`
5423
+ ])
5424
+ ])
5343
5425
  ];
5344
5426
  if (recovery.canonicalProgressAt !== null) {
5345
5427
  lines.push(`Canonical recovery fence (Yui durable CAS): ${recovery.canonicalProgressAt}`, ...(recovery.canonicalProgressEvidence === undefined
@@ -5370,6 +5452,10 @@ function renderRunShow(run, recovery, retirement) {
5370
5452
  }
5371
5453
  return `${lines.join("\n")}\n`;
5372
5454
  }
5455
+ function truncateRunShowText(value) {
5456
+ const normalized = value.replaceAll(/\s+/gu, " ").trim();
5457
+ return normalized.length <= 400 ? normalized : `${normalized.slice(0, 399)}…`;
5458
+ }
5373
5459
  /**
5374
5460
  * Issue 04: builds the terminal yield outcome from the command inputs. The
5375
5461
  * same construction feeds both the first commit and the idempotent replay, so
@@ -5462,6 +5548,70 @@ function yieldRunStatus(args, store, options) {
5462
5548
  + `Request: ${run.yieldReceipt.requestId}\n`
5463
5549
  + `Committed: ${run.yieldReceipt.committedAt}\n`, { receipt: run.yieldReceipt });
5464
5550
  }
5551
+ function recordRejectedReviewYield(store, input) {
5552
+ if (input.run.purpose !== "review" || input.run.reviewRoundId === undefined) {
5553
+ throw new Error(`Only a Review Run can record rejected yield evidence: ${input.run.id}.`);
5554
+ }
5555
+ const receiptId = agentRunDeliveryReceiptId(input.run);
5556
+ const sessions = store.getTaskRoleSessionSet(input.run.taskId, input.run.roleName);
5557
+ const durableSession = sessions?.sessions[input.run.effective.agentId];
5558
+ const attempt = createRejectedYieldAttempt({
5559
+ taskId: input.run.taskId,
5560
+ runId: input.run.id,
5561
+ roleName: input.run.roleName,
5562
+ reviewRoundId: input.run.reviewRoundId,
5563
+ receiptId,
5564
+ rejectionReason: input.rejectionReason,
5565
+ summary: input.outcome.summary,
5566
+ ...(input.outcome.reviewResult === undefined
5567
+ ? {}
5568
+ : { reviewResult: input.outcome.reviewResult }),
5569
+ ...(input.nativeSessionId === undefined
5570
+ ? {}
5571
+ : { nativeSessionId: input.nativeSessionId }),
5572
+ ...(input.launchId === undefined ? {} : { launchId: input.launchId }),
5573
+ ...(durableSession?.nativeSessionId === undefined
5574
+ ? {}
5575
+ : { durableNativeSessionId: durableSession.nativeSessionId }),
5576
+ ...(durableSession?.launchId === undefined
5577
+ ? {}
5578
+ : { durableLaunchId: durableSession.launchId }),
5579
+ ...(sessions?.inFlight?.runId === undefined
5580
+ ? {}
5581
+ : { inFlightRunId: sessions.inFlight.runId }),
5582
+ ...(sessions?.inFlight?.receiptId === undefined
5583
+ ? {}
5584
+ : { inFlightReceiptId: sessions.inFlight.receiptId }),
5585
+ ...(input.run.assignment.contextSnapshotRef === undefined
5586
+ ? {}
5587
+ : { contextSnapshot: input.run.assignment.contextSnapshotRef }),
5588
+ activeRun: input.activeRun === null
5589
+ ? null
5590
+ : {
5591
+ id: input.activeRun.id,
5592
+ receiptId: agentRunDeliveryReceiptId(input.activeRun),
5593
+ ...(input.activeRun.assignment.contextSnapshotRef === undefined
5594
+ ? {}
5595
+ : { contextSnapshot: input.activeRun.assignment.contextSnapshotRef })
5596
+ },
5597
+ attemptedAt: input.now
5598
+ });
5599
+ const existing = store.listEvents(input.run.taskId).find((event) => (event.type === RUN_YIELD_REJECTED_EVENT
5600
+ && event.payload.runId === input.run.id
5601
+ && event.payload.receiptId === receiptId
5602
+ && event.payload.attemptDigest === attempt.attemptDigest));
5603
+ if (existing !== undefined) {
5604
+ const persisted = rejectedYieldAttemptFromTaskEvent(existing);
5605
+ if (persisted === null) {
5606
+ throw new Error(`Rejected yield attempt is malformed: ${existing.id}.`);
5607
+ }
5608
+ return { attempt: persisted, event: existing, created: false };
5609
+ }
5610
+ const event = createTaskEvent(store.nextEventId(input.run.taskId), input.run.taskId, RUN_YIELD_REJECTED_EVENT, rejectedYieldAttemptEventPayload(attempt), input.now);
5611
+ store.saveEvent(input.run.taskId, event);
5612
+ routeRoleEvent(store, event, input.run.roleName, "run-yield-rejected", input.now);
5613
+ return { attempt, event, created: true };
5614
+ }
5465
5615
  function yieldRun(args, store, options) {
5466
5616
  const usage = "Task run yield usage: yui task run yield <task>/<run> (--summary <text>|--summary-file <path|->).";
5467
5617
  const parsed = parseTail(args, new Set(["--summary", "--summary-file"]), usage);
@@ -5491,8 +5641,6 @@ function yieldRun(args, store, options) {
5491
5641
  throw usageError(inactiveTaskMessage(task, "yielding a run"));
5492
5642
  const role = requireRole(tx, task.id, active.roleName);
5493
5643
  const pointer = activeRunPointer(tx, active);
5494
- if (pointer?.id !== active.id)
5495
- throw usageError(`Run is not active for ${task.id}/${role.name}: ${active.id}.`);
5496
5644
  const taskFinalContract = active.purpose === "execution"
5497
5645
  && active.workItemId !== undefined
5498
5646
  ? taskFinalReviewContractForMutation(tx, task.id, options)
@@ -5545,6 +5693,27 @@ function yieldRun(args, store, options) {
5545
5693
  : { reviewResult: yieldOutcome.reviewResult })
5546
5694
  }, now);
5547
5695
  if (terminalization.disposition !== "applied" || terminalization.run === null) {
5696
+ if (active.purpose === "review" && active.reviewRoundId !== undefined) {
5697
+ const rejected = recordRejectedReviewYield(tx, {
5698
+ run: active,
5699
+ activeRun: pointer,
5700
+ rejectionReason: terminalization.reason ?? "obsolete",
5701
+ outcome: yieldOutcome,
5702
+ ...(options.environment?.YUI_NATIVE_SESSION_ID === undefined
5703
+ ? {}
5704
+ : { nativeSessionId: options.environment.YUI_NATIVE_SESSION_ID }),
5705
+ ...(options.environment?.YUI_LAUNCH_ID === undefined
5706
+ ? {}
5707
+ : { launchId: options.environment.YUI_LAUNCH_ID }),
5708
+ now
5709
+ });
5710
+ return {
5711
+ kind: "rejected",
5712
+ run: active,
5713
+ rejected,
5714
+ notifyLeader: rejected.created
5715
+ };
5716
+ }
5548
5717
  throw usageError(`Run ${active.id} no longer matches its exact execution fence: `
5549
5718
  + `${terminalization.reason ?? "obsolete"}.`);
5550
5719
  }
@@ -5727,6 +5896,7 @@ function yieldRun(args, store, options) {
5727
5896
  if (receipt !== null) {
5728
5897
  tx.saveAgentRun(receipt);
5729
5898
  return {
5899
+ kind: "yielded",
5730
5900
  run: receipt,
5731
5901
  reviewDispatch,
5732
5902
  notifyLeader: leaderHandoff !== null
@@ -5734,11 +5904,21 @@ function yieldRun(args, store, options) {
5734
5904
  }
5735
5905
  }
5736
5906
  return {
5907
+ kind: "yielded",
5737
5908
  run: terminal,
5738
5909
  reviewDispatch,
5739
5910
  notifyLeader: leaderHandoff !== null
5740
5911
  };
5741
5912
  });
5913
+ if (yielded.kind === "rejected") {
5914
+ if (yielded.notifyLeader) {
5915
+ notifyMailbox(options.runtime, leaderMailbox(yielded.run.taskId), yielded.run.taskId);
5916
+ }
5917
+ throw usageError(`Run ${yielded.run.id} no longer matches its exact execution fence: `
5918
+ + `${yielded.rejected.attempt.rejectionReason}. `
5919
+ + `Rejected Reviewer output was recorded as ${yielded.rejected.event.id} `
5920
+ + "(unaccepted diagnostic evidence only).");
5921
+ }
5742
5922
  if (yielded.notifyLeader) {
5743
5923
  notifyMailbox(options.runtime, leaderMailbox(yielded.run.taskId), yielded.run.taskId);
5744
5924
  }
@@ -6267,6 +6447,8 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
6267
6447
  export function failPendingReviewRound(taskId, reviewRoundId, summary, store, options = {}) {
6268
6448
  const now = clock(options);
6269
6449
  const failed = store.transaction((tx) => {
6450
+ const task = requireTask(tx, taskId);
6451
+ taskActor(tx, options, task.id);
6270
6452
  const round = tx.getReviewRound(taskId, reviewRoundId);
6271
6453
  if (round === null)
6272
6454
  throw usageError(`ReviewRound not found: ${taskId}/${reviewRoundId}.`);
@@ -6565,8 +6747,8 @@ function assertTaskOpen(task) {
6565
6747
  if (task.status === "retired")
6566
6748
  throw usageError(`Task is retired: ${task.id}.`);
6567
6749
  }
6568
- function taskActor(options, taskId) {
6569
- return resolveTaskActor(options.environment, taskId);
6750
+ function taskActor(store, options, taskId) {
6751
+ return resolveTaskLocalActor(store, options.environment, taskId, options.yuiHome);
6570
6752
  }
6571
6753
  function taskRoleDispatchMode(store, taskId, roleName, sessions, agentId, effective) {
6572
6754
  return roleSessionDispatchModeWithConversationSwitch(sessions, store.listEvents(taskId), store.getWorkMailbox({ kind: "role", taskId, roleName }), roleName, agentId, effective);
@@ -6741,7 +6923,7 @@ function taskBriefCommand(args, store, options) {
6741
6923
  const task = requireTask(tx, parsed.positionals[0]);
6742
6924
  assertTaskOpen(task);
6743
6925
  const existing = tx.getTaskBrief(task.id);
6744
- const updatedBy = taskActor(options, task.id);
6926
+ const updatedBy = taskActor(tx, options, task.id);
6745
6927
  const brief = existing === null
6746
6928
  ? createTaskBrief({
6747
6929
  objective: requiredText(parsed.options.get("--objective"), "--objective"),
@@ -6792,7 +6974,7 @@ function taskDecisionCommand(args, store, options) {
6792
6974
  const result = store.transaction((tx) => {
6793
6975
  const task = requireTask(tx, parsed.positionals[0]);
6794
6976
  assertTaskOpen(task);
6795
- const actor = taskActor(options, task.id);
6977
+ const actor = taskActor(tx, options, task.id);
6796
6978
  const decision = createDecision(tx.nextDecisionId(task.id), task.id, title, rationale, now);
6797
6979
  tx.saveDecision(task.id, decision);
6798
6980
  recordTaskEvent(tx, task.id, "decision.recorded", {
@@ -6863,7 +7045,7 @@ function taskDecisionCommand(args, store, options) {
6863
7045
  const result = store.transaction((tx) => {
6864
7046
  const task = requireTask(tx, parsed.positionals[0]);
6865
7047
  assertTaskOpen(task);
6866
- const actor = taskActor(options, task.id);
7048
+ const actor = taskActor(tx, options, task.id);
6867
7049
  const existing = tx.getDecision(task.id, parsed.positionals[1]);
6868
7050
  if (existing === null)
6869
7051
  throw dataError(`Decision not found: ${parsed.positionals[1]}.`);
@@ -6901,7 +7083,7 @@ function taskMilestoneCommand(args, store, options) {
6901
7083
  const result = store.transaction((tx) => {
6902
7084
  const task = requireTask(tx, parsed.positionals[0]);
6903
7085
  assertTaskOpen(task);
6904
- if (taskActor(options, task.id) !== "leader") {
7086
+ if (taskActor(tx, options, task.id) !== "leader") {
6905
7087
  throw usageError("Only the Task Leader can add a Milestone.");
6906
7088
  }
6907
7089
  const milestone = createMilestone(tx.nextMilestoneId(task.id), task.id, title, summary, now);