@zq-silk/yui 0.14.1 → 0.15.0

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 (185) hide show
  1. package/ARCHITECTURE.md +91 -47
  2. package/README.md +275 -145
  3. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  4. package/dist/cli/commandCatalog.js +74 -109
  5. package/dist/cli/helpRenderer.js +1 -2
  6. package/dist/cli/interactionCandidates.js +10 -0
  7. package/dist/cli/interactionPolicy.js +76 -37
  8. package/dist/cli/roleWizard.js +5 -6
  9. package/dist/cli/updateCommand.js +17 -9
  10. package/dist/cli/updateOrchestrator.js +87 -19
  11. package/dist/cli/updatePorts.js +79 -13
  12. package/dist/cli/upgradeCommand.js +105 -23
  13. package/dist/cli.js +268 -231
  14. package/dist/commands/agentCommands.js +13 -0
  15. package/dist/commands/configCommands.js +7 -19
  16. package/dist/commands/configOverview.js +3 -2
  17. package/dist/commands/controllerCommands.js +1 -1
  18. package/dist/commands/durableJobCommands.js +1 -1
  19. package/dist/commands/executionAuditCommands.js +4 -9
  20. package/dist/commands/globalRoleCommands.js +17 -0
  21. package/dist/commands/profileCommands.js +200 -30
  22. package/dist/commands/sessionCommands.js +1 -1
  23. package/dist/commands/taskActor.js +29 -127
  24. package/dist/commands/taskChangeSetCommands.js +1 -3
  25. package/dist/commands/taskCommands.js +1771 -2107
  26. package/dist/commands/taskCompletionGate.js +27 -103
  27. package/dist/commands/taskContextCommand.js +47 -80
  28. package/dist/commands/taskExecutionCommands.js +9 -27
  29. package/dist/commands/taskInputCommands.js +10 -10
  30. package/dist/commands/taskIntegrationCommands.js +69 -61
  31. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  32. package/dist/commands/taskNextActionCommand.js +8 -29
  33. package/dist/commands/taskOverviewCommand.js +25 -3
  34. package/dist/commands/taskPublicationCommands.js +151 -85
  35. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  36. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  37. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  38. package/dist/commands/taskUpstreamCommands.js +79 -95
  39. package/dist/config/configCatalog.js +1 -1
  40. package/dist/context/contextSnapshot.js +17 -2
  41. package/dist/context/sourceTurnContext.js +30 -0
  42. package/dist/context/turnContextPack.js +107 -48
  43. package/dist/context/turnInputContract.js +11 -4
  44. package/dist/context/wakeNotification.js +84 -22
  45. package/dist/controller/agentRuntimeObserver.js +9 -12
  46. package/dist/controller/clientRuntime.js +13 -13
  47. package/dist/controller/controller.js +11 -11
  48. package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
  49. package/dist/controller/jobControl.js +11 -10
  50. package/dist/controller/resourceInventory.js +6 -6
  51. package/dist/controller/resourceInventoryLinux.js +2 -2
  52. package/dist/controller/runtime.js +18 -61
  53. package/dist/controller/runtimeEventInbox.js +57 -34
  54. package/dist/controller/runtimeEventProcessor.js +36 -96
  55. package/dist/controller/runtimeHookTurnFence.js +33 -57
  56. package/dist/controller/runtimeLaunchCoordinator.js +44 -40
  57. package/dist/controller/runtimeObservationHook.js +18 -12
  58. package/dist/controller/sessionNotify.js +43 -28
  59. package/dist/controller/sessionOwnerReconciliation.js +21 -21
  60. package/dist/controller/structuredProviderObservation.js +13 -16
  61. package/dist/coordination/workMailboxQueue.js +132 -0
  62. package/dist/core/controllerServer.js +6 -5
  63. package/dist/doctor/doctor.js +37 -14
  64. package/dist/domain/agentResultTransport.js +60 -0
  65. package/dist/execution/executionHealth.js +111 -118
  66. package/dist/execution/reviewMainTurn.js +161 -0
  67. package/dist/execution/workItemExecution.js +454 -0
  68. package/dist/execution/workItemExecutionProjection.js +309 -0
  69. package/dist/execution/workItemMainTurn.js +164 -0
  70. package/dist/executor/agentExecutor.js +19 -19
  71. package/dist/executor/executorRegistry.js +7 -28
  72. package/dist/executor/fileRoleLaunchPlanner.js +16 -82
  73. package/dist/external/githubPublicationVerifier.js +114 -0
  74. package/dist/external/gitlabPublicationVerifier.js +158 -0
  75. package/dist/external/pinnedCommandRunner.js +73 -0
  76. package/dist/integration/changeSetManifest.js +0 -3
  77. package/dist/integration/deliveryObligation.js +43 -6
  78. package/dist/integration/gitIntegrationService.js +284 -101
  79. package/dist/integration/integrationAttempt.js +64 -14
  80. package/dist/integration/integrationQueueEntry.js +5 -35
  81. package/dist/integration/integrationQueueService.js +65 -194
  82. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  83. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  84. package/dist/message/message.js +16 -0
  85. package/dist/observability/executionAudit.js +4 -25
  86. package/dist/observability/faultClassification.js +58 -70
  87. package/dist/observability/orchestrationMetrics.js +8 -50
  88. package/dist/observability/runtimeIdentity.js +48 -50
  89. package/dist/profile/agentProfile.js +60 -15
  90. package/dist/profile/agentProfileRuntime.js +100 -0
  91. package/dist/release/releaseWorkflowPorts.js +5 -68
  92. package/dist/release/runtimeRelease.js +9 -1
  93. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  94. package/dist/repository/gitWorkspace.js +9 -67
  95. package/dist/repository/taskBaseFreshness.js +3 -1
  96. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  97. package/dist/repository/taskWorkspacePreparer.js +252 -411
  98. package/dist/resources/liveReferences.js +6 -55
  99. package/dist/resources/resourceDiscovery.js +1 -30
  100. package/dist/resources/resourceRegistrar.js +0 -23
  101. package/dist/review/deltaRecheck.js +8 -65
  102. package/dist/review/reviewAcceptance.js +22 -19
  103. package/dist/review/reviewConfig.js +0 -10
  104. package/dist/review/reviewDecision.js +2 -2
  105. package/dist/review/reviewRound.js +147 -273
  106. package/dist/review/reviewerAvailability.js +13 -23
  107. package/dist/role/role.js +3 -0
  108. package/dist/runtime/agentDriverObservation.js +1 -1
  109. package/dist/runtime/agentHost.js +51 -51
  110. package/dist/runtime/builtinAgentDrivers.js +9 -2
  111. package/dist/runtime/codexAppServerRuntime.js +23 -5
  112. package/dist/runtime/exactControlPlane.js +22 -296
  113. package/dist/runtime/firstProgressAdvisory.js +1 -1
  114. package/dist/runtime/index.js +1 -1
  115. package/dist/runtime/launchBroker.js +11 -11
  116. package/dist/runtime/lifecycleReservation.js +2 -2
  117. package/dist/runtime/managedCaller.js +111 -0
  118. package/dist/runtime/ports.js +16 -3
  119. package/dist/runtime/preallocatedNativeSession.js +2 -2
  120. package/dist/runtime/processExitObservation.js +3 -2
  121. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  122. package/dist/runtime/runtimeBinding.js +1 -1
  123. package/dist/runtime/runtimeObservation.js +33 -16
  124. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  125. package/dist/runtime/runtimeStopReceipt.js +11 -11
  126. package/dist/runtime/sessionLaunchRequest.js +2 -2
  127. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  128. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  129. package/dist/runtime/sessionReconciliation.js +4 -4
  130. package/dist/runtime/sessionTerminationGuard.js +9 -9
  131. package/dist/runtime/sessionTokenMetrics.js +5 -7
  132. package/dist/runtime/structuredProviderHost.js +5 -5
  133. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  134. package/dist/runtime/tmuxAdapters.js +32 -30
  135. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  136. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  137. package/dist/scheduler/roleTurnLiveness.js +6 -6
  138. package/dist/scheduler/roleTurnStall.js +8 -9
  139. package/dist/scheduler/taskExecutionProjection.js +19 -41
  140. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  141. package/dist/storage/currentTaskStore.js +6 -4
  142. package/dist/storage/sqliteSchema.js +135 -80
  143. package/dist/storage/sqliteStore.js +69 -52
  144. package/dist/storage/storageSchema.js +92 -230
  145. package/dist/storage/storageVersions.js +12 -13
  146. package/dist/storage/taskStore.js +8 -8
  147. package/dist/storage/upgrade/upgradeOrchestrator.js +276 -85
  148. package/dist/task/completionReadiness.js +17 -98
  149. package/dist/task/deliveryGuard.js +9 -11
  150. package/dist/task/draftPlan.js +142 -0
  151. package/dist/task/nextAction.js +179 -477
  152. package/dist/task/publicationVerification.js +1 -0
  153. package/dist/task/remoteDelivery.js +233 -0
  154. package/dist/task/task.js +108 -4
  155. package/dist/task/taskRecordReference.js +0 -1
  156. package/dist/turn/turn.js +109 -16
  157. package/dist/verification/verificationGateService.js +0 -26
  158. package/dist/version.js +3 -3
  159. package/dist/web/assets/client/components.js +99 -121
  160. package/dist/web/assets/client/i18n.js +136 -58
  161. package/dist/web/assets/client/view.js +44 -0
  162. package/dist/web/assets/styles/cards.js +3 -15
  163. package/dist/web/assets/styles/widgets.js +4 -0
  164. package/dist/web/webSnapshot.js +29 -14
  165. package/dist/workItem/dependencyGate.js +34 -0
  166. package/dist/workItem/workItem.js +99 -266
  167. package/dist/workspace/workItemChangeSetManager.js +15 -59
  168. package/docs/task-local-identity.md +9 -9
  169. package/i18n/README.zh-CN.md +189 -74
  170. package/package.json +1 -1
  171. package/skills/yui-leader/SKILL.md +272 -782
  172. package/skills/yui-operator/SKILL.md +190 -365
  173. package/skills/yui-reviewer/SKILL.md +40 -33
  174. package/skills/yui-runtime/SKILL.md +22 -0
  175. package/skills/yui-worker/SKILL.md +19 -6
  176. package/dist/execution/candidateConvergence.js +0 -623
  177. package/dist/execution/executionGroup.js +0 -854
  178. package/dist/execution/resourceBroker.js +0 -426
  179. package/dist/executor/launchPlan.js +0 -16
  180. package/dist/review/reviewFinding.js +0 -240
  181. package/dist/review/reviewFindingLedger.js +0 -550
  182. package/dist/review/reviewOutcomeClassifier.js +0 -255
  183. package/dist/scheduler/resourceQueueProjection.js +0 -71
  184. package/dist/storage/upgrade/recordVersions.js +0 -82
  185. package/dist/task/repairWave.js +0 -150
