@zq-silk/yui 0.15.11 → 0.16.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 (280) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/ARCHITECTURE.zh-CN.md +8 -4
  3. package/README.md +13 -5
  4. package/dist/agentRun/agentRun.js +3 -0
  5. package/dist/artifacts/artifactCommitLock.js +16 -55
  6. package/dist/artifacts/taskArtifactRepository.js +3 -3
  7. package/dist/cli/agentConfigurationPicker.js +1 -1
  8. package/dist/cli/commandCatalog.js +92 -48
  9. package/dist/cli/completion.js +18 -18
  10. package/dist/cli/completionWizard.js +1 -1
  11. package/dist/cli/dynamicCompletion.js +1 -1
  12. package/dist/cli/interactionPolicy.js +11 -3
  13. package/dist/cli/invocationAuthority.js +64 -0
  14. package/dist/cli/managedDiagnostics.js +2 -2
  15. package/dist/cli/parseRepeatable.js +35 -0
  16. package/dist/cli/updatePorts.js +26 -5
  17. package/dist/cli.js +1303 -1235
  18. package/dist/commands/agentCommands.js +4 -4
  19. package/dist/commands/capabilityCommands.js +1 -1
  20. package/dist/commands/configCommands.js +6 -57
  21. package/dist/commands/configOverview.js +2 -2
  22. package/dist/commands/durableJobCommands.js +12 -6
  23. package/dist/commands/executionAuditCommands.js +10 -0
  24. package/dist/commands/globalRoleCommands.js +78 -29
  25. package/dist/commands/grantCommands.js +0 -3
  26. package/dist/commands/jobCommands.js +1 -4
  27. package/dist/commands/operatorCommands.js +3 -3
  28. package/dist/commands/projectCommands.js +45 -25
  29. package/dist/commands/resourcesCommands.js +10 -40
  30. package/dist/commands/roleConfiguration.js +2 -6
  31. package/dist/commands/taskActivationCommands.js +4 -8
  32. package/dist/commands/taskAgentCapabilities.js +9 -0
  33. package/dist/commands/taskCommandSupport.js +155 -0
  34. package/dist/commands/taskCommandTypes.js +1 -0
  35. package/dist/commands/taskCommands.js +355 -732
  36. package/dist/commands/taskCompletionGate.js +1 -1
  37. package/dist/commands/taskExecutionCommands.js +1 -1
  38. package/dist/commands/taskFactCommands.js +325 -0
  39. package/dist/commands/taskInputCommands.js +12 -42
  40. package/dist/commands/taskIntegrationCommands.js +20 -58
  41. package/dist/commands/taskNextActionCommand.js +7 -6
  42. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  43. package/dist/commands/taskPublicationCommands.js +12 -3
  44. package/dist/commands/taskPublicationVerifyCommand.js +24 -40
  45. package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
  46. package/dist/commands/taskUpstreamCommands.js +19 -8
  47. package/dist/commands/workflowCommands.js +6 -1
  48. package/dist/completion/completionInstaller.js +26 -26
  49. package/dist/completion/completionState.js +26 -26
  50. package/dist/completion/fileCompletionManager.js +6 -11
  51. package/dist/config/configCatalog.js +0 -2
  52. package/dist/config/timeZone.js +14 -0
  53. package/dist/config/yuiConfig.js +0 -29
  54. package/dist/context/runContextPack.js +5 -3
  55. package/dist/context/taskCatalog.js +185 -0
  56. package/dist/context/taskContext.js +152 -151
  57. package/dist/context/wakeRunReferences.js +11 -0
  58. package/dist/controller/agentCapabilities.js +58 -0
  59. package/dist/controller/agentRuntimeObserver.js +6 -11
  60. package/dist/controller/clientRuntime.js +22 -14
  61. package/dist/controller/controller.js +20 -24
  62. package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
  63. package/dist/controller/globalInputDelivery.js +13 -0
  64. package/dist/controller/jobClient.js +4 -4
  65. package/dist/controller/jobControl.js +60 -25
  66. package/dist/controller/jobSupervisor.js +4 -4
  67. package/dist/controller/providerRetryAdmission.js +100 -0
  68. package/dist/controller/providerRetryDelivery.js +218 -0
  69. package/dist/controller/runtime.js +85 -43
  70. package/dist/controller/runtimeEventProcessor.js +0 -5
  71. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  72. package/dist/controller/taskAgentError.js +35 -0
  73. package/dist/coordination/workMailboxQueue.js +2 -24
  74. package/dist/core/controllerClient.js +2 -2
  75. package/dist/core/fileLockOwner.js +74 -0
  76. package/dist/decision/decision.js +17 -0
  77. package/dist/doctor/doctor.js +18 -9
  78. package/dist/event/taskEvent.js +12 -0
  79. package/dist/execution/workItemExecutionProjection.js +2 -2
  80. package/dist/executor/agentCapabilityConfig.js +43 -0
  81. package/dist/executor/agentConfigurationCatalog.js +49 -11
  82. package/dist/executor/agentExecutor.js +5 -8
  83. package/dist/executor/fileRoleLaunchPlanner.js +11 -53
  84. package/dist/executor/taskAgentCapabilities.js +64 -0
  85. package/dist/integration/deliveryObligation.js +1 -69
  86. package/dist/integration/gitIntegrationService.js +79 -90
  87. package/dist/integration/integrationAttempt.js +3 -14
  88. package/dist/integration/integrationSourceApplication.js +4 -12
  89. package/dist/integration/manifestTags.js +2 -2
  90. package/dist/job/durableJob.js +4 -25
  91. package/dist/job/jobAssignmentScope.js +22 -0
  92. package/dist/job/jobOperation.js +16 -0
  93. package/dist/job/jobRunner.js +2 -1
  94. package/dist/job/stepDirectory.js +15 -0
  95. package/dist/kernel/builtinCapabilities.js +15 -11
  96. package/dist/kernel/kernelPorts.js +1 -17
  97. package/dist/lifecycle/exactRunTerminalization.js +5 -10
  98. package/dist/message/globalProviderRetry.js +15 -0
  99. package/dist/message/inputControlResolution.js +3 -4
  100. package/dist/message/message.js +17 -20
  101. package/dist/message/messageContinuation.js +1 -1
  102. package/dist/milestone/milestone.js +11 -0
  103. package/dist/observability/executionAudit.js +19 -0
  104. package/dist/observability/orchestrationMetrics.js +24 -1
  105. package/dist/observability/runtimeIdentity.js +1 -17
  106. package/dist/output/agentConfigurationPresentation.js +2 -0
  107. package/dist/output/timePresentation.js +1 -14
  108. package/dist/plugins/pluginService.js +12 -4
  109. package/dist/release/releaseWorkflowPorts.js +45 -96
  110. package/dist/release/runtimeRelease.js +9 -15
  111. package/dist/repository/gitWorkspace.js +371 -105
  112. package/dist/repository/projectMaintenanceLock.js +98 -82
  113. package/dist/repository/taskBaseFreshness.js +1 -1
  114. package/dist/repository/taskWorkspaceCoordinator.js +82 -144
  115. package/dist/repository/taskWorkspacePreparer.js +113 -47
  116. package/dist/repository/workspaceCleanupInspection.js +187 -0
  117. package/dist/resources/autoResourceGc.js +4 -77
  118. package/dist/resources/liveReferences.js +7 -54
  119. package/dist/resources/resourceDiscovery.js +5 -118
  120. package/dist/resources/resourceGc.js +233 -181
  121. package/dist/resources/resourceRegistrar.js +5 -4
  122. package/dist/resources/resourceRegistry.js +3 -37
  123. package/dist/resources/resourceRegistryStore.js +13 -36
  124. package/dist/resources/sqliteResourceRegistry.js +43 -26
  125. package/dist/review/deltaRecheck.js +1 -1
  126. package/dist/review/reviewAcceptance.js +1 -1
  127. package/dist/review/reviewDecision.js +1 -1
  128. package/dist/review/reviewRound.js +8 -7
  129. package/dist/review/taskFinalReviewContractResolution.js +1 -1
  130. package/dist/runtime/acpProtocol.js +4 -51
  131. package/dist/runtime/acpSession.js +10 -75
  132. package/dist/runtime/acpSessionConfiguration.js +1 -8
  133. package/dist/runtime/agentEndpoint.js +3 -2
  134. package/dist/runtime/agentError.js +5 -3
  135. package/dist/runtime/agentFailureContext.js +43 -0
  136. package/dist/runtime/agentHost.js +42 -25
  137. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  138. package/dist/runtime/codexAppServerRuntime.js +34 -3
  139. package/dist/runtime/codexInteractiveHost.js +2 -2
  140. package/dist/runtime/index.js +0 -1
  141. package/dist/runtime/managedCaller.js +21 -1
  142. package/dist/runtime/providerControl.js +5 -1
  143. package/dist/runtime/providerErrors.js +38 -0
  144. package/dist/runtime/providerRetry.js +198 -0
  145. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  146. package/dist/runtime/runtimeObservation.js +0 -5
  147. package/dist/runtime/runtimeSessionCandidate.js +2 -3
  148. package/dist/runtime/sessionOwnerIdentity.js +1 -1
  149. package/dist/runtime/sessionTokenMetrics.js +15 -5
  150. package/dist/runtime/structuredProviderHost.js +10 -43
  151. package/dist/runtime/taskUsageMetrics.js +275 -0
  152. package/dist/runtime/tmuxAdapters.js +6 -8
  153. package/dist/scheduler/activeRoleRunDelivery.js +19 -7
  154. package/dist/scheduler/leaderWakeupProcessor.js +24 -6
  155. package/dist/scheduler/operatorEvent.js +12 -20
  156. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  157. package/dist/scheduler/ports.js +5 -8
  158. package/dist/scheduler/roleRunLiveness.js +4 -4
  159. package/dist/scheduler/roleRunStall.js +16 -23
  160. package/dist/scheduler/taskExecutionProjection.js +49 -36
  161. package/dist/scheduler/taskObservabilityProjection.js +6 -45
  162. package/dist/scheduler/taskWake.js +5 -10
  163. package/dist/scheduler/wakeReason.js +2 -0
  164. package/dist/scheduler/wakeupQueue.js +2 -4
  165. package/dist/setup/setupCommand.js +1 -1
  166. package/dist/storage/contextRecords.js +106 -0
  167. package/dist/storage/currentTaskStore.js +1 -1
  168. package/dist/storage/homeLayout.js +13 -17
  169. package/dist/storage/migrations/agentFailureContext.js +22 -0
  170. package/dist/storage/migrations/currentInputContract.js +86 -0
  171. package/dist/storage/migrations/currentRuntimeContract.js +228 -0
  172. package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
  173. package/dist/storage/migrations/integrationContinuation.js +5 -4
  174. package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
  175. package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
  176. package/dist/storage/migrations/verificationPlanV1.js +162 -0
  177. package/dist/storage/migrations/verificationPolicy.js +74 -0
  178. package/dist/storage/migrations/workItemHistory.js +46 -0
  179. package/dist/storage/persistenceWorker.js +12 -4
  180. package/dist/storage/recordValidation.js +87 -0
  181. package/dist/storage/sqliteSchema.js +173 -1
  182. package/dist/storage/sqliteStore.js +172 -161
  183. package/dist/storage/storageSchema.js +2 -14
  184. package/dist/storage/storageVersions.js +1 -1
  185. package/dist/storage/storeRpc.js +16 -7
  186. package/dist/storage/taskCatalog.js +123 -0
  187. package/dist/storage/taskStore.js +5 -7
  188. package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
  189. package/dist/task/archiveDiagnostics.js +1 -0
  190. package/dist/task/archivePreflight.js +124 -0
  191. package/dist/task/completionReadiness.js +5 -24
  192. package/dist/task/draftPlan.js +0 -53
  193. package/dist/task/nextAction.js +7 -13
  194. package/dist/task/publicationAdoption.js +56 -0
  195. package/dist/task/publicationReference.js +10 -0
  196. package/dist/task/remoteDelivery.js +31 -16
  197. package/dist/task/remoteDeliveryService.js +89 -0
  198. package/dist/task/taskActivation.js +2 -25
  199. package/dist/task/taskActivationService.js +0 -2
  200. package/dist/task/taskRecordReference.js +0 -1
  201. package/dist/task/taskSubmission.js +3 -14
  202. package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
  203. package/dist/telemetry/sqliteTelemetryStore.js +49 -49
  204. package/dist/telemetry/telemetryWiring.js +4 -3
  205. package/dist/tmux/tmuxManager.js +29 -20
  206. package/dist/verification/gateArtifact.js +15 -24
  207. package/dist/verification/gateArtifactStore.js +14 -7
  208. package/dist/verification/verificationGateService.js +29 -88
  209. package/dist/verification/verificationPlan.js +27 -72
  210. package/dist/web/assets/client/app.js +92 -17
  211. package/dist/web/assets/client/components.js +55 -13
  212. package/dist/web/assets/client/i18n.js +72 -4
  213. package/dist/web/assets/client/taskSurface.js +6 -5
  214. package/dist/web/assets/client/view.js +35 -7
  215. package/dist/web/assets/shell.js +6 -0
  216. package/dist/web/assets/styles/layout.js +7 -0
  217. package/dist/web/webServer.js +12 -3
  218. package/dist/web/webSnapshot.js +14 -85
  219. package/dist/web/webTaskSurface.js +17 -46
  220. package/dist/workItem/workItem.js +1 -12
  221. package/dist/workspace/cleanupInspection.js +63 -0
  222. package/dist/workspace/workItemChangeSetManager.js +110 -50
  223. package/docs/agent-result-consumption.md +4 -0
  224. package/docs/agent-result-consumption.zh-CN.md +3 -0
  225. package/docs/agent-runtime-drivers.md +7 -0
  226. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  227. package/docs/architecture/README.md +2 -0
  228. package/docs/architecture/README.zh-CN.md +3 -1
  229. package/docs/architecture/capabilities-and-resources.md +42 -5
  230. package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
  231. package/docs/managed-turn-and-session-runtime.md +121 -1
  232. package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
  233. package/docs/observability/README.md +62 -0
  234. package/docs/observability/README.zh-CN.md +47 -0
  235. package/docs/plugin-sdk.md +4 -2
  236. package/docs/project-refresh.md +77 -0
  237. package/docs/project-refresh.zh-CN.md +59 -0
  238. package/docs/provider-retry.md +70 -0
  239. package/docs/provider-runtime.md +3 -1
  240. package/docs/provider-runtime.zh-CN.md +4 -2
  241. package/docs/release-workflow.md +190 -7
  242. package/docs/release-workflow.zh-CN.md +139 -5
  243. package/docs/roles-and-configuration.md +29 -0
  244. package/docs/roles-and-configuration.zh-CN.md +21 -0
  245. package/docs/sqlite-control-plane-design.md +16 -3
  246. package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
  247. package/docs/task-dag-semantics.md +3 -3
  248. package/docs/task-dag-semantics.zh-CN.md +2 -2
  249. package/docs/task-delivery.md +223 -17
  250. package/docs/task-delivery.zh-CN.md +171 -14
  251. package/docs/task-discovery.md +101 -0
  252. package/docs/task-discovery.zh-CN.md +85 -0
  253. package/docs/testing/verification-levels.md +161 -9
  254. package/docs/testing/verification-levels.zh-CN.md +116 -9
  255. package/i18n/README.zh-CN.md +13 -7
  256. package/package.json +1 -1
  257. package/skills/yui-leader/SKILL.md +5 -0
  258. package/skills/yui-leader/references/execution.md +3 -2
  259. package/skills/yui-leader/references/integration.md +9 -5
  260. package/skills/yui-leader/references/planning.md +9 -3
  261. package/skills/yui-operator/SKILL.md +24 -8
  262. package/skills/yui-reviewer/SKILL.md +4 -0
  263. package/skills/yui-runtime/SKILL.md +17 -2
  264. package/skills/yui-runtime/references/publication.md +26 -4
  265. package/skills/yui-runtime/references/recovery.md +71 -0
  266. package/dist/agent/agentRegistry.js +0 -10
  267. package/dist/agentRun/runIdentity.js +0 -22
  268. package/dist/commands/deliveryGuardPreflight.js +0 -30
  269. package/dist/commands/taskIntegrationQueueCommands.js +0 -226
  270. package/dist/commands/taskOverviewCommand.js +0 -376
  271. package/dist/completion/completionWizard.js +0 -125
  272. package/dist/context/dispatchContext.js +0 -110
  273. package/dist/context/wakeNotification.js +0 -144
  274. package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
  275. package/dist/integration/integrationQueueEntry.js +0 -191
  276. package/dist/integration/integrationQueueService.js +0 -810
  277. package/dist/release/fakeReleasePorts.js +0 -55
  278. package/dist/runtime/sessionOwnerRegistry.js +0 -137
  279. package/dist/task/deliveryGuard.js +0 -226
  280. /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
