@zq-silk/yui 0.13.10 → 0.14.1

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 (230) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +113 -300
  3. package/dist/agent/argumentPolicy.js +2 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  5. package/dist/cli/commandCatalog.js +39 -77
  6. package/dist/cli/interactionCandidates.js +4 -4
  7. package/dist/cli/interactionPolicy.js +7 -10
  8. package/dist/cli/invocationRouter.js +1 -1
  9. package/dist/cli/updateCommand.js +6 -27
  10. package/dist/cli/updateOrchestrator.js +63 -484
  11. package/dist/cli/updatePorts.js +15 -333
  12. package/dist/cli/upgradeCommand.js +8 -65
  13. package/dist/cli.js +57 -242
  14. package/dist/commands/configCommands.js +11 -62
  15. package/dist/commands/executionAuditCommands.js +23 -23
  16. package/dist/commands/globalRoleCommands.js +2 -7
  17. package/dist/commands/jobCommands.js +1 -4
  18. package/dist/commands/projectCommands.js +5 -5
  19. package/dist/commands/releaseCommands.js +3 -10
  20. package/dist/commands/resourcesCommands.js +2 -2
  21. package/dist/commands/sessionCommands.js +2 -2
  22. package/dist/commands/taskActor.js +31 -39
  23. package/dist/commands/taskChangeSetCommands.js +5 -12
  24. package/dist/commands/taskCommands.js +464 -1249
  25. package/dist/commands/taskContextCommand.js +35 -36
  26. package/dist/commands/taskExecutionCommands.js +33 -46
  27. package/dist/commands/taskInputCommands.js +20 -42
  28. package/dist/commands/taskNextActionCommand.js +9 -9
  29. package/dist/commands/taskOverviewCommand.js +56 -95
  30. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  31. package/dist/commands/taskUpstreamCommands.js +3 -3
  32. package/dist/commands/taskWorkspaceCommands.js +7 -79
  33. package/dist/commands/telemetryCommands.js +32 -124
  34. package/dist/commands/workflowCommands.js +3 -3
  35. package/dist/completion/fileCompletionManager.js +1 -1
  36. package/dist/config/configCatalog.js +4 -6
  37. package/dist/config/yuiConfig.js +6 -43
  38. package/dist/context/dispatchContext.js +14 -30
  39. package/dist/context/roleSessionContext.js +2 -2
  40. package/dist/context/sessionBootstrapManifest.js +20 -3
  41. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  42. package/dist/context/turnInputContract.js +204 -0
  43. package/dist/context/wakeNotification.js +2 -2
  44. package/dist/controller/agentRuntimeObserver.js +18 -18
  45. package/dist/controller/clientRuntime.js +13 -20
  46. package/dist/controller/controller.js +89 -116
  47. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  48. package/dist/controller/jobControl.js +17 -17
  49. package/dist/controller/resourceInventory.js +6 -6
  50. package/dist/controller/resourceInventoryLinux.js +38 -59
  51. package/dist/controller/resourceInventoryRpc.js +3 -3
  52. package/dist/controller/resourceInventoryWorker.js +1 -1
  53. package/dist/controller/runtime.js +46 -53
  54. package/dist/controller/runtimeEventInbox.js +23 -31
  55. package/dist/controller/runtimeEventProcessor.js +15 -15
  56. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  57. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  58. package/dist/controller/runtimeObservationHook.js +11 -8
  59. package/dist/controller/sessionNotify.js +11 -11
  60. package/dist/controller/structuredProviderObservation.js +89 -16
  61. package/dist/coordination/workMailbox.js +309 -579
  62. package/dist/coordination/workMailboxQueue.js +5 -6
  63. package/dist/core/controllerClient.js +1 -91
  64. package/dist/core/controllerServer.js +3 -6
  65. package/dist/doctor/doctor.js +51 -314
  66. package/dist/execution/candidateConvergence.js +3 -3
  67. package/dist/execution/executionGroup.js +32 -33
  68. package/dist/execution/executionHealth.js +36 -38
  69. package/dist/execution/resourceBroker.js +12 -12
  70. package/dist/executor/agentExecutor.js +30 -265
  71. package/dist/executor/effectiveLaunch.js +9 -19
  72. package/dist/executor/executorRegistry.js +43 -17
  73. package/dist/executor/fileRoleLaunchPlanner.js +91 -57
  74. package/dist/executor/launchPlan.js +3 -3
  75. package/dist/executor/workspacePreflightClassification.js +16 -16
  76. package/dist/grant/capabilityGrant.js +20 -25
  77. package/dist/input/inputRequest.js +12 -12
  78. package/dist/integration/changeSet.js +9 -13
  79. package/dist/integration/changeSetManifest.js +1 -1
  80. package/dist/integration/gitIntegrationService.js +2 -2
  81. package/dist/integration/integrationQueueService.js +5 -21
  82. package/dist/integration/overlapDiagnostics.js +2 -2
  83. package/dist/interaction/operatorPresentation.js +1 -1
  84. package/dist/job/jobRunner.js +1 -1
  85. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  86. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  87. package/dist/message/message.js +6 -6
  88. package/dist/observability/executionAudit.js +74 -86
  89. package/dist/observability/faultClassification.js +7 -24
  90. package/dist/observability/orchestrationMetrics.js +5 -5
  91. package/dist/observability/runtimeIdentity.js +34 -101
  92. package/dist/profile/agentProfile.js +1 -1
  93. package/dist/release/cliHomeReleaseFence.js +3 -3
  94. package/dist/release/releaseWorkflowEngine.js +6 -10
  95. package/dist/release/releaseWorkflowPorts.js +2 -2
  96. package/dist/release/workflowFileLock.js +1 -1
  97. package/dist/repository/checkoutSwap.js +1 -1
  98. package/dist/repository/gitWorkspace.js +0 -13
  99. package/dist/repository/project.js +1 -1
  100. package/dist/repository/projectMaintenanceLock.js +1 -2
  101. package/dist/repository/taskBaseFreshness.js +13 -13
  102. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  103. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  104. package/dist/repository/taskWorkspacePreparer.js +63 -620
  105. package/dist/resources/autoResourceGc.js +3 -3
  106. package/dist/resources/liveReferences.js +1 -1
  107. package/dist/resources/resourceGc.js +1 -1
  108. package/dist/resources/sqliteResourceRegistry.js +1 -1
  109. package/dist/review/reviewDecision.js +7 -7
  110. package/dist/review/reviewFindingLedger.js +1 -1
  111. package/dist/review/reviewOutcomeClassifier.js +35 -50
  112. package/dist/review/reviewRound.js +14 -25
  113. package/dist/review/reviewerAvailability.js +4 -4
  114. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  115. package/dist/role/role.js +6 -3
  116. package/dist/runtime/agentDriver.js +4 -0
  117. package/dist/runtime/agentHost.js +151 -36
  118. package/dist/runtime/builtinAgentDrivers.js +79 -2
  119. package/dist/runtime/codexAppServerRuntime.js +122 -0
  120. package/dist/runtime/continuationManager.js +3 -5
  121. package/dist/runtime/exactControlPlane.js +17 -42
  122. package/dist/runtime/firstProgressAdvisory.js +1 -1
  123. package/dist/runtime/index.js +2 -2
  124. package/dist/runtime/launchDiagnostics.js +2 -2
  125. package/dist/runtime/lifecycleReservation.js +4 -4
  126. package/dist/runtime/processExitObservation.js +1 -1
  127. package/dist/runtime/processExitOutbox.js +0 -32
  128. package/dist/runtime/promptEnvelope.js +9 -9
  129. package/dist/runtime/providerContinuation.js +2 -2
  130. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  131. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  132. package/dist/runtime/recentTurnIds.js +38 -0
  133. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  134. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  135. package/dist/runtime/runtimeObservation.js +58 -21
  136. package/dist/runtime/runtimeProjection.js +19 -27
  137. package/dist/runtime/sessionLaunchRequest.js +2 -2
  138. package/dist/runtime/structuredProviderHost.js +177 -12
  139. package/dist/runtime/tmuxAdapters.js +46 -11
  140. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  141. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  142. package/dist/scheduler/operatorEvent.js +4 -6
  143. package/dist/scheduler/resourceQueueProjection.js +71 -0
  144. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  145. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  146. package/dist/scheduler/taskExecutionProjection.js +99 -99
  147. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  148. package/dist/scheduler/taskWake.js +4 -4
  149. package/dist/scheduler/wakeReason.js +6 -10
  150. package/dist/scheduler/wakeupQueue.js +0 -9
  151. package/dist/setup/setupCommand.js +2 -2
  152. package/dist/storage/currentTaskStore.js +46 -0
  153. package/dist/storage/persistenceWorker.js +1 -1
  154. package/dist/storage/sqliteSchema.js +81 -485
  155. package/dist/storage/sqliteStore.js +206 -365
  156. package/dist/storage/storageSchema.js +1 -15
  157. package/dist/storage/storageVersions.js +8 -9
  158. package/dist/storage/storeRpc.js +10 -34
  159. package/dist/storage/taskStore.js +63 -3415
  160. package/dist/storage/upgrade/recordVersions.js +55 -149
  161. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  162. package/dist/task/completionReadiness.js +5 -5
  163. package/dist/task/deliveryGuard.js +10 -10
  164. package/dist/task/nextAction.js +74 -77
  165. package/dist/task/task.js +2 -4
  166. package/dist/task/taskRecordReference.js +3 -3
  167. package/dist/task/taskRecordRetirement.js +1 -1
  168. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  169. package/dist/telemetry/telemetryConfig.js +14 -14
  170. package/dist/telemetry/telemetryWiring.js +8 -14
  171. package/dist/turn/turn.js +356 -0
  172. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  173. package/dist/verification/gateArtifactStore.js +2 -2
  174. package/dist/web/assets/client/app.js +1 -1
  175. package/dist/web/assets/client/components.js +29 -54
  176. package/dist/web/assets/client/i18n.js +17 -37
  177. package/dist/web/assets/client/view.js +37 -37
  178. package/dist/web/assets/shell.js +1 -1
  179. package/dist/web/assets/styles/cards.js +5 -5
  180. package/dist/web/assets/styles/widgets.js +1 -2
  181. package/dist/web/webSnapshot.js +65 -60
  182. package/dist/workItem/workItem.js +11 -11
  183. package/dist/workspace/workItemChangeSetManager.js +1 -1
  184. package/docs/task-local-identity.md +5 -5
  185. package/i18n/README.zh-CN.md +42 -55
  186. package/package.json +1 -1
  187. package/skills/yui-leader/SKILL.md +85 -83
  188. package/skills/yui-operator/SKILL.md +16 -16
  189. package/skills/yui-reviewer/SKILL.md +10 -10
  190. package/skills/yui-runtime/SKILL.md +22 -24
  191. package/skills/yui-worker/SKILL.md +16 -18
  192. package/dist/context/runContextContract.js +0 -162
  193. package/dist/executor/turnCompletion.js +0 -126
  194. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  195. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  196. package/dist/run/agentRun.js +0 -356
  197. package/dist/run/rejectedYieldAttempt.js +0 -221
  198. package/dist/run/runControlRequest.js +0 -50
  199. package/dist/run/yieldReceipt.js +0 -65
  200. package/dist/runtime/turnCompletion.js +0 -3
  201. package/dist/scheduler/actionability.js +0 -323
  202. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  203. package/dist/scheduler/activeTaskProgress.js +0 -126
  204. package/dist/storage/compatibleTaskStore.js +0 -248
  205. package/dist/storage/migration/baseline.js +0 -78
  206. package/dist/storage/migration/classifier.js +0 -51
  207. package/dist/storage/migration/compatibleCodec.js +0 -53
  208. package/dist/storage/migration/engine.js +0 -147
  209. package/dist/storage/migration/index.js +0 -33
  210. package/dist/storage/migration/planner.js +0 -154
  211. package/dist/storage/migration/productionRegistry.js +0 -3931
  212. package/dist/storage/migration/registry.js +0 -169
  213. package/dist/storage/migration/report.js +0 -54
  214. package/dist/storage/migration/types.js +0 -31
  215. package/dist/storage/upgrade/homeClassification.js +0 -353
  216. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  217. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  218. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  219. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  220. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  221. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  222. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  223. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  224. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  225. package/dist/storage/upgrade/sqliteStateMigration.js +0 -926
  226. package/dist/storage/upgrade/switchProgress.js +0 -80
  227. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  228. package/dist/storage/upgradeCoordination.js +0 -187
  229. package/dist/storage/upgradeFence.js +0 -366
  230. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -1,65 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { requireIdentity, requireText, requireTimestamp } from "../domain/validation.js";
