@kbediako/codex-orchestrator 0.1.38 → 0.2.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 (311) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +46 -317
  3. package/bin/codex-orchestrator.js +161 -0
  4. package/codex.orchestrator.json +149 -13
  5. package/dist/bin/codex-orchestrator.js +797 -1154
  6. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +50 -0
  7. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +22 -4
  8. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +3 -3
  9. package/dist/orchestrator/src/cli/adapters/CommandTester.js +2 -2
  10. package/dist/orchestrator/src/cli/adapters/cloudFailureDiagnostics.js +295 -11
  11. package/dist/orchestrator/src/cli/coStatusAttachCliShell.js +402 -0
  12. package/dist/orchestrator/src/cli/coStatusCliShell.js +451 -0
  13. package/dist/orchestrator/src/cli/coStatusOperatorAutopilotCliShell.js +120 -0
  14. package/dist/orchestrator/src/cli/codexCliShell.js +119 -0
  15. package/dist/orchestrator/src/cli/codexDefaultsSetup.js +265 -36
  16. package/dist/orchestrator/src/cli/config/delegationConfig.js +317 -5
  17. package/dist/orchestrator/src/cli/config/repoConfigPolicy.js +2 -3
  18. package/dist/orchestrator/src/cli/config/userConfig.js +28 -13
  19. package/dist/orchestrator/src/cli/control/authenticatedControlRouteGate.js +69 -0
  20. package/dist/orchestrator/src/cli/control/authenticatedRouteComposition.js +267 -0
  21. package/dist/orchestrator/src/cli/control/authenticatedRouteController.js +5 -0
  22. package/dist/orchestrator/src/cli/control/authenticatedRouteDispatcher.js +41 -0
  23. package/dist/orchestrator/src/cli/control/compatibilityIssuePresenter.js +1035 -0
  24. package/dist/orchestrator/src/cli/control/confirmationApproveController.js +62 -0
  25. package/dist/orchestrator/src/cli/control/confirmationCreateController.js +69 -0
  26. package/dist/orchestrator/src/cli/control/confirmationIssueConsumeController.js +43 -0
  27. package/dist/orchestrator/src/cli/control/confirmationListController.js +22 -0
  28. package/dist/orchestrator/src/cli/control/confirmationValidateController.js +58 -0
  29. package/dist/orchestrator/src/cli/control/confirmations.js +25 -3
  30. package/dist/orchestrator/src/cli/control/controlActionCancelConfirmation.js +65 -0
  31. package/dist/orchestrator/src/cli/control/controlActionController.js +77 -0
  32. package/dist/orchestrator/src/cli/control/controlActionControllerSequencing.js +161 -0
  33. package/dist/orchestrator/src/cli/control/controlActionExecution.js +142 -0
  34. package/dist/orchestrator/src/cli/control/controlActionFinalization.js +43 -0
  35. package/dist/orchestrator/src/cli/control/controlActionOutcome.js +60 -0
  36. package/dist/orchestrator/src/cli/control/controlActionPreflight.js +476 -0
  37. package/dist/orchestrator/src/cli/control/controlAuthenticatedRouteHandoff.js +57 -0
  38. package/dist/orchestrator/src/cli/control/controlBootstrapAssembly.js +39 -0
  39. package/dist/orchestrator/src/cli/control/controlBootstrapMetadataPersistence.js +16 -0
  40. package/dist/orchestrator/src/cli/control/controlEventTransport.js +49 -0
  41. package/dist/orchestrator/src/cli/control/controlExpiryLifecycle.js +102 -0
  42. package/dist/orchestrator/src/cli/control/controlHostOwnership.js +480 -0
  43. package/dist/orchestrator/src/cli/control/controlHostSupervision.js +630 -0
  44. package/dist/orchestrator/src/cli/control/controlOversightFacade.js +8 -0
  45. package/dist/orchestrator/src/cli/control/controlOversightReadContract.js +1 -0
  46. package/dist/orchestrator/src/cli/control/controlOversightReadService.js +16 -0
  47. package/dist/orchestrator/src/cli/control/controlOversightUpdateContract.js +1 -0
  48. package/dist/orchestrator/src/cli/control/controlPersistenceFiles.js +6 -0
  49. package/dist/orchestrator/src/cli/control/controlQuestionChildResolution.js +18 -0
  50. package/dist/orchestrator/src/cli/control/controlRequestContext.js +42 -0
  51. package/dist/orchestrator/src/cli/control/controlRequestController.js +9 -0
  52. package/dist/orchestrator/src/cli/control/controlRequestPredispatch.js +17 -0
  53. package/dist/orchestrator/src/cli/control/controlRequestRouteDispatch.js +44 -0
  54. package/dist/orchestrator/src/cli/control/controlRuntime.js +1003 -0
  55. package/dist/orchestrator/src/cli/control/controlServer.js +23 -1456
  56. package/dist/orchestrator/src/cli/control/controlServerAuditAndErrorHelpers.js +115 -0
  57. package/dist/orchestrator/src/cli/control/controlServerAuthenticatedRouteBranch.js +29 -0
  58. package/dist/orchestrator/src/cli/control/controlServerBootstrapLifecycle.js +30 -0
  59. package/dist/orchestrator/src/cli/control/controlServerBootstrapStartSequence.js +21 -0
  60. package/dist/orchestrator/src/cli/control/controlServerOwnedRuntimeLifecycle.js +67 -0
  61. package/dist/orchestrator/src/cli/control/controlServerPublicLifecycle.js +756 -0
  62. package/dist/orchestrator/src/cli/control/controlServerPublicRouteHelpers.js +86 -0
  63. package/dist/orchestrator/src/cli/control/controlServerReadyInstanceLifecycle.js +25 -0
  64. package/dist/orchestrator/src/cli/control/controlServerReadyInstanceStartup.js +18 -0
  65. package/dist/orchestrator/src/cli/control/controlServerRequestBodyHelpers.js +37 -0
  66. package/dist/orchestrator/src/cli/control/controlServerRequestShell.js +40 -0
  67. package/dist/orchestrator/src/cli/control/controlServerRequestShellBinding.js +17 -0
  68. package/dist/orchestrator/src/cli/control/controlServerSeedLoading.js +27 -0
  69. package/dist/orchestrator/src/cli/control/controlServerSeededRuntimeAssembly.js +186 -0
  70. package/dist/orchestrator/src/cli/control/controlServerStartupInputPreparation.js +31 -0
  71. package/dist/orchestrator/src/cli/control/controlServerStartupSequence.js +49 -0
  72. package/dist/orchestrator/src/cli/control/controlState.js +233 -2
  73. package/dist/orchestrator/src/cli/control/controlStatusDashboard.js +1904 -0
  74. package/dist/orchestrator/src/cli/control/controlTelegramBridgeBootstrapLifecycle.js +22 -0
  75. package/dist/orchestrator/src/cli/control/controlTelegramBridgeLifecycle.js +67 -0
  76. package/dist/orchestrator/src/cli/control/controlTelegramBridgeOversightFacadeFactory.js +8 -0
  77. package/dist/orchestrator/src/cli/control/controlTelegramCommandController.js +49 -0
  78. package/dist/orchestrator/src/cli/control/controlTelegramDispatchRead.js +40 -0
  79. package/dist/orchestrator/src/cli/control/controlTelegramPollingController.js +89 -0
  80. package/dist/orchestrator/src/cli/control/controlTelegramProjectionNotificationController.js +29 -0
  81. package/dist/orchestrator/src/cli/control/controlTelegramPushState.js +63 -0
  82. package/dist/orchestrator/src/cli/control/controlTelegramQuestionRead.js +13 -0
  83. package/dist/orchestrator/src/cli/control/controlTelegramReadController.js +216 -0
  84. package/dist/orchestrator/src/cli/control/controlTelegramUpdateHandler.js +63 -0
  85. package/dist/orchestrator/src/cli/control/controlWatcher.js +73 -5
  86. package/dist/orchestrator/src/cli/control/delegationRegisterController.js +35 -0
  87. package/dist/orchestrator/src/cli/control/dynamicToolBridgePolicy.js +139 -0
  88. package/dist/orchestrator/src/cli/control/eventsSseController.js +12 -0
  89. package/dist/orchestrator/src/cli/control/linearBudgetState.js +1789 -0
  90. package/dist/orchestrator/src/cli/control/linearDispatchSource.js +1137 -0
  91. package/dist/orchestrator/src/cli/control/linearGraphqlClient.js +150 -0
  92. package/dist/orchestrator/src/cli/control/linearRateLimit.js +102 -0
  93. package/dist/orchestrator/src/cli/control/linearWebhookController.js +499 -0
  94. package/dist/orchestrator/src/cli/control/liveLinearAdvisoryRuntime.js +70 -0
  95. package/dist/orchestrator/src/cli/control/observabilityApiController.js +173 -0
  96. package/dist/orchestrator/src/cli/control/observabilityReadModel.js +500 -0
  97. package/dist/orchestrator/src/cli/control/observabilitySurface.js +284 -0
  98. package/dist/orchestrator/src/cli/control/observabilityUpdateNotifier.js +22 -0
  99. package/dist/orchestrator/src/cli/control/operatorDashboardPresenter.js +252 -0
  100. package/dist/orchestrator/src/cli/control/providerAgentCapacity.js +70 -0
  101. package/dist/orchestrator/src/cli/control/providerControlHostFreshnessGauge.js +1068 -0
  102. package/dist/orchestrator/src/cli/control/providerIntakeState.js +473 -0
  103. package/dist/orchestrator/src/cli/control/providerIssueHandoff.js +6811 -0
  104. package/dist/orchestrator/src/cli/control/providerIssueObservability.js +1348 -0
  105. package/dist/orchestrator/src/cli/control/providerIssueRetryQueue.js +84 -0
  106. package/dist/orchestrator/src/cli/control/providerLinearRuntimeProof.js +588 -0
  107. package/dist/orchestrator/src/cli/control/providerLinearScreenshotProof.js +473 -0
  108. package/dist/orchestrator/src/cli/control/providerLinearWorkerTruth.js +383 -0
  109. package/dist/orchestrator/src/cli/control/providerLinearWorkflowAudit.js +254 -0
  110. package/dist/orchestrator/src/cli/control/providerLinearWorkflowFacade.js +5573 -0
  111. package/dist/orchestrator/src/cli/control/providerLinearWorkflowStates.js +115 -0
  112. package/dist/orchestrator/src/cli/control/providerMergeCloseout.js +1868 -0
  113. package/dist/orchestrator/src/cli/control/providerOperatorAutopilot.js +1580 -0
  114. package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLifecycle.js +154 -0
  115. package/dist/orchestrator/src/cli/control/providerOperatorAutopilotLocalRolloutExecution.js +1006 -0
  116. package/dist/orchestrator/src/cli/control/providerPollingHealth.js +435 -0
  117. package/dist/orchestrator/src/cli/control/providerTerminalCleanup.js +516 -0
  118. package/dist/orchestrator/src/cli/control/providerWorkerHosts.js +191 -0
  119. package/dist/orchestrator/src/cli/control/providerWorkflowConfigStore.js +515 -0
  120. package/dist/orchestrator/src/cli/control/questionChildResolutionAdapter.js +361 -0
  121. package/dist/orchestrator/src/cli/control/questionQueueController.js +181 -0
  122. package/dist/orchestrator/src/cli/control/questionReadRetryDeduplication.js +9 -0
  123. package/dist/orchestrator/src/cli/control/questionReadSequence.js +10 -0
  124. package/dist/orchestrator/src/cli/control/securityViolationController.js +27 -0
  125. package/dist/orchestrator/src/cli/control/selectedRunProjection.js +1885 -0
  126. package/dist/orchestrator/src/cli/control/telegramOversightApiClient.js +48 -0
  127. package/dist/orchestrator/src/cli/control/telegramOversightBridge.js +180 -0
  128. package/dist/orchestrator/src/cli/control/telegramOversightBridgeProjectionDeliveryQueue.js +25 -0
  129. package/dist/orchestrator/src/cli/control/telegramOversightBridgeRuntimeLifecycle.js +45 -0
  130. package/dist/orchestrator/src/cli/control/telegramOversightBridgeStateStore.js +77 -0
  131. package/dist/orchestrator/src/cli/control/telegramOversightControlActionApiClient.js +45 -0
  132. package/dist/orchestrator/src/cli/control/trackerDispatchPilot.js +439 -0
  133. package/dist/orchestrator/src/cli/control/uiDataController.js +34 -0
  134. package/dist/orchestrator/src/cli/control/uiSessionController.js +100 -0
  135. package/dist/orchestrator/src/cli/controlHostCliShell.js +860 -0
  136. package/dist/orchestrator/src/cli/controlHostFreshnessGaugeCliShell.js +129 -0
  137. package/dist/orchestrator/src/cli/controlHostSupervisionCliShell.js +2127 -0
  138. package/dist/orchestrator/src/cli/delegationCliShell.js +62 -0
  139. package/dist/orchestrator/src/cli/delegationServer.js +567 -678
  140. package/dist/orchestrator/src/cli/delegationServerCliShell.js +52 -0
  141. package/dist/orchestrator/src/cli/delegationServerQuestionFlowShell.js +228 -0
  142. package/dist/orchestrator/src/cli/delegationServerToolDispatchShell.js +411 -0
  143. package/dist/orchestrator/src/cli/delegationServerTransport.js +274 -0
  144. package/dist/orchestrator/src/cli/delegationSetup.js +51 -171
  145. package/dist/orchestrator/src/cli/devtoolsCliShell.js +34 -0
  146. package/dist/orchestrator/src/cli/doctor.js +678 -164
  147. package/dist/orchestrator/src/cli/doctorCliRequestShell.js +72 -0
  148. package/dist/orchestrator/src/cli/doctorCliShell.js +138 -0
  149. package/dist/orchestrator/src/cli/doctorUsage.js +119 -15
  150. package/dist/orchestrator/src/cli/exec/experience.js +16 -2
  151. package/dist/orchestrator/src/cli/exec/summary.js +3 -0
  152. package/dist/orchestrator/src/cli/execCliShell.js +51 -0
  153. package/dist/orchestrator/src/cli/flowCliRequestShell.js +44 -0
  154. package/dist/orchestrator/src/cli/flowCliShell.js +239 -0
  155. package/dist/orchestrator/src/cli/frontendTestCliRequestShell.js +80 -0
  156. package/dist/orchestrator/src/cli/frontendTestCliShell.js +41 -0
  157. package/dist/orchestrator/src/cli/init.js +95 -1
  158. package/dist/orchestrator/src/cli/initCliShell.js +50 -0
  159. package/dist/orchestrator/src/cli/linearCliShell.js +1200 -0
  160. package/dist/orchestrator/src/cli/mcpEnableCliShell.js +132 -0
  161. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +3 -2
  162. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +56 -0
  163. package/dist/orchestrator/src/cli/orchestrator.js +66 -1376
  164. package/dist/orchestrator/src/cli/planCliShell.js +19 -0
  165. package/dist/orchestrator/src/cli/prCliShell.js +41 -0
  166. package/dist/orchestrator/src/cli/providerLinearChildLanePhaseContract.js +204 -0
  167. package/dist/orchestrator/src/cli/providerLinearChildLaneRunner.js +1835 -0
  168. package/dist/orchestrator/src/cli/providerLinearChildLaneShell.js +2420 -0
  169. package/dist/orchestrator/src/cli/providerLinearChildStreamShell.js +385 -0
  170. package/dist/orchestrator/src/cli/providerLinearWorkerRunner.js +6834 -0
  171. package/dist/orchestrator/src/cli/resumeCliShell.js +14 -0
  172. package/dist/orchestrator/src/cli/reviewCliLaunchShell.js +72 -0
  173. package/dist/orchestrator/src/cli/rlm/alignment.js +3 -3
  174. package/dist/orchestrator/src/cli/rlm/context.js +94 -7
  175. package/dist/orchestrator/src/cli/rlm/rlmCodexRuntimeShell.js +546 -0
  176. package/dist/orchestrator/src/cli/rlm/symbolic.js +4 -2
  177. package/dist/orchestrator/src/cli/rlmCliRequestShell.js +42 -0
  178. package/dist/orchestrator/src/cli/rlmCompletionCliShell.js +46 -0
  179. package/dist/orchestrator/src/cli/rlmLaunchCliShell.js +51 -0
  180. package/dist/orchestrator/src/cli/rlmRunner.js +83 -523
  181. package/dist/orchestrator/src/cli/run/blockMemory.js +500 -0
  182. package/dist/orchestrator/src/cli/run/manifest.js +410 -73
  183. package/dist/orchestrator/src/cli/run/manifestPersister.js +45 -14
  184. package/dist/orchestrator/src/cli/run/runMemoryController.js +216 -0
  185. package/dist/orchestrator/src/cli/run/source0.js +690 -0
  186. package/dist/orchestrator/src/cli/run/workspacePath.js +101 -0
  187. package/dist/orchestrator/src/cli/runtime/mode.js +2 -1
  188. package/dist/orchestrator/src/cli/runtime/provider.js +39 -2
  189. package/dist/orchestrator/src/cli/selfCheckCliShell.js +12 -0
  190. package/dist/orchestrator/src/cli/services/commandRunner.js +698 -18
  191. package/dist/orchestrator/src/cli/services/execRuntime.js +66 -1
  192. package/dist/orchestrator/src/cli/services/orchestratorAutoScoutEvidenceRecorder.js +71 -0
  193. package/dist/orchestrator/src/cli/services/orchestratorCloudBranchResolution.js +8 -0
  194. package/dist/orchestrator/src/cli/services/orchestratorCloudEnvironmentResolution.js +22 -0
  195. package/dist/orchestrator/src/cli/services/orchestratorCloudExecutionLifecycleShell.js +39 -0
  196. package/dist/orchestrator/src/cli/services/orchestratorCloudPromptBuilder.js +37 -0
  197. package/dist/orchestrator/src/cli/services/orchestratorCloudRouteFallbackContract.js +45 -0
  198. package/dist/orchestrator/src/cli/services/orchestratorCloudRouteShell.js +36 -0
  199. package/dist/orchestrator/src/cli/services/orchestratorCloudTargetExecutor.js +277 -0
  200. package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycle.js +98 -0
  201. package/dist/orchestrator/src/cli/services/orchestratorControlPlaneLifecycleShell.js +54 -0
  202. package/dist/orchestrator/src/cli/services/orchestratorExecutionLifecycle.js +112 -0
  203. package/dist/orchestrator/src/cli/services/orchestratorExecutionModePolicy.js +27 -0
  204. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteAdapterShell.js +59 -0
  205. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteDecisionShell.js +57 -0
  206. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouteState.js +21 -0
  207. package/dist/orchestrator/src/cli/services/orchestratorExecutionRouter.js +2 -0
  208. package/dist/orchestrator/src/cli/services/orchestratorLocalPipelineExecutor.js +149 -0
  209. package/dist/orchestrator/src/cli/services/orchestratorLocalRouteShell.js +63 -0
  210. package/dist/orchestrator/src/cli/services/orchestratorPlanShell.js +54 -0
  211. package/dist/orchestrator/src/cli/services/orchestratorPlanTargetTracker.js +16 -0
  212. package/dist/orchestrator/src/cli/services/orchestratorResumePreparationShell.js +84 -0
  213. package/dist/orchestrator/src/cli/services/orchestratorResumeTokenValidation.js +15 -0
  214. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleCompletion.js +31 -0
  215. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleExecutionRegistration.js +37 -0
  216. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleOrchestrationShell.js +83 -0
  217. package/dist/orchestrator/src/cli/services/orchestratorRunLifecycleTaskManagerShell.js +37 -0
  218. package/dist/orchestrator/src/cli/services/orchestratorRuntimeManifestMutation.js +20 -0
  219. package/dist/orchestrator/src/cli/services/orchestratorStartPreparationShell.js +56 -0
  220. package/dist/orchestrator/src/cli/services/orchestratorStatusShell.js +70 -0
  221. package/dist/orchestrator/src/cli/services/pipelineResolver.js +7 -3
  222. package/dist/orchestrator/src/cli/services/plannerMemory.js +119 -0
  223. package/dist/orchestrator/src/cli/services/runPreparation.js +7 -3
  224. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +9 -0
  225. package/dist/orchestrator/src/cli/setupBootstrapShell.js +114 -0
  226. package/dist/orchestrator/src/cli/setupCliShell.js +51 -0
  227. package/dist/orchestrator/src/cli/skillsCliShell.js +56 -0
  228. package/dist/orchestrator/src/cli/startCliRequestShell.js +53 -0
  229. package/dist/orchestrator/src/cli/startCliShell.js +68 -0
  230. package/dist/orchestrator/src/cli/statusCliShell.js +22 -0
  231. package/dist/orchestrator/src/cli/utils/authProvenanceFingerprint.js +27 -0
  232. package/dist/orchestrator/src/cli/utils/cloudPreflight.js +285 -7
  233. package/dist/orchestrator/src/cli/utils/codexFeatures.js +60 -0
  234. package/dist/orchestrator/src/cli/utils/delegationConfigParser.js +250 -0
  235. package/dist/orchestrator/src/cli/utils/delegationMcpHealth.js +1382 -0
  236. package/dist/orchestrator/src/cli/utils/devtools.js +2 -54
  237. package/dist/orchestrator/src/cli/utils/mcpServerEntry.js +53 -0
  238. package/dist/orchestrator/src/cli/utils/packageProgramResolver.js +151 -0
  239. package/dist/orchestrator/src/cli/utils/providerOverrideEnv.js +71 -0
  240. package/dist/orchestrator/src/cli/utils/trailingJsonObject.js +59 -0
  241. package/dist/orchestrator/src/learning/crystalizer.js +2 -2
  242. package/dist/orchestrator/src/manager.js +74 -4
  243. package/dist/orchestrator/src/persistence/ExperienceStore.js +233 -49
  244. package/dist/orchestrator/src/persistence/TaskStateStore.js +6 -6
  245. package/dist/orchestrator/src/persistence/lockFile.js +70 -4
  246. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +39 -0
  247. package/dist/orchestrator/src/sync/createCloudSyncWorker.js +3 -2
  248. package/dist/orchestrator/src/utils/atomicWrite.js +17 -2
  249. package/dist/packages/orchestrator/src/exec/unified-exec.js +99 -6
  250. package/dist/packages/orchestrator/src/instructions/promptPacks.js +150 -19
  251. package/dist/packages/sdk-node/src/orchestrator.js +137 -13
  252. package/dist/packages/shared/config/designConfig.js +8 -1
  253. package/dist/packages/shared/streams/stdio.js +1 -1
  254. package/dist/scripts/design/pipeline/permit.js +15 -0
  255. package/dist/scripts/lib/docs-catalog.js +399 -0
  256. package/dist/scripts/lib/docs-helpers.js +87 -5
  257. package/dist/scripts/lib/pr-watch-merge.js +1088 -80
  258. package/dist/scripts/lib/provider-run-contract.js +26 -0
  259. package/dist/scripts/lib/review-command-intent-classification.js +532 -0
  260. package/dist/scripts/lib/review-command-probe-classification.js +385 -0
  261. package/dist/scripts/lib/review-execution-boundary-preflight.js +279 -0
  262. package/dist/scripts/lib/review-execution-runtime.js +753 -0
  263. package/dist/scripts/lib/review-execution-state.js +1144 -0
  264. package/dist/scripts/lib/review-execution-telemetry.js +215 -0
  265. package/dist/scripts/lib/review-inspection-target-parsing.js +78 -0
  266. package/dist/scripts/lib/review-launch-attempt.js +601 -0
  267. package/dist/scripts/lib/review-meta-surface-boundary-analysis.js +300 -0
  268. package/dist/scripts/lib/review-meta-surface-normalization.js +746 -0
  269. package/dist/scripts/lib/review-non-interactive-handoff.js +61 -0
  270. package/dist/scripts/lib/review-prompt-context.js +376 -0
  271. package/dist/scripts/lib/review-scope-advisory.js +286 -0
  272. package/dist/scripts/lib/review-scope-paths.js +123 -0
  273. package/dist/scripts/lib/review-shell-command-parser.js +389 -0
  274. package/dist/scripts/lib/review-shell-env-interpreter.js +340 -0
  275. package/dist/scripts/lib/run-manifests.js +192 -36
  276. package/dist/scripts/lib/spark-policy-classifier.js +593 -0
  277. package/dist/scripts/run-review.js +507 -1777
  278. package/docs/README.md +43 -20
  279. package/docs/book/README.md +19 -0
  280. package/docs/book/codex-cli-0124-adoption.md +68 -0
  281. package/docs/book/local-hook-impact.md +73 -0
  282. package/docs/book/operations.md +60 -0
  283. package/docs/book/public-posture.md +34 -0
  284. package/docs/book/setup.md +91 -0
  285. package/docs/book/skills.md +11 -0
  286. package/docs/guides/codex-version-policy.md +104 -0
  287. package/docs/public/downstream-setup.md +113 -0
  288. package/docs/public/provider-onboarding.md +173 -0
  289. package/package.json +23 -10
  290. package/plugins/codex-orchestrator/.codex-plugin/plugin.json +30 -0
  291. package/plugins/codex-orchestrator/.mcp.json +13 -0
  292. package/plugins/codex-orchestrator/launcher.mjs +361 -0
  293. package/schemas/manifest.json +411 -0
  294. package/skills/README.md +26 -0
  295. package/skills/collab-subagents-first/SKILL.md +1 -1
  296. package/skills/delegation-usage/DELEGATION_GUIDE.md +30 -12
  297. package/skills/delegation-usage/SKILL.md +25 -14
  298. package/skills/land/SKILL.md +77 -0
  299. package/skills/linear/SKILL.md +255 -0
  300. package/skills/release/SKILL.md +47 -3
  301. package/skills/standalone-review/SKILL.md +6 -1
  302. package/templates/README.md +4 -2
  303. package/templates/codex/.codex/agents/awaiter-high.toml +2 -2
  304. package/templates/codex/.codex/agents/worker-complex.toml +1 -1
  305. package/templates/codex/.codex/config.toml +3 -4
  306. package/templates/codex/.codex/providers/README.md +13 -0
  307. package/templates/codex/.codex/providers/control.example.json +18 -0
  308. package/templates/codex/.codex/providers/provider.env.example +15 -0
  309. package/templates/codex/AGENTS.md +15 -8
  310. package/templates/codex/mcp-client.json +5 -1
  311. package/docs/assets/setup.gif +0 -0