@@ -0,0 +1,275 @@
1
+ import { builtinDriverIdForAdapter } from "./builtinAgentDrivers.js";
2
+ import { isRuntimeTokenEvidence, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
3
+ import { projectSessionTokenObservations, unobservedSessionTokenMetrics } from "./sessionTokenMetrics.js";
4
+ /**
5
+ * Pure lifetime projection. No transcript reads, collection, persistence,
6
+ * lifecycle decisions, guessed Run identities, or cumulative counter allocation.
7
+ */
8
+ export function projectTaskUsageMetrics(input) {
9
+ const now = input.now ?? new Date();
10
+ const events = input.events.filter((event) => event.taskId === input.task.id);
11
+ const observations = events.flatMap((event) => {
12
+ const observation = runtimeObservationFromTaskEvent(event);
13
+ return observation !== null
14
+ && observation.fence.taskId === input.task.id
15
+ && Date.parse(observation.receivedAt) <= now.getTime()
16
+ ? [observation] : [];
17
+ }).sort(compare);
18
+ const runs = new Map(input.runs.filter((run) => run.taskId === input.task.id).map((run) => [run.id, run]));
19
+ const inScope = (observation) => {
20
+ if (input.workItemId === undefined)
21
+ return true;
22
+ const run = exactRun(observation, runs);
23
+ return run?.workItemId === input.workItemId;
24
+ };
25
+ const scoped = observations.filter(inScope);
26
+ const buckets = new Map();
27
+ for (const observation of observations) {
28
+ const identity = sessionIdentity(observation);
29
+ if (identity === null)
30
+ continue;
31
+ const key = identityKey(identity);
32
+ const bucket = buckets.get(key) ?? { identity, observations: [] };
33
+ bucket.observations.push(observation);
34
+ buckets.set(key, bucket);
35
+ }
36
+ const resourceOwners = new Map();
37
+ for (const { identity, observations: all } of buckets.values()) {
38
+ if (!all.some((observation) => !child(observation) && usageAuthority(observation)))
39
+ continue;
40
+ const resource = resourceKey(identity);
41
+ const owners = resourceOwners.get(resource) ?? new Set();
42
+ owners.add(identity.roleName);
43
+ resourceOwners.set(resource, owners);
44
+ }
45
+ const sessions = [...buckets.values()]
46
+ .sort((a, b) => identityKey(a.identity).localeCompare(identityKey(b.identity)))
47
+ .flatMap(({ identity, observations: all }) => {
48
+ if (!all.some(inScope))
49
+ return [];
50
+ const main = all.filter((observation) => !child(observation) && usageAuthority(observation));
51
+ const evidence = main.filter(isRuntimeTokenEvidence);
52
+ const metrics = projectSessionTokenObservations(main, identity);
53
+ let displayedMetrics = input.workItemId === undefined ? metrics : unobservedSessionTokenMetrics(identity);
54
+ const reasons = [];
55
+ if (all.some(child))
56
+ reasons.push("child-usage-overlap-unproven");
57
+ if (evidence.some(({ payload }) => payload.observationQuality === "partial"
58
+ || payload.observationQuality === "unavailable"))
59
+ reasons.push("source-coverage-partial");
60
+ let tokens = attributableTokens(evidence, metrics);
61
+ if (input.workItemId !== undefined) {
62
+ // Establish semantics and request ownership before filtering. Filtering
63
+ // first could hide a mixed stream or allocate a revised request twice.
64
+ if (metrics.cumulativeTotal.status !== "observed") {
65
+ tokens = metric(null, ["session-usage-unavailable"]);
66
+ }
67
+ else if (evidence.some(({ payload }) => payload.usage?.semantics === "cumulative-session")) {
68
+ tokens = metric(null, ["cumulative-not-allocatable-to-work-item"]);
69
+ }
70
+ else {
71
+ const requests = new Map();
72
+ for (const observation of evidence) {
73
+ if (observation.payload.usage?.semantics !== "request-context")
74
+ continue;
75
+ const key = observation.payload.activityId ?? observation.semanticKey;
76
+ requests.set(key, [...(requests.get(key) ?? []), observation]);
77
+ }
78
+ const selected = [];
79
+ for (const versions of requests.values()) {
80
+ if (!versions.some(inScope))
81
+ continue;
82
+ const owners = new Set(versions.map((observation) => exactRun(observation, runs)?.id));
83
+ if (owners.size !== 1 || owners.has(undefined) || !versions.every(inScope)) {
84
+ reasons.push("request-run-attribution-conflict");
85
+ }
86
+ else {
87
+ selected.push(...versions);
88
+ }
89
+ }
90
+ displayedMetrics = projectSessionTokenObservations(selected, identity);
91
+ const subtotal = displayedMetrics.cumulativeTotal;
92
+ tokens = metric(subtotal.status === "observed" ? subtotal.totalTokens : null, subtotal.status === "observed" ? [] : ["no-attributable-request-usage"]);
93
+ }
94
+ }
95
+ if ((resourceOwners.get(resourceKey(identity))?.size ?? 0) > 1) {
96
+ tokens = metric(null, ["native-session-role-overlap"]);
97
+ displayedMetrics = unobservedSessionTokenMetrics(identity);
98
+ }
99
+ tokens = metric(tokens.value, [...tokens.reasons, ...reasons]);
100
+ return [{
101
+ identity,
102
+ semantics: [...new Set(evidence.flatMap(({ payload }) => payload.usage === undefined ? [] : [payload.usage.semantics]))].sort(),
103
+ sources: [...new Set(evidence.map(({ authority, payload }) => payload.observerSource?.sourceId ?? authority))].sort(),
104
+ observedThrough: all.filter(inScope).at(-1).receivedAt,
105
+ tokens,
106
+ metrics: displayedMetrics
107
+ }];
108
+ });
109
+ const reasons = [];
110
+ if (scoped.some((observation) => sessionIdentity(observation) === null))
111
+ reasons.push("session-identity-missing");
112
+ for (const run of runs.values()) {
113
+ if (input.workItemId !== undefined && run.workItemId !== input.workItemId)
114
+ continue;
115
+ if (!observations.some((observation) => exactRun(observation, runs)?.id === run.id
116
+ && usageAuthority(observation) && isRuntimeTokenEvidence(observation)))
117
+ reasons.push("run-usage-unobserved");
118
+ }
119
+ const tokenValues = sessions.flatMap(({ tokens }) => tokens.value === null ? [] : [tokens.value]);
120
+ const sum = safeSum(tokenValues);
121
+ const tokens = metric(tokenValues.length === 0 ? null : sum, [...reasons, ...sessions.flatMap(({ tokens }) => tokens.reasons),
122
+ ...(sum === null ? ["counter-overflow"] : []),
123
+ ...(sessions.length === 0 ? ["no-token-evidence"] : [])]);
124
+ const operations = new Map();
125
+ for (const observation of observations) {
126
+ const identity = sessionIdentity(observation);
127
+ const { nativeTurnId } = observation.fence;
128
+ if (!usageAuthority(observation) || child(observation) || identity === null || nativeTurnId === undefined
129
+ || !observation.kind.startsWith("operation.")
130
+ || observation.payload.operation !== "tool" || observation.payload.operationId === undefined)
131
+ continue;
132
+ const key = JSON.stringify([resourceKey(identity), nativeTurnId, observation.payload.operationId]);
133
+ const versions = operations.get(key) ?? [];
134
+ versions.push(observation);
135
+ operations.set(key, versions);
136
+ }
137
+ const toolCount = [...operations.values()].filter((versions) => {
138
+ if (input.workItemId === undefined)
139
+ return true;
140
+ const owners = new Set(versions.map((observation) => exactRun(observation, runs)?.id));
141
+ return owners.size === 1 && !owners.has(undefined) && versions.every(inScope);
142
+ }).length;
143
+ return {
144
+ scope: "task-lifetime", taskId: input.task.id,
145
+ ...(input.workItemId === undefined ? {} : { workItemId: input.workItemId }),
146
+ observedThrough: scoped.at(-1)?.receivedAt ?? null,
147
+ tokens,
148
+ toolCalls: metric(toolCount === 0 ? null : toolCount, ["tool-history-compacted"]),
149
+ elapsedSeconds: input.workItemId === undefined
150
+ ? taskElapsed(input.task, events, now) : metric(null, ["work-item-lifecycle-not-task-elapsed"]),
151
+ executionSeconds: executionDuration(scoped, now),
152
+ coverage: {
153
+ basis: "task-fenced-observations",
154
+ observedSessions: sessions.length,
155
+ tokenSessions: tokenValues.length,
156
+ completeness: "observed-sources-only"
157
+ },
158
+ sessions
159
+ };
160
+ }
161
+ function attributableTokens(evidence, metrics) {
162
+ const total = metrics.cumulativeTotal;
163
+ if (total.status !== "observed")
164
+ return metric(null, ["session-usage-unavailable"]);
165
+ const cumulative = evidence.filter(({ payload }) => payload.usage?.semantics === "cumulative-session");
166
+ if (cumulative.length === 0)
167
+ return metric(total.totalTokens);
168
+ const first = cumulative[0].payload.usage;
169
+ const baseline = first.inputTokens + first.outputTokens;
170
+ if (baseline === 0)
171
+ return metric(total.totalTokens);
172
+ // session.started also occurs on resume: it cannot prove a fresh counter.
173
+ // A nonzero first counter may predate this Task, so exclude it. A single
174
+ // snapshot does not prove even a zero delta.
175
+ return cumulative.length < 2
176
+ ? metric(null, ["nonzero-session-baseline"])
177
+ : metric(total.totalTokens - baseline, ["nonzero-session-baseline"]);
178
+ }
179
+ function exactRun(observation, runs) {
180
+ const { fence } = observation;
181
+ const run = fence.runId === undefined ? undefined : runs.get(fence.runId);
182
+ if (run === undefined || fence.nativeSessionId === undefined || run.roleName !== fence.roleName
183
+ || run.effective.agentId !== fence.agentId)
184
+ return undefined;
185
+ try {
186
+ return builtinDriverIdForAdapter(run.effective.adapterId) === fence.driverId ? run : undefined;
187
+ }
188
+ catch {
189
+ return undefined;
190
+ }
191
+ }
192
+ function sessionIdentity({ fence }) {
193
+ return fence.taskId === undefined || fence.nativeSessionId === undefined ? null : {
194
+ taskId: fence.taskId, roleName: fence.roleName, agentId: fence.agentId,
195
+ driverId: fence.driverId, nativeSessionId: fence.nativeSessionId
196
+ };
197
+ }
198
+ function identityKey(identity) {
199
+ return JSON.stringify([identity.taskId, identity.roleName, identity.agentId, identity.driverId, identity.nativeSessionId]);
200
+ }
201
+ function resourceKey(identity) {
202
+ return JSON.stringify([identity.agentId, identity.driverId, identity.nativeSessionId]);
203
+ }
204
+ function child({ fence }) {
205
+ return fence.continuationId !== undefined || fence.parentContinuationId !== undefined;
206
+ }
207
+ function usageAuthority(observation) {
208
+ return observation.authority === "provider-structured" || observation.authority === "driver-inferred";
209
+ }
210
+ function compare(left, right) {
211
+ return left.receivedAt.localeCompare(right.receivedAt)
212
+ || (left.sequence ?? -1) - (right.sequence ?? -1)
213
+ || (left.ordinal ?? -1) - (right.ordinal ?? -1)
214
+ || left.eventId.localeCompare(right.eventId);
215
+ }
216
+ function metric(value, reasons = []) {
217
+ return {
218
+ value, status: value === null ? "unknown" : reasons.length === 0 ? "known" : "partial",
219
+ reasons: [...new Set(reasons)].sort()
220
+ };
221
+ }
222
+ function safeSum(values) {
223
+ const sum = values.reduce((a, b) => a + b, 0);
224
+ return Number.isSafeInteger(sum) && sum >= 0 ? sum : null;
225
+ }
226
+ function taskElapsed(task, events, now) {
227
+ const terminal = task.status === "completed"
228
+ || task.status === "cancelled" || task.status === "archived";
229
+ const cancelledAt = events.filter((event) => event.type === "task.cancelled")
230
+ .map(({ createdAt }) => createdAt).sort().at(-1);
231
+ const end = terminal ? task.completedAt ?? task.retiredAt ?? cancelledAt : now.toISOString();
232
+ const elapsed = end === undefined ? NaN : (Date.parse(end) - Date.parse(task.createdAt)) / 1000;
233
+ return Number.isFinite(elapsed) && elapsed >= 0
234
+ ? metric(elapsed) : metric(null, ["task-lifecycle-time-missing"]);
235
+ }
236
+ function executionDuration(observations, now) {
237
+ const turns = new Map();
238
+ for (const observation of observations) {
239
+ const identity = sessionIdentity(observation);
240
+ const { nativeTurnId } = observation.fence;
241
+ if (identity === null || nativeTurnId === undefined || child(observation) || !usageAuthority(observation))
242
+ continue;
243
+ const resource = resourceKey(identity);
244
+ const key = JSON.stringify([resource, nativeTurnId]);
245
+ const turn = turns.get(key) ?? { resource };
246
+ const time = Date.parse(observation.observedAt ?? observation.receivedAt);
247
+ if (observation.kind === "turn.accepted")
248
+ turn.start = Math.min(turn.start ?? time, time);
249
+ if (["turn.completed", "turn.failed", "turn.cancelled"].includes(observation.kind)) {
250
+ turn.end = Math.max(turn.end ?? time, time);
251
+ }
252
+ turns.set(key, turn);
253
+ }
254
+ const intervals = new Map();
255
+ for (const turn of turns.values()) {
256
+ if (turn.start === undefined || turn.end === undefined || turn.end < turn.start || turn.end > now.getTime())
257
+ continue;
258
+ intervals.set(turn.resource, [...(intervals.get(turn.resource) ?? []), [turn.start, turn.end]]);
259
+ }
260
+ let milliseconds = 0;
261
+ for (const ranges of intervals.values()) {
262
+ ranges.sort((a, b) => a[0] - b[0]);
263
+ let end = -Infinity;
264
+ for (const range of ranges) {
265
+ milliseconds += Math.max(0, range[1] - Math.max(end, range[0]));
266
+ end = Math.max(end, range[1]);
267
+ }
268
+ }
269
+ return metric(intervals.size === 0 ? null : milliseconds / 1000, ["native-turn-history-compacted"]);
270
+ }
271
+ /** CLI/audit formatting shares the same metric contract; Web localizes labels. */
272
+ export function formatUsageMetric(metric, suffix = "") {
273
+ return metric.value === null ? `unknown (${metric.reasons.join(", ")})`
274
+ : `${metric.value}${suffix}${metric.status === "partial" ? ` (partial: ${metric.reasons.join(", ")})` : ""}`;
275
+ }
@@ -1,14 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { fileURLToPath } from "node:url";
3
2
  import { resolve } from "node:path";
4
- import { createRuntimeBinding } from "./runtimeBinding.js";
5
- import { normalizeRuntimeOwner } from "./runtimeOwner.js";
6
- import { RuntimeHostContentionError, RuntimeHostUnavailableError, promptPushOutcome } from "./ports.js";
3
+ import { fileURLToPath } from "node:url";
7
4
  import { providerDeliveryFailureFrom } from "./agentError.js";
5
+ import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostRunControl, sendAgentHostSteerControl, waitForAgentHostLaunchAck } from "./agentHost.js";
6
+ import { launchBrokerForHome } from "./launchBroker.js";
8
7
  import { toRuntimeLaunchFailure } from "./launchDiagnostics.js";