@@ -29,7 +29,7 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
29
29
  adapterId: run.effective.adapterId,
30
30
  turnId: run.id,
31
31
  progressAt: run.createdAt,
32
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId }),
32
+ ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
33
33
  ...(session?.nativeSessionId === undefined
34
34
  ? {}
35
35
  : { nativeSessionId: session.nativeSessionId })
@@ -64,7 +64,7 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
64
64
  ...(session?.nativeSessionId === undefined
65
65
  ? {}
66
66
  : { nativeSessionId: session.nativeSessionId }),
67
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId })
67
+ ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId })
68
68
  }]
69
69
  : []))
70
70
  : [], targetedInventory);
@@ -94,7 +94,7 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
94
94
  taskId: task.id,
95
95
  roleName: role.name,
96
96
  turnId: run.id,
97
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId }),
97
+ ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
98
98
  ...(session?.nativeSessionId === undefined
99
99
  ? {}
100
100
  : { nativeSessionId: session.nativeSessionId }),
@@ -123,16 +123,16 @@ export async function reconcileExitedRoleTurns(store, delivery, now, selection,
123
123
  mode: "resume",
124
124
  turnId: run.id,
125
125
  nativeSessionId: session.nativeSessionId,
126
- ...(session.launchId === undefined
126
+ ...(session.runtimeGenerationId === undefined
127
127
  ? {}
128
- : { hostActivationId: session.launchId })
128
+ : { hostActivationId: session.runtimeGenerationId })
129
129
  });
