@zq-silk/yui 0.14.0 → 0.14.2

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 (179) hide show
  1. package/ARCHITECTURE.md +64 -35
  2. package/README.md +258 -125
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +5 -8
  5. package/dist/cli/commandCatalog.js +74 -109
  6. package/dist/cli/helpRenderer.js +1 -2
  7. package/dist/cli/interactionCandidates.js +10 -0
  8. package/dist/cli/interactionPolicy.js +76 -37
  9. package/dist/cli/roleWizard.js +5 -6
  10. package/dist/cli/updateOrchestrator.js +10 -8
  11. package/dist/cli/updatePorts.js +17 -13
  12. package/dist/cli/upgradeCommand.js +2 -5
  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/durableJobCommands.js +1 -1
  18. package/dist/commands/executionAuditCommands.js +4 -9
  19. package/dist/commands/globalRoleCommands.js +17 -0
  20. package/dist/commands/profileCommands.js +200 -30
  21. package/dist/commands/sessionCommands.js +1 -1
  22. package/dist/commands/taskActor.js +29 -127
  23. package/dist/commands/taskChangeSetCommands.js +1 -3
  24. package/dist/commands/taskCommands.js +1771 -2107
  25. package/dist/commands/taskCompletionGate.js +27 -103
  26. package/dist/commands/taskContextCommand.js +47 -80
  27. package/dist/commands/taskExecutionCommands.js +9 -27
  28. package/dist/commands/taskInputCommands.js +10 -10
  29. package/dist/commands/taskIntegrationCommands.js +69 -61
  30. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  31. package/dist/commands/taskNextActionCommand.js +8 -29
  32. package/dist/commands/taskOverviewCommand.js +25 -3
  33. package/dist/commands/taskPublicationCommands.js +151 -85
  34. package/dist/commands/taskPublicationVerifyCommand.js +181 -0
  35. package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
  36. package/dist/commands/taskRoleRuntimeStatus.js +5 -6
  37. package/dist/commands/taskUpstreamCommands.js +79 -95
  38. package/dist/config/configCatalog.js +1 -1
  39. package/dist/context/contextSnapshot.js +17 -2
  40. package/dist/context/sessionBootstrapManifest.js +18 -1
  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 +1 -0
  63. package/dist/domain/agentResultTransport.js +60 -0
  64. package/dist/execution/executionHealth.js +111 -118
  65. package/dist/execution/reviewMainTurn.js +161 -0
  66. package/dist/execution/workItemExecution.js +454 -0
  67. package/dist/execution/workItemExecutionProjection.js +309 -0
  68. package/dist/execution/workItemMainTurn.js +164 -0
  69. package/dist/executor/agentExecutor.js +19 -19
  70. package/dist/executor/executorRegistry.js +7 -28
  71. package/dist/executor/fileRoleLaunchPlanner.js +51 -83
  72. package/dist/external/githubPublicationVerifier.js +114 -0
  73. package/dist/external/gitlabPublicationVerifier.js +158 -0
  74. package/dist/external/pinnedCommandRunner.js +73 -0
  75. package/dist/integration/changeSetManifest.js +0 -3
  76. package/dist/integration/deliveryObligation.js +43 -6
  77. package/dist/integration/gitIntegrationService.js +284 -101
  78. package/dist/integration/integrationAttempt.js +64 -14
  79. package/dist/integration/integrationQueueEntry.js +5 -35
  80. package/dist/integration/integrationQueueService.js +65 -194
  81. package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
  82. package/dist/lifecycle/exactTurnTerminalization.js +100 -97
  83. package/dist/message/message.js +16 -0
  84. package/dist/observability/executionAudit.js +4 -25
  85. package/dist/observability/faultClassification.js +58 -70
  86. package/dist/observability/orchestrationMetrics.js +8 -50
  87. package/dist/profile/agentProfile.js +60 -15
  88. package/dist/profile/agentProfileRuntime.js +100 -0
  89. package/dist/release/releaseWorkflowPorts.js +5 -68
  90. package/dist/repository/executionLaneGitSnapshot.js +80 -0
  91. package/dist/repository/gitWorkspace.js +9 -67
  92. package/dist/repository/taskBaseFreshness.js +3 -1
  93. package/dist/repository/taskWorkspaceCoordinator.js +32 -4
  94. package/dist/repository/taskWorkspacePreparer.js +252 -411
  95. package/dist/resources/liveReferences.js +6 -55
  96. package/dist/resources/resourceDiscovery.js +1 -30
  97. package/dist/resources/resourceRegistrar.js +0 -23
  98. package/dist/review/deltaRecheck.js +8 -65
  99. package/dist/review/reviewAcceptance.js +22 -19
  100. package/dist/review/reviewConfig.js +0 -10
  101. package/dist/review/reviewDecision.js +2 -2
  102. package/dist/review/reviewRound.js +147 -273
  103. package/dist/review/reviewerAvailability.js +13 -23
  104. package/dist/role/role.js +3 -0
  105. package/dist/runtime/agentDriverObservation.js +1 -1
  106. package/dist/runtime/agentHost.js +51 -51
  107. package/dist/runtime/builtinAgentDrivers.js +9 -2
  108. package/dist/runtime/codexAppServerRuntime.js +23 -5
  109. package/dist/runtime/exactControlPlane.js +2 -267
  110. package/dist/runtime/firstProgressAdvisory.js +1 -1
  111. package/dist/runtime/index.js +1 -1
  112. package/dist/runtime/launchBroker.js +11 -11
  113. package/dist/runtime/lifecycleReservation.js +2 -2
  114. package/dist/runtime/managedCaller.js +111 -0
  115. package/dist/runtime/ports.js +16 -3
  116. package/dist/runtime/preallocatedNativeSession.js +2 -2
  117. package/dist/runtime/processExitObservation.js +3 -2
  118. package/dist/runtime/providerContinuationReconciliationService.js +1 -1
  119. package/dist/runtime/runtimeBinding.js +1 -1
  120. package/dist/runtime/runtimeObservation.js +33 -16
  121. package/dist/runtime/runtimeSessionCandidate.js +2 -2
  122. package/dist/runtime/runtimeStopReceipt.js +11 -11
  123. package/dist/runtime/sessionLaunchRequest.js +2 -2
  124. package/dist/runtime/sessionOwnerIdentity.js +9 -9
  125. package/dist/runtime/sessionOwnerRegistry.js +13 -13
  126. package/dist/runtime/sessionReconciliation.js +4 -4
  127. package/dist/runtime/sessionTerminationGuard.js +9 -9
  128. package/dist/runtime/sessionTokenMetrics.js +5 -7
  129. package/dist/runtime/structuredProviderHost.js +5 -5
  130. package/dist/runtime/taskRuntimeIsolation.js +24 -24
  131. package/dist/runtime/tmuxAdapters.js +32 -30
  132. package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
  133. package/dist/scheduler/leaderWakeupProcessor.js +78 -55
  134. package/dist/scheduler/roleTurnLiveness.js +6 -6
  135. package/dist/scheduler/roleTurnStall.js +8 -9
  136. package/dist/scheduler/taskExecutionProjection.js +19 -41
  137. package/dist/scheduler/taskObservabilityProjection.js +75 -122
  138. package/dist/storage/sqliteSchema.js +1 -21
  139. package/dist/storage/sqliteStore.js +62 -47
  140. package/dist/storage/storageSchema.js +1 -8
  141. package/dist/storage/storageVersions.js +7 -4
  142. package/dist/storage/taskStore.js +8 -8
  143. package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
  144. package/dist/task/completionReadiness.js +17 -98
  145. package/dist/task/deliveryGuard.js +9 -11
  146. package/dist/task/draftPlan.js +142 -0
  147. package/dist/task/nextAction.js +179 -477
  148. package/dist/task/publicationVerification.js +1 -0
  149. package/dist/task/remoteDelivery.js +233 -0
  150. package/dist/task/task.js +108 -4
  151. package/dist/task/taskRecordReference.js +0 -1
  152. package/dist/turn/turn.js +109 -16
  153. package/dist/verification/verificationGateService.js +0 -26
  154. package/dist/web/assets/client/components.js +99 -121
  155. package/dist/web/assets/client/i18n.js +136 -58
  156. package/dist/web/assets/client/view.js +44 -0
  157. package/dist/web/assets/styles/cards.js +3 -15
  158. package/dist/web/assets/styles/widgets.js +4 -0
  159. package/dist/web/webSnapshot.js +29 -14
  160. package/dist/workItem/dependencyGate.js +34 -0
  161. package/dist/workItem/workItem.js +99 -266
  162. package/dist/workspace/workItemChangeSetManager.js +15 -59
  163. package/docs/task-local-identity.md +1 -1
  164. package/i18n/README.zh-CN.md +165 -59
  165. package/package.json +1 -1
  166. package/skills/yui-leader/SKILL.md +272 -782
  167. package/skills/yui-operator/SKILL.md +190 -365
  168. package/skills/yui-reviewer/SKILL.md +40 -33
  169. package/skills/yui-runtime/SKILL.md +27 -6
  170. package/skills/yui-worker/SKILL.md +19 -6
  171. package/dist/execution/candidateConvergence.js +0 -623
  172. package/dist/execution/executionGroup.js +0 -854
  173. package/dist/execution/resourceBroker.js +0 -426
  174. package/dist/executor/launchPlan.js +0 -16
  175. package/dist/review/reviewFinding.js +0 -240
  176. package/dist/review/reviewFindingLedger.js +0 -550
  177. package/dist/review/reviewOutcomeClassifier.js +0 -255
  178. package/dist/scheduler/resourceQueueProjection.js +0 -71
  179. 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
  }
