@zq-silk/yui 0.6.9 → 0.6.11

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 (56) hide show
  1. package/README.md +10 -3
  2. package/dist/cli/commandCatalog.js +1 -1
  3. package/dist/commands/taskCommands.js +62 -27
  4. package/dist/commands/taskContextCommand.js +27 -1
  5. package/dist/commands/taskRoleRuntimeStatus.js +17 -6
  6. package/dist/controller/agentRuntimeObserver.js +6 -3
  7. package/dist/controller/controller.js +29 -47
  8. package/dist/controller/fileSchedulerStoreAdapter.js +546 -255
  9. package/dist/controller/runtime.js +8 -1
  10. package/dist/controller/runtimeEventInbox.js +16 -5
  11. package/dist/controller/runtimeHookRunFence.js +51 -5
  12. package/dist/controller/runtimeObservationHook.js +8 -2
  13. package/dist/coordination/workMailbox.js +408 -28
  14. package/dist/coordination/workMailboxQueue.js +12 -10
  15. package/dist/executor/agentExecutor.js +102 -94
  16. package/dist/executor/executorRegistry.js +47 -2
  17. package/dist/executor/fileRoleLaunchPlanner.js +4 -2
  18. package/dist/lifecycle/exactRunTerminalization.js +1 -7
  19. package/dist/repository/taskWorkspaceCoordinator.js +9 -4
  20. package/dist/runtime/agentDriver.js +83 -4
  21. package/dist/runtime/agentDriverObservation.js +25 -10
  22. package/dist/runtime/builtinAgentDrivers.js +168 -18
  23. package/dist/runtime/codexAppServerRuntime.js +355 -0
  24. package/dist/runtime/continuationManager.js +117 -0
  25. package/dist/runtime/index.js +2 -0
  26. package/dist/runtime/lifecycleReservation.js +4 -3
  27. package/dist/runtime/promptEnvelope.js +14 -3
  28. package/dist/runtime/providerContinuation.js +225 -0
  29. package/dist/runtime/providerContinuationReconciliationService.js +172 -0
  30. package/dist/runtime/providerRuntimeIdentity.js +232 -0
  31. package/dist/runtime/providerRuntimeReconciler.js +166 -0
  32. package/dist/runtime/runtimeContinuationProjection.js +34 -0
  33. package/dist/runtime/runtimeObservation.js +198 -9
  34. package/dist/runtime/runtimeProjection.js +162 -7
  35. package/dist/scheduler/activeRoleRunDelivery.js +314 -1
  36. package/dist/scheduler/leaderWakeupProcessor.js +1 -1
  37. package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +8 -7
  39. package/dist/scheduler/roleRunStall.js +4 -2
  40. package/dist/scheduler/taskExecutionProjection.js +2 -2
  41. package/dist/storage/migration/productionRegistry.js +474 -1
  42. package/dist/storage/sqliteSchema.js +102 -21
  43. package/dist/storage/sqliteStore.js +52 -110
  44. package/dist/storage/storageVersions.js +1 -1
  45. package/dist/storage/storeRpc.js +0 -1
  46. package/dist/storage/taskStore.js +40 -53
  47. package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
  48. package/dist/web/assets/client/app.js +1 -1
  49. package/dist/web/assets/client/components.js +233 -4
  50. package/dist/web/assets/client/i18n.js +166 -2
  51. package/dist/web/assets/client/view.js +30 -13
  52. package/dist/web/assets/styles/cards.js +62 -0
  53. package/dist/web/assets/styles/widgets.js +1 -0
  54. package/dist/web/webSnapshot.js +11 -2
  55. package/package.json +1 -1
  56. package/skills/yui-leader/SKILL.md +14 -9
package/README.md CHANGED
@@ -798,9 +798,16 @@ The dashboard opens on an overview cockpit: four operational metrics (active
798
798
  tasks, open inputs waiting on you, completed tasks, and the total), a
799
799
  cross-task attention inbox that surfaces every open InputRequest with its
800
800
  question and urgency so you can answer without drilling in, and the list of