130
130
  store.saveRoleTurnPrepared({
131
131
  task,
132
132
  role,
133
133
  turn: run,
134
134
  session: recovered.session ?? session,
135
- ...(recovered.launchId === undefined ? {} : { launchId: recovered.launchId }),
135
+ ...(recovered.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: recovered.runtimeGenerationId }),
136
136
  now
137
137
  });
138
138
  liveStatuses?.set(`${task.id}\0${role.name}`, "present");
@@ -58,7 +58,7 @@ export function projectRoleTurnHealth(input) {
58
58
  : session.status === "ended"
59
59
  ? session.endReason === "failed" ? "broken" : "stopped"
60
60
  : !hasResourceIdentityText(session.nativeSessionId)
61
- && !hasResourceIdentityText(session.launchId)
61
+ && !hasResourceIdentityText(session.runtimeGenerationId)
62
62
  ? "unknown"
63
63
  : "matching";
64
64
  const resourceActivity = hostLiveness === "present"
@@ -212,9 +212,8 @@ export function latestTurnDurableProgressAt(store, taskId, roleName, turnId, pre
212
212
  const changeSets = store.listChangeSets?.(taskId)
213
213
  .filter(({ workItemId }) => workItemId === run.workItemId)
214
214
  ?? [];
215
- const changeSetIds = new Set(changeSets.map(({ id }) => id));
216
215
  const integrations = store.listIntegrationAttempts?.(taskId)
217
- .filter(({ changeSetIds: ids }) => ids.some((id) => changeSetIds.has(id)))
216
+ .filter(({ source }) => (source.kind === "work-item" && source.workItemId === run.workItemId))
218
217
  ?? [];
219
218
  const inputProgress = store.listInputRequests?.(taskId)
220
219
  .filter((request) => (request.requester.turnId === run.id
@@ -509,7 +508,7 @@ export async function reconcileStalledRoleTurns(store, delivery, now, selection,
509
508
  progressAt: run.createdAt,
510
509
  agentId: session?.agentId ?? run.effective.agentId,
511
510
  adapterId: session?.adapterId ?? run.effective.adapterId,
512
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId }),
511
+ ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
513
512
  ...(session?.nativeSessionId === undefined
514
513
  ? {}
515
514
  : { nativeSessionId: session.nativeSessionId })
@@ -520,7 +519,7 @@ export async function reconcileStalledRoleTurns(store, delivery, now, selection,
520
519
  progressAt: currentRoleTurnProgressAt(store, task.id, role.name, run).progressAt,
521
520
  agentId: session?.agentId ?? run.effective.agentId,
522
521
  adapterId: session?.adapterId ?? run.effective.adapterId,
523
- ...(session?.launchId === undefined ? {} : { launchId: session.launchId }),
522
+ ...(session?.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: session.runtimeGenerationId }),
524
523
  ...(session?.nativeSessionId === undefined
525
524
  ? {}
526
525
  : { nativeSessionId: session.nativeSessionId })
@@ -624,9 +623,9 @@ export async function reconcileStalledRoleTurns(store, delivery, now, selection,
624
623
  ...(candidate.session.nativeSessionId === undefined
625
624
  ? {}
626
625
  : { nativeSessionId: candidate.session.nativeSessionId }),
627
- ...(candidate.session.launchId === undefined
626
+ ...(candidate.session.runtimeGenerationId === undefined
628
627
  ? {}
629
- : { launchId: candidate.session.launchId })
628
+ : { runtimeGenerationId: candidate.session.runtimeGenerationId })
630
629
  };
631
630
  const resourceSnapshot = resourceForRun(resourceEvidence, candidate.task.id, candidate.role.name, candidate.run.id);
632
631
  const resourceIsCurrent = live === "present"
@@ -946,10 +945,10 @@ function resourceEvidenceMatchesCurrentRun(resource, expected, progressAt) {
946
945
  || identity.agentId !== expected.agentId
947
946
  || identity.adapterId !== expected.adapterId
948
947
  || identity.nativeSessionId !== expected.nativeSessionId
949
- || identity.launchId !== expected.launchId)
948
+ || identity.runtimeGenerationId !== expected.runtimeGenerationId)
950
949
  return false;
951
950
  return hasResourceIdentityText(identity.nativeSessionId)
952
- || hasResourceIdentityText(identity.launchId);
951
+ || hasResourceIdentityText(identity.runtimeGenerationId);
953
952
  }
954
953
  function hasResourceIdentityText(value) {
955
954
  return typeof value === "string" && value.trim().length > 0;
@@ -35,7 +35,7 @@ export function buildTaskExecutionProjection(store, taskId, taskOverride, now =
35
35
  turns,
36
36
  executionGroups: store.listWorkItems === undefined && store.listReviewRounds === undefined
37
37
  ? []
38
- : collectExecutionGroups(store.listWorkItems?.(taskId) ?? [], store.listReviewRounds?.(taskId) ?? []),
38
+ : collectExecutionGroups(store.listWorkItems?.(taskId) ?? [], store.listReviewRounds?.(taskId) ?? [], turns),
39
39
  workItems: store.listWorkItems?.(taskId) ?? [],
40
40
  ...(store.listContextSnapshots === undefined
41
41
  ? {}
@@ -67,11 +67,12 @@ export function buildTaskExecutionProjection(store, taskId, taskOverride, now =
67
67
  * unchanged revision.
68
68
  */
69
69
  export function projectTaskExecutionFromFacts(facts) {
70
+ const turns = operationalTaskRecords(facts.turns, facts.events ?? [], "turn");
70
71
  const executionGroups = facts.executionGroups
71
- ?? collectExecutionGroups(facts.workItems ?? [], facts.reviewRounds ?? []);
72
+ ?? collectExecutionGroups(facts.workItems ?? [], facts.reviewRounds ?? [], turns);
72
73
  return projectTaskExecution({
73
74
  ...facts,
74
- turns: operationalTaskRecords(facts.turns, facts.events ?? [], "turn"),
75
+ turns,
75
76
  executionGroups
76
77
  });
77
78
  }
@@ -79,21 +80,18 @@ export function projectTaskExecution(facts) {
79
80
  const { task, roles, turns, workItems = [], inputRequests = [], reviewRounds = [], integrations = [], events = [], pendingWakeup = null, leaderMailbox = null, leaderFailure = null, roleSessions = [], executionGroups = [] } = facts;
80
81
  const now = facts.now ?? new Date();
81
82
  const groupSummaries = executionGroups.map((group) => {
82
- const stageGroups = workItems.find((item) => (item.executionGroups.some(({ id }) => id === group.id)))?.executionGroups;
83
83
  return summarizeExecutionGroupHealth({
84
84
  group,
85
- ...(stageGroups === undefined ? {} : { stageGroups }),
86
85
  turns,
87
86
  sessions: roleSessions,
88
87
  events,
89
88
  now,
90
- policy: facts.runtimeHealthPolicy
89
+ ...(facts.runtimeHealthPolicy === undefined
90
+ ? {}
91
+ : { policy: facts.runtimeHealthPolicy })
91
92
  });
92
93
  });
93
- const observabilityGroups = uniqueExecutionGroups([
94
- ...executionGroups,
95
- ...workItems.flatMap((item) => item.executionGroups)
96
- ]);
94
+ const observabilityGroups = uniqueExecutionGroups(executionGroups);
97
95
  const sessionTokens = roleSessions.map((session) => {
98
96
  const identity = resolveSessionTokenIdentity({ taskId: task.id, ...session });
99
97
  return Object.freeze({
@@ -105,7 +103,6 @@ export function projectTaskExecution(facts) {
105
103
  const observability = buildTaskObservabilityProjection({
106
104
  workItems,
107
105
  executionGroups: observabilityGroups,
108
- groupSummaries,
109
106
  turns,
110
107
  events,
111
108
  contextSnapshots: facts.contextSnapshots,
@@ -474,37 +471,18 @@ function uniqueExecutionGroups(groups) {
474
471
  return true;
475
472
  });
476
473
  }
477
- function collectExecutionGroups(workItems, reviewRounds) {
478
- const workItemsById = new Map(workItems.map((item) => [item.id, item]));
479
- const orderedRounds = [...reviewRounds].sort((left, right) => (left.id.localeCompare(right.id, undefined, { numeric: true })));
480
- const latestTaskRound = orderedRounds
481
- .filter((round) => (round.scope ?? "work-item") === "task")
482
- .at(-1);
483
- const latestWorkItemRounds = new Map();
484
- for (const round of orderedRounds) {
485
- if ((round.scope ?? "work-item") === "task"
486
- || round.workItemId === undefined
487
- || round.candidateId === undefined)
488
- continue;
489
- const item = workItemsById.get(round.workItemId);
490
- if (item === undefined
491
- || item.status === "retired"
492
- || item.candidates.at(-1)?.id !== round.candidateId)
493
- continue;
494
- latestWorkItemRounds.set(`${round.workItemId}\0${round.candidateId}`, round);
495
- }
496
- const operationalReviewRoundIds = new Set([
497
- ...(latestTaskRound === undefined ? [] : [latestTaskRound.id]),
498
- ...[...latestWorkItemRounds.values()].map(({ id }) => id)
499
- ]);
474
+ function collectExecutionGroups(workItems, reviewRounds, _turns) {
500
475
  const groups = [
501
- ...workItems.filter(({ status }) => status !== "retired").flatMap((item) => {
502
- const group = currentWorkItemExecutionGroup(item);
476
+ ...workItems.flatMap((item) => {
477
+ const group = item.status === "retired"
478
+ ? undefined
479
+ : currentWorkItemExecutionGroup(item);
503
480
  return group === undefined ? [] : [group];
504
481
  }),
505
- ...reviewRounds.flatMap((round) => (!operationalReviewRoundIds.has(round.id) || round.executionGroup === undefined
506
- ? []
507
- : [round.executionGroup]))
482
+ ...reviewRounds.flatMap((round) => ((round.status === "pending" || round.status === "running")
483
+ && round.executionGroup !== undefined
484
+ ? [round.executionGroup]
485
+ : []))
508
486
  ];
509
487
  const seen = new Set();
510
488
  return groups.filter((group) => {
@@ -623,7 +601,7 @@ function collectBlockers(workItems, reviewRounds, integrations, openInputs, task
623
601
  }
624
602
  if (item.status === "pending" && (item.dependsOn ?? []).some((id) => {
625
603
  const status = byId.get(id)?.status;
626
- return status !== "completed" && status !== "retired";
604
+ return status !== "completed";
627
605
  })) {
628
606
  blockers.push({
629
607
  kind: "work",
@@ -638,7 +616,7 @@ function collectBlockers(workItems, reviewRounds, integrations, openInputs, task
638
616
  kind: "review",
639
617
  id: round.id,
640
618
  owner: "leader",
641
- summary: round.summary ?? `ReviewRound ${round.id} failed.`
619
+ summary: round.failure?.message ?? `ReviewRound ${round.id} failed.`
642
620
  });
643
621
  }
644
622
  for (const attempt of integrations.filter(({ status }) => status === "blocked" || status === "failed")) {
@@ -1,5 +1,3 @@
1
- import { isDeepStrictEqual } from "node:util";
2
- import { observedExecutionResourceUsage } from "../execution/resourceBroker.js";
3
1
  /**
4
2
  * Build the read-only DAG, execution, cost, and context view consumed by CLI
5
3
  * and Web. It deliberately derives every value from existing Task records and
@@ -7,48 +5,20 @@ import { observedExecutionResourceUsage } from "../execution/resourceBroker.js";
7
5
  */
8
6
  export function buildTaskObservabilityProjection(input) {
9
7
  const now = input.now ?? new Date();
10
- const summariesById = new Map((input.groupSummaries ?? []).map((summary) => [summary.groupId, summary]));
11
8
  const dag = projectDag(input.workItems);
12
9
  const workItems = input.workItems.map((item) => {
13
10
  const groups = item.executionGroups;
14
- const executionGroups = groups.flatMap((group) => {
15
- const summary = summariesById.get(group.id);
16
- return summary === undefined ? [] : [summary];
17
- });
18
- const stages = groups.map((group) => {
19
- const summary = summariesById.get(group.id);
20
- return Object.freeze({
21
- groupId: group.id,
22
- ...(group.stage === undefined ? {} : {
23
- mode: group.stage.mode,
24
- stage: group.stage.stage,
25
- round: group.stage.round,
26
- stageAttempt: group.stage.stageAttempt
27
- }),
28
- laneCount: group.lanes.length,
29
- activeLaneCount: group.lanes.filter(({ status }) => status === "pending" || status === "running").length,
30
- terminalLaneCount: group.lanes.filter(({ status }) => isTerminalLane(status)).length,
31
- ...(group.resolution === undefined ? {} : { resolution: group.resolution.decision }),
32
- ...(summary?.resources === undefined ? {} : { resources: summary.resources })
33
- });
34
- });
35
- const itemCost = projectCost(groups, input.turns, input.events, now);
11
+ const itemCost = projectWorkItemCost(groups, input.turns, now);
36
12
  const itemContext = projectContext(groups, input.turns, input.contextSnapshots);
37
- const evidence = groups.flatMap((group) => group.lanes.flatMap((lane) => (lane.result?.evidence ?? [])));
38
- const openFindingCount = groups.reduce((count, group) => count + group.lanes.reduce((laneCount, lane) => laneCount + (lane.result?.findings ?? [])
39
- .filter(({ status }) => status === "open").length, 0), 0);
13
+ const producerObservability = projectWorkItemProducerObservability(item, input.turns);
40
14
  return Object.freeze({
41
15
  workItemId: item.id,
42
16
  title: item.title,
43
17
  status: item.status,
44
- ...(item.currentExecutionGroupId === undefined ? {} : { currentGroupId: item.currentExecutionGroupId }),
45
18
  groupIds: groups.map(({ id }) => id),
46
- executionGroups,
47
- stages,
48
19
  cost: itemCost,
49
20
  context: itemContext,
50
- evidenceCount: evidence.length,
51
- openFindingCount
21
+ resultCount: producerObservability?.resultCount ?? null
52
22
  });
53
23
  });
54
24
  const cost = projectCost(input.executionGroups, input.turns, input.events, now);
@@ -61,6 +31,26 @@ export function buildTaskObservabilityProjection(input) {
61
31
  sessionTokens: Object.freeze([...(input.sessionTokens ?? [])])
62
32
  });
63
33
  }
34
+ function projectWorkItemProducerObservability(item, turns) {
35
+ const successfulLanes = item.executionGroups.flatMap((group) => group.lanes.flatMap((lane) => (lane.disposition === "succeeded" ? [{ group, lane }] : [])));
36
+ let resultCount = 0;
37
+ for (const { group, lane } of successfulLanes) {
38
+ const turn = lane.successfulTurnId === undefined
39
+ ? undefined
40
+ : turns.find(({ id }) => id === lane.successfulTurnId);
41
+ if (turn === undefined
42
+ || turn.result === undefined
43
+ || turn.status !== "completed"
44
+ || turn.taskId !== item.taskId
45
+ || turn.workItemId !== item.id
46
+ || turn.executionGroupId !== group.id
47
+ || turn.executionLaneId !== lane.id
48
+ || turn.roleName !== lane.roleName)
49
+ return null;
50
+ resultCount += 1;
51
+ }
52
+ return { resultCount };
53
+ }
64
54
  function projectDag(workItems) {
65
55
  const byId = new Map(workItems.map((item) => [item.id, item]));
66
56
  const dependents = new Map();
@@ -114,6 +104,10 @@ function rootCauses(id, byId) {
114
104
  result.push(currentId);
115
105
  return;
116
106
  }
107
+ if (item.status === "retired") {
108
+ result.push(item.id);
109
+ return;
110
+ }
117
111
  const unresolved = item.dependsOn.filter((dependency) => !dependencySatisfied(dependency, byId));
118
112
  if (unresolved.length === 0) {
119
113
  if (item.status === "failed" || item.status === "awaiting_acceptance")
@@ -121,7 +115,7 @@ function rootCauses(id, byId) {
121
115
  return;
122
116
  }
123
117
  for (const dependency of unresolved) {
124
- const target = resolveDependency(dependency, byId);
118
+ const target = byId.get(dependency);
125
119
  if (target?.status === "failed" || target?.status === "awaiting_acceptance")
126
120
  result.push(target.id);
127
121
  else
@@ -132,8 +126,7 @@ function rootCauses(id, byId) {
132
126
  return [...new Set(result)];
133
127
  }
134
128
  function dependencySatisfied(id, byId) {
135
- const target = resolveDependency(id, byId);
136
- return target !== undefined && (target.status === "completed" || (target.status === "retired" && target.disposition?.replacementWorkItemId === undefined));
129
+ return byId.get(id)?.status === "completed";
137
130
  }
138
131
  function dependencyEdgeStatus(id, byId) {
139
132
  const target = byId.get(id);
@@ -141,53 +134,52 @@ function dependencyEdgeStatus(id, byId) {
141
134
  return "dead";
142
135
  if (dependencySatisfied(id, byId))
143
136
  return "satisfied";
144
- const resolved = resolveDependency(id, byId);
145
- if (resolved === undefined)
137
+ if (target.status === "retired")
146
138
  return "dead";
147
- if (resolved.status === "failed" || resolved.status === "awaiting_acceptance")
139
+ if (target.status === "failed" || target.status === "awaiting_acceptance")
148
140
  return "failed-open";
149
141
  return "active";
150
142
  }
151
- function resolveDependency(id, byId) {
152
- const visited = new Set();
153
- let current = byId.get(id);
154
- while (current?.status === "retired" && current.disposition?.replacementWorkItemId !== undefined) {
155
- if (visited.has(current.id))
156
- return undefined;
157
- visited.add(current.id);
158
- current = byId.get(current.disposition.replacementWorkItemId);
159
- }
160
- return current;
143
+ function projectCost(groups, turns, _events, now) {
144
+ const uniqueGroups = [...new Map(groups.map((group) => [group.id, group])).values()];
145
+ const groupIds = new Set(uniqueGroups.map(({ id }) => id));
146
+ const attempts = turns.filter(({ executionGroupId }) => (executionGroupId !== undefined && groupIds.has(executionGroupId)));
147
+ const laneCount = uniqueGroups.reduce((total, group) => total + group.lanes.length, 0);
148
+ return Object.freeze({
149
+ tokens: 0,
150
+ toolCalls: 0,
151
+ wallClockSeconds: uniqueGroups.reduce((total, group) => total + groupDurationSeconds(group, now), 0),
152
+ tokensObservable: false,
153
+ toolCallsObservable: false,
154
+ laneCount,
155
+ groupCount: uniqueGroups.length,
156
+ retryCount: Math.max(0, attempts.length - laneCount),
157
+ marginalValuePercent: null,
158
+ marginalValueStatus: "unavailable"
159
+ });
161
160
  }
162
- function projectCost(groups, turns, events, now) {
163
- const stageGroups = latestStageGroups(groups);
164
- let tokens = 0;
165
- let toolCalls = 0;
166
- let wallClockSeconds = 0;
167
- let tokensObservable = true;
168
- let toolCallsObservable = true;
169
- let laneCount = 0;
170
- let retryCount = 0;
171
- for (const group of stageGroups) {
172
- const lineage = groups.filter((candidate) => sameStage(candidate, group));
173
- const usage = observedExecutionResourceUsage({ group, stageGroups: lineage, turns, events });
174
- tokens += usage.tokens;
175
- toolCalls += usage.toolCalls;
176
- tokensObservable = tokensObservable && (usage.tokensObservable ?? true);
177
- toolCallsObservable = toolCallsObservable && (usage.toolCallsObservable ?? true);
178
- wallClockSeconds += stageDurationSeconds(lineage, now);
179
- laneCount += group.lanes.length;
180
- retryCount += Math.max(0, lineage.length - 1);
181
- }
161
+ function projectWorkItemCost(groups, turns, now) {
162
+ const groupIds = new Set(groups.map(({ id }) => id));
163
+ const attempts = turns.filter(({ executionGroupId }) => (executionGroupId !== undefined && groupIds.has(executionGroupId)));
164
+ const wallClockSeconds = groups.reduce((total, group) => {
165
+ const started = Date.parse(group.createdAt);
166
+ const ended = group.lanes.some(({ disposition }) => disposition === "open")
167
+ ? now.getTime()
168
+ : Date.parse(group.updatedAt);
169
+ return total + (Number.isFinite(started) && Number.isFinite(ended)
170
+ ? Math.max(0, Math.floor((ended - started) / 1_000))
171
+ : 0);
172
+ }, 0);
173
+ const laneCount = groups.reduce((total, group) => total + group.lanes.length, 0);
182
174
  return Object.freeze({
183
- tokens,
184
- toolCalls,
175
+ tokens: 0,
176
+ toolCalls: 0,
185
177
  wallClockSeconds,
186
- tokensObservable,
187
- toolCallsObservable,
178
+ tokensObservable: false,
179
+ toolCallsObservable: false,
188
180
  laneCount,
189
- groupCount: stageGroups.length,
190
- retryCount,
181
+ groupCount: groups.length,
182
+ retryCount: Math.max(0, attempts.length - laneCount),
191
183
  marginalValuePercent: null,
192
184
  marginalValueStatus: "unavailable"
193
185
  });
@@ -195,7 +187,7 @@ function projectCost(groups, turns, events, now) {
195
187
  function projectContext(groups, turns, snapshots) {
196
188
  const refs = new Map();
197
189
  for (const group of groups) {
198
- const ref = group.stage?.contextSnapshotRef;
190
+ const ref = group.assignment.contextSnapshotRef;
199
191
  if (ref !== undefined)
200
192
  refs.set(ref.id, ref);
201
193
  }
@@ -231,51 +223,12 @@ function projectContext(groups, turns, snapshots) {
231
223
  compressionStatus: "unavailable"
232
224
  });
233
225
  }
234
- function latestStageGroups(groups) {
235
- const latest = new Map();
236
- for (const group of groups) {
237
- const key = group.stage === undefined ? `group:${group.id}` : stageKey(group);
238
- const existing = latest.get(key);
239
- if (existing === undefined || compareStageGroup(existing, group) < 0)
240
- latest.set(key, group);
241
- }
242
- return [...latest.values()].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
243
- }
244
- function sameStage(left, right) {
245
- if (left.stage === undefined || right.stage === undefined)
246
- return left.id === right.id;
247
- return left.taskId === right.taskId
248
- && executionTargetKey(left) === executionTargetKey(right)
249
- && left.stage.mode === right.stage.mode
250
- && left.stage.stage === right.stage.stage
251
- && left.stage.round === right.stage.round
252
- && isDeepStrictEqual(left.stage.budget, right.stage.budget)
253
- && isDeepStrictEqual(left.stage.resources, right.stage.resources);
254
- }
255
- function stageKey(group) {
256
- const stage = group.stage;
257
- return `${executionTargetKey(group)}\0${stage.mode}\0${stage.stage}\0${stage.round}`;
258
- }
259
- function executionTargetKey(group) {
260
- const target = group.target;
261
- return `${target.kind}\0${target.workItemId ?? ""}\0${target.candidateId ?? ""}`;
262
- }
263
- function compareStageGroup(left, right) {
264
- return (left.stage?.stageAttempt ?? 1) - (right.stage?.stageAttempt ?? 1)
265
- || left.updatedAt.localeCompare(right.updatedAt)
266
- || left.id.localeCompare(right.id);
267
- }
268
- function stageDurationSeconds(groups, now) {
269
- const started = groups.map(({ createdAt }) => Date.parse(createdAt)).filter(Number.isFinite);
270
- const ended = groups.map((group) => Date.parse(group.updatedAt)).filter(Number.isFinite);
271
- if (started.length === 0)
272
- return 0;
273
- const hasOpenWork = groups.some((group) => group.lanes.some(({ status }) => (status === "pending" || status === "running")));
274
- const end = hasOpenWork || ended.length === 0
226
+ function groupDurationSeconds(group, now) {
227
+ const started = Date.parse(group.createdAt);
228
+ const ended = group.lanes.some(({ disposition }) => disposition === "open")
275
229
  ? now.getTime()
276
- : Math.max(...ended);
277
- return Math.max(0, Math.floor((end - Math.min(...started)) / 1_000));
278
- }
279
- function isTerminalLane(status) {
280
- return status === "completed" || status === "failed" || status === "skipped";
230
+ : Date.parse(group.updatedAt);
231
+ return Number.isFinite(started) && Number.isFinite(ended)
232
+ ? Math.max(0, Math.floor((ended - started) / 1_000))
233
+ : 0;
281
234
  }
@@ -2,12 +2,11 @@ import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { StorageRecordError } from "./taskStore.js";
4
4
  import { readSqliteHomeIdentity, SqliteTaskStore } from "./sqliteStore.js";
5
- import { ensureStorageSchema, inspectStorageSchema } from "./storageSchema.js";
6
- export const CURRENT_DATABASE_FILENAME = "yui.db";
5
+ import { CURRENT_DATABASE_FILENAME, inspectStorageSchema } from "./storageSchema.js";
6
+ export { CURRENT_DATABASE_FILENAME } from "./storageSchema.js";
7
7
  /** Initialize a new Home, or open an existing Home at the exact current contract. */
8
8
  export function initializeCurrentTaskStore(home) {
9
9
  if (inspectStorageSchema(home).status === "uninitialized") {
10
- ensureStorageSchema(home);
11
10
  return new SqliteTaskStore(home);
12
11
  }
13
12
  return openCurrentTaskStore(home);
@@ -16,7 +15,10 @@ export function initializeCurrentTaskStore(home) {
16
15
  export function openCurrentTaskStore(home) {
17
16
  const schema = inspectStorageSchema(home);
18
17
  if (schema.status !== "current") {
19
- throw new StorageRecordError("This Home does not use the current storage contract. Preserve it for read-only history and initialize a new Home.");
18
+ throw new StorageRecordError(schema.status === "upgradeable"
19
+ ? `This Home uses storage version ${schema.currentVersion}; run \`yui upgrade\` `
20
+ + `to reach ${schema.latestVersion}.`
21
+ : "This Home does not use a supported storage contract.");
20
22
  }
21
23
  if (!existsSync(join(home, CURRENT_DATABASE_FILENAME))) {
22
24
  throw new StorageRecordError("The current Home is incomplete: yui.db is missing. Preserve it for diagnosis and initialize a new Home.");