@zq-silk/yui 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +101 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/task/nextAction.js +1 -1
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
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.
|
|
802
|
-
(
|
|
803
|
-
|
|
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
|
});
|
|
@@ -1802,7 +1838,7 @@ function dispatchWork(args, store, options) {
|
|
|
1802
1838
|
if (item.assignee === undefined) {
|
|
1803
1839
|
throw usageError(`Work Item has no Task Role assignee: ${item.id}. `
|
|
1804
1840
|
+ `The Task Leader must run "yui task work update ${item.id} running", `
|
|
1805
|
-
+ "then execute it directly or create
|
|
1841
|
+
+ "then execute it directly or create native subagents in the Leader Session.");
|
|
1806
1842
|
}
|
|
1807
1843
|
if (expanding && taskActor(options, task.id) !== "leader") {
|
|
1808
1844
|
throw usageError("Only the Task Leader may expand a running ExecutionGroup.");
|
|
@@ -2938,8 +2974,7 @@ function assertTaskReviewRequestLane(store, taskId, reviewerRoleName, reusableRo
|
|
|
2938
2974
|
taskId,
|
|
2939
2975
|
roleName: reviewerRoleName
|
|
2940
2976
|
}));
|
|
2941
|
-
const hasMailboxWork = (mailbox) => (mailbox
|
|
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
|
-
&&
|
|
3007
|
+
&& reviewerPending === null
|
|
2972
3008
|
&& reviewerMailbox.processing.executionRef !== undefined
|
|
2973
3009
|
&& isDeepStrictEqual(reviewerMailbox.processing.executionRef, exactRun);
|
|
2974
|
-
const pendingMatches =
|
|
2975
|
-
&& reviewerMailbox?.
|
|
2976
|
-
&&
|
|
2977
|
-
&&
|
|
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
|
|
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
|
-
||
|
|
3279
|
+
|| reviewerPending !== null) {
|
|
3245
3280
|
throw usageError(`Reviewer mailbox has unrelated processing work: ${round.reviewerRoleName}.`);
|
|
3246
3281
|
}
|
|
3247
3282
|
}
|
|
3248
|
-
else if (
|
|
3249
|
-
const 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
|
|
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
|
|
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
|
-
&&
|
|
3906
|
+
&& reviewerPending === null;
|
|
3872
3907
|
const pendingMatches = reviewerRunId !== undefined
|
|
3873
3908
|
&& reviewerMailbox?.processing === null
|
|
3874
|
-
&&
|
|
3875
|
-
&&
|
|
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
|
|
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
|
-
||
|
|
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 (
|
|
3921
|
-
const exact =
|
|
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 (
|
|
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 {
|
|
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"
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|