@zq-silk/yui 0.13.9 → 0.14.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 (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. 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
- }