3
- const DIGEST_PREFIX_LENGTH = 16;
4
- function canonicalDigestValue(value) {
5
- if (value === null || typeof value !== "object")
6
- return JSON.stringify(value) ?? "null";
7
- if (Array.isArray(value)) {
8
- return `[${value.map(canonicalDigestValue).join(",")}]`;
9
- }
10
- const entries = Object.entries(value)
11
- .filter(([, entry]) => entry !== undefined)
12
- .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
13
- return `{${entries
14
- .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalDigestValue(entry)}`)
15
- .join(",")}}`;
16
- }
17
- /**
18
- * Computes the terminal outcome digest. The canonical form sorts object keys
19
- * and drops `undefined` fields so the same outcome always hashes identically
20
- * regardless of object construction order.
21
- */
22
- export function computeYieldOutcomeDigest(input) {
23
- return createHash("sha256")
24
- .update(canonicalDigestValue(input))
25
- .digest("hex");
26
- }
27
- export function formatYieldReceiptRequestId(taskId, runId, outcomeDigest) {
28
- return `yield:${taskId}:${runId}:${outcomeDigest.slice(0, DIGEST_PREFIX_LENGTH)}`;
29
- }
30
- export function formatYieldReceiptId(taskId, runId, outcomeDigest) {
31
- return `yield-receipt:${taskId}:${runId}:${outcomeDigest.slice(0, DIGEST_PREFIX_LENGTH)}`;
32
- }
33
- export function createYieldReceipt(taskId, runId, outcome, now) {
34
- const outcomeDigest = computeYieldOutcomeDigest(outcome);
35
- return validateYieldReceipt({
36
- schemaVersion: 1,
37
- requestId: formatYieldReceiptRequestId(taskId, runId, outcomeDigest),
38
- receiptId: formatYieldReceiptId(taskId, runId, outcomeDigest),
39
- outcomeDigest,
40
- committedAt: now.toISOString()
41
- });
42
- }
43
- export function validateYieldReceipt(value) {
44
- if (value.schemaVersion !== 1) {
45
- throw new Error("Yield receipt must use schemaVersion 1.");
46
- }
47
- requireIdentity(value.requestId, "Yield receipt requestId");
48
- requireIdentity(value.receiptId, "Yield receipt receiptId");
49
- requireText(value.outcomeDigest, "Yield receipt outcomeDigest");
50
- requireTimestamp(value.committedAt, "Yield receipt committedAt");
51
- return value;
52
- }
53
- /**
54
- * Matches a presented outcome against a terminal Run's committed receipt.
55
- * The same digest replays the receipt; a different digest fails closed.
56
- */
57
- export function matchYieldReceipt(receipt, outcome) {
58
- if (receipt === undefined)
59
- return null;
60
- const presented = computeYieldOutcomeDigest(outcome);
61
- if (presented === receipt.outcomeDigest) {
62
- return { kind: "replayed", receipt };
63
- }
64
- return { kind: "digest-mismatch", existing: receipt };
65
- }
@@ -1,3 +0,0 @@
1
- // Compatibility facade. The authoritative Turn-completion model belongs to
2
- // the Execution domain; runtime adapters only translate external Hook facts.
3
- export * from "../executor/turnCompletion.js";
@@ -1,323 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
3
- import { isDurableJobTerminal } from "../job/durableJob.js";
4
- import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
5
- import { executionStageSpendClosed, observedExecutionResourceUsage, projectExecutionStageResources } from "../execution/resourceBroker.js";
6
- /**
7
- * Canonical SHA-256 digest over the normalized actionable facts. Pure and
8
- * deterministic: the same facts always produce the same digest regardless of
9
- * collection order.
10
- */
11
- export function computeActionabilityDigest(input) {
12
- const facts = [...input.facts]
13
- .map((fact) => ({ key: fact.key, value: fact.value }))
14
- .sort((left, right) => (left.key < right.key ? -1 : left.key > right.key ? 1 : 0));
15
- const canonical = JSON.stringify({
16
- taskId: input.taskId,
17
- taskStatus: input.taskStatus,
18
- facts
19
- });
20
- return createHash("sha256").update(canonical).digest("hex");
21
- }
22
- const TERMINAL_WORK_ITEM_STATUSES = new Set(["completed", "retired"]);
23
- const CONSUMED_INTEGRATION_STATUSES = new Set(["committed", "superseded"]);
24
- /** Exact durable Broker queue shared by admission and wake actionability. */
25
- export function projectQueuedResourceLaneIdentities(store, now) {
26
- return store.listActiveTaskIds().flatMap((taskId) => {
27
- const taskRuns = store.listAgentRuns(taskId);
28
- const taskEvents = store.listEvents(taskId);
29
- const workItemLanes = store.listWorkItems(taskId).flatMap((item) => {
30
- if (item.status !== "running")
31
- return [];
32
- const group = currentWorkItemExecutionGroup(item);
33
- if (group === undefined || group.resolution !== undefined)
34
- return [];
35
- if (group.stage !== undefined) {
36
- const resources = projectExecutionStageResources({
37
- group,
38
- stageGroups: item.executionGroups,
39
- usage: observedExecutionResourceUsage({
40
- group,
41
- stageGroups: item.executionGroups,
42
- runs: taskRuns,
43
- events: taskEvents
44
- }),
45
- now
46
- });
47
- if (executionStageSpendClosed(resources))
48
- return [];
49
- }
50
- return group.lanes.flatMap((lane) => {
51
- if (lane.status !== "pending"
52
- || lane.effective === undefined
53
- || lane.runId !== undefined)
54
- return [];
55
- return [{
56
- taskId,
57
- workItemId: item.id,
58
- executionGroupId: group.id,
59
- executionLaneId: lane.id,
60
- providerId: lane.effective.adapterId,
61
- agentId: lane.effective.agentId,
62
- ...(lane.effective.model === undefined ? {} : { model: lane.effective.model }),
63
- requestedAt: lane.updatedAt
64
- }];
65
- });
66
- });
67
- const reviewLanes = store.listReviewRounds(taskId).flatMap((round) => {
68
- if (round.status !== "pending" && round.status !== "running")
69
- return [];
70
- const group = round.executionGroup;
71
- if (group === undefined || group.resolution !== undefined)
72
- return [];
73
- return group.lanes.flatMap((lane) => {
74
- if (lane.status !== "pending"
75
- || lane.effective === undefined
76
- || lane.runId !== undefined)
77
- return [];
78
- return [{
79
- taskId,
80
- ...(round.workItemId === undefined ? {} : { workItemId: round.workItemId }),
81
- executionGroupId: group.id,
82
- executionLaneId: lane.id,
83
- providerId: lane.effective.adapterId,
84
- agentId: lane.effective.agentId,
85
- ...(lane.effective.model === undefined ? {} : { model: lane.effective.model }),
86
- requestedAt: lane.updatedAt
87
- }];
88
- });
89
- });
90
- return [...workItemLanes, ...reviewLanes];
91
- });
92
- }
93
- /**
94
- * True only when the Task still owns a Resource-Broker-queued Lane that a
95
- * later Leader dispatch may start. Terminal owners retain their immutable
96
- * Group history, but can never reserve live queue capacity.
97
- */
98
- export function hasDispatchableQueuedResourceLane(store, taskId) {
99
- const workItemGroups = (store.listWorkItems?.(taskId) ?? [])
100
- .filter(({ status }) => status === "running")
101
- .flatMap(({ executionGroups }) => executionGroups);
102
- const reviewGroups = (store.listReviewRounds?.(taskId) ?? [])
103
- .filter(({ status }) => status === "pending" || status === "running")
104
- .flatMap(({ executionGroup }) => executionGroup === undefined ? [] : [executionGroup]);
105
- return [...workItemGroups, ...reviewGroups].some((group) => (group.resolution === undefined
106
- && group.lanes.some((lane) => (lane.status === "pending"
107
- && lane.effective !== undefined
108
- && lane.runId === undefined))));
109
- }
110
- /**
111
- * Fold a Task's durable records into the normalized actionable facts. This
112
- * function only reads; it never starts a Controller, queues a wake, writes a
113
- * Message, or mutates any record.
114
- *
115
- * Included facts (each changes only when the durable record changes):
116
- * - Active Runs: the ownership/liveness picture. A Run starting or ending
117
- * changes the digest so a lost executor is detected.
118
- * - Non-terminal/failed WorkItems: pending, running, awaiting-acceptance, and
119
- * failed items still need Leader or Worker action.
120
- * - Non-completed ReviewRounds: pending/running/failed rounds still need
121
- * Leader disposition or a Reviewer.
122
- * - Unresolved IntegrationAttempts: running/validating/blocked/failed attempts
123
- * the Leader may need to resolve.
124
- * - Terminal DurableJobs: new terminal results the Leader should observe.
125
- * - InputRequests: open and answered/cancelled requests.
126
- * - User directives explicitly marked `wakePolicy=leader`.
127
- *
128
- * Excluded: pure revision growth, progress/heartbeat events, repeated scans of
129
- * an unchanged blocker, read-only status requests, and waits already owned by
130
- * an active Worker/Reviewer/Job (the active-Run facts above capture ownership).
131
- */
132
- export function collectTaskActionability(store, taskId, now = new Date()) {
133
- const task = store.getTask(taskId);
134
- if (task === null) {
135
- throw new Error(`Task not found for actionability projection: ${taskId}.`);
136
- }
137
- const facts = [];
138
- const events = store.listEvents?.(taskId) ?? [];
139
- for (const run of operationalTaskRecords(store.listAgentRuns(taskId), events, "agent-run").filter((candidate) => candidate.status === "active")) {
140
- facts.push({
141
- key: `active-run:${run.id}`,
142
- value: [
143
- run.roleName,
144
- run.workItemId ?? "",
145
- run.reviewRoundId ?? "",
146
- run.updatedAt
147
- ].join("|")
148
- });
149
- }
150
- const workItems = store.listWorkItems?.(taskId) ?? [];
151
- const reviewRounds = store.listReviewRounds?.(taskId) ?? [];
152
- for (const item of workItems) {
153
- if (TERMINAL_WORK_ITEM_STATUSES.has(item.status))
154
- continue;
155
- facts.push({
156
- key: `work-item:${item.id}`,
157
- value: `${item.status}|${item.updatedAt}`
158
- });
159
- for (const group of item.executionGroups) {
160
- if (group.resolution !== undefined || group.stage?.resources === undefined)
161
- continue;
162
- facts.push({
163
- key: `resource-deadline:${group.id}`,
164
- value: now.getTime() >= Date.parse(group.stage.resources.deadlineAt)
165
- ? "reached"
166
- : "open"
167
- });
168
- }
169
- }
170
- // A Resource-Broker-queued Lane is durable but deliberately has no active
171
- // Run. Include the global capacity and fair-queue projection only for Tasks
172
- // that are actually queued, so either capacity or an older reservation being
173
- // released changes their digest without polling or a second scheduler.
174
- const queueProjectionAvailable = store.listActiveTaskIds !== undefined
175
- && store.listWorkItems !== undefined
176
- && store.listReviewRounds !== undefined
177
- && store.listEvents !== undefined;
178
- const queuedResourceLanes = queueProjectionAvailable
179
- ? projectQueuedResourceLaneIdentities({
180
- listActiveTaskIds: () => store.listActiveTaskIds(),
181
- listAgentRuns: (candidateTaskId) => store.listAgentRuns(candidateTaskId),
182
- listWorkItems: (candidateTaskId) => store.listWorkItems(candidateTaskId),
183
- listReviewRounds: (candidateTaskId) => store.listReviewRounds(candidateTaskId),
184
- listEvents: (candidateTaskId) => store.listEvents(candidateTaskId)
185
- }, now)
186
- : [];
187
- const hasQueuedResourceLane = queueProjectionAvailable
188
- ? queuedResourceLanes.some((lane) => lane.taskId === taskId)
189
- : hasDispatchableQueuedResourceLane(store, taskId);
190
- if (hasQueuedResourceLane && store.listActiveTaskIds !== undefined) {
191
- const activeResourceScopes = store.listActiveTaskIds().flatMap((activeTaskId) => (operationalTaskRecords(store.listAgentRuns(activeTaskId), store.listEvents?.(activeTaskId) ?? [], "agent-run").flatMap((run) => (run.status !== "active"
192
- || run.executionGroupId === undefined
193
- || run.executionLaneId === undefined
194
- ? []
195
- : [
196
- "home",
197
- `task:${activeTaskId}`,
198
- ...(run.workItemId === undefined
199
- ? []
200
- : [`work-item:${activeTaskId}/${run.workItemId}`]),
201
- `group:${activeTaskId}/${run.executionGroupId}`,
202
- `provider:${run.effective.adapterId}`,
203
- `agent:${run.effective.agentId}`,
204
- `model:${run.effective.adapterId}/${run.effective.model ?? "default"}`
205
- ]))));
206
- const activeResourceCounts = new Map();
207
- for (const scope of activeResourceScopes) {
208
- activeResourceCounts.set(scope, (activeResourceCounts.get(scope) ?? 0) + 1);
209
- }
210
- facts.push({
211
- key: "resource-broker:active-capacity",
212
- value: [...activeResourceCounts]
213
- .sort(([left], [right]) => left.localeCompare(right))
214
- .map(([scope, count]) => `${scope}=${count}`)
215
- .join("|")
216
- });
217
- if (queueProjectionAvailable) {
218
- facts.push({
219
- key: "resource-broker:fair-queue",
220
- value: queuedResourceLanes
221
- .map((lane) => [
222
- lane.requestedAt,
223
- lane.taskId,
224
- lane.workItemId ?? "",
225
- lane.executionGroupId,
226
- lane.executionLaneId,
227
- lane.providerId,
228
- lane.agentId,
229
- lane.model ?? "default"
230
- ].join("|"))
231
- .sort()
232
- .join("\n")
233
- });
234
- }
235
- }
236
- for (const round of reviewRounds) {
237
- if (round.status === "completed")
238
- continue;
239
- facts.push({
240
- key: `review:${round.id}`,
241
- value: `${round.status}|${round.endedAt ?? round.createdAt}`
242
- });
243
- }
244
- for (const attempt of store.listIntegrationAttempts?.(taskId) ?? []) {
245
- if (CONSUMED_INTEGRATION_STATUSES.has(attempt.status))
246
- continue;
247
- facts.push({
248
- key: `integration:${attempt.id}`,
249
- value: `${attempt.status}|${attempt.updatedAt}`
250
- });
251
- }
252
- for (const job of store.listDurableJobs?.(taskId) ?? []) {
253
- if (!isDurableJobTerminal(job.status))
254
- continue;
255
- facts.push({
256
- key: `durable-job:${job.id}`,
257
- value: `${job.status}|${job.terminalAt ?? job.updatedAt}`
258
- });
259
- }
260
- for (const request of store.listInputRequests?.(taskId) ?? []) {
261
- facts.push({
262
- key: `input:${request.id}`,
263
- value: `${request.status}|${request.updatedAt}`
264
- });
265
- }
266
- for (const message of operationalTaskRecords(store.listMessages?.(taskId) ?? [], events, "message")) {
267
- if (message.wakePolicy !== "leader")
268
- continue;
269
- facts.push({
270
- key: `directive:${message.id}`,
271
- value: message.createdAt
272
- });
273
- }
274
- return { taskId, taskStatus: task.status, facts };
275
- }
276
- /**
277
- * Derive the Leader Run disposition from the Task execution projection status.
278
- * This is machine-derived at yield time so the Scheduler does not depend on
279
- * free-text Leader summaries.
280
- */
281
- export function deriveLeaderRunDisposition(projectionStatus, taskStatus) {
282
- if (taskStatus !== "active")
283
- return "completed";
284
- switch (projectionStatus) {
285
- case "blocked":
286
- return "blocked";
287
- case "waiting-on-agents":
288
- case "waiting-user":
289
- return "waiting";
290
- case "completed":
291
- case "retired":
292
- case "archived":
293
- return "completed";
294
- default:
295
- return "progress";
296
- }
297
- }
298
- /**
299
- * Decide whether a `task-orphaned` scan should create a new Leader Run.
300
- *
301
- * Suppression applies only when the last Leader Run ended `waiting` or
302
- * `blocked` and its `observedActionabilityDigest` equals the current digest.
303
- * A `progress`/`completed` disposition, a missing digest, or a changed digest
304
- * always wakes. Computation errors fail open.
305
- */
306
- export function decideOrphanWake(input) {
307
- const { currentDigest, lastLeaderRun } = input;
308
- if (lastLeaderRun === null)
309
- return { kind: "wake", digest: currentDigest };
310
- if (lastLeaderRun.status === "active")
311
- return { kind: "wake", digest: currentDigest };
312
- const disposition = lastLeaderRun.disposition;
313
- if (disposition !== "waiting" && disposition !== "blocked") {
314
- return { kind: "wake", digest: currentDigest };
315
- }
316
- const observed = lastLeaderRun.observedActionabilityDigest;
317
- if (observed === undefined)
318
- return { kind: "wake", digest: currentDigest };
319
- if (observed === currentDigest) {
320
- return { kind: "suppress", digest: currentDigest, observedDigest: observed };
321
- }
322
- return { kind: "wake", digest: currentDigest };
323
- }