@@ -460,28 +460,10 @@ BEGIN
460
460
  AND turn_id = NEW.turn_id AND generation = NEW.generation;
461
461
  END;
462
462
  `;
463
- /**
464
- * Cross-Round semantic review
465
- * findings with stable keys and Leader dispositions. Failed execution attempts
466
- * never create rows; only completed Rounds feed the ledger.
467
- */
468
- const BASELINE_REVIEW_FINDING_SQL = `
469
- CREATE TABLE IF NOT EXISTS review_findings (
470
- task_id TEXT NOT NULL,
471
- finding_id TEXT NOT NULL,
472
- stable_key TEXT NOT NULL,
473
- severity TEXT NOT NULL,
474
- payload TEXT NOT NULL,
475
- updated_at TEXT NOT NULL,
476
- PRIMARY KEY (task_id, finding_id)
477
- );
478
- CREATE INDEX IF NOT EXISTS idx_review_findings_task ON review_findings(task_id);
479
- CREATE INDEX IF NOT EXISTS idx_review_findings_stable_key ON review_findings(task_id, stable_key);
480
- `;
481
463
  /**
482
464
  * Session owner physical identity records.
483
465
  *
484
- * One row per runtime generation, keyed by launch id. The payload column
466
+ * One row per runtime generation, keyed by runtime generation id. The payload column
485
467
  * stores the full versioned JSON record; typed columns support the
486
468
  * reconciliation queries (task/role lookup, PID liveness).
487
469
  */
@@ -671,7 +653,6 @@ const MIGRATION_1_SQL = [
671
653
  BASELINE_JOB_AND_RELEASE_SQL,
672
654
  BASELINE_JOB_CALLER_SQL,
673
655
  BASELINE_TELEMETRY_AGGREGATE_SQL,
674
- BASELINE_REVIEW_FINDING_SQL,
675
656
  BASELINE_SESSION_OWNER_SQL,
676
657
  BASELINE_RESOURCE_REGISTRY_SQL,
677
658
  BASELINE_GATE_ARTIFACT_SQL,
@@ -886,7 +867,6 @@ export const SQLITE_SCHEMA_TABLES = [
886
867
  "turns",
887
868
  "active_turns",
888
869
  "review_rounds",
889
- "review_findings",
890
870
  "change_sets",
891
871
  "integration_attempts",
892
872
  "messages",