@zq-silk/yui 0.6.4 → 0.6.6

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.
@@ -1,4 +1,4 @@
1
- import { selectedSchedulerRoles, selectedSchedulerTasks } from "./ports.js";
1
+ import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
2
2
  import { formatTaskRecordReference } from "../task/taskRecordReference.js";
3
3
  import { queueLeaderWakeup } from "./wakeupQueue.js";
4
4
  import { currentRoleRunProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleRunStall.js";
@@ -7,9 +7,9 @@ export const EXITED_ROLE_RUN_SUMMARY = "The role's tmux session exited before th
7
7
  * Lightweight liveness only: an active AgentRun whose tmux role is absent is
8
8
  * failed, then the Leader is durably queued. No TTL, cooldown, or schedules.
9
9
  */
10
- export async function reconcileExitedRoleRuns(store, delivery, now, selection, excludedRunRefs = new Set(), liveStatuses, resourceEvidence) {
10
+ export async function reconcileExitedRoleRuns(store, delivery, now, selection, excludedRunRefs = new Set(), liveStatuses, resourceEvidence, targetedInventory = selection !== undefined && !selection.full) {
11
11
  const failed = [];
12
- const candidates = selectedSchedulerTasks(store, selection).flatMap((task) => (selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
12
+ const candidates = selectedActiveSchedulerTasks(store, selection).flatMap((task) => (selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
13
13
  const run = store.getActiveAgentRun(task.id, role.name);
14
14
  if (run === null)
15
15
  return [];
@@ -35,32 +35,39 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
35
35
  })));
36
36
  if (candidates.length === 0)
37
37
  return failed;
38
- const completing = new Set(store.listPendingRuntimeTurnCompletions().map((completion) => (`${completion.taskId}\0${completion.roleName}\0${completion.runId}`)));
38
+ const candidateTaskIds = [...new Set(candidates.map(({ task }) => task.id))]
39
+ .sort((left, right) => left.localeCompare(right, undefined, { numeric: true }));
40
+ const completing = new Set(store.listPendingRuntimeTurnCompletions(candidateTaskIds).map((completion) => (`${completion.taskId}\0${completion.roleName}\0${completion.runId}`)));
39
41
  const eligible = candidates.filter(({ task, role, run }) => (!excludedRunRefs.has(formatTaskRecordReference(task.id, run.id, "agentRun"))
40
42
  && !completing.has(`${task.id}\0${role.name}\0${run.id}`)));
41
- // Build one complete provider inventory for every active Run, including
42
- // delivery-uncertain and completion-pending Runs. The stall phase reuses
43
- // this snapshot so one scheduler pass never probes the same pane twice.
43
+ // Full reconciliation builds one complete provider inventory for every
44
+ // active Run, including delivery-uncertain and completion-pending Runs.
45
+ // The stall phase reuses that snapshot so one full pass never probes the
46
+ // same pane twice. When targetedInventory is true, a dirty pass instead
47
+ // uses exact probes below; stall reconciliation intentionally does not run
48
+ // for that bounded selection.
44
49
  const batchSnapshot = liveStatuses !== undefined
45
50
  && candidates.every(({ task, role }) => liveStatuses.has(`${task.id}\0${role.name}`))
46
51
  ? {
47
52
  statuses: liveStatuses,
48
53
  resources: resourceEvidence ?? new Map()
49
54
  }
50
- : await inspectRoleStatuses(delivery, candidates, candidates.flatMap(({ task, role, run, session }) => (isResourceCandidate(task, run, now)
51
- ? [{
52
- taskId: task.id,
53
- roleName: role.name,
54
- runId: run.id,
55
- agentId: run.effective.agentId,
56
- adapterId: run.effective.adapterId,
57
- progressAt: currentRoleRunProgressAt(store, task.id, role.name, run).progressAt,
58
- ...(session?.nativeSessionId === undefined
59
- ? {}
60
- : { nativeSessionId: session.nativeSessionId }),
61
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId })
62
- }]
63
- : [])));
55
+ : await inspectRoleStatuses(delivery, candidates, !targetedInventory
56
+ ? candidates.flatMap(({ task, role, run, session }) => (isResourceCandidate(task, run, now)
57
+ ? [{
58
+ taskId: task.id,
59
+ roleName: role.name,
60
+ runId: run.id,
61
+ agentId: run.effective.agentId,
62
+ adapterId: run.effective.adapterId,
63
+ progressAt: currentRoleRunProgressAt(store, task.id, role.name, run).progressAt,
64
+ ...(session?.nativeSessionId === undefined
65
+ ? {}
66
+ : { nativeSessionId: session.nativeSessionId }),
67
+ ...(session?.launchId === undefined ? {} : { launchId: session.launchId })
68
+ }]
69
+ : []))
70
+ : [], targetedInventory);
64
71
  if (liveStatuses !== undefined) {
65
72
  for (const [key, status] of batchSnapshot.statuses)
66
73
  liveStatuses.set(key, status);
@@ -106,7 +113,22 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
106
113
  }
107
114
  return failed;
108
115
  }