801
- currently active tasks. Selecting a task opens an anchored detail view
802
- (Summary, Focus, Work items, Runs, Roles, History, Messages) with a sticky
803
- tab bar that tracks the visible section.
801
+ currently active tasks. Each task row carries a derived execution status
802
+ (progressing, needs attention, blocked, recovering) so stalled or failed
803
+ work is visible before you open a task. Selecting a task opens an anchored
804
+ detail view (Summary, Focus, Work items, Runs, Roles, History, Messages)
805
+ with a sticky tab bar that tracks the visible section. The Summary tab leads
806
+ with an execution band that consolidates the Task's owner, current action,
807
+ attention list, blockers, and fail-closed indicators; Work items surface
808
+ their current ExecutionGroup with per-lane status, Candidates, and
809
+ retirement disposition; Runs show purpose, execution lineage, yield receipt,
810
+ and Leader disposition.
804
811
 
805
812
  The control room supports English and Simplified Chinese, selecting an initial locale from the browser and remembering manual changes. The theme selector switches between the dark Control Room, the light Paper Ledger, and the dark-blue Atlas themes. Both choices are stored only in browser `localStorage`; they do not modify `YUI_HOME`.
806
813
 
@@ -270,7 +270,7 @@ const taskChildren = [
270
270
  {
271
271
  name: "send",
272
272
  summary: "Send a Task message.",
273
- usage: "yui task message send <id> (<body>|--body-file <path|->)",
273
+ usage: "yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none] [--delivery-mode followup|steer]",
274
274
  options: ["--body-file"],
275
275
  fileOptions: ["--body-file"]
276
276
  },
@@ -26,7 +26,9 @@ import { createTaskBrief, updateTaskBrief } from "../brief/taskBrief.js";
26
26
  import { createDecision, supersedeDecision } from "../decision/decision.js";
27
27
  import { createMilestone } from "../milestone/milestone.js";
28
28
  import { enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
29
+ import { mailboxHasWork as workMailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
29
30
  import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
31
+ import { blockingProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
30
32
  import { activateTask, addTaskProjectBinding, archiveTask, completeTask, createTask, retireTask, reopenTask, updateTaskMetadata } from "../task/task.js";
31
33
  import { formatAgentRunReceiptId, resolveTaskRecordReference } from "../task/taskRecordReference.js";
32
34
  import { resolveProject } from "../repository/project.js";
@@ -180,6 +182,11 @@ export function preflightTaskCompletion(taskId, store, options = {}) {
180
182
  throw usageError(`Task ${task.id} has an unsettled integration queue entry: `
181
183
  + `${unsettledQueueEntry.id}/${unsettledQueueEntry.status}.`);
182
184
  }
185
+ const continuationBlockers = blockingProviderContinuations(store.listEvents(task.id));
186
+ if (continuationBlockers.length > 0) {
187
+ throw usageError(`Task ${task.id} has ${continuationBlockers.length} Provider continuation(s) `
188
+ + "that may still write the Workspace or have an identity conflict.");
189
+ }
183
190
  const isolatedWorkspace = store.listManagedWorkspaces(task.id)
184
191
  .find(({ owner }) => owner.type === "work-item");
185
192
  if (isolatedWorkspace?.owner.type === "work-item") {
@@ -675,6 +682,11 @@ function completeTaskCommand(args, store, options) {
675
682
  if (unsettledWork !== undefined) {
676
683
  throw usageError(`Task ${task.id} has unsettled work: ${unsettledWork.id}.`);
677
684
  }
685
+ const continuationBlockers = blockingProviderContinuations(tx.listEvents(task.id));
686
+ if (continuationBlockers.length > 0) {
687
+ throw usageError(`Task ${task.id} has ${continuationBlockers.length} Provider continuation(s) `
688
+ + "that may still write the Workspace or have an identity conflict.");
689
+ }
678
690
  let terminalizedLeaderRun = false;
679
691
  const leaderEntry = activeRuns.find(({ role }) => role.name === LEADER_ROLE);
680
692
  if (leaderEntry !== undefined) {
@@ -853,6 +865,11 @@ function archiveTaskCommand(args, store, options) {
853
865
  if (activeArchiveJob !== undefined) {
854
866
  throw usageError(`Task ${task.id} has an active DurableJob: ${activeArchiveJob.id}/${activeArchiveJob.status}.`);
855
867
  }
868
+ const continuationBlockers = blockingProviderContinuations(tx.listEvents(task.id));
869
+ if (continuationBlockers.length > 0) {
870
+ throw usageError(`Task ${task.id} still owns unsettled Provider continuation(s); `
871
+ + "reconcile or cancel them before archiving.");
872
+ }
856
873
  if (task.cwd !== undefined || tx.listManagedWorkspaces(task.id).length > 0) {
857
874
  throw usageError(`Task ${task.id} still has managed worktrees; clean them before archiving.`);
858
875
  }
@@ -1053,8 +1070,8 @@ function reconcileTaskCommand(args, store, options) {
1053
1070
  function taskMessageCommand(args, store, options) {
1054
1071
  const [command, ...rest] = args;
1055
1072
  if (command === "send") {
1056
- const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none].";
1057
- const parsed = parseTail(rest, new Set(["--body-file", "--wake-policy"]), usage);
1073
+ const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none] [--delivery-mode followup|steer].";
1074
+ const parsed = parseTail(rest, new Set(["--body-file", "--wake-policy", "--delivery-mode"]), usage);
1058
1075
  if (parsed.positionals.length < 1 || parsed.positionals.length > 2)
1059
1076
  throw usageError(usage);
1060
1077
  const body = readCommandText(parsed.positionals[1], parsed.options.get("--body-file"), "--body", usage);
@@ -1069,6 +1086,13 @@ function taskMessageCommand(args, store, options) {
1069
1086
  else {
1070
1087
  throw usageError(`--wake-policy must be 'leader' or 'none': ${wakePolicyRaw}.`);
1071
1088
  }
1089
+ const deliveryModeRaw = parsed.options.get("--delivery-mode") ?? "followup";
1090
+ if (deliveryModeRaw !== "followup" && deliveryModeRaw !== "steer") {
1091
+ throw usageError(`--delivery-mode must be 'followup' or 'steer': ${deliveryModeRaw}.`);
1092
+ }
1093
+ if (deliveryModeRaw === "steer" && wakePolicy === "none") {
1094
+ throw usageError("--delivery-mode steer cannot be combined with --wake-policy none.");
1095
+ }
1072
1096
  const now = clock(options);
1073
1097
  const result = store.transaction((tx) => {
1074
1098
  const task = requireTask(tx, parsed.positionals[0]);
@@ -1085,7 +1109,19 @@ function taskMessageCommand(args, store, options) {
1085
1109
  if (task.status === "active"
1086
1110
  && actor !== "leader"
1087
1111
  && wakePolicy !== "none") {
1088
- enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, message.id)]);
1112
+ enqueueWork(tx, leaderMailbox(task.id), actor === "operator" ? "operator-input" : "user-message", now, [messageRef(task.id, message.id)], deliveryModeRaw === "steer"
1113
+ ? {
1114
+ source: actor,
1115
+ dedupeKey: `user-correction:${task.id}:${message.id}`,
1116
+ deliveryMode: "steer-if-safe",
1117
+ lane: "user-correction"
1118
+ }
1119
+ : {
1120
+ source: actor,
1121
+ dedupeKey: `message:${task.id}:${message.id}`,
1122
+ deliveryMode: "followup",
1123
+ lane: "normal"
1124
+ });
1089
1125
  }
1090
1126
  return { task, message, actor };
1091
1127
  });
@@ -2938,8 +2974,7 @@ function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRo
2938
2974
  taskId,
2939
2975
  roleName: reviewerRoleName
2940
2976
  }));
2941
- const hasMailboxWork = (mailbox) => (mailbox?.processing !== null && mailbox?.processing !== undefined
2942
- || mailbox?.pending !== null && mailbox?.pending !== undefined);
2977
+ const hasMailboxWork = (mailbox) => (mailbox !== null && workMailboxHasWork(mailbox));
2943
2978
  const activePointer = store.getActiveAgentRun(taskId, reviewerRoleName);
2944
2979
  const activeRuns = store.listAgentRuns(taskId).filter((entry) => (entry.roleName === reviewerRoleName && entry.status === "active"));
2945
2980
  if (reusableRound === undefined || reusableRound.status === "pending") {
@@ -2966,16 +3001,16 @@ function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRo
2966
3001
  throw usageError(`Existing Task-final ReviewRound ${reusableRound.id} is running without its exact Reviewer execution.`);
2967
3002
  }
2968
3003
  const exactRun = runRef(taskId, reviewerRunId);
3004
+ const reviewerPending = reviewerMailbox === null ? null : nextPendingBatch(reviewerMailbox);
2969
3005
  const processingMatches = reviewerMailbox?.processing !== null
2970
3006
  && reviewerMailbox?.processing !== undefined
2971
- && reviewerMailbox.pending === null
3007
+ && reviewerPending === null
2972
3008
  && reviewerMailbox.processing.executionRef !== undefined
2973
3009
  && isDeepStrictEqual(reviewerMailbox.processing.executionRef, exactRun);
2974
- const pendingMatches = reviewerMailbox?.pending !== null
2975
- && reviewerMailbox?.pending !== undefined
2976
- && reviewerMailbox.processing === null
2977
- && reviewerMailbox.pending.requestCount === 1
2978
- && reviewerMailbox.pending.refs.some((ref) => isDeepStrictEqual(ref, exactRun));
3010
+ const pendingMatches = reviewerPending !== null
3011
+ && reviewerMailbox?.processing === null
3012
+ && reviewerPending.requestCount === 1
3013
+ && reviewerPending.refs.some((ref) => isDeepStrictEqual(ref, exactRun));
2979
3014
  if (hasMailboxWork(reviewerMailbox) && !processingMatches && !pendingMatches) {
2980
3015
  throw usageError(`Reviewer mailbox has unrelated active work: ${reviewerRoleName}.`);
2981
3016
  }
@@ -3069,8 +3104,7 @@ function retryFailedTaskReviewRound(args, store, options) {
3069
3104
  taskId: task.id,
3070
3105
  roleName: reviewer.name
3071
3106
  }));
3072
- const hasMailboxWork = (mailbox) => (mailbox?.processing !== null && mailbox?.processing !== undefined
3073
- || mailbox?.pending !== null && mailbox?.pending !== undefined);
3107
+ const hasMailboxWork = (mailbox) => (mailbox !== null && workMailboxHasWork(mailbox));
3074
3108
  const activeReviewerRuns = tx.listAgentRuns(task.id).filter((entry) => (entry.roleName === reviewer.name && entry.status === "active"));
3075
3109
  const activePointer = tx.getActiveAgentRun(task.id, reviewer.name);
3076
3110
  if (activePointer !== null || activeReviewerRuns.length > 0) {
@@ -3236,17 +3270,18 @@ function settleStaleFinalReviewRun(args, store, options) {
3236
3270
  }
3237
3271
  const reviewerTarget = roleMailbox(task.id, round.reviewerRoleName);
3238
3272
  const reviewerMailbox = tx.getWorkMailbox(reviewerTarget);
3273
+ const reviewerPending = reviewerMailbox === null ? null : nextPendingBatch(reviewerMailbox);
3239
3274
  const exactRunRef = runRef(task.id, run.id);
3240
3275
  if (reviewerMailbox?.processing !== null && reviewerMailbox?.processing !== undefined) {
3241
3276
  const processing = reviewerMailbox.processing;
3242
3277
  if (processing.executionRef === undefined
3243
3278
  || !isDeepStrictEqual(processing.executionRef, exactRunRef)
3244
- || reviewerMailbox.pending !== null) {
3279
+ || reviewerPending !== null) {
3245
3280
  throw usageError(`Reviewer mailbox has unrelated processing work: ${round.reviewerRoleName}.`);
3246
3281
  }
3247
3282
  }
3248
- else if (reviewerMailbox?.pending !== null && reviewerMailbox?.pending !== undefined) {
3249
- const pending = reviewerMailbox.pending;
3283
+ else if (reviewerPending !== null) {
3284
+ const pending = reviewerPending;
3250
3285
  if (pending.requestCount !== 1
3251
3286
  || !pending.refs.some((ref) => isDeepStrictEqual(ref, exactRunRef))) {
3252
3287
  throw usageError(`Reviewer mailbox has unrelated pending work: ${round.reviewerRoleName}.`);
@@ -3260,7 +3295,7 @@ function settleStaleFinalReviewRun(args, store, options) {
3260
3295
  if (runtimeMailbox?.processing !== null && runtimeMailbox?.processing !== undefined) {
3261
3296
  throw usageError(`Reviewer runtime lifecycle is pending: ${round.reviewerRoleName}.`);
3262
3297
  }
3263
- if (runtimeMailbox?.pending !== null && runtimeMailbox?.pending !== undefined) {
3298
+ if (runtimeMailbox !== null && workMailboxHasWork(runtimeMailbox)) {
3264
3299
  throw usageError(`Reviewer runtime lifecycle has pending work: ${round.reviewerRoleName}.`);
3265
3300
  }
3266
3301
  // A matching pending/processing dispatch belongs to this failed Run and
@@ -3834,13 +3869,13 @@ function retryFailedReviewRun(previous, store, options, now) {
3834
3869
  }
3835
3870
  const reviewerMailboxTarget = roleMailbox(task.id, round.reviewerRoleName);
3836
3871
  const reviewerMailbox = tx.getWorkMailbox(reviewerMailboxTarget);
3872
+ const reviewerPending = reviewerMailbox === null ? null : nextPendingBatch(reviewerMailbox);
3837
3873
  const runtimeMailbox = tx.getWorkMailbox(runtimeLifecycleTarget({
3838
3874
  scope: "task",
3839
3875
  taskId: task.id,
3840
3876
  roleName: round.reviewerRoleName
3841
3877
  }));
3842
- const hasMailboxWork = (mailbox) => (mailbox?.processing !== null && mailbox?.processing !== undefined
3843
- || mailbox?.pending !== null && mailbox?.pending !== undefined);
3878
+ const hasMailboxWork = (mailbox) => (mailbox !== null && workMailboxHasWork(mailbox));
3844
3879
  const reviewer = requireRole(tx, task.id, round.reviewerRoleName);
3845
3880
  const activePointer = tx.getActiveAgentRun(task.id, reviewer.name);
3846
3881
  const activeReviewerRuns = tx.listAgentRuns(task.id).filter((entry) => (entry.roleName === reviewer.name && entry.status === "active"));
@@ -3868,11 +3903,11 @@ function retryFailedReviewRun(previous, store, options, now) {
3868
3903
  const processingMatches = reviewerRunId !== undefined
3869
3904
  && reviewerMailbox?.processing?.executionRef !== undefined
3870
3905
  && isDeepStrictEqual(reviewerMailbox.processing.executionRef, runRef(task.id, reviewerRunId))
3871
- && reviewerMailbox.pending === null;
3906
+ && reviewerPending === null;
3872
3907
  const pendingMatches = reviewerRunId !== undefined
3873
3908
  && reviewerMailbox?.processing === null
3874
- && reviewerMailbox.pending?.requestCount === 1
3875
- && reviewerMailbox.pending.refs.some((ref) => (isDeepStrictEqual(ref, runRef(task.id, reviewerRunId))));
3909
+ && reviewerPending?.requestCount === 1
3910
+ && reviewerPending.refs.some((ref) => (isDeepStrictEqual(ref, runRef(task.id, reviewerRunId))));
3876
3911
  if (activePointer !== null
3877
3912
  && (!activeMatches
3878
3913
  || (!processingMatches && !pendingMatches)
@@ -3898,7 +3933,7 @@ function retryFailedReviewRun(previous, store, options, now) {
3898
3933
  if (runtimeMailbox?.processing !== null && runtimeMailbox?.processing !== undefined) {
3899
3934
  throw usageError(`Reviewer runtime lifecycle is pending: ${reviewer.name}.`);
3900
3935
  }
3901
- if (runtimeMailbox?.pending !== null && runtimeMailbox?.pending !== undefined) {
3936
+ if (runtimeMailbox !== null && workMailboxHasWork(runtimeMailbox)) {
3902
3937
  throw usageError(`Reviewer runtime lifecycle has pending work: ${reviewer.name}.`);
3903
3938
  }
3904
3939
  const validation = validateExactRunReviewRound(tx, run, { allowTerminal: true });
@@ -3912,16 +3947,16 @@ function retryFailedReviewRun(previous, store, options, now) {
3912
3947
  if (reviewerMailbox?.processing !== null && reviewerMailbox?.processing !== undefined) {
3913
3948
  if (reviewerMailbox.processing.executionRef === undefined
3914
3949
  || !isDeepStrictEqual(reviewerMailbox.processing.executionRef, exactOldRunRef)
3915
- || reviewerMailbox.pending !== null) {
3950
+ || reviewerPending !== null) {
3916
3951
  throw usageError(`Reviewer mailbox is busy: ${reviewer.name}.`);
3917
3952
  }
3918
3953
  settleExactWorkExecution(tx, reviewerMailboxTarget, exactOldRunRef);
3919
3954
  }
3920
- else if (reviewerMailbox?.pending !== null && reviewerMailbox?.pending !== undefined) {
3921
- const exact = reviewerMailbox.pending.refs.some((ref) => (isDeepStrictEqual(ref, exactOldRunRef)));
3955
+ else if (reviewerPending !== null) {
3956
+ const exact = reviewerPending.refs.some((ref) => (isDeepStrictEqual(ref, exactOldRunRef)));
3922
3957
  if (!exact)
3923
3958
  throw usageError(`Reviewer mailbox has unrelated pending work: ${reviewer.name}.`);
3924
- if (reviewerMailbox.pending.requestCount !== 1) {
3959
+ if (reviewerPending.requestCount !== 1) {
3925
3960
  throw usageError(`Reviewer mailbox has merged pending work: ${reviewer.name}.`);
3926
3961
  }
3927
3962
  settleExactWorkExecution(tx, reviewerMailboxTarget, exactOldRunRef);
@@ -32,6 +32,14 @@ export function runTaskContextCommand(args, store) {
32
32
  throw new Error(`Task execution projection disappeared: ${task.id}.`);
33
33
  }
34
34
  const roleSessionSets = reader.listRoleSessionSets(task.id);
35
+ const coordinationMailboxes = [
36
+ reader.getWorkMailbox({ kind: "task", taskId: task.id }),
37
+ ...roles.map((role) => reader.getWorkMailbox({
38
+ kind: "role",
39
+ taskId: task.id,
40
+ roleName: role.name
41
+ }))
42
+ ].filter((mailbox) => mailbox !== null);
35
43
  const agentRuns = chronological(reader.listAgentRuns(task.id));
36
44
  const reviewRounds = chronological(reader.listReviewRounds(task.id));
37
45
  const changeSets = chronological(reader.listChangeSets(task.id));
@@ -51,6 +59,7 @@ export function runTaskContextCommand(args, store) {
51
59
  roles,
52
60
  managedWorkspaces: reader.listManagedWorkspaces(task.id),
53
61
  roleSessionSets,
62
+ coordinationMailboxes,
54
63
  roleSessionRecoveries: roles.map((role) => (inspectTaskRoleSessionRecovery(task.id, role.name, reader))),
55
64
  workItems,
56
65
  agentRuns,
@@ -64,7 +73,7 @@ export function runTaskContextCommand(args, store) {
64
73
  nextAction: projectNextAction(nextActionFacts)
65
74
  };
66
75
  });
67
- const { task, execution, reviewConfig, brief, activeDecisions, milestones, roles, managedWorkspaces, roleSessionSets, roleSessionRecoveries, workItems, agentRuns, reviewRounds, changeSets, integrations, messages, openInputRequests, resolvedInputRequests, events, nextAction } = data;
76
+ const { task, execution, reviewConfig, brief, activeDecisions, milestones, roles, managedWorkspaces, roleSessionSets, coordinationMailboxes, roleSessionRecoveries, workItems, agentRuns, reviewRounds, changeSets, integrations, messages, openInputRequests, resolvedInputRequests, events, nextAction } = data;
68
77
  const timeZone = store.getConfig().timeZone;
69
78
  const displayedActiveDecisions = activeDecisions.slice(-RECENT_RECORD_LIMIT);
70
79
  const displayedWorkItems = currentAndRecentWorkItems(workItems);
@@ -196,6 +205,11 @@ export function runTaskContextCommand(args, store) {
196
205
  ];
197
206
  })),
198
207
  "",
208
+ `Coordination mailboxes (${coordinationMailboxes.length}):`,
209
+ ...(coordinationMailboxes.length === 0
210
+ ? [" None."]
211
+ : coordinationMailboxes.flatMap(renderCoordinationMailbox)),
212
+ "",
199
213
  `Current and recent work items (${displayedWorkItems.length}${workItems.length > displayedWorkItems.length ? ` of ${workItems.length}` : ""}):`,
200
214
  ...(displayedWorkItems.length === 0
201
215
  ? [" None."]
@@ -299,6 +313,18 @@ export function runTaskContextCommand(args, store) {
299
313
  data
300
314
  };
301
315
  }
316
+ function renderCoordinationMailbox(mailbox) {
317
+ const target = mailbox.target.kind === "role"
318
+ ? `role/${mailbox.target.roleName}`
319
+ : mailbox.target.kind;
320
+ const pending = [mailbox.pending.userCorrection, mailbox.pending.normal]
321
+ .filter((batch) => batch !== null);
322
+ return [
323
+ ` ${target}: cursor normal=${mailbox.pending.cursors.normal}, correction=${mailbox.pending.cursors.userCorrection}; next=${mailbox.nextSequence}`,
324
+ ` Delivery: ${mailbox.inputDelivery?.status ?? "none"}; processing: ${mailbox.processing?.batchId ?? "none"}; pending batches: ${pending.length}`,
325
+ ...pending.map((batch) => (` Pending ${batch.fromSequence}-${batch.toSequence}: ${batch.reasons.join(", ")} · refs ${batch.refs.map((ref) => `${ref.type}:${ref.id}`).join(", ") || "none"}`))
326
+ ];
327
+ }
302
328
  function latestStallKind(events, runId) {
303
329
  const event = [...events]
304
330
  .filter((candidate) => candidate.type === "run.stalled" && candidate.payload.runId === runId)
@@ -2,7 +2,7 @@ import { isDeepStrictEqual } from "node:util";
2
2
  import { hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
3
3
  import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
4
4
  import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
5
- import { evaluateRuntimeAttention, projectRuntimeObservation, projectRuntimeTaskEvents, runtimeDisplayStatus } from "../runtime/runtimeProjection.js";
5
+ import { evaluateRuntimeAttention, projectRuntimeMailbox, projectRuntimeObservation, projectRuntimeTaskEvents, runtimeDisplayStatus } from "../runtime/runtimeProjection.js";
6
6
  import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
7
7
  import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
8
8
  export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
@@ -154,7 +154,7 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
154
154
  ? { managed: false, path: role.workspace }
155
155
  : { ...managedWorkspace, managed: true };
156
156
  const events = store.listEvents(taskId);
157
- const runtime = projectTaskRoleRuntime(activeRun, nativeSession, tmux, events, now);
157
+ const runtime = projectTaskRoleRuntime(activeRun, nativeSession, tmux, events, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }), now);
158
158
  const stalled = activeRun !== null && isRoleRunStalled(events, activeRun.id);
159
159
  const stallProgressAt = activeRun === null
160
160
  ? undefined
@@ -223,7 +223,10 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
223
223
  };
224
224
  }
225
225
  if (tmux.state === "exited") {
226
- return { health: "failed", healthReason: "the tmux pane has exited" };
226
+ return {
227
+ health: "needs-attention",
228
+ healthReason: "the tmux pane exited; Provider Conversation/continuation state is unobservable"
229
+ };
227
230
  }
228
231
  if (activeRun !== null) {
229
232
  if (role.status !== "running") {
@@ -258,12 +261,18 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
258
261
  : "the Agent Driver has not reported recent structured runtime activity"
259
262
  };
260
263
  }
261
- if (runtime.status === "broken" || runtime.status === "stopped") {
264
+ if (runtime.status === "broken") {
262
265
  return {
263
266
  health: "failed",
264
267
  healthReason: `the Agent Driver runtime is ${runtime.status}`
265
268
  };
266
269
  }
270
+ if (runtime.status === "stopped") {
271
+ return {
272
+ health: "needs-attention",
273
+ healthReason: "the Provider Activation ended while the Yui Run remains active"
274
+ };
275
+ }
267
276
  if (runtime.status.startsWith("waiting-")) {
268
277
  return {
269
278
  health: runtime.status === "waiting-user" ? "blocked-input" : "waiting",
@@ -318,7 +327,7 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
318
327
  ? { health: "ready", healthReason: "the native Agent pane is ready without active work" }
319
328
  : { health: "idle", healthReason: "there is no active work or live tmux pane" };
320
329
  }
321
- function projectTaskRoleRuntime(run, session, tmux, events, now) {
330
+ function projectTaskRoleRuntime(run, session, tmux, events, mailbox, now) {
322
331
  if (run === null || session?.launchId === undefined)
323
332
  return null;
324
333
  let driverId;
@@ -350,9 +359,11 @@ function projectTaskRoleRuntime(run, session, tmux, events, now) {
350
359
  receiptId: formatAgentRunReceiptId(run.taskId, run.id)
351
360
  };
352
361
  let projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
362
+ projection = projectRuntimeMailbox(projection, mailbox);
353
363
  projection = projectRuntimeObservation(projection, createRuntimeObservation({
354
- schemaVersion: 1,
364
+ schemaVersion: 2,
355
365
  eventId: `runtime-host-${run.id}`,
366
+ semanticKey: `runtime-host-${run.id}`,
356
367
  kind: "host.observed",
357
368
  authority: "host",
358
369
  receivedAt: run.updatedAt,
@@ -62,8 +62,9 @@ export class AgentRuntimeObserver {
62
62
  if (existingState === undefined && freshSession && state.usage === undefined) {
63
63
  const zero = Object.freeze({ inputTokens: 0, outputTokens: 0 });
64
64
  this.inbox.enqueueObservation(createRuntimeObservation({
65
- schemaVersion: 1,
65
+ schemaVersion: 2,
66
66
  eventId: observationId("baseline", fence, source.sourceId, "zero"),
67
+ semanticKey: observationId("baseline", fence, source.sourceId, "zero"),
67
68
  kind: "activity.observed",
68
69
  authority: "controller",
69
70
  receivedAt: at,
@@ -78,8 +79,9 @@ export class AgentRuntimeObserver {
78
79
  const health = JSON.stringify([sample.status, sample.detail ?? null]);
79
80
  if (state.health !== health) {
80
81
  this.inbox.enqueueObservation(createRuntimeObservation({
81
- schemaVersion: 1,
82
+ schemaVersion: 2,
82
83
  eventId: observationId("health", fence, source.sourceId, health),
84
+ semanticKey: observationId("health", fence, source.sourceId, health),
83
85
  kind: "observer.health",
84
86
  authority: "diagnostic",
85
87
  receivedAt: at,
@@ -102,8 +104,9 @@ export class AgentRuntimeObserver {
102
104
  if (usageChanged || (activityChanged && state.cursor !== undefined)) {
103
105
  const usage = sample.usage;
104
106
  this.inbox.enqueueObservation(createRuntimeObservation({
105
- schemaVersion: 1,
107
+ schemaVersion: 2,
106
108
  eventId: observationId("activity", fence, source.sourceId, JSON.stringify([usage ?? null, sample.activityId ?? null])),
109
+ semanticKey: observationId("activity", fence, source.sourceId, JSON.stringify([usage ?? null, sample.activityId ?? null])),
107
110
  kind: "activity.observed",
108
111
  authority: "driver-inferred",
109
112
  receivedAt: at,