8
+ import { RuntimeHostContentionError, RuntimeHostUnavailableError, promptPushOutcome } from "./ports.js";
9
+ import { createRuntimeBinding } from "./runtimeBinding.js";
10
+ import { normalizeRuntimeOwner } from "./runtimeOwner.js";
9
11
  import { requireSafeIdentity } from "./validation.js";
10
- import { launchBrokerForHome } from "./launchBroker.js";
11
- import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostRunControl, sendAgentHostSteerControl, waitForAgentHostLaunchAck } from "./agentHost.js";
12
12
  const DEFAULT_INACTIVITY_TIMEOUT_MS = 300_000;
13
13
  /**
14
14
  * Runtime lifecycle adapter for the current tmux host. The returned hostRef is
@@ -656,8 +656,6 @@ async function killRole(tmux, hostId, roleName) {
656
656
  await tmux.killRoleAsync(hostId, roleName);
657
657
  }
658
658
  async function stopExactRole(tmux, hostId, roleName) {
659
- if (await probeRoleStatus(tmux, hostId, roleName) !== "running")
660
- return;
661
659
  try {
662
660
  await killRole(tmux, hostId, roleName);
663
661
  }
@@ -25,7 +25,7 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
25
25
  for (const task of selectedActiveSchedulerTasks(store, selection, {
26
26
  includePlanningDrafts: true
27
27
  })) {
28
- store.prepareMessageContinuations?.(task.id, now);
28
+ store.prepareMessageContinuations(task.id, now);
29
29
  for (const role of selectedSchedulerRoles(store, task.id, selection)) {
30
30
  const run = store.getActiveRun(task.id, role.name);
31
31
  if (run === null)
@@ -49,8 +49,20 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
49
49
  roleName: role.name,
50
50
  runId: run.id
51
51
  });
52
- const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
52
+ const sessionSet = store.getTaskRoleSessionSet(task.id, role.name);
53
53
  const binding = sessionSet?.providerBinding ?? null;
54
+ if (binding?.retry !== undefined && (binding.retry.successorRunId === run.id
55
+ || binding.retry.successorReviewRoundId !== undefined && binding.retry.successorReviewRoundId === run.reviewRoundId)
56
+ && ["cancelled", "exhausted", "needs-attention"].includes(binding.retry.status)
57
+ && binding.run?.runId !== run.id) {
58
+ return failRunDelivery(store, run, now, "runtime-failed", `Scheduled Provider retry ${binding.retry.status}: ${binding.retry.reason ?? "not admitted"}`);
59
+ }
60
+ if (binding?.retry?.status === "waiting"
61
+ && (binding.retry.successorRunId === run.id || binding.retry.successorReviewRoundId !== undefined
62
+ && binding.retry.successorReviewRoundId === run.reviewRoundId)
63
+ && now.getTime() < Date.parse(binding.retry.nextEligibleAt)) {
64
+ return { ...base, status: "skipped", reason: "not-ready" };
65
+ }
54
66
  const observedRun = binding?.run ?? null;
55
67
  const initialAttemptId = formatRunReceiptId(task.id, run.id);
56
68
  const currentProviderTurn = managedProviderTurnId(observedRun) === run.id ? observedRun : null;
@@ -85,7 +97,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
85
97
  // report. Keep the exact input fenced; never submit it again.
86
98
  return { ...base, status: "skipped", reason: "delivery-uncertain", error: reason };
87
99
  }
88
- const lastSubmitError = (store.listEvents?.(task.id) ?? []).filter((event) => event.type === "runtime.agent-error" && event.payload.runId === run.id
100
+ const lastSubmitError = store.listEvents(task.id).filter((event) => event.type === "runtime.agent-error" && event.payload.runId === run.id
89
101
  && event.payload.roleName === role.name && event.payload.phase === "turn-submit").at(-1);
90
102
  if (currentProviderTurn === null && lastSubmitError?.payload.inputDisposition === "unknown") {
91
103
  return { ...base, status: "skipped", reason: "delivery-uncertain", error: lastSubmitError.payload.message };
@@ -102,7 +114,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
102
114
  // The first attempted admission may already have opened this fixed native
103
115
  // Session. A busy retry resumes it; it does not repeat the original launch.
104
116
  const preparedHere = existingSession?.nativeSessionId !== undefined
105
- && (store.listEvents?.(task.id) ?? []).some((event) => event.type === "run.session-prepared"
117
+ && store.listEvents(task.id).some((event) => event.type === "run.session-prepared"
106
118
  && event.payload.runId === run.id && event.payload.nativeSessionId === existingSession.nativeSessionId);
107
119
  const mode = (currentProviderTurn?.status === "deferred" || preparedHere) && existingSession?.nativeSessionId !== undefined
108
120
  ? "resume" : run.mode;
@@ -171,7 +183,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
171
183
  }
172
184
  if (outcome.status === "busy" || outcome.status === "unavailable") {
173
185
  if (outcome.status === "busy" && outcome.failure?.inputDisposition === "not-accepted") {
174
- store.recordAgentError?.({ taskId: task.id, roleName: role.name, runId: run.id,
186
+ store.recordAgentError({ taskId: task.id, roleName: role.name, runId: run.id,
175
187
  source: "host", phase: "turn-submit", message: outcome.failure.detail,
176
188
  raw: outcome.failure.raw ?? serializeAgentErrorRaw(outcome.failure),
177
189
  inputDisposition: "not-accepted", ...providerDeliveryFailureFacts(outcome.failure),
@@ -196,7 +208,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
196
208
  : formatProviderDeliveryFailure(failure);
197
209
  // This path is the common Provider write failure and previously left no
198
210
  // durable fact at all, so the cause was unrecoverable after the fact.
199
- store.recordAgentError?.({
211
+ store.recordAgentError({
200
212
  taskId: task.id,
201
213
  roleName: role.name,
202
214
  runId: run.id,
@@ -226,7 +238,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
226
238
  const message = error instanceof Error ? error.message : String(error);
227
239
  // Preserve the structured cause alongside the readable diagnosis.
228
240
  const causeName = innermostCauseName(error);
229
- store.recordAgentError?.({
241
+ store.recordAgentError({
230
242
  taskId: task.id,
231
243
  roleName: role.name,
232
244
  runId: run.id,
@@ -3,6 +3,10 @@ import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
3
3
  import { isSchedulerTaskWorkspaceReady } from "./ports.js";
4
4
  import { taskOwnsManagedWorkspace } from "../task/task.js";
5
5
  import { hasImmediateWakeReason } from "./wakeReason.js";
6
+ import { providerRetryPending } from "../runtime/providerRetry.js";
7
+ import { RuntimeLaunchError } from "../runtime/ports.js";
8
+ import { RuntimeLifecycleBusyError } from "../runtime/lifecycleReservation.js";
9
+ import { redactAgentErrorText } from "../runtime/agentError.js";
6
10
  export const LEADER_WAKE_AGGREGATION_MS = 60_000;
7
11
  /** Deliver a durable mailbox notification, not an implicit execution assignment.
8
12
  * Acceptance settles only its claimed batch; no Agent report is required.
@@ -28,12 +32,16 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
28
32
  results.push({ ...base, status: "skipped", reason: "aggregating" });
29
33
  continue;
30
34
  }
31
- if (task.status === "draft" && store.prepareDraftPlanning?.(task.id, now)) {
35
+ if (task.status === "draft" && store.prepareDraftPlanning(task.id, now)) {
32
36
  results.push({ ...base, status: "skipped", reason: "not-ready" });
33
37
  continue;
34
38
  }
35
- const sessions = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
39
+ const sessions = store.getTaskRoleSessionSet(task.id, role.name);
36
40
  const provider = sessions?.providerBinding;
41
+ if (providerRetryPending(provider)) {
42
+ results.push({ ...base, status: "skipped", reason: "not-ready" });
43
+ continue;
44
+ }
37
45
  if (provider?.authority.owner === "human" || provider?.authority.owner === "unknown") {
38
46
  results.push({ ...base, status: "skipped", reason: "busy" });
39
47
  continue;
@@ -50,13 +58,17 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
50
58
  ...(notification.disposition === "unknown" ? {
51
59
  error: `Notification acceptance is unknown; the fixed wake is retained and will not be replayed. `
52
60
  + `After establishing native quiescence, release only this claim with yui task wake resolve ${task.id} ${notification.wakeId} --reason <evidence>.`
61
+ } : notification.disposition === "rejected" ? {
62
+ error: `Notification was rejected; inspect yui task wake show ${task.id} ${notification.wakeId}. `
63
+ + `After correcting the cause, retry with yui task wake retry ${task.id} ${notification.wakeId} --reason <correction>.`
53
64
  } : {}) });
54
65
  continue;
55
66
  }
56
67
  let attempted = false;
68
+ let effective = role.effective;
57
69
  try {
58
70
  const session = store.getRoleSession(task.id, role.name, role.effective.agentId);
59
- const effective = session?.status === "active" ? session.effective : role.effective;
71
+ effective = session?.status === "active" ? session.effective : role.effective;
60
72
  if (session?.status === "active" && !roleSessionMayContinue(session.effective, role.effective)) {
61
73
  throw new Error("Leader Session configuration changed; explicitly apply it before notification delivery.");
62
74
  }
@@ -91,12 +103,18 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
91
103
  : outcome.status === "rejected" ? "rejected"
92
104
  : outcome.status === "pending" ? null : "deferred";
93
105
  if (disposition !== null)
94
- store.settleLeaderNotification(task.id, notification.attemptId, disposition, now, outcome.failure?.detail);
106
+ store.settleLeaderNotification(task.id, notification.attemptId, disposition, now, outcome.failure?.detail, { effective, raw: outcome.failure?.raw ?? outcome.failure?.detail ?? `Notification ${disposition}.`,
107
+ phase: "turn-submit" });
95
108
  results.push({ ...base, status: accepted ? "dispatched" : "skipped", reason: accepted ? undefined : "not-ready" });
96
109
  }
97
110
  catch (error) {
98
- const detail = error instanceof Error ? error.message : String(error);
99
- store.settleLeaderNotification(task.id, notification.attemptId, attempted ? "unknown" : "deferred", now, detail);
111
+ const detail = redactAgentErrorText(error instanceof Error ? error.message : String(error));
112
+ // Match explicit AgentRun admission: only positive, typed backpressure
113
+ // permits another automatic launch. A config/auth/executable failure
114
+ // needs correction; an exception after submit cannot authorize replay.
115
+ const retryable = error instanceof RuntimeLifecycleBusyError
116
+ || (error instanceof RuntimeLaunchError && error.retryable);
117
+ store.settleLeaderNotification(task.id, notification.attemptId, attempted ? "unknown" : retryable ? "deferred" : "rejected", now, detail, { effective, raw: detail, phase: attempted ? "turn-submit" : "session-start" });
100
118
  results.push({ ...base, status: "failed", reason: "not-ready", error: detail });
101
119
  }
102
120
  finally {
@@ -1,31 +1,23 @@
1
1
  import { enqueueWork } from "../coordination/workMailboxQueue.js";
2
2
  import { createTaskEvent } from "../event/taskEvent.js";
3
- import { queueLeaderWakeup } from "./wakeupQueue.js";
4
3
  export const LEADER_ATTENTION_REQUIRED_EVENT = "leader.attention-required";
5
- /** The sole Task-event boundary into the global Operator mailbox. */
6
- export function enqueueOperatorEvent(store, event, reason, now) {
7
- if (store.getTask?.(event.taskId)?.status === "archived")
4
+ /** One event, one supervisor signal; the Leader wake is this mailbox's projection. */
5
+ export function enqueueSupervisorEvent(store, event, recipient, reason, now) {
6
+ if (store.getTask(event.taskId)?.status === "archived")
8
7
  return;
9
- enqueueWork(store, { kind: "operator" }, reason, now, [
8
+ const target = recipient === "operator" ? { kind: "operator" }
9
+ : { kind: "role", taskId: event.taskId, roleName: "leader" };
10
+ enqueueWork(store, target, reason, now, [
10
11
  { type: "event", taskId: event.taskId, id: event.id }
11
- ], {
12
- source: "task-event",
13
- dedupeKey: `operator-event:${event.taskId}:${event.id}`
14
- });
12
+ ], { source: "task-event", dedupeKey: `${recipient}-event:${event.taskId}:${event.id}` });
13
+ }
14
+ /** The sole Task-event boundary into the global Operator mailbox. */
15
+ export function enqueueOperatorEvent(store, event, reason, now) {
16
+ enqueueSupervisorEvent(store, event, "operator", reason, now);
15
17
  }
16
18
  /** Routes one Role fact to its supervisor without exposing Provider details to Operator code. */
17
19
  export function routeRoleEvent(store, event, roleName, reason, now) {
18
- if (store.getTask?.(event.taskId)?.status === "archived")
19
- return;
20
- if (roleName === "leader") {
21
- enqueueOperatorEvent(store, event, reason, now);
22
- return;
23
- }
24
- enqueueWork(store, { kind: "role", taskId: event.taskId, roleName: "leader" }, reason, now, [{ type: "event", taskId: event.taskId, id: event.id }], {
25
- source: "task-event",
26
- dedupeKey: `leader-event:${event.taskId}:${event.id}`
27
- });
28
- queueLeaderWakeup(store, event.taskId, reason, now);
20
+ enqueueSupervisorEvent(store, event, roleName === "leader" ? "operator" : "leader", reason, now);
29
21
  }
30
22
  /** Records the semantic boundary used when a Leader can no longer continue. */
31
23
  export function recordLeaderAttentionRequired(store, input) {
@@ -26,7 +26,7 @@ export async function processOperatorInputNotifications(store, delivery, selecti
26
26
  ? [{ kind: "input", request }]
27
27
  : [];
28
28
  }
29
- if (ref.type !== "event" || store.listEvents === undefined)
29
+ if (ref.type !== "event")
30
30
  return [];
31
31
  const event = store.listEvents(ref.taskId).find(({ id }) => id === ref.id);
32
32
  return event === undefined ? [] : [{ kind: "event", event }];
@@ -1,5 +1,5 @@
1
- import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
2
1
  import { taskOwnsManagedWorkspace } from "../task/task.js";
2
+ import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
3
3
  /**
4
4
  * Whether the Task's workspace state admits a launch.
5
5
  *
@@ -72,14 +72,11 @@ export function selectedActiveSchedulerTasks(store, selection, options) {
72
72
  return planningDrafts && hasActivePlanningRun(store, task);
73
73
  };
74
74
  if (selection === undefined || selection.full) {
75
- const indexedTaskIds = store.listActiveTaskIds?.();
76
- if (indexedTaskIds === undefined) {
77
- return store.listTasks().filter((task) => (admits(task) && !selection?.blockedTaskIds?.has(task.id)));
78
- }
75
+ const indexedTaskIds = store.listActiveTaskIds();
79
76
  // The active index is bounded and authoritative for active Tasks; planning
80
77
  // Drafts come from their own bounded index rather than a full-history scan.
81
78
  const taskIds = planningDrafts
82
- ? [...indexedTaskIds, ...(store.listPlanningDraftTaskIds?.() ?? [])]
79
+ ? [...indexedTaskIds, ...store.listPlanningDraftTaskIds()]
83
80
  : [...indexedTaskIds];
84
81
  const seen = new Set();
85
82
  return taskIds.flatMap((taskId) => {
@@ -106,9 +103,9 @@ export function selectedActiveSchedulerTasks(store, selection, options) {
106
103
  function hasActivePlanningRun(store, task) {
107
104
  if (task.status !== "draft")
108
105
  return false;
109
- const roles = store.listRoles?.(task.id) ?? [];
106
+ const roles = store.listRoles(task.id);
110
107
  return roles.some((role) => {
111
- const run = store.getActiveRun?.(task.id, role.name) ?? null;
108
+ const run = store.getActiveRun(task.id, role.name);
112
109
  return run !== null
113
110
  && run.status === "active"
114
111
  && isSchedulerPlanningDraft(task, run.purpose);
@@ -1,6 +1,6 @@
1
- import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
2
- import { formatTaskRecordReference } from "../task/taskRecordReference.js";
3
1
  import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
2
+ import { formatTaskRecordReference } from "../task/taskRecordReference.js";
3
+ import { selectedActiveSchedulerTasks, selectedSchedulerRoles } from "./ports.js";
4
4
  import { currentRoleRunProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleRunStall.js";
5
5
  /**
6
6
  * Lightweight liveness only. Pane/process loss is runtime-health evidence,
@@ -85,7 +85,7 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
85
85
  }
86
86
  }
87
87
  }
88
- for (const { task, role, run, session, inspection } of eligible) {
88
+ for (const { task, role, run, session } of eligible) {
89
89
  const status = batchSnapshot.statuses.get(`${task.id}\0${role.name}`);
90
90
  if (status === undefined)
91
91
  throw new Error("Role liveness snapshot is incomplete.");
@@ -93,7 +93,7 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
93
93
  continue;
94
94
  const hostExit = batchSnapshot.hostExits.get(`${task.id}\0${role.name}`);
95
95
  if (hostExit !== undefined) {
96
- store.saveRoleHostExitObservation?.({
96
+ store.saveRoleHostExitObservation({
97
97
  taskId: task.id,
98
98
  roleName: role.name,
99
99
  runId: run.id,