@@ -0,0 +1,1789 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import process from 'node:process';
5
+ import { acquireLockWithRetry } from '../../persistence/lockFile.js';
6
+ import { writeJsonAtomic } from '../utils/fs.js';
7
+ import { resolveCodexOrchestratorHome } from '../utils/codexPaths.js';
8
+ import { resolveLinearApiTokenFingerprint, resolveLinearRequestTimeoutMs } from './linearGraphqlClient.js';
9
+ import { extractLinearRateLimitDetailsFromHeaders } from './linearRateLimit.js';
10
+ const LINEAR_BUDGET_STATE_SCHEMA_VERSION = 2;
11
+ const LINEAR_BUDGET_ALIAS_SCHEMA_VERSION = 1;
12
+ const LINEAR_BUDGET_STATE_DIRNAME = 'linear-budget';
13
+ const LINEAR_BUDGET_SCOPE_DIRNAME = 'scopes';
14
+ const LINEAR_BUDGET_ALIAS_DIRNAME = 'aliases';
15
+ const LINEAR_BUDGET_DEFAULT_CONSTRAINED_POLL_INTERVAL_MS = 30_000;
16
+ const LINEAR_BUDGET_DEFAULT_LOW_POLL_INTERVAL_MS = 60_000;
17
+ const LINEAR_BUDGET_DEFAULT_ENDPOINT_CONSTRAINED_POLL_INTERVAL_MS = 45_000;
18
+ const LINEAR_BUDGET_DEFAULT_ENDPOINT_LOW_POLL_INTERVAL_MS = 90_000;
19
+ const LINEAR_BUDGET_REQUEST_HEADROOM_RESERVE_RATIO = 0.01;
20
+ const LINEAR_BUDGET_REQUEST_HEADROOM_RESERVE_MAX = 50;
21
+ const LINEAR_BUDGET_ENDPOINT_REQUEST_HEADROOM_RESERVE_RATIO = 0.05;
22
+ const LINEAR_BUDGET_ENDPOINT_REQUEST_HEADROOM_RESERVE_MAX = 5;
23
+ const LINEAR_BUDGET_REQUEST_BURN_HISTORY_LIMIT = 64;
24
+ const LINEAR_BUDGET_UNKNOWN_RESET_EXHAUSTED_GRACE_MS = LINEAR_BUDGET_DEFAULT_LOW_POLL_INTERVAL_MS;
25
+ const LINEAR_BUDGET_LOCK_RETRY = {
26
+ maxAttempts: 25,
27
+ initialDelayMs: 10,
28
+ backoffFactor: 1.5,
29
+ maxDelayMs: 250,
30
+ staleMs: 30_000
31
+ };
32
+ const LINEAR_BUDGET_RESERVATION_DEFAULT_TTL_GRACE_MS = 5_000;
33
+ const LINEAR_BUDGET_POLL_JITTER_RATIO = 0.1;
34
+ const LINEAR_BUDGET_POLL_JITTER_MAX_MS = 10_000;
35
+ export async function readSharedLinearBudgetStatus(env = process.env, options = {}) {
36
+ const paths = resolveLinearBudgetStatePaths(env);
37
+ if (!paths) {
38
+ return null;
39
+ }
40
+ const persisted = await readNewestPersistedLinearBudgetStatus(paths, null);
41
+ return persisted ? hydrateLinearBudgetStatus(persisted, options) : null;
42
+ }
43
+ export async function recordLinearBudgetHeadersObservation(input) {
44
+ const details = extractLinearRateLimitDetailsFromHeaders(input.headers);
45
+ if (!hasRecordableLinearBudgetDetails(details)) {
46
+ return await readSharedLinearBudgetStatus(input.env ?? process.env);
47
+ }
48
+ return await recordLinearBudgetObservation({
49
+ env: input.env,
50
+ source: input.source,
51
+ details,
52
+ observedAt: input.observedAt,
53
+ scope: input.scope,
54
+ assumeUnknownResetsExhausted: false
55
+ });
56
+ }
57
+ export async function recordLinearBudgetRateLimitObservation(input) {
58
+ if (!hasRecordableLinearBudgetDetails(input.rateLimit.details)) {
59
+ return await readSharedLinearBudgetStatus(input.env ?? process.env);
60
+ }
61
+ return await recordLinearBudgetObservation({
62
+ env: input.env,
63
+ source: input.source,
64
+ details: input.rateLimit.details,
65
+ observedAt: input.observedAt,
66
+ scope: input.scope,
67
+ assumeUnknownResetsExhausted: true
68
+ });
69
+ }
70
+ export function resolveLinearBudgetPreflight(input) {
71
+ const budget = input.budget;
72
+ if (!budget) {
73
+ return { ok: true };
74
+ }
75
+ const minimumRequestsRemaining = normalizePositiveInteger(input.minimum_requests_remaining) ?? 1;
76
+ const inferredComplexityFloor = normalizePositiveInteger(input.minimum_complexity_remaining) ?? inferOperationComplexityFloor(budget.request_complexity, minimumRequestsRemaining);
77
+ const details = buildSharedLinearRateLimitDetails(budget, {
78
+ shared_budget_fail_fast: true,
79
+ operation: input.operation,
80
+ ...(input.allow_below_request_reserve === true
81
+ ? {
82
+ shared_budget_request_headroom_override: 'allow_below_request_reserve'
83
+ }
84
+ : {})
85
+ });
86
+ if (budget.cooldown_active) {
87
+ return {
88
+ ok: false,
89
+ error: {
90
+ code: 'linear_rate_limited',
91
+ message: 'Linear shared budget cooldown is active.',
92
+ status: 429,
93
+ retryable: true,
94
+ details
95
+ }
96
+ };
97
+ }
98
+ const requestShortfall = resolveBucketShortfall([
99
+ ['requests', budget.requests],
100
+ ['endpoint_requests', budget.endpoint_requests]
101
+ ], minimumRequestsRemaining);
102
+ if (requestShortfall) {
103
+ return {
104
+ ok: false,
105
+ error: {
106
+ code: 'linear_rate_limited',
107
+ message: `Linear shared budget is insufficient for ${input.operation}.`,
108
+ status: 429,
109
+ retryable: true,
110
+ details: {
111
+ ...details,
112
+ required_requests_remaining: minimumRequestsRemaining,
113
+ shortfall_bucket: requestShortfall.bucket,
114
+ shortfall_remaining: requestShortfall.remaining
115
+ }
116
+ }
117
+ };
118
+ }
119
+ if (inferredComplexityFloor !== null) {
120
+ const complexityShortfall = resolveBucketShortfall([
121
+ ['complexity', budget.complexity],
122
+ ['endpoint_complexity', budget.endpoint_complexity]
123
+ ], inferredComplexityFloor);
124
+ if (complexityShortfall) {
125
+ return {
126
+ ok: false,
127
+ error: {
128
+ code: 'linear_rate_limited',
129
+ message: `Linear shared complexity budget is insufficient for ${input.operation}.`,
130
+ status: 429,
131
+ retryable: true,
132
+ details: {
133
+ ...details,
134
+ required_complexity_remaining: inferredComplexityFloor,
135
+ shortfall_bucket: complexityShortfall.bucket,
136
+ shortfall_remaining: complexityShortfall.remaining
137
+ }
138
+ }
139
+ };
140
+ }
141
+ }
142
+ if (input.allow_below_request_reserve !== true) {
143
+ const requestReserveShortfall = resolveRequestReserveShortfall([
144
+ ['requests', budget.requests],
145
+ ['endpoint_requests', budget.endpoint_requests]
146
+ ], minimumRequestsRemaining);
147
+ if (requestReserveShortfall) {
148
+ return {
149
+ ok: false,
150
+ error: {
151
+ code: 'linear_rate_limited',
152
+ message: `Linear shared request headroom reserve is insufficient for ${input.operation}.`,
153
+ status: 429,
154
+ retryable: true,
155
+ details: {
156
+ ...details,
157
+ required_requests_remaining: minimumRequestsRemaining,
158
+ request_headroom_reserve_bucket: requestReserveShortfall.bucket,
159
+ request_headroom_remaining: requestReserveShortfall.remaining,
160
+ request_headroom_reserve: requestReserveShortfall.reserve,
161
+ request_headroom_usable_remaining: Math.max(0, requestReserveShortfall.usable_remaining)
162
+ }
163
+ }
164
+ };
165
+ }
166
+ }
167
+ const exhaustedBucket = findExhaustedLinearBudgetBucket(budget);
168
+ if (exhaustedBucket) {
169
+ return {
170
+ ok: false,
171
+ error: {
172
+ code: 'linear_rate_limited',
173
+ message: 'Linear shared budget is exhausted.',
174
+ status: 429,
175
+ retryable: true,
176
+ details: {
177
+ ...details,
178
+ exhausted_bucket: exhaustedBucket
179
+ }
180
+ }
181
+ };
182
+ }
183
+ return { ok: true };
184
+ }
185
+ export async function reserveLinearBudgetReservation(input) {
186
+ const env = input.env ?? process.env;
187
+ const paths = resolveLinearBudgetStatePaths(env);
188
+ if (!paths) {
189
+ return {
190
+ ok: true,
191
+ budget: null,
192
+ reservation: null
193
+ };
194
+ }
195
+ const requestUnits = normalizePositiveInteger(input.request_units) ?? 1;
196
+ const reservationTtlMs = normalizePositiveInteger(input.ttl_ms) ??
197
+ resolveLinearRequestTimeoutMs(env) + LINEAR_BUDGET_RESERVATION_DEFAULT_TTL_GRACE_MS;
198
+ const lockScopeKey = await resolveLinearBudgetLockScopeKey(paths);
199
+ return await withLinearBudgetStateLock(paths, async () => {
200
+ const persisted = await readNewestPersistedLinearBudgetStatus(paths, null);
201
+ if (!persisted) {
202
+ return {
203
+ ok: true,
204
+ budget: null,
205
+ reservation: null
206
+ };
207
+ }
208
+ const cleanedPersisted = pruneExpiredReservations(persisted);
209
+ const selectedBudget = hydrateLinearBudgetStatus(cleanedPersisted, {
210
+ operation: input.operation
211
+ });
212
+ const inferredComplexityFloor = normalizePositiveInteger(input.minimum_complexity_remaining) ??
213
+ inferOperationComplexityFloor(selectedBudget.request_complexity, requestUnits);
214
+ const preflight = resolveLinearBudgetPreflight({
215
+ budget: selectedBudget,
216
+ operation: input.operation,
217
+ minimum_requests_remaining: normalizePositiveInteger(input.minimum_requests_remaining) ?? requestUnits,
218
+ minimum_complexity_remaining: inferredComplexityFloor,
219
+ allow_below_request_reserve: input.allow_below_request_reserve === true
220
+ });
221
+ if (!preflight.ok) {
222
+ return {
223
+ ok: false,
224
+ error: preflight.error
225
+ };
226
+ }
227
+ const shouldReserve = selectedBudget.requests !== null ||
228
+ selectedBudget.complexity !== null ||
229
+ selectedBudget.endpoint_requests !== null ||
230
+ selectedBudget.endpoint_complexity !== null;
231
+ if (!shouldReserve) {
232
+ if (cleanedPersisted !== persisted) {
233
+ await writePersistedLinearBudgetStatus(paths, cleanedPersisted);
234
+ }
235
+ return {
236
+ ok: true,
237
+ budget: selectedBudget,
238
+ reservation: null
239
+ };
240
+ }
241
+ const expiresAt = new Date(Date.now() + reservationTtlMs).toISOString();
242
+ const reservationRecord = {
243
+ id: randomUUID(),
244
+ operation: input.operation,
245
+ endpoint_key: selectedBudget.selected_endpoint_key,
246
+ requests: requestUnits,
247
+ complexity: inferredComplexityFloor,
248
+ created_at: new Date().toISOString(),
249
+ expires_at: expiresAt
250
+ };
251
+ const nextPersisted = {
252
+ ...cleanedPersisted,
253
+ reservations: [...cleanedPersisted.reservations, reservationRecord]
254
+ };
255
+ await writePersistedLinearBudgetStatus(paths, nextPersisted);
256
+ const hydrated = hydrateLinearBudgetStatus(nextPersisted, {
257
+ operation: input.operation
258
+ });
259
+ return {
260
+ ok: true,
261
+ budget: hydrated,
262
+ reservation: {
263
+ id: reservationRecord.id,
264
+ endpoint_key: reservationRecord.endpoint_key,
265
+ endpoint_name: hydrated.endpoint_name,
266
+ requests: reservationRecord.requests,
267
+ complexity: reservationRecord.complexity,
268
+ release: async () => {
269
+ await releaseLinearBudgetReservation({
270
+ env,
271
+ reservationId: reservationRecord.id
272
+ });
273
+ }
274
+ }
275
+ };
276
+ }, lockScopeKey);
277
+ }
278
+ export async function releaseLinearBudgetReservation(input) {
279
+ const reservationId = normalizeOptionalString(input.reservationId);
280
+ if (!reservationId) {
281
+ return;
282
+ }
283
+ const env = input.env ?? process.env;
284
+ const paths = resolveLinearBudgetStatePaths(env);
285
+ if (!paths) {
286
+ return;
287
+ }
288
+ const lockScopeKey = await resolveLinearBudgetLockScopeKey(paths);
289
+ await withLinearBudgetStateLock(paths, async () => {
290
+ const persisted = await readNewestPersistedLinearBudgetStatus(paths, null);
291
+ if (!persisted) {
292
+ return;
293
+ }
294
+ const nextReservations = persisted.reservations.filter((entry) => entry.id !== reservationId);
295
+ if (nextReservations.length === persisted.reservations.length) {
296
+ return;
297
+ }
298
+ await writePersistedLinearBudgetStatus(paths, {
299
+ ...persisted,
300
+ reservations: nextReservations
301
+ });
302
+ }, lockScopeKey);
303
+ }
304
+ export function resolveLinearPollingInterval(input) {
305
+ const budget = input.budget;
306
+ if (!budget) {
307
+ return {
308
+ interval_ms: input.default_interval_ms,
309
+ reason: null,
310
+ linear_budget: null
311
+ };
312
+ }
313
+ const nowMs = input.nowMs ?? Date.now();
314
+ if (budget.cooldown_active) {
315
+ const cooldownUntilMs = parseIsoToMs(budget.cooldown_until);
316
+ const cooldownWaitMs = cooldownUntilMs === null ? input.default_interval_ms : Math.max(0, cooldownUntilMs - nowMs);
317
+ return {
318
+ interval_ms: Math.max(input.default_interval_ms, cooldownWaitMs),
319
+ reason: budget.suppression_reason ?? 'linear_budget_shared_cooldown',
320
+ linear_budget: budget
321
+ };
322
+ }
323
+ const pressure = resolveMaterializedBudgetPressure(budget);
324
+ const requestHeadroomGuard = resolveRequestPollingHeadroomGuard({
325
+ budget,
326
+ defaultIntervalMs: input.default_interval_ms,
327
+ nowMs
328
+ });
329
+ if (pressure.suppression === 'none') {
330
+ if (requestHeadroomGuard) {
331
+ const intervalMs = applyDeterministicPositiveJitter(requestHeadroomGuard.interval_ms, `${requestHeadroomGuard.reason}|${budget.selected_endpoint_key ?? 'global'}|${budget.observed_at}`, nowMs);
332
+ return {
333
+ interval_ms: intervalMs,
334
+ reason: requestHeadroomGuard.reason,
335
+ linear_budget: {
336
+ ...budget,
337
+ suppression: requestHeadroomGuard.suppression,
338
+ suppression_reason: requestHeadroomGuard.reason
339
+ }
340
+ };
341
+ }
342
+ return {
343
+ interval_ms: input.default_interval_ms,
344
+ reason: null,
345
+ linear_budget: budget
346
+ };
347
+ }
348
+ let baseIntervalMs = input.default_interval_ms;
349
+ if (pressure.suppression === 'constrained') {
350
+ baseIntervalMs = Math.max(input.default_interval_ms, pressure.endpoint_specific
351
+ ? LINEAR_BUDGET_DEFAULT_ENDPOINT_CONSTRAINED_POLL_INTERVAL_MS
352
+ : LINEAR_BUDGET_DEFAULT_CONSTRAINED_POLL_INTERVAL_MS);
353
+ }
354
+ else {
355
+ baseIntervalMs = Math.max(input.default_interval_ms, pressure.endpoint_specific
356
+ ? LINEAR_BUDGET_DEFAULT_ENDPOINT_LOW_POLL_INTERVAL_MS
357
+ : LINEAR_BUDGET_DEFAULT_LOW_POLL_INTERVAL_MS);
358
+ }
359
+ let reason = pressure.reason;
360
+ let suppression = pressure.suppression;
361
+ if (requestHeadroomGuard && requestHeadroomGuard.interval_ms > baseIntervalMs) {
362
+ baseIntervalMs = requestHeadroomGuard.interval_ms;
363
+ if (reason === null ||
364
+ reason.startsWith('linear_budget_requests_') ||
365
+ reason.startsWith('linear_budget_endpoint_requests_')) {
366
+ reason = requestHeadroomGuard.reason;
367
+ suppression = requestHeadroomGuard.suppression;
368
+ }
369
+ }
370
+ const intervalMs = applyDeterministicPositiveJitter(baseIntervalMs, `${reason ?? 'linear_budget'}|${budget.selected_endpoint_key ?? 'global'}|${budget.observed_at}`, nowMs);
371
+ return {
372
+ interval_ms: intervalMs,
373
+ reason,
374
+ linear_budget: {
375
+ ...budget,
376
+ suppression,
377
+ suppression_reason: reason
378
+ }
379
+ };
380
+ }
381
+ export function buildSharedLinearRateLimitDetails(budget, extraDetails = {}) {
382
+ return {
383
+ ...(budget.retry_after_seconds !== null ? { retry_after_seconds: budget.retry_after_seconds } : {}),
384
+ ...(budget.request_id ? { request_id: budget.request_id } : {}),
385
+ ...(budget.cooldown_until ? { shared_budget_cooldown_until: budget.cooldown_until } : {}),
386
+ shared_budget_observed_at: budget.observed_at,
387
+ shared_budget_source: budget.source,
388
+ shared_budget_cooldown_active: budget.cooldown_active,
389
+ shared_budget_suppression: budget.suppression,
390
+ shared_budget_scope_kind: budget.scope_kind,
391
+ shared_budget_scope_key: budget.scope_key,
392
+ ...(budget.viewer_id ? { shared_budget_viewer_id: budget.viewer_id } : {}),
393
+ ...(budget.workspace_id ? { shared_budget_workspace_id: budget.workspace_id } : {}),
394
+ ...(budget.suppression_reason ? { shared_budget_suppression_reason: budget.suppression_reason } : {}),
395
+ ...(budget.endpoint_name ? { endpoint_name: budget.endpoint_name } : {}),
396
+ ...(budget.selected_endpoint_key ? { selected_endpoint_key: budget.selected_endpoint_key } : {}),
397
+ ...(budget.request_complexity !== null ? { request_complexity: budget.request_complexity } : {}),
398
+ ...(budget.reservations_active > 0 ? { shared_budget_reservations_active: budget.reservations_active } : {}),
399
+ ...serializeLinearBudgetBucketDetails('requests', budget.requests),
400
+ ...serializeLinearBudgetBucketDetails('endpoint_requests', budget.endpoint_requests),
401
+ ...serializeLinearBudgetBucketDetails('complexity', budget.complexity),
402
+ ...serializeLinearBudgetBucketDetails('endpoint_complexity', budget.endpoint_complexity),
403
+ ...extraDetails
404
+ };
405
+ }
406
+ async function recordLinearBudgetObservation(input) {
407
+ const env = input.env ?? process.env;
408
+ const paths = resolveLinearBudgetStatePaths(env);
409
+ if (!paths) {
410
+ return null;
411
+ }
412
+ const observedAt = normalizeOptionalString(input.observedAt) ?? new Date().toISOString();
413
+ const observation = buildLinearBudgetObservation({
414
+ details: input.details,
415
+ source: input.source,
416
+ observedAt,
417
+ assumeUnknownResetsExhausted: input.assumeUnknownResetsExhausted
418
+ });
419
+ const lockScopeKey = await resolveLinearBudgetLockScopeKey(paths, input.scope);
420
+ return await withLinearBudgetStateLock(paths, async () => {
421
+ const existing = await readNewestPersistedLinearBudgetStatus(paths, normalizeScopeHint(input.scope));
422
+ const scope = await resolveWriteScope(paths, input.scope, existing);
423
+ const staleObservation = isStalePersistedLinearBudgetObservation(existing, observation);
424
+ const mergedWithoutHistory = mergePersistedLinearBudgetStatus(existing, observation, scope);
425
+ const merged = appendPersistedLinearBudgetRequestBurnHistory(mergedWithoutHistory, staleObservation
426
+ ? null
427
+ : buildPersistedLinearBudgetRequestBurnHistoryEntry({
428
+ env,
429
+ existing,
430
+ observation,
431
+ merged: mergedWithoutHistory
432
+ }));
433
+ await writePersistedLinearBudgetStatus(paths, merged);
434
+ if (scope.kind === 'user') {
435
+ await writePersistedLinearBudgetAlias(paths, scope);
436
+ }
437
+ return hydrateLinearBudgetStatus(merged);
438
+ }, lockScopeKey);
439
+ }
440
+ function buildLinearBudgetObservation(input) {
441
+ const requests = parseLinearBudgetBucket(input.details, 'requests');
442
+ const complexity = parseLinearBudgetBucket(input.details, 'complexity');
443
+ const requestComplexity = parseNumberLike(input.details.request_complexity);
444
+ const endpointObservation = buildLinearBudgetEndpointObservation({
445
+ details: input.details,
446
+ source: input.source,
447
+ observedAt: input.observedAt,
448
+ assumeUnknownResetsExhausted: input.assumeUnknownResetsExhausted
449
+ });
450
+ return {
451
+ observed_at: input.observedAt,
452
+ source: input.source,
453
+ request_id: normalizeOptionalString(input.details.request_id),
454
+ retry_after_seconds: parseNumberLike(input.details.retry_after_seconds),
455
+ requests,
456
+ complexity,
457
+ request_complexity: requestComplexity,
458
+ endpoint: endpointObservation,
459
+ assume_unknown_resets_exhausted: input.assumeUnknownResetsExhausted
460
+ };
461
+ }
462
+ function buildLinearBudgetEndpointObservation(input) {
463
+ const endpointName = normalizeOptionalString(input.details.endpoint_name);
464
+ const requests = parseLinearBudgetBucket(input.details, 'endpoint_requests');
465
+ const complexity = parseLinearBudgetBucket(input.details, 'endpoint_complexity');
466
+ const requestComplexity = parseNumberLike(input.details.request_complexity);
467
+ if (!endpointName && !requests && !complexity && requestComplexity === null) {
468
+ return null;
469
+ }
470
+ return {
471
+ key: buildEndpointKey(endpointName, input.source),
472
+ endpoint_name: endpointName,
473
+ aliases: [input.source],
474
+ observed_at: input.observedAt,
475
+ requests,
476
+ complexity,
477
+ request_complexity: requestComplexity,
478
+ assume_unknown_resets_exhausted: input.assumeUnknownResetsExhausted
479
+ };
480
+ }
481
+ function mergePersistedLinearBudgetStatus(existing, observation, scope) {
482
+ if (isStalePersistedLinearBudgetObservation(existing, observation)) {
483
+ return adoptPersistedScope(existing, scope);
484
+ }
485
+ const base = existing ? adoptPersistedScope(pruneExpiredReservations(existing), scope) : createEmptyPersistedLinearBudgetStatus(scope);
486
+ const endpoints = clonePersistedEndpoints(base.endpoints);
487
+ let selectedEndpointKey = base.selected_endpoint_key;
488
+ if (observation.endpoint) {
489
+ const upsertedEndpoint = upsertPersistedEndpointObservation(endpoints, observation.endpoint);
490
+ selectedEndpointKey = upsertedEndpoint.selectedEndpointKey;
491
+ }
492
+ const merged = {
493
+ ...base,
494
+ observed_at: observation.observed_at,
495
+ source: observation.source,
496
+ request_id: observation.request_id,
497
+ retry_after_seconds: observation.retry_after_seconds,
498
+ requests: mergeLinearBudgetBucket(base.requests, observation.requests),
499
+ complexity: mergeLinearBudgetBucket(base.complexity, observation.complexity),
500
+ request_complexity: observation.request_complexity ?? base.request_complexity,
501
+ selected_endpoint_key: selectedEndpointKey,
502
+ endpoints,
503
+ reservations: base.reservations
504
+ };
505
+ merged.cooldown_until = resolveCooldownUntil({
506
+ persisted: merged,
507
+ observation
508
+ });
509
+ return merged;
510
+ }
511
+ function isStalePersistedLinearBudgetObservation(existing, observation) {
512
+ const existingObservedAtMs = existing ? parseIsoToMs(existing.observed_at) : null;
513
+ const observationObservedAtMs = parseIsoToMs(observation.observed_at);
514
+ return (existing !== null &&
515
+ existingObservedAtMs !== null &&
516
+ observationObservedAtMs !== null &&
517
+ observationObservedAtMs < existingObservedAtMs);
518
+ }
519
+ function createEmptyPersistedLinearBudgetStatus(scope) {
520
+ return {
521
+ schema_version: LINEAR_BUDGET_STATE_SCHEMA_VERSION,
522
+ scope_kind: scope.kind,
523
+ scope_key: scope.key,
524
+ viewer_id: scope.viewer_id,
525
+ workspace_id: scope.workspace_id,
526
+ token_fingerprints: [scope.token_fingerprint],
527
+ observed_at: new Date().toISOString(),
528
+ source: 'unknown',
529
+ request_id: null,
530
+ retry_after_seconds: null,
531
+ cooldown_until: null,
532
+ requests: null,
533
+ complexity: null,
534
+ request_complexity: null,
535
+ selected_endpoint_key: null,
536
+ endpoints: {},
537
+ reservations: [],
538
+ request_burn_history: []
539
+ };
540
+ }
541
+ function adoptPersistedScope(persisted, scope) {
542
+ return {
543
+ ...persisted,
544
+ scope_kind: scope.kind,
545
+ scope_key: scope.key,
546
+ viewer_id: scope.viewer_id ?? persisted.viewer_id,
547
+ workspace_id: scope.workspace_id ?? persisted.workspace_id,
548
+ token_fingerprints: uniqueStrings([...persisted.token_fingerprints, scope.token_fingerprint])
549
+ };
550
+ }
551
+ function clonePersistedLinearBudgetStatus(value) {
552
+ return {
553
+ ...value,
554
+ token_fingerprints: [...value.token_fingerprints],
555
+ requests: cloneBucket(value.requests),
556
+ complexity: cloneBucket(value.complexity),
557
+ endpoints: clonePersistedEndpoints(value.endpoints),
558
+ reservations: value.reservations.map((entry) => ({ ...entry })),
559
+ request_burn_history: clonePersistedLinearBudgetRequestBurnHistory(value.request_burn_history)
560
+ };
561
+ }
562
+ function clonePersistedEndpoints(value) {
563
+ return Object.fromEntries(Object.entries(value).map(([key, endpoint]) => [
564
+ key,
565
+ {
566
+ endpoint_name: endpoint.endpoint_name,
567
+ aliases: [...endpoint.aliases],
568
+ observed_at: endpoint.observed_at,
569
+ requests: cloneBucket(endpoint.requests),
570
+ complexity: cloneBucket(endpoint.complexity),
571
+ request_complexity: endpoint.request_complexity
572
+ }
573
+ ]));
574
+ }
575
+ function upsertPersistedEndpointObservation(endpoints, observation) {
576
+ const targetKey = resolvePersistedEndpointTargetKey(endpoints, observation);
577
+ const existing = endpoints[targetKey] ?? null;
578
+ const merged = {
579
+ endpoint_name: observation.endpoint_name ?? existing?.endpoint_name ?? null,
580
+ aliases: uniqueStrings([...(existing?.aliases ?? []), ...observation.aliases]),
581
+ observed_at: maxIsoTimestamp(existing?.observed_at ?? null, observation.observed_at) ?? observation.observed_at,
582
+ requests: mergeLinearBudgetBucket(existing?.requests ?? null, observation.requests),
583
+ complexity: mergeLinearBudgetBucket(existing?.complexity ?? null, observation.complexity),
584
+ request_complexity: observation.request_complexity ?? existing?.request_complexity ?? null
585
+ };
586
+ for (const [key, endpoint] of Object.entries(endpoints)) {
587
+ if (key === targetKey) {
588
+ continue;
589
+ }
590
+ if ((observation.endpoint_name && endpoint.endpoint_name === observation.endpoint_name) ||
591
+ endpoint.aliases.some((alias) => observation.aliases.includes(alias))) {
592
+ merged.aliases = uniqueStrings([...merged.aliases, ...endpoint.aliases]);
593
+ merged.requests = mergeLinearBudgetBucket(merged.requests, endpoint.requests);
594
+ merged.complexity = mergeLinearBudgetBucket(merged.complexity, endpoint.complexity);
595
+ merged.request_complexity = merged.request_complexity ?? endpoint.request_complexity ?? null;
596
+ merged.observed_at = maxIsoTimestamp(merged.observed_at, endpoint.observed_at) ?? merged.observed_at;
597
+ delete endpoints[key];
598
+ }
599
+ }
600
+ endpoints[targetKey] = merged;
601
+ return {
602
+ selectedEndpointKey: targetKey
603
+ };
604
+ }
605
+ function resolvePersistedEndpointTargetKey(endpoints, observation) {
606
+ if (endpoints[observation.key]) {
607
+ return observation.key;
608
+ }
609
+ if (observation.endpoint_name) {
610
+ for (const [key, endpoint] of Object.entries(endpoints)) {
611
+ if (endpoint.endpoint_name === observation.endpoint_name) {
612
+ return key;
613
+ }
614
+ }
615
+ }
616
+ for (const [key, endpoint] of Object.entries(endpoints)) {
617
+ if (endpoint.aliases.some((alias) => observation.aliases.includes(alias))) {
618
+ return observation.endpoint_name ? buildEndpointKey(observation.endpoint_name, observation.aliases[0] ?? key) : key;
619
+ }
620
+ }
621
+ return observation.key;
622
+ }
623
+ function mergePersistedLinearBudgetReservations(existing, candidate) {
624
+ if (candidate.scope_kind === 'user') {
625
+ return clonePersistedLinearBudgetReservations(candidate.reservations);
626
+ }
627
+ if (existing.scope_kind === 'user') {
628
+ return clonePersistedLinearBudgetReservations(existing.reservations);
629
+ }
630
+ return clonePersistedLinearBudgetReservations(candidate.reservations);
631
+ }
632
+ function comparePersistedReservationCreatedAt(left, right) {
633
+ const leftCreatedAtMs = parseIsoToMs(left.created_at) ?? Number.NEGATIVE_INFINITY;
634
+ const rightCreatedAtMs = parseIsoToMs(right.created_at) ?? Number.NEGATIVE_INFINITY;
635
+ return leftCreatedAtMs - rightCreatedAtMs;
636
+ }
637
+ function clonePersistedLinearBudgetReservations(value) {
638
+ return value.map((entry) => ({ ...entry })).sort(comparePersistedReservationCreatedAt);
639
+ }
640
+ function clonePersistedLinearBudgetRequestBurnHistory(value) {
641
+ return [...(value ?? [])]
642
+ .map((entry) => ({ ...entry }))
643
+ .sort(comparePersistedLinearBudgetRequestBurnHistoryEntry);
644
+ }
645
+ function comparePersistedLinearBudgetRequestBurnHistoryEntry(left, right) {
646
+ const leftRecordedAtMs = parseIsoToMs(left.recorded_at) ?? parseIsoToMs(left.observed_at) ?? Number.NEGATIVE_INFINITY;
647
+ const rightRecordedAtMs = parseIsoToMs(right.recorded_at) ?? parseIsoToMs(right.observed_at) ?? Number.NEGATIVE_INFINITY;
648
+ return leftRecordedAtMs - rightRecordedAtMs;
649
+ }
650
+ function mergePersistedLinearBudgetRequestBurnHistory(existing, candidate) {
651
+ const deduped = new Map();
652
+ for (const entry of [...(existing ?? []), ...(candidate ?? [])]) {
653
+ const key = [
654
+ entry.recorded_at,
655
+ entry.observed_at,
656
+ entry.source,
657
+ entry.request_id ?? '',
658
+ entry.request_bucket ?? '',
659
+ entry.remaining ?? '',
660
+ entry.reset_at ?? ''
661
+ ].join('|');
662
+ deduped.set(key, { ...entry });
663
+ }
664
+ return [...deduped.values()]
665
+ .sort(comparePersistedLinearBudgetRequestBurnHistoryEntry)
666
+ .slice(-LINEAR_BUDGET_REQUEST_BURN_HISTORY_LIMIT);
667
+ }
668
+ function appendPersistedLinearBudgetRequestBurnHistory(persisted, entry) {
669
+ if (!entry) {
670
+ return persisted;
671
+ }
672
+ const requestBurnHistory = mergePersistedLinearBudgetRequestBurnHistory(persisted.request_burn_history, [entry]);
673
+ return {
674
+ ...persisted,
675
+ request_burn_history: requestBurnHistory
676
+ };
677
+ }
678
+ function buildPersistedLinearBudgetRequestBurnHistoryEntry(input) {
679
+ const requestSample = resolveLinearBudgetRequestBurnSample(input.existing, input.observation);
680
+ if (!requestSample) {
681
+ return null;
682
+ }
683
+ const hydrated = hydrateLinearBudgetStatus(input.merged, {
684
+ operation: input.observation.source
685
+ });
686
+ return {
687
+ recorded_at: new Date().toISOString(),
688
+ observed_at: input.observation.observed_at,
689
+ source: input.observation.source,
690
+ operation: normalizeOptionalString(input.observation.source) ?? 'unknown',
691
+ run_id: resolveLinearBudgetObservationRunId(input.env),
692
+ process_pid: process.pid,
693
+ process_title: normalizeOptionalString(process.title),
694
+ request_id: input.observation.request_id,
695
+ request_bucket: requestSample.bucket,
696
+ remaining: requestSample.remaining,
697
+ remaining_delta: requestSample.remaining_delta,
698
+ reset_at: requestSample.reset_at,
699
+ suppression_reason: hydrated.suppression_reason,
700
+ cooldown_reason: hydrated.cooldown_active ? hydrated.suppression_reason : null
701
+ };
702
+ }
703
+ function resolveLinearBudgetRequestBurnSample(existing, observation) {
704
+ if (observation.requests) {
705
+ return {
706
+ bucket: 'requests',
707
+ remaining: observation.requests.remaining,
708
+ remaining_delta: resolveLinearBudgetRemainingDelta(existing?.requests?.remaining ?? null, observation.requests.remaining),
709
+ reset_at: observation.requests.reset_at
710
+ };
711
+ }
712
+ if (observation.endpoint?.requests) {
713
+ const targetKey = existing?.endpoints
714
+ ? resolvePersistedEndpointTargetKey(clonePersistedEndpoints(existing.endpoints), observation.endpoint)
715
+ : observation.endpoint.key;
716
+ return {
717
+ bucket: 'endpoint_requests',
718
+ remaining: observation.endpoint.requests.remaining,
719
+ remaining_delta: resolveLinearBudgetRemainingDelta(existing?.endpoints[targetKey]?.requests?.remaining ?? null, observation.endpoint.requests.remaining),
720
+ reset_at: observation.endpoint.requests.reset_at
721
+ };
722
+ }
723
+ return null;
724
+ }
725
+ function resolveLinearBudgetRemainingDelta(previousRemaining, nextRemaining) {
726
+ return previousRemaining !== null && nextRemaining !== null ? nextRemaining - previousRemaining : null;
727
+ }
728
+ function resolveLinearBudgetObservationRunId(env) {
729
+ return normalizeOptionalString(env.CODEX_ORCHESTRATOR_RUN_ID)
730
+ ?? normalizeOptionalString(env.CODEX_ORCHESTRATOR_PROVIDER_CONTROL_HOST_RUN_ID);
731
+ }
732
+ function hydrateLinearBudgetStatus(persisted, options = {}) {
733
+ const cleanedReservations = pruneExpiredReservations(persisted).reservations;
734
+ const normalizedEndpoints = Object.fromEntries(Object.entries(persisted.endpoints).map(([key, endpoint]) => [
735
+ key,
736
+ {
737
+ key,
738
+ endpoint_name: endpoint.endpoint_name,
739
+ aliases: [...endpoint.aliases],
740
+ observed_at: endpoint.observed_at,
741
+ requests: normalizeExpiredLinearBudgetBucket(endpoint.requests, endpoint.observed_at, {
742
+ requestReserveBucket: 'endpoint_requests'
743
+ }),
744
+ complexity: normalizeExpiredLinearBudgetBucket(endpoint.complexity, endpoint.observed_at),
745
+ request_complexity: endpoint.request_complexity
746
+ }
747
+ ]));
748
+ const base = {
749
+ observed_at: persisted.observed_at,
750
+ source: persisted.source,
751
+ request_id: persisted.request_id,
752
+ retry_after_seconds: persisted.retry_after_seconds,
753
+ cooldown_until: persisted.cooldown_until,
754
+ cooldown_active: isFutureIsoTimestamp(persisted.cooldown_until),
755
+ suppression: 'none',
756
+ suppression_reason: null,
757
+ scope_kind: persisted.scope_kind,
758
+ scope_key: persisted.scope_key,
759
+ viewer_id: persisted.viewer_id,
760
+ workspace_id: persisted.workspace_id,
761
+ token_fingerprints: [...persisted.token_fingerprints],
762
+ requests: normalizeExpiredLinearBudgetBucket(persisted.requests, persisted.observed_at, {
763
+ requestReserveBucket: 'requests'
764
+ }),
765
+ endpoint_requests: null,
766
+ complexity: normalizeExpiredLinearBudgetBucket(persisted.complexity, persisted.observed_at),
767
+ endpoint_complexity: null,
768
+ endpoint_name: null,
769
+ selected_endpoint_key: persisted.selected_endpoint_key,
770
+ request_complexity: persisted.request_complexity,
771
+ endpoints: normalizedEndpoints,
772
+ reservations: cleanedReservations.map((entry) => ({ ...entry })),
773
+ request_burn_history: clonePersistedLinearBudgetRequestBurnHistory(persisted.request_burn_history),
774
+ reservations_active: cleanedReservations.length
775
+ };
776
+ const materialized = materializeBudgetForOperation(base, options.operation ?? null);
777
+ if (materialized.cooldown_active) {
778
+ return {
779
+ ...materialized,
780
+ suppression: 'cooldown',
781
+ suppression_reason: 'linear_budget_shared_cooldown'
782
+ };
783
+ }
784
+ const pressure = resolveMaterializedBudgetPressure(materialized);
785
+ return {
786
+ ...materialized,
787
+ suppression: pressure.suppression,
788
+ suppression_reason: pressure.reason
789
+ };
790
+ }
791
+ function materializeBudgetForOperation(budget, operation) {
792
+ const view = resolveLinearBudgetOperationView(budget, operation ?? null);
793
+ return {
794
+ ...budget,
795
+ requests: view.requests,
796
+ endpoint_requests: view.endpoint_requests,
797
+ complexity: view.complexity,
798
+ endpoint_complexity: view.endpoint_complexity,
799
+ endpoint_name: view.endpoint_name,
800
+ selected_endpoint_key: view.endpoint_key,
801
+ request_complexity: view.request_complexity,
802
+ reservations_active: view.reservations_active
803
+ };
804
+ }
805
+ function resolveLinearBudgetOperationView(budget, operation) {
806
+ const normalizedOperation = normalizeOptionalString(operation);
807
+ const matchedEndpointKeys = normalizedOperation
808
+ ? resolveMatchingEndpointKeys(budget.endpoints, normalizedOperation)
809
+ : null;
810
+ const reservations = budget.reservations.filter((entry) => isFutureIsoTimestamp(entry.expires_at));
811
+ const selectedEndpointKey = resolveSelectedEndpointKey(budget, normalizedOperation, matchedEndpointKeys, reservations);
812
+ const selectedEndpoint = selectedEndpointKey ? budget.endpoints[selectedEndpointKey] ?? null : null;
813
+ const globalRequestsReserved = reservations.reduce((sum, entry) => sum + entry.requests, 0);
814
+ const globalComplexityReserved = reservations.reduce((sum, entry) => sum + (entry.complexity ?? 0), 0);
815
+ const endpointReservations = selectedEndpointKey
816
+ ? reservations.filter((entry) => entry.endpoint_key === selectedEndpointKey)
817
+ : [];
818
+ const endpointRequestsReserved = endpointReservations.reduce((sum, entry) => sum + entry.requests, 0);
819
+ const endpointComplexityReserved = endpointReservations.reduce((sum, entry) => sum + (entry.complexity ?? 0), 0);
820
+ return {
821
+ requests: subtractReservationFromBucket(budget.requests, globalRequestsReserved),
822
+ endpoint_requests: subtractReservationFromBucket(selectedEndpoint?.requests ?? null, endpointRequestsReserved),
823
+ complexity: subtractReservationFromBucket(budget.complexity, globalComplexityReserved),
824
+ endpoint_complexity: subtractReservationFromBucket(selectedEndpoint?.complexity ?? null, endpointComplexityReserved),
825
+ endpoint_key: selectedEndpointKey,
826
+ endpoint_name: selectedEndpoint?.endpoint_name ?? null,
827
+ request_complexity: resolveOperationRequestComplexity(budget, normalizedOperation, selectedEndpoint, matchedEndpointKeys),
828
+ reservations_active: reservations.length,
829
+ reservation_count: selectedEndpoint ? endpointReservations.length : reservations.length
830
+ };
831
+ }
832
+ function resolveSelectedEndpointKey(budget, operation, matchedEndpointKeys = null, reservations = []) {
833
+ if (operation) {
834
+ return selectMostConstrainedEndpointKey(budget.endpoints, matchedEndpointKeys ?? [], reservations);
835
+ }
836
+ if (budget.selected_endpoint_key && budget.endpoints[budget.selected_endpoint_key]) {
837
+ return budget.selected_endpoint_key;
838
+ }
839
+ return selectMostConstrainedEndpointKey(budget.endpoints);
840
+ }
841
+ function selectMostConstrainedEndpointKey(endpoints, candidateKeys = Object.keys(endpoints), reservations = []) {
842
+ let selected = { key: null, rank: -1, headroom: Number.POSITIVE_INFINITY };
843
+ for (const key of candidateKeys) {
844
+ const endpoint = endpoints[key];
845
+ if (!endpoint) {
846
+ continue;
847
+ }
848
+ const endpointReservations = reservations.filter((entry) => entry.endpoint_key === key);
849
+ const requestsReserved = endpointReservations.reduce((sum, entry) => sum + entry.requests, 0);
850
+ const complexityReserved = endpointReservations.reduce((sum, entry) => sum + (entry.complexity ?? 0), 0);
851
+ const pressure = resolveEndpointPressure({
852
+ requests: subtractReservationFromBucket(endpoint.requests, requestsReserved),
853
+ complexity: subtractReservationFromBucket(endpoint.complexity, complexityReserved)
854
+ });
855
+ if (pressure.rank > selected.rank ||
856
+ (pressure.rank === selected.rank && pressure.headroom < selected.headroom)) {
857
+ selected = {
858
+ key,
859
+ rank: pressure.rank,
860
+ headroom: pressure.headroom
861
+ };
862
+ }
863
+ }
864
+ return selected.key;
865
+ }
866
+ function resolveMatchingEndpointKeys(endpoints, operation) {
867
+ const matches = new Set();
868
+ const sourceKey = buildEndpointKey(null, operation);
869
+ if (endpoints[sourceKey]) {
870
+ matches.add(sourceKey);
871
+ }
872
+ const operationPrefix = `${operation}:`;
873
+ for (const [key, endpoint] of Object.entries(endpoints)) {
874
+ if (endpoint.aliases.some((alias) => alias === operation ||
875
+ alias.startsWith(operationPrefix) ||
876
+ operation.startsWith(`${alias}:`))) {
877
+ matches.add(key);
878
+ }
879
+ }
880
+ return [...matches];
881
+ }
882
+ function resolveOperationRequestComplexity(budget, operation, selectedEndpoint, matchedEndpointKeys) {
883
+ if (!operation) {
884
+ return selectedEndpoint?.request_complexity ?? budget.request_complexity;
885
+ }
886
+ const matchedComplexities = (matchedEndpointKeys ?? [])
887
+ .map((key) => budget.endpoints[key]?.request_complexity ?? null)
888
+ .filter((value) => value !== null);
889
+ if (matchedComplexities.length === 0) {
890
+ return null;
891
+ }
892
+ return Math.max(...matchedComplexities);
893
+ }
894
+ function resolveMaterializedBudgetPressure(budget) {
895
+ let selected = resolveBucketPressure('requests', budget.requests, false);
896
+ for (const next of [
897
+ resolveBucketPressure('complexity', budget.complexity, false),
898
+ resolveBucketPressure('endpoint_requests', budget.endpoint_requests, true),
899
+ resolveBucketPressure('endpoint_complexity', budget.endpoint_complexity, true)
900
+ ]) {
901
+ if (next.rank > selected.rank) {
902
+ selected = next;
903
+ }
904
+ }
905
+ return selected;
906
+ }
907
+ function resolveEndpointPressure(endpoint) {
908
+ const selected = [
909
+ resolveBucketPressure('endpoint_requests', endpoint.requests, true),
910
+ resolveBucketPressure('endpoint_complexity', endpoint.complexity, true)
911
+ ].reduce((selected, next) => (next.rank > selected.rank ? next : selected), {
912
+ rank: 0,
913
+ suppression: 'none',
914
+ reason: null,
915
+ endpoint_specific: true
916
+ });
917
+ return {
918
+ rank: selected.rank,
919
+ headroom: resolveEndpointHeadroom(endpoint.requests, endpoint.complexity)
920
+ };
921
+ }
922
+ function resolveEndpointHeadroom(requests, complexity) {
923
+ const candidates = [requests?.remaining ?? null, complexity?.remaining ?? null].filter((value) => value !== null);
924
+ return candidates.length > 0 ? Math.min(...candidates) : Number.POSITIVE_INFINITY;
925
+ }
926
+ function resolveBucketPressure(bucketKey, bucket, endpointSpecific) {
927
+ if (!bucket || bucket.remaining === null) {
928
+ return { rank: 0, suppression: 'none', reason: null, endpoint_specific: endpointSpecific };
929
+ }
930
+ if (bucket.remaining <= 0) {
931
+ return {
932
+ rank: endpointSpecific ? 4 : 3,
933
+ suppression: 'exhausted',
934
+ reason: bucketKey === 'endpoint_requests' || bucketKey === 'endpoint_complexity'
935
+ ? `linear_budget_${bucketKey}_exhausted`
936
+ : `linear_budget_${bucketKey}_exhausted`,
937
+ endpoint_specific: endpointSpecific
938
+ };
939
+ }
940
+ const limit = bucket.limit;
941
+ if (limit === null || limit <= 0) {
942
+ return { rank: 0, suppression: 'none', reason: null, endpoint_specific: endpointSpecific };
943
+ }
944
+ const lowThreshold = Math.max(1, Math.min(10, Math.floor(limit * 0.02)));
945
+ const constrainedThreshold = Math.max(lowThreshold + 1, Math.min(50, Math.floor(limit * 0.1)));
946
+ if (bucket.remaining <= lowThreshold) {
947
+ return {
948
+ rank: endpointSpecific ? 3 : 2,
949
+ suppression: 'low',
950
+ reason: `linear_budget_${bucketKey}_low`,
951
+ endpoint_specific: endpointSpecific
952
+ };
953
+ }
954
+ if (bucket.remaining <= constrainedThreshold) {
955
+ return {
956
+ rank: endpointSpecific ? 2 : 1,
957
+ suppression: 'constrained',
958
+ reason: `linear_budget_${bucketKey}_constrained`,
959
+ endpoint_specific: endpointSpecific
960
+ };
961
+ }
962
+ return { rank: 0, suppression: 'none', reason: null, endpoint_specific: endpointSpecific };
963
+ }
964
+ function resolveRequestPollingHeadroomGuard(input) {
965
+ let selected = null;
966
+ for (const candidate of [
967
+ resolveRequestBucketPollingHeadroomGuard({
968
+ bucket: input.budget.requests,
969
+ bucketKey: 'requests',
970
+ endpointSpecific: false,
971
+ defaultIntervalMs: input.defaultIntervalMs,
972
+ nowMs: input.nowMs
973
+ }),
974
+ resolveRequestBucketPollingHeadroomGuard({
975
+ bucket: input.budget.endpoint_requests,
976
+ bucketKey: 'endpoint_requests',
977
+ endpointSpecific: true,
978
+ defaultIntervalMs: input.defaultIntervalMs,
979
+ nowMs: input.nowMs
980
+ })
981
+ ]) {
982
+ if (!candidate) {
983
+ continue;
984
+ }
985
+ if (!selected || candidate.interval_ms > selected.interval_ms) {
986
+ selected = candidate;
987
+ }
988
+ }
989
+ return selected;
990
+ }
991
+ function resolveRequestBucketPollingHeadroomGuard(input) {
992
+ const bucket = input.bucket;
993
+ if (!bucket || bucket.remaining === null || bucket.remaining <= 0) {
994
+ return null;
995
+ }
996
+ const resetAtMs = parseIsoToMs(bucket.reset_at);
997
+ if (resetAtMs === null || resetAtMs <= input.nowMs) {
998
+ return null;
999
+ }
1000
+ const reserve = resolveRequestPollingHeadroomReserve(bucket.limit, input.endpointSpecific);
1001
+ const usableRemaining = Math.max(1, bucket.remaining - reserve);
1002
+ const intervalFloorMs = Math.ceil((resetAtMs - input.nowMs) / usableRemaining);
1003
+ if (!Number.isFinite(intervalFloorMs) || intervalFloorMs <= input.defaultIntervalMs) {
1004
+ return null;
1005
+ }
1006
+ const lowIntervalMs = input.endpointSpecific
1007
+ ? LINEAR_BUDGET_DEFAULT_ENDPOINT_LOW_POLL_INTERVAL_MS
1008
+ : LINEAR_BUDGET_DEFAULT_LOW_POLL_INTERVAL_MS;
1009
+ return {
1010
+ interval_ms: intervalFloorMs,
1011
+ suppression: intervalFloorMs >= lowIntervalMs ? 'low' : 'constrained',
1012
+ reason: `linear_budget_${input.bucketKey}_reset_headroom`,
1013
+ endpoint_specific: input.endpointSpecific
1014
+ };
1015
+ }
1016
+ function resolveRequestPollingHeadroomReserve(limit, endpointSpecific) {
1017
+ if (limit === null || limit <= 0) {
1018
+ return 1;
1019
+ }
1020
+ const ratio = endpointSpecific
1021
+ ? LINEAR_BUDGET_ENDPOINT_REQUEST_HEADROOM_RESERVE_RATIO
1022
+ : LINEAR_BUDGET_REQUEST_HEADROOM_RESERVE_RATIO;
1023
+ const maxReserve = endpointSpecific
1024
+ ? LINEAR_BUDGET_ENDPOINT_REQUEST_HEADROOM_RESERVE_MAX
1025
+ : LINEAR_BUDGET_REQUEST_HEADROOM_RESERVE_MAX;
1026
+ return Math.max(1, Math.min(maxReserve, Math.floor(limit * ratio)));
1027
+ }
1028
+ function inferOperationComplexityFloor(requestComplexity, minimumRequestsRemaining) {
1029
+ if (requestComplexity === null) {
1030
+ return null;
1031
+ }
1032
+ return Math.max(1, requestComplexity * Math.max(1, minimumRequestsRemaining));
1033
+ }
1034
+ function resolveBucketShortfall(entries, requiredRemaining) {
1035
+ for (const [bucketKey, bucket] of entries) {
1036
+ if (bucket && bucket.remaining !== null && bucket.remaining < requiredRemaining) {
1037
+ return {
1038
+ bucket: bucketKey,
1039
+ remaining: bucket.remaining
1040
+ };
1041
+ }
1042
+ }
1043
+ return null;
1044
+ }
1045
+ function resolveRequestReserveShortfall(entries, requiredRemaining) {
1046
+ for (const [bucketKey, bucket] of entries) {
1047
+ if (!bucket || bucket.remaining === null) {
1048
+ continue;
1049
+ }
1050
+ const reserve = resolveRequestPollingHeadroomReserve(bucket.limit, bucketKey === 'endpoint_requests');
1051
+ const usableRemaining = bucket.remaining - reserve;
1052
+ if (usableRemaining < requiredRemaining) {
1053
+ return {
1054
+ bucket: bucketKey,
1055
+ remaining: bucket.remaining,
1056
+ reserve,
1057
+ usable_remaining: usableRemaining
1058
+ };
1059
+ }
1060
+ }
1061
+ return null;
1062
+ }
1063
+ function findExhaustedLinearBudgetBucket(budget) {
1064
+ for (const [bucketKey, bucket] of [
1065
+ ['requests', budget.requests],
1066
+ ['endpoint_requests', budget.endpoint_requests],
1067
+ ['complexity', budget.complexity],
1068
+ ['endpoint_complexity', budget.endpoint_complexity]
1069
+ ]) {
1070
+ if (bucket && bucket.remaining !== null && bucket.remaining <= 0) {
1071
+ return bucketKey;
1072
+ }
1073
+ }
1074
+ return null;
1075
+ }
1076
+ function parseLinearBudgetBucket(details, bucketKey) {
1077
+ const limit = parseNumberLike(details[`${bucketKey}_limit`]);
1078
+ const remaining = parseNumberLike(details[`${bucketKey}_remaining`]);
1079
+ const resetAt = normalizeOptionalString(details[`${bucketKey}_reset_at`]);
1080
+ if (limit === null && remaining === null && resetAt === null) {
1081
+ return null;
1082
+ }
1083
+ return {
1084
+ limit,
1085
+ remaining,
1086
+ reset_at: resetAt
1087
+ };
1088
+ }
1089
+ function resolveCooldownUntil(input) {
1090
+ const candidateMs = collectPersistedLinearBudgetCooldownCandidates({
1091
+ persisted: input.persisted,
1092
+ observedAt: input.observation.observed_at,
1093
+ retryAfterSeconds: input.observation.retry_after_seconds
1094
+ });
1095
+ if (input.observation.assume_unknown_resets_exhausted) {
1096
+ maybeCollectResetCandidate(candidateMs, input.observation.requests, true);
1097
+ maybeCollectResetCandidate(candidateMs, input.observation.complexity, true);
1098
+ }
1099
+ return finalizeCooldownCandidateMs(candidateMs);
1100
+ }
1101
+ function resolvePersistedLinearBudgetCooldownUntil(input) {
1102
+ return finalizeCooldownCandidateMs(collectPersistedLinearBudgetCooldownCandidates({
1103
+ persisted: input.persisted,
1104
+ observedAt: input.observedAt,
1105
+ retryAfterSeconds: input.retryAfterSeconds
1106
+ }));
1107
+ }
1108
+ function collectPersistedLinearBudgetCooldownCandidates(input) {
1109
+ const candidateMs = [];
1110
+ const observedAtMs = parseIsoToMs(input.observedAt);
1111
+ if (observedAtMs !== null && input.retryAfterSeconds !== null && input.retryAfterSeconds >= 0) {
1112
+ candidateMs.push(observedAtMs + input.retryAfterSeconds * 1000);
1113
+ }
1114
+ for (const bucket of [input.persisted.requests, input.persisted.complexity]) {
1115
+ maybeCollectResetCandidate(candidateMs, bucket, false);
1116
+ }
1117
+ return candidateMs;
1118
+ }
1119
+ function finalizeCooldownCandidateMs(candidateMs) {
1120
+ if (candidateMs.length === 0) {
1121
+ return null;
1122
+ }
1123
+ const latestMs = Math.max(...candidateMs);
1124
+ return Number.isFinite(latestMs) ? new Date(latestMs).toISOString() : null;
1125
+ }
1126
+ function maybeCollectResetCandidate(candidates, bucket, assumeUnknownRemainingExhausted) {
1127
+ if (!bucket?.reset_at) {
1128
+ return;
1129
+ }
1130
+ const resetAtMs = parseIsoToMs(bucket.reset_at);
1131
+ if (resetAtMs === null) {
1132
+ return;
1133
+ }
1134
+ const exhausted = bucket.remaining !== null ? bucket.remaining <= 0 : assumeUnknownRemainingExhausted;
1135
+ if (exhausted) {
1136
+ candidates.push(resetAtMs);
1137
+ }
1138
+ }
1139
+ function serializeLinearBudgetBucketDetails(bucketKey, bucket) {
1140
+ if (!bucket) {
1141
+ return {};
1142
+ }
1143
+ return {
1144
+ ...(bucket.limit !== null ? { [`${bucketKey}_limit`]: bucket.limit } : {}),
1145
+ ...(bucket.remaining !== null ? { [`${bucketKey}_remaining`]: bucket.remaining } : {}),
1146
+ ...(bucket.reset_at ? { [`${bucketKey}_reset_at`]: bucket.reset_at } : {})
1147
+ };
1148
+ }
1149
+ function resolveLinearBudgetStatePaths(env) {
1150
+ const tokenFingerprint = resolveLinearApiTokenFingerprint(env);
1151
+ if (!tokenFingerprint) {
1152
+ return null;
1153
+ }
1154
+ const isVitestRuntime = normalizeOptionalString(env.VITEST) === 'true' ||
1155
+ normalizeOptionalString(process.env.VITEST) === 'true' ||
1156
+ normalizeOptionalString(env.NODE_ENV) === 'test' ||
1157
+ normalizeOptionalString(process.env.NODE_ENV) === 'test';
1158
+ if (isVitestRuntime && !normalizeOptionalString(env.CODEX_HOME)) {
1159
+ return null;
1160
+ }
1161
+ const directory = join(resolveCodexOrchestratorHome(env), LINEAR_BUDGET_STATE_DIRNAME);
1162
+ return {
1163
+ directory,
1164
+ scopesDir: join(directory, LINEAR_BUDGET_SCOPE_DIRNAME),
1165
+ aliasesDir: join(directory, LINEAR_BUDGET_ALIAS_DIRNAME),
1166
+ legacyStatePath: join(directory, `${tokenFingerprint}.json`),
1167
+ aliasPath: join(directory, LINEAR_BUDGET_ALIAS_DIRNAME, `${tokenFingerprint}.json`),
1168
+ lockPath: join(directory, `${tokenFingerprint}.lock`),
1169
+ tokenFingerprint
1170
+ };
1171
+ }
1172
+ async function withLinearBudgetStateLock(paths, callback, lockScopeKey = paths.tokenFingerprint) {
1173
+ const lock = await acquireLockWithRetry({
1174
+ taskId: `linear-budget-${lockScopeKey.slice(0, 12)}`,
1175
+ lockPath: resolveLinearBudgetLockPath(paths, lockScopeKey),
1176
+ retry: LINEAR_BUDGET_LOCK_RETRY,
1177
+ ensureDirectory: async () => {
1178
+ await mkdir(dirname(resolveLinearBudgetLockPath(paths, lockScopeKey)), { recursive: true });
1179
+ },
1180
+ createError: (taskId, attempts) => new Error(`Failed to acquire Linear budget state lock for ${taskId} after ${attempts} attempts.`)
1181
+ });
1182
+ try {
1183
+ return await callback();
1184
+ }
1185
+ finally {
1186
+ await lock.release();
1187
+ }
1188
+ }
1189
+ async function resolveLinearBudgetLockScopeKey(paths, scopeHint = undefined) {
1190
+ const normalizedHint = normalizeScopeHint(scopeHint);
1191
+ if (normalizedHint?.viewer_id) {
1192
+ return resolveUserScopeKey(normalizedHint.viewer_id, normalizedHint.workspace_id);
1193
+ }
1194
+ const alias = await readPersistedLinearBudgetAlias(paths.aliasPath, paths.tokenFingerprint);
1195
+ if (alias?.scope_kind === 'user') {
1196
+ return alias.scope_key;
1197
+ }
1198
+ return paths.tokenFingerprint;
1199
+ }
1200
+ function resolveLinearBudgetLockPath(paths, lockScopeKey) {
1201
+ return join(paths.directory, `${lockScopeKey}.lock`);
1202
+ }
1203
+ async function readNewestPersistedLinearBudgetStatus(paths, scopeHint) {
1204
+ const alias = await readPersistedLinearBudgetAlias(paths.aliasPath, paths.tokenFingerprint);
1205
+ const candidatePaths = new Set([paths.legacyStatePath]);
1206
+ if (alias) {
1207
+ candidatePaths.add(resolveScopeStatePath(paths, alias.scope_key));
1208
+ }
1209
+ if (scopeHint?.viewer_id) {
1210
+ candidatePaths.add(resolveScopeStatePath(paths, resolveUserScopeKey(scopeHint.viewer_id, scopeHint.workspace_id)));
1211
+ }
1212
+ const candidates = [];
1213
+ for (const candidatePath of candidatePaths) {
1214
+ const persisted = await readPersistedLinearBudgetStatus(candidatePath, paths.tokenFingerprint);
1215
+ if (!persisted) {
1216
+ continue;
1217
+ }
1218
+ candidates.push(persisted);
1219
+ }
1220
+ return mergePersistedLinearBudgetCandidates(candidates);
1221
+ }
1222
+ function mergePersistedLinearBudgetCandidates(candidates) {
1223
+ if (candidates.length === 0) {
1224
+ return null;
1225
+ }
1226
+ const sorted = [...candidates].sort(comparePersistedLinearBudgetObservedAt);
1227
+ const preferredUserScope = [...sorted]
1228
+ .reverse()
1229
+ .find((candidate) => candidate.scope_kind === 'user' && candidate.viewer_id) ?? null;
1230
+ let merged = clonePersistedLinearBudgetStatus(sorted[0]);
1231
+ for (const candidate of sorted.slice(1)) {
1232
+ merged = mergePersistedLinearBudgetCandidate(merged, candidate);
1233
+ }
1234
+ if (!preferredUserScope) {
1235
+ return merged;
1236
+ }
1237
+ return {
1238
+ ...merged,
1239
+ scope_kind: preferredUserScope.scope_kind,
1240
+ scope_key: preferredUserScope.scope_key,
1241
+ viewer_id: preferredUserScope.viewer_id,
1242
+ workspace_id: preferredUserScope.workspace_id
1243
+ };
1244
+ }
1245
+ function comparePersistedLinearBudgetObservedAt(left, right) {
1246
+ const leftObservedAtMs = parseIsoToMs(left.observed_at) ?? Number.NEGATIVE_INFINITY;
1247
+ const rightObservedAtMs = parseIsoToMs(right.observed_at) ?? Number.NEGATIVE_INFINITY;
1248
+ return leftObservedAtMs - rightObservedAtMs;
1249
+ }
1250
+ function mergePersistedLinearBudgetCandidate(existing, candidate) {
1251
+ const endpoints = clonePersistedEndpoints(existing.endpoints);
1252
+ for (const [key, endpoint] of Object.entries(candidate.endpoints)) {
1253
+ upsertPersistedEndpointObservation(endpoints, {
1254
+ key,
1255
+ endpoint_name: endpoint.endpoint_name,
1256
+ aliases: [...endpoint.aliases],
1257
+ observed_at: endpoint.observed_at,
1258
+ requests: cloneBucket(endpoint.requests),
1259
+ complexity: cloneBucket(endpoint.complexity),
1260
+ request_complexity: endpoint.request_complexity,
1261
+ assume_unknown_resets_exhausted: false
1262
+ });
1263
+ }
1264
+ const selectedEndpointKey = candidate.selected_endpoint_key ?? existing.selected_endpoint_key;
1265
+ const merged = {
1266
+ ...clonePersistedLinearBudgetStatus(existing),
1267
+ observed_at: maxIsoTimestamp(existing.observed_at, candidate.observed_at) ?? candidate.observed_at,
1268
+ source: candidate.source,
1269
+ request_id: candidate.request_id ?? existing.request_id,
1270
+ retry_after_seconds: candidate.retry_after_seconds,
1271
+ cooldown_until: null,
1272
+ requests: mergeLinearBudgetBucket(existing.requests, candidate.requests),
1273
+ complexity: mergeLinearBudgetBucket(existing.complexity, candidate.complexity),
1274
+ request_complexity: candidate.request_complexity ?? existing.request_complexity,
1275
+ selected_endpoint_key: selectedEndpointKey && !endpoints[selectedEndpointKey] ? null : selectedEndpointKey,
1276
+ token_fingerprints: uniqueStrings([...existing.token_fingerprints, ...candidate.token_fingerprints]),
1277
+ endpoints,
1278
+ reservations: mergePersistedLinearBudgetReservations(existing, candidate),
1279
+ request_burn_history: mergePersistedLinearBudgetRequestBurnHistory(existing.request_burn_history, candidate.request_burn_history)
1280
+ };
1281
+ merged.cooldown_until = resolvePersistedLinearBudgetCooldownUntil({
1282
+ persisted: merged,
1283
+ observedAt: candidate.observed_at,
1284
+ retryAfterSeconds: candidate.retry_after_seconds
1285
+ });
1286
+ return merged;
1287
+ }
1288
+ async function readPersistedLinearBudgetStatus(statePath, tokenFingerprint) {
1289
+ try {
1290
+ const parsed = JSON.parse(await readFile(statePath, 'utf8'));
1291
+ return parsePersistedLinearBudgetStatus(parsed, tokenFingerprint);
1292
+ }
1293
+ catch (error) {
1294
+ if (error?.code === 'ENOENT') {
1295
+ return null;
1296
+ }
1297
+ return null;
1298
+ }
1299
+ }
1300
+ function parsePersistedLinearBudgetStatus(value, tokenFingerprint) {
1301
+ if (!value || typeof value !== 'object') {
1302
+ return null;
1303
+ }
1304
+ const record = value;
1305
+ if (record.schema_version === LINEAR_BUDGET_STATE_SCHEMA_VERSION) {
1306
+ return parseSchemaV2LinearBudgetStatus(record, tokenFingerprint);
1307
+ }
1308
+ if (record.schema_version === 1) {
1309
+ return parseLegacySchemaV1LinearBudgetStatus(record, tokenFingerprint);
1310
+ }
1311
+ return null;
1312
+ }
1313
+ function parseSchemaV2LinearBudgetStatus(record, tokenFingerprint) {
1314
+ const scopeKind = normalizeOptionalString(record.scope_kind);
1315
+ const scopeKey = normalizeOptionalString(record.scope_key);
1316
+ const observedAt = normalizeOptionalString(record.observed_at);
1317
+ const source = normalizeOptionalString(record.source);
1318
+ if ((scopeKind !== 'user' && scopeKind !== 'token') || !scopeKey || !observedAt || !source) {
1319
+ return null;
1320
+ }
1321
+ const tokenFingerprints = Array.isArray(record.token_fingerprints)
1322
+ ? record.token_fingerprints
1323
+ .map((entry) => normalizeOptionalString(entry))
1324
+ .filter((entry) => entry !== null)
1325
+ : [];
1326
+ if (tokenFingerprints.length === 0) {
1327
+ tokenFingerprints.push(tokenFingerprint);
1328
+ }
1329
+ const endpoints = record.endpoints && typeof record.endpoints === 'object'
1330
+ ? Object.fromEntries(Object.entries(record.endpoints)
1331
+ .map(([key, value]) => [key, parsePersistedLinearBudgetEndpointStatus(value)])
1332
+ .filter((entry) => entry[1] !== null))
1333
+ : {};
1334
+ const reservations = Array.isArray(record.reservations)
1335
+ ? record.reservations
1336
+ .map((entry) => parsePersistedLinearBudgetReservation(entry))
1337
+ .filter((entry) => entry !== null)
1338
+ : [];
1339
+ const requestBurnHistory = Array.isArray(record.request_burn_history)
1340
+ ? record.request_burn_history
1341
+ .map((entry) => parsePersistedLinearBudgetRequestBurnHistoryEntry(entry))
1342
+ .filter((entry) => entry !== null)
1343
+ : [];
1344
+ return {
1345
+ schema_version: 2,
1346
+ scope_kind: scopeKind,
1347
+ scope_key: scopeKey,
1348
+ viewer_id: normalizeOptionalString(record.viewer_id),
1349
+ workspace_id: normalizeOptionalString(record.workspace_id),
1350
+ token_fingerprints: uniqueStrings(tokenFingerprints),
1351
+ observed_at: observedAt,
1352
+ source,
1353
+ request_id: normalizeOptionalString(record.request_id),
1354
+ retry_after_seconds: parseNumberLike(record.retry_after_seconds),
1355
+ cooldown_until: normalizeOptionalString(record.cooldown_until),
1356
+ requests: parsePersistedLinearBudgetBucket(record.requests),
1357
+ complexity: parsePersistedLinearBudgetBucket(record.complexity),
1358
+ request_complexity: parseNumberLike(record.request_complexity),
1359
+ selected_endpoint_key: normalizeOptionalString(record.selected_endpoint_key),
1360
+ endpoints,
1361
+ reservations,
1362
+ request_burn_history: requestBurnHistory
1363
+ };
1364
+ }
1365
+ function parseLegacySchemaV1LinearBudgetStatus(record, tokenFingerprint) {
1366
+ if (normalizeOptionalString(record.token_fingerprint) !== tokenFingerprint) {
1367
+ return null;
1368
+ }
1369
+ const observedAt = normalizeOptionalString(record.observed_at);
1370
+ const source = normalizeOptionalString(record.source);
1371
+ if (!observedAt || !source) {
1372
+ return null;
1373
+ }
1374
+ const legacyEndpointRequests = parsePersistedLinearBudgetBucket(record.endpoint_requests);
1375
+ const legacyEndpointComplexity = parsePersistedLinearBudgetBucket(record.endpoint_complexity);
1376
+ const selectedEndpointKey = legacyEndpointRequests || legacyEndpointComplexity ? buildEndpointKey(null, source) : null;
1377
+ return {
1378
+ schema_version: 2,
1379
+ scope_kind: 'token',
1380
+ scope_key: tokenFingerprint,
1381
+ viewer_id: null,
1382
+ workspace_id: null,
1383
+ token_fingerprints: [tokenFingerprint],
1384
+ observed_at: observedAt,
1385
+ source,
1386
+ request_id: normalizeOptionalString(record.request_id),
1387
+ retry_after_seconds: parseNumberLike(record.retry_after_seconds),
1388
+ cooldown_until: normalizeOptionalString(record.cooldown_until),
1389
+ requests: parsePersistedLinearBudgetBucket(record.requests),
1390
+ complexity: parsePersistedLinearBudgetBucket(record.complexity),
1391
+ request_complexity: null,
1392
+ selected_endpoint_key: selectedEndpointKey,
1393
+ endpoints: selectedEndpointKey === null
1394
+ ? {}
1395
+ : {
1396
+ [selectedEndpointKey]: {
1397
+ endpoint_name: null,
1398
+ aliases: [source],
1399
+ observed_at: observedAt,
1400
+ requests: legacyEndpointRequests,
1401
+ complexity: legacyEndpointComplexity,
1402
+ request_complexity: null
1403
+ }
1404
+ },
1405
+ reservations: [],
1406
+ request_burn_history: []
1407
+ };
1408
+ }
1409
+ function parsePersistedLinearBudgetEndpointStatus(value) {
1410
+ if (!value || typeof value !== 'object') {
1411
+ return null;
1412
+ }
1413
+ const record = value;
1414
+ const observedAt = normalizeOptionalString(record.observed_at);
1415
+ if (!observedAt) {
1416
+ return null;
1417
+ }
1418
+ const aliases = Array.isArray(record.aliases)
1419
+ ? record.aliases
1420
+ .map((entry) => normalizeOptionalString(entry))
1421
+ .filter((entry) => entry !== null)
1422
+ : [];
1423
+ return {
1424
+ endpoint_name: normalizeOptionalString(record.endpoint_name),
1425
+ aliases: uniqueStrings(aliases),
1426
+ observed_at: observedAt,
1427
+ requests: parsePersistedLinearBudgetBucket(record.requests),
1428
+ complexity: parsePersistedLinearBudgetBucket(record.complexity),
1429
+ request_complexity: parseNumberLike(record.request_complexity)
1430
+ };
1431
+ }
1432
+ function parsePersistedLinearBudgetReservation(value) {
1433
+ if (!value || typeof value !== 'object') {
1434
+ return null;
1435
+ }
1436
+ const record = value;
1437
+ const id = normalizeRequiredString(record.id);
1438
+ const operation = normalizeRequiredString(record.operation);
1439
+ const createdAt = normalizeOptionalString(record.created_at);
1440
+ const expiresAt = normalizeOptionalString(record.expires_at);
1441
+ const requests = normalizePositiveInteger(record.requests);
1442
+ const complexity = normalizePositiveInteger(record.complexity);
1443
+ if (!id || !operation || !createdAt || !expiresAt || requests === null) {
1444
+ return null;
1445
+ }
1446
+ return {
1447
+ id,
1448
+ operation,
1449
+ endpoint_key: normalizeOptionalString(record.endpoint_key),
1450
+ requests,
1451
+ complexity,
1452
+ created_at: createdAt,
1453
+ expires_at: expiresAt
1454
+ };
1455
+ }
1456
+ function parsePersistedLinearBudgetRequestBurnHistoryEntry(value) {
1457
+ if (!value || typeof value !== 'object') {
1458
+ return null;
1459
+ }
1460
+ const record = value;
1461
+ const recordedAt = normalizeOptionalString(record.recorded_at);
1462
+ const observedAt = normalizeOptionalString(record.observed_at);
1463
+ const source = normalizeRequiredString(record.source);
1464
+ const requestBucket = normalizeOptionalString(record.request_bucket);
1465
+ if (!recordedAt || !observedAt || !source) {
1466
+ return null;
1467
+ }
1468
+ const operation = normalizeRequiredString(record.operation) ?? source;
1469
+ return {
1470
+ recorded_at: recordedAt,
1471
+ observed_at: observedAt,
1472
+ source,
1473
+ operation,
1474
+ run_id: normalizeOptionalString(record.run_id),
1475
+ process_pid: parseNumberLike(record.process_pid),
1476
+ process_title: normalizeOptionalString(record.process_title),
1477
+ request_id: normalizeOptionalString(record.request_id),
1478
+ request_bucket: requestBucket === 'requests' || requestBucket === 'endpoint_requests'
1479
+ ? requestBucket
1480
+ : null,
1481
+ remaining: parseNumberLike(record.remaining),
1482
+ remaining_delta: parseSignedInteger(record.remaining_delta),
1483
+ reset_at: normalizeOptionalString(record.reset_at),
1484
+ suppression_reason: normalizeOptionalString(record.suppression_reason),
1485
+ cooldown_reason: normalizeOptionalString(record.cooldown_reason)
1486
+ };
1487
+ }
1488
+ function parsePersistedLinearBudgetBucket(value) {
1489
+ if (!value || typeof value !== 'object') {
1490
+ return null;
1491
+ }
1492
+ const record = value;
1493
+ const limit = parseNumberLike(record.limit);
1494
+ const remaining = parseNumberLike(record.remaining);
1495
+ const resetAt = normalizeOptionalString(record.reset_at);
1496
+ if (limit === null && remaining === null && resetAt === null) {
1497
+ return null;
1498
+ }
1499
+ return {
1500
+ limit,
1501
+ remaining,
1502
+ reset_at: resetAt
1503
+ };
1504
+ }
1505
+ async function writePersistedLinearBudgetStatus(paths, persisted) {
1506
+ const statePath = resolvePersistedStatePath(paths, persisted);
1507
+ await mkdir(dirname(statePath), { recursive: true });
1508
+ await writeJsonAtomic(statePath, persisted);
1509
+ }
1510
+ async function writePersistedLinearBudgetAlias(paths, scope) {
1511
+ const aliasRecord = {
1512
+ schema_version: LINEAR_BUDGET_ALIAS_SCHEMA_VERSION,
1513
+ token_fingerprint: paths.tokenFingerprint,
1514
+ scope_kind: scope.kind,
1515
+ scope_key: scope.key,
1516
+ viewer_id: scope.viewer_id,
1517
+ workspace_id: scope.workspace_id,
1518
+ updated_at: new Date().toISOString()
1519
+ };
1520
+ await mkdir(dirname(paths.aliasPath), { recursive: true });
1521
+ await writeJsonAtomic(paths.aliasPath, aliasRecord);
1522
+ }
1523
+ async function readPersistedLinearBudgetAlias(aliasPath, tokenFingerprint) {
1524
+ try {
1525
+ const parsed = JSON.parse(await readFile(aliasPath, 'utf8'));
1526
+ if (!parsed || typeof parsed !== 'object') {
1527
+ return null;
1528
+ }
1529
+ const record = parsed;
1530
+ if (record.schema_version !== LINEAR_BUDGET_ALIAS_SCHEMA_VERSION) {
1531
+ return null;
1532
+ }
1533
+ if (normalizeOptionalString(record.token_fingerprint) !== tokenFingerprint) {
1534
+ return null;
1535
+ }
1536
+ const scopeKind = normalizeOptionalString(record.scope_kind);
1537
+ const scopeKey = normalizeOptionalString(record.scope_key);
1538
+ const updatedAt = normalizeOptionalString(record.updated_at);
1539
+ if ((scopeKind !== 'user' && scopeKind !== 'token') || !scopeKey || !updatedAt) {
1540
+ return null;
1541
+ }
1542
+ return {
1543
+ schema_version: 1,
1544
+ token_fingerprint: tokenFingerprint,
1545
+ scope_kind: scopeKind,
1546
+ scope_key: scopeKey,
1547
+ viewer_id: normalizeOptionalString(record.viewer_id),
1548
+ workspace_id: normalizeOptionalString(record.workspace_id),
1549
+ updated_at: updatedAt
1550
+ };
1551
+ }
1552
+ catch (error) {
1553
+ if (error?.code === 'ENOENT') {
1554
+ return null;
1555
+ }
1556
+ return null;
1557
+ }
1558
+ }
1559
+ async function resolveWriteScope(paths, scopeHint, existing) {
1560
+ const normalizedHint = normalizeScopeHint(scopeHint);
1561
+ if (normalizedHint?.viewer_id) {
1562
+ return {
1563
+ kind: 'user',
1564
+ key: resolveUserScopeKey(normalizedHint.viewer_id, normalizedHint.workspace_id),
1565
+ viewer_id: normalizedHint.viewer_id,
1566
+ workspace_id: normalizedHint.workspace_id,
1567
+ token_fingerprint: paths.tokenFingerprint
1568
+ };
1569
+ }
1570
+ if (existing?.scope_kind === 'user' && existing.viewer_id) {
1571
+ return {
1572
+ kind: 'user',
1573
+ key: existing.scope_key,
1574
+ viewer_id: existing.viewer_id,
1575
+ workspace_id: existing.workspace_id,
1576
+ token_fingerprint: paths.tokenFingerprint
1577
+ };
1578
+ }
1579
+ const alias = await readPersistedLinearBudgetAlias(paths.aliasPath, paths.tokenFingerprint);
1580
+ if (alias?.scope_kind === 'user' && alias.viewer_id) {
1581
+ return {
1582
+ kind: 'user',
1583
+ key: alias.scope_key,
1584
+ viewer_id: alias.viewer_id,
1585
+ workspace_id: alias.workspace_id,
1586
+ token_fingerprint: paths.tokenFingerprint
1587
+ };
1588
+ }
1589
+ return {
1590
+ kind: 'token',
1591
+ key: paths.tokenFingerprint,
1592
+ viewer_id: null,
1593
+ workspace_id: normalizedHint?.workspace_id ?? existing?.workspace_id ?? alias?.workspace_id ?? null,
1594
+ token_fingerprint: paths.tokenFingerprint
1595
+ };
1596
+ }
1597
+ function normalizeScopeHint(value) {
1598
+ if (!value) {
1599
+ return null;
1600
+ }
1601
+ const viewerId = normalizeOptionalString(value.viewerId);
1602
+ const workspaceId = normalizeOptionalString(value.workspaceId);
1603
+ if (!viewerId && !workspaceId) {
1604
+ return null;
1605
+ }
1606
+ return {
1607
+ viewer_id: viewerId,
1608
+ workspace_id: workspaceId
1609
+ };
1610
+ }
1611
+ function resolvePersistedStatePath(paths, persisted) {
1612
+ return persisted.scope_kind === 'user'
1613
+ ? resolveScopeStatePath(paths, persisted.scope_key)
1614
+ : paths.legacyStatePath;
1615
+ }
1616
+ function resolveScopeStatePath(paths, scopeKey) {
1617
+ return join(paths.scopesDir, `${scopeKey}.json`);
1618
+ }
1619
+ function resolveUserScopeKey(viewerId, workspaceId) {
1620
+ return createHash('sha256')
1621
+ .update(`linear-user:${workspaceId ?? 'unknown-workspace'}:${viewerId}`)
1622
+ .digest('hex');
1623
+ }
1624
+ function buildEndpointKey(endpointName, source) {
1625
+ if (endpointName) {
1626
+ return `endpoint:${normalizeKeyFragment(endpointName)}`;
1627
+ }
1628
+ return `source:${normalizeKeyFragment(source)}`;
1629
+ }
1630
+ function normalizeKeyFragment(value) {
1631
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/gu, '-').replace(/^-+|-+$/gu, '') || 'unknown';
1632
+ }
1633
+ function cloneBucket(bucket) {
1634
+ return bucket
1635
+ ? {
1636
+ limit: bucket.limit,
1637
+ remaining: bucket.remaining,
1638
+ reset_at: bucket.reset_at
1639
+ }
1640
+ : null;
1641
+ }
1642
+ function mergeLinearBudgetBucket(existing, observation) {
1643
+ if (!existing) {
1644
+ return cloneBucket(observation);
1645
+ }
1646
+ if (!observation) {
1647
+ return cloneBucket(existing);
1648
+ }
1649
+ return {
1650
+ limit: observation.limit ?? existing.limit,
1651
+ remaining: observation.remaining ?? existing.remaining,
1652
+ reset_at: observation.reset_at ?? existing.reset_at
1653
+ };
1654
+ }
1655
+ function subtractReservationFromBucket(bucket, reservedAmount) {
1656
+ if (!bucket || bucket.remaining === null || reservedAmount <= 0) {
1657
+ return bucket;
1658
+ }
1659
+ return {
1660
+ limit: bucket.limit,
1661
+ remaining: bucket.remaining - reservedAmount,
1662
+ reset_at: bucket.reset_at
1663
+ };
1664
+ }
1665
+ function normalizeExpiredLinearBudgetBucket(bucket, observedAt, options = {}) {
1666
+ if (!bucket) {
1667
+ return null;
1668
+ }
1669
+ const resetAtMs = parseIsoToMs(bucket.reset_at);
1670
+ if (resetAtMs === null) {
1671
+ const observedAtMs = parseIsoToMs(observedAt);
1672
+ if (observedAtMs !== null &&
1673
+ bucket.remaining !== null &&
1674
+ bucket.remaining <= 0 &&
1675
+ observedAtMs + LINEAR_BUDGET_UNKNOWN_RESET_EXHAUSTED_GRACE_MS <= Date.now()) {
1676
+ if (bucket.limit === null) {
1677
+ return null;
1678
+ }
1679
+ return {
1680
+ limit: bucket.limit,
1681
+ remaining: null,
1682
+ reset_at: null
1683
+ };
1684
+ }
1685
+ const requestReserveBucket = options.requestReserveBucket;
1686
+ if (requestReserveBucket) {
1687
+ const reserve = resolveRequestPollingHeadroomReserve(bucket.limit, requestReserveBucket === 'endpoint_requests');
1688
+ if (observedAtMs !== null &&
1689
+ bucket.remaining !== null &&
1690
+ bucket.remaining <= reserve &&
1691
+ observedAtMs + LINEAR_BUDGET_UNKNOWN_RESET_EXHAUSTED_GRACE_MS <= Date.now()) {
1692
+ return {
1693
+ limit: bucket.limit,
1694
+ remaining: null,
1695
+ reset_at: null
1696
+ };
1697
+ }
1698
+ }
1699
+ return bucket;
1700
+ }
1701
+ if (resetAtMs > Date.now()) {
1702
+ return bucket;
1703
+ }
1704
+ if (bucket.limit === null) {
1705
+ return null;
1706
+ }
1707
+ return {
1708
+ limit: bucket.limit,
1709
+ remaining: null,
1710
+ reset_at: null
1711
+ };
1712
+ }
1713
+ function pruneExpiredReservations(persisted) {
1714
+ const reservations = persisted.reservations.filter((entry) => isFutureIsoTimestamp(entry.expires_at));
1715
+ return reservations.length === persisted.reservations.length
1716
+ ? persisted
1717
+ : {
1718
+ ...persisted,
1719
+ reservations
1720
+ };
1721
+ }
1722
+ function applyDeterministicPositiveJitter(baseIntervalMs, seed, nowMs) {
1723
+ if (baseIntervalMs <= 0) {
1724
+ return baseIntervalMs;
1725
+ }
1726
+ const maxJitterMs = Math.min(LINEAR_BUDGET_POLL_JITTER_MAX_MS, Math.max(1_000, Math.floor(baseIntervalMs * LINEAR_BUDGET_POLL_JITTER_RATIO)));
1727
+ const timeBucket = Math.floor(nowMs / Math.max(baseIntervalMs, 1));
1728
+ const hash = stableHash(`${seed}|${timeBucket}`);
1729
+ return baseIntervalMs + (hash % (maxJitterMs + 1));
1730
+ }
1731
+ function stableHash(value) {
1732
+ let hash = 0;
1733
+ for (const character of value) {
1734
+ hash = (hash * 31 + character.charCodeAt(0)) >>> 0;
1735
+ }
1736
+ return hash;
1737
+ }
1738
+ function maxIsoTimestamp(left, right) {
1739
+ const leftMs = parseIsoToMs(left);
1740
+ const rightMs = parseIsoToMs(right);
1741
+ if (leftMs === null) {
1742
+ return right;
1743
+ }
1744
+ if (rightMs === null) {
1745
+ return left;
1746
+ }
1747
+ return rightMs >= leftMs ? right : left;
1748
+ }
1749
+ function uniqueStrings(values) {
1750
+ return [...new Set(values.filter((value) => value.trim().length > 0))];
1751
+ }
1752
+ function normalizePositiveInteger(value) {
1753
+ const parsed = parseNumberLike(value);
1754
+ return parsed !== null && parsed > 0 ? parsed : null;
1755
+ }
1756
+ function parseSignedInteger(value) {
1757
+ return parseNumberLike(value);
1758
+ }
1759
+ function parseNumberLike(value) {
1760
+ if (typeof value === 'number' && Number.isFinite(value)) {
1761
+ return Math.trunc(value);
1762
+ }
1763
+ if (typeof value !== 'string' || value.trim().length === 0) {
1764
+ return null;
1765
+ }
1766
+ const parsed = Number.parseInt(value, 10);
1767
+ return Number.isInteger(parsed) ? parsed : null;
1768
+ }
1769
+ function parseIsoToMs(value) {
1770
+ const normalized = normalizeOptionalString(value);
1771
+ if (!normalized) {
1772
+ return null;
1773
+ }
1774
+ const parsed = Date.parse(normalized);
1775
+ return Number.isFinite(parsed) ? parsed : null;
1776
+ }
1777
+ function isFutureIsoTimestamp(value) {
1778
+ const parsed = parseIsoToMs(value);
1779
+ return parsed !== null && parsed > Date.now();
1780
+ }
1781
+ function normalizeOptionalString(value) {
1782
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
1783
+ }
1784
+ function normalizeRequiredString(value) {
1785
+ return normalizeOptionalString(value);
1786
+ }
1787
+ function hasRecordableLinearBudgetDetails(details) {
1788
+ return Object.keys(details).some((key) => key !== 'request_id' && key !== 'errors');
1789
+ }