109
- async function inspectRoleStatuses(delivery, candidates, resourceInputs) {
116
+ async function inspectRoleStatuses(delivery, candidates, resourceInputs, targeted) {
117
+ // Dirty reconciliation already owns exact Task/Role keys. Probe those keys
118
+ // directly so a concurrent dirty batch does not repeat the provider's
119
+ // global inventory (and its optional process-resource scan) once per Task.
120
+ // Full reconciliation retains the adapter's batch contract below.
121
+ if (targeted && delivery.inspectRole !== undefined) {
122
+ const statuses = new Map();
123
+ for (const candidate of candidates) {
124
+ const key = `${candidate.task.id}\0${candidate.role.name}`;
125
+ if (statuses.has(key)) {
126
+ throw new Error("Tmux Role targeted liveness selection is invalid.");
127
+ }
128
+ statuses.set(key, await delivery.inspectRole(candidate.inspection));
129
+ }
130
+ return { statuses, resources: new Map() };
131
+ }
110
132
  if (delivery.inspectRoles !== undefined) {
111
133
  return exactBatchInventory(await delivery.inspectRoles(candidates.map(({ inspection }) => inspection), resourceInputs), candidates);
112
134
  }
@@ -1,4 +1,4 @@
1
- import { selectedSchedulerRoles, selectedSchedulerTasks } from "./ports.js";
1
+ import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
2
2
  /**
3
3
  * Default window of no durable progress before a live-but-idle Run becomes a
4
4
  * traceable needs-attention signal. It is deliberately long: a healthy Run that
@@ -187,7 +187,7 @@ export function latestRunProgressAt(events, runId) {
187
187
  * the provider-neutral fallback. Resource evidence carries this value as an
188
188
  * exact fence, but never advances it.
189
189
  */
190
- export function currentRoleRunProgressAt(store, taskId, roleName, run, events = store.listEvents?.(taskId) ?? []) {
190
+ export function currentRoleRunProgressAt(store, taskId, roleName, run, events, progressFacts) {
191
191
  let richerProgress;
192
192
  try {
193
193
  richerProgress = store.getRunDurableProgress?.(taskId, roleName, run.id);
@@ -209,10 +209,28 @@ export function currentRoleRunProgressAt(store, taskId, roleName, run, events =
209
209
  : { evidence: richerProgress.evidence })
210
210
  };
211
211
  }
212
+ // A present fold port is authoritative even when this Run has no entry. Do
213
+ // not evaluate a default history argument or fall back to listEvents in
214
+ // that case: an absent entry is the folded empty fact.
215
+ const folded = store.getRunProgressFacts !== undefined;
216
+ const foldedFacts = folded
217
+ ? progressFacts ?? store.getRunProgressFacts?.(taskId, run.id)
218
+ : undefined;
219
+ if (folded) {
220
+ const fallbackProgressAt = latestDurableProgressAt({
221
+ deliveredAt: run.deliveredAt,
222
+ latestCheckpointAt: foldedFacts?.latestCheckpointAt,
223
+ latestActivityAt: foldedFacts?.latestActivityAt
224
+ });
225
+ return { progressAt: fallbackProgressAt };
226
+ }
227
+ // Legacy ports retain the per-Run event-history fallback. Resolve the
228
+ // history only in this branch so fold-backed callers perform zero scans.
229
+ const history = events ?? store.listEvents?.(taskId) ?? [];
212
230
  const fallbackProgressAt = latestDurableProgressAt({
213
231
  deliveredAt: run.deliveredAt,
214
- latestCheckpointAt: latestRunProgressAt(events, run.id),
215
- latestActivityAt: latestRunActivityAt(events, run.id)
232
+ latestCheckpointAt: latestRunProgressAt(history, run.id),
233
+ latestActivityAt: latestRunActivityAt(history, run.id)
216
234
  });
217
235
  return {
218
236
  progressAt: fallbackProgressAt
@@ -227,13 +245,13 @@ export function latestRunDurableProgressAt(store, taskId, roleName, runId, preco
227
245
  const run = store.getAgentRun(taskId, runId);
228
246
  if (run === null || run.taskId !== taskId || run.roleName !== roleName)
229
247
  return null;
230
- const events = store.listEvents(taskId);
231
248
  // The adapter folds checkpoint/activity once per revision. When the fold
232
249
  // port exists, a missing per-Run entry is an authoritative empty fold: use
233
250
  // its (possibly undefined) values directly and never re-scan the whole
234
251
  // history per candidate. Legacy callers without the port omit precomputed
235
252
  // and keep the per-candidate full-history scans.
236
253
  const folded = precomputed !== undefined;
254
+ const events = folded ? undefined : store.listEvents(taskId);
237
255
  const latestCheckpointAt = folded
238
256
  ? precomputed.latestCheckpointAt
239
257
  : latestRunProgressAt(events, run.id);
@@ -480,26 +498,25 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
480
498
  // existing mailbox work without manufacturing another episode.
481
499
  if (selection !== undefined && !selection.full)
482
500
  return [];
483
- if (store.listEvents === undefined || store.recordRoleRunStall === undefined)
501
+ if ((store.getRunProgressFacts === undefined && store.listEvents === undefined)
502
+ || store.recordRoleRunStall === undefined)
484
503
  return [];
485
504
  // When the fold port exists, a missing per-Run entry is an authoritative
486
505
  // empty fold: the stall reconciliation must not re-scan the whole history
487
506
  // per candidate. Legacy stores without the port keep the per-candidate
488
507
  // scans.
489
508
  const foldPortPresent = store.getRunProgressFacts !== undefined;
490
- const candidates = selectedSchedulerTasks(store, selection).flatMap((task) => (task.status !== "active"
491
- ? []
492
- : selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
493
- const run = store.getActiveAgentRun(task.id, role.name);
494
- if (run === null || run.status !== "active")
495
- return [];
496
- return [{
497
- task,
498
- role,
499
- run,
500
- session: store.getRoleSession(task.id, role.name, run.effective.agentId)
501
- }];
502
- })));
509
+ const candidates = selectedActiveSchedulerTasks(store, selection).flatMap((task) => (selectedSchedulerRoles(store, task.id, selection).flatMap((role) => {
510
+ const run = store.getActiveAgentRun(task.id, role.name);
511
+ if (run === null || run.status !== "active")
512
+ return [];
513
+ return [{
514
+ task,
515
+ role,
516
+ run,
517
+ session: store.getRoleSession(task.id, role.name, run.effective.agentId)
518
+ }];
519
+ })));
503
520
  const stallCandidates = candidates.filter(({ run }) => isStallCandidate(run, now, windowMs));
504
521
  if (stallCandidates.length === 0)
505
522
  return [];
@@ -617,15 +634,22 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
617
634
  });
618
635
  continue;
619
636
  }
620
- const events = store.listEvents(candidate.task.id);
621
- const progressFacts = store.getRunProgressFacts?.(candidate.task.id, candidate.run.id);
637
+ // Fold-backed adapters expose an authoritative per-Run fact, so do not
638
+ // load the Task history just to satisfy currentRoleRunProgressAt or its
639
+ // fallback. Legacy ports retain one history read for these projections.
640
+ const progressFacts = foldPortPresent
641
+ ? store.getRunProgressFacts?.(candidate.task.id, candidate.run.id)
642
+ : undefined;
643
+ const events = foldPortPresent
644
+ ? undefined
645
+ : store.listEvents?.(candidate.task.id);
622
646
  // Before exact acceptance there is no execution progress clock. Keep the
623
647
  // delivery watch anchored to the Run creation/transport boundary even if
624
648
  // checkpoints, output, or related WorkItem/Review/Integration records are
625
649
  // newer; those facts are useful evidence but cannot prove provider
626
650
  // acceptance or reset delivery timeout. Once accepted, deliveredAt is the
627
651
  // semantic baseline and the durable fold may advance it.
628
- const progress = currentRoleRunProgressAt(store, candidate.task.id, candidate.role.name, candidate.run, events);
652
+ const progress = currentRoleRunProgressAt(store, candidate.task.id, candidate.role.name, candidate.run, events, progressFacts);
629
653
  const progressAt = progress.progressAt;
630
654
  const evaluation = evaluateRoleRunStall({
631
655
  progressAt,
@@ -633,7 +657,7 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
633
657
  windowMs,
634
658
  lastAttentionProgressAt: foldPortPresent
635
659
  ? progressFacts?.latestStall?.progressAt
636
- : latestStallProgressAt(events, candidate.run.id)
660
+ : latestStallProgressAt(events ?? [], candidate.run.id)
637
661
  });
638
662
  const runAgentId = candidate.run.effective.agentId;
639
663
  const runAdapterId = candidate.run.effective.adapterId;
@@ -710,7 +734,7 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
710
734
  const { candidate, progressAt } = current;
711
735
  const previous = foldPortPresent
712
736
  ? store.getRunProgressFacts?.(candidate.task.id, candidate.run.id)?.latestStall
713
- : latestStallEvidenceKey(store.listEvents(candidate.task.id), candidate.run.id);
737
+ : latestStallEvidenceKey(store.listEvents?.(candidate.task.id) ?? [], candidate.run.id);
714
738
  if (previous !== undefined
715
739
  && Date.parse(progressAt) > Date.parse(previous.progressAt)) {
716
740
  // A new semantic progress point closes the previous episode first. It
@@ -784,7 +808,7 @@ export async function reconcileStalledRoleRuns(store, delivery, now, selection,
784
808
  const { candidate, progressAt } = current;
785
809
  const previous = foldPortPresent
786
810
  ? store.getRunProgressFacts?.(candidate.task.id, candidate.run.id)?.latestStall
787
- : latestStallEvidenceKey(store.listEvents(candidate.task.id), candidate.run.id);
811
+ : latestStallEvidenceKey(store.listEvents?.(candidate.task.id) ?? [], candidate.run.id);
788
812
  if (previous !== undefined
789
813
  && Date.parse(progressAt) > Date.parse(previous.progressAt)) {
790
814
  store.recordRoleRunProgress?.({