@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,516 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { access } from 'node:fs/promises';
3
+ import process from 'node:process';
4
+ import { promisify } from 'node:util';
5
+ import { getProviderLinearIssueContext } from './providerLinearWorkflowFacade.js';
6
+ import { isoTimestamp } from '../utils/time.js';
7
+ const execFileAsync = promisify(execFile);
8
+ export const DEFAULT_PROVIDER_TERMINAL_CLEANUP_COMMENT_TEMPLATE = 'Closing because the Linear issue for branch {{branch}} entered a terminal state without merge.';
9
+ const PROVIDER_TERMINAL_CLEANUP_COMMAND_TIMEOUT_MS = 5_000;
10
+ const PROVIDER_TERMINAL_CLEANUP_TOTAL_BUDGET_MS = 15_000;
11
+ const PROVIDER_TERMINAL_CLEANUP_MAX_ATTACHED_PRS = 20;
12
+ export function resolveProviderTerminalCleanupConfig(value) {
13
+ const record = asRecord(value);
14
+ const terminalCleanup = asRecord(record?.terminal_cleanup ?? record?.terminalCleanup);
15
+ const enabled = readBoolean(terminalCleanup, 'enabled') ?? false;
16
+ const closeAttachedPr = asRecord(terminalCleanup?.close_attached_pr ?? terminalCleanup?.closeAttachedPr);
17
+ return {
18
+ enabled,
19
+ closeAttachedPr: {
20
+ enabled: readBoolean(closeAttachedPr, 'enabled') ?? enabled,
21
+ commentTemplate: readNonEmptyString(closeAttachedPr, 'comment_template', 'commentTemplate') ??
22
+ DEFAULT_PROVIDER_TERMINAL_CLEANUP_COMMENT_TEMPLATE
23
+ }
24
+ };
25
+ }
26
+ export async function runProviderTerminalCleanup(input, deps = {}) {
27
+ const attemptedAt = deps.now?.() ?? isoTimestamp();
28
+ const env = input.env ?? process.env;
29
+ const readIssueContext = deps.readIssueContext ?? getProviderLinearIssueContext;
30
+ const runCommand = deps.runCommand ?? runProviderTerminalCleanupCommand;
31
+ const issueIdentifier = normalizeOptionalString(input.issueIdentifier);
32
+ const nowMs = deps.nowMs ?? Date.now;
33
+ if (!input.config.enabled) {
34
+ return buildResult({
35
+ attemptedAt,
36
+ issueId: input.issueId,
37
+ issueIdentifier,
38
+ workspacePath: input.workspacePath,
39
+ status: 'disabled',
40
+ summary: 'Terminal cleanup hook is disabled.',
41
+ branch: null
42
+ });
43
+ }
44
+ if (!input.config.closeAttachedPr.enabled) {
45
+ return buildResult({
46
+ attemptedAt,
47
+ issueId: input.issueId,
48
+ issueIdentifier,
49
+ workspacePath: input.workspacePath,
50
+ status: 'disabled',
51
+ summary: 'Attached PR auto-close is disabled for terminal cleanup.',
52
+ branch: null
53
+ });
54
+ }
55
+ if (!(await workspaceExists(input.workspacePath))) {
56
+ return buildResult({
57
+ attemptedAt,
58
+ issueId: input.issueId,
59
+ issueIdentifier,
60
+ workspacePath: input.workspacePath,
61
+ status: 'noop',
62
+ summary: 'Workspace was already absent before terminal cleanup.',
63
+ branch: null
64
+ });
65
+ }
66
+ const branchResult = await runCommand({
67
+ command: 'git',
68
+ args: ['-C', input.workspacePath, 'branch', '--show-current'],
69
+ cwd: input.workspacePath
70
+ });
71
+ if (!branchResult.ok) {
72
+ return buildResult({
73
+ attemptedAt,
74
+ issueId: input.issueId,
75
+ issueIdentifier,
76
+ workspacePath: input.workspacePath,
77
+ status: 'failed',
78
+ summary: 'Terminal cleanup could not determine the workspace branch.',
79
+ error: formatCommandFailure('git', branchResult),
80
+ branch: null
81
+ });
82
+ }
83
+ const branch = normalizeOptionalString(branchResult.stdout);
84
+ const headResult = await runCommand({
85
+ command: 'git',
86
+ args: ['-C', input.workspacePath, 'rev-parse', 'HEAD'],
87
+ cwd: input.workspacePath
88
+ });
89
+ if (!headResult.ok) {
90
+ return buildResult({
91
+ attemptedAt,
92
+ issueId: input.issueId,
93
+ issueIdentifier,
94
+ workspacePath: input.workspacePath,
95
+ status: 'failed',
96
+ summary: branch === null
97
+ ? 'Terminal cleanup could not determine the detached workspace HEAD.'
98
+ : 'Terminal cleanup could not determine the workspace HEAD.',
99
+ error: formatCommandFailure('git', headResult),
100
+ branch
101
+ });
102
+ }
103
+ const workspaceHeadOid = normalizeOptionalString(headResult.stdout);
104
+ if (!workspaceHeadOid) {
105
+ if (!branch) {
106
+ return buildResult({
107
+ attemptedAt,
108
+ issueId: input.issueId,
109
+ issueIdentifier,
110
+ workspacePath: input.workspacePath,
111
+ status: 'noop',
112
+ summary: 'Workspace is detached or has no current branch; skipping attached PR cleanup.',
113
+ branch: null
114
+ });
115
+ }
116
+ return buildResult({
117
+ attemptedAt,
118
+ issueId: input.issueId,
119
+ issueIdentifier,
120
+ workspacePath: input.workspacePath,
121
+ status: 'failed',
122
+ summary: 'Terminal cleanup could not determine the workspace HEAD.',
123
+ branch
124
+ });
125
+ }
126
+ const workspaceTargetLabel = formatWorkspaceTargetLabel(branch, workspaceHeadOid);
127
+ const originUrlResult = await runCommand({
128
+ command: 'git',
129
+ args: ['-C', input.workspacePath, 'remote', 'get-url', 'origin'],
130
+ cwd: input.workspacePath
131
+ });
132
+ if (!originUrlResult.ok) {
133
+ return buildResult({
134
+ attemptedAt,
135
+ issueId: input.issueId,
136
+ issueIdentifier,
137
+ workspacePath: input.workspacePath,
138
+ status: 'failed',
139
+ summary: 'Terminal cleanup could not determine the workspace origin remote.',
140
+ error: formatCommandFailure('git', originUrlResult),
141
+ branch
142
+ });
143
+ }
144
+ const workspaceRepoKey = parseGitHubRepositoryUrl(originUrlResult.stdout);
145
+ if (!workspaceRepoKey) {
146
+ return buildResult({
147
+ attemptedAt,
148
+ issueId: input.issueId,
149
+ issueIdentifier,
150
+ workspacePath: input.workspacePath,
151
+ status: 'failed',
152
+ summary: 'Terminal cleanup could not determine the workspace GitHub repository.',
153
+ error: JSON.stringify(originUrlResult.stdout.trim()),
154
+ branch
155
+ });
156
+ }
157
+ const issueContext = await readIssueContext({
158
+ issueId: input.issueId,
159
+ env
160
+ });
161
+ if (!issueContext.ok) {
162
+ return buildResult({
163
+ attemptedAt,
164
+ issueId: input.issueId,
165
+ issueIdentifier,
166
+ workspacePath: input.workspacePath,
167
+ status: 'failed',
168
+ summary: `Terminal cleanup could not load Linear issue context for ${input.issueId}.`,
169
+ error: `${issueContext.error.code}: ${issueContext.error.message}`,
170
+ branch
171
+ });
172
+ }
173
+ const attachedPrUrls = collectAttachedGitHubPrUrls(issueContext.issue.attachments);
174
+ if (attachedPrUrls.length === 0) {
175
+ return buildResult({
176
+ attemptedAt,
177
+ issueId: input.issueId,
178
+ issueIdentifier,
179
+ workspacePath: input.workspacePath,
180
+ status: 'noop',
181
+ summary: `No attached GitHub PRs were present for ${workspaceTargetLabel}.`,
182
+ branch
183
+ });
184
+ }
185
+ const matchingOpenPrUrls = [];
186
+ const closedPrUrls = [];
187
+ const errors = [];
188
+ let resolvedBranch = branch;
189
+ const cleanupDeadlineMs = nowMs() + PROVIDER_TERMINAL_CLEANUP_TOTAL_BUDGET_MS;
190
+ const sameRepoAttachedPrUrls = attachedPrUrls.filter((attachedPrUrl) => parseGitHubPullRequestUrl(attachedPrUrl)?.repoKey === workspaceRepoKey);
191
+ const attachedPrUrlsToProcess = sameRepoAttachedPrUrls.slice(0, PROVIDER_TERMINAL_CLEANUP_MAX_ATTACHED_PRS);
192
+ if (sameRepoAttachedPrUrls.length > attachedPrUrlsToProcess.length) {
193
+ errors.push(`skipped ${sameRepoAttachedPrUrls.length - attachedPrUrlsToProcess.length} attached PR(s) beyond cleanup cap of ${PROVIDER_TERMINAL_CLEANUP_MAX_ATTACHED_PRS}`);
194
+ }
195
+ let processedAttachedPrCount = 0;
196
+ for (const attachedPrUrl of attachedPrUrlsToProcess) {
197
+ if (nowMs() >= cleanupDeadlineMs) {
198
+ errors.push(`cleanup budget exceeded after processing ${processedAttachedPrCount} attached PR(s)`);
199
+ break;
200
+ }
201
+ processedAttachedPrCount += 1;
202
+ const prView = await runCommand({
203
+ command: 'gh',
204
+ args: [
205
+ 'pr',
206
+ 'view',
207
+ attachedPrUrl,
208
+ '--json',
209
+ 'state,headRefName,headRefOid,url,headRepository,headRepositoryOwner,isCrossRepository'
210
+ ],
211
+ cwd: input.workspacePath
212
+ });
213
+ if (!prView.ok) {
214
+ errors.push(`gh pr view ${attachedPrUrl}: ${formatCommandFailure('gh', prView)}`);
215
+ continue;
216
+ }
217
+ const prDetails = parsePrViewResponse(prView.stdout);
218
+ if (!prDetails) {
219
+ errors.push(`gh pr view ${attachedPrUrl}: invalid JSON response`);
220
+ continue;
221
+ }
222
+ const sameBaseRepository = prDetails.repoKey === workspaceRepoKey;
223
+ const sameHeadRepository = prDetails.headRepoKey === workspaceRepoKey;
224
+ const matchesBranch = branch !== null &&
225
+ prDetails.headRefName === branch &&
226
+ prDetails.headRefOid === workspaceHeadOid;
227
+ const matchesHead = branch === null &&
228
+ workspaceHeadOid !== null &&
229
+ prDetails.headRefOid !== null &&
230
+ prDetails.headRefOid === workspaceHeadOid;
231
+ if (prDetails.state !== 'OPEN' ||
232
+ !sameBaseRepository ||
233
+ !sameHeadRepository ||
234
+ (!matchesBranch && !matchesHead)) {
235
+ continue;
236
+ }
237
+ const closingBranch = prDetails.headRefName ??
238
+ branch ??
239
+ resolvedBranch ??
240
+ `HEAD ${shortOid(workspaceHeadOid)}`;
241
+ resolvedBranch ??= prDetails.headRefName;
242
+ matchingOpenPrUrls.push(prDetails.url ?? attachedPrUrl);
243
+ if (nowMs() >= cleanupDeadlineMs) {
244
+ errors.push(`cleanup budget exceeded before closing ${attachedPrUrl}`);
245
+ break;
246
+ }
247
+ const prClose = await runCommand({
248
+ command: 'gh',
249
+ args: [
250
+ 'pr',
251
+ 'close',
252
+ attachedPrUrl,
253
+ '--comment',
254
+ renderClosingComment(input.config.closeAttachedPr.commentTemplate, closingBranch)
255
+ ],
256
+ cwd: input.workspacePath
257
+ });
258
+ if (!prClose.ok) {
259
+ errors.push(`gh pr close ${attachedPrUrl}: ${formatCommandFailure('gh', prClose)}`);
260
+ continue;
261
+ }
262
+ closedPrUrls.push(prDetails.url ?? attachedPrUrl);
263
+ }
264
+ if (errors.length > 0) {
265
+ return buildResult({
266
+ attemptedAt,
267
+ issueId: input.issueId,
268
+ issueIdentifier,
269
+ workspacePath: input.workspacePath,
270
+ status: 'failed',
271
+ summary: matchingOpenPrUrls.length === 0
272
+ ? `Terminal cleanup encountered ${errors.length} attached PR error(s) for ${workspaceTargetLabel}.`
273
+ : `Terminal cleanup closed ${closedPrUrls.length} of ${matchingOpenPrUrls.length} matching attached PR(s) for ${formatWorkspaceTargetLabel(resolvedBranch, workspaceHeadOid)} and encountered ${errors.length} error(s).`,
274
+ error: errors.join(' | '),
275
+ branch: resolvedBranch,
276
+ attachedPrUrls,
277
+ matchingOpenPrUrls,
278
+ closedPrUrls
279
+ });
280
+ }
281
+ if (matchingOpenPrUrls.length === 0) {
282
+ return buildResult({
283
+ attemptedAt,
284
+ issueId: input.issueId,
285
+ issueIdentifier,
286
+ workspacePath: input.workspacePath,
287
+ status: 'noop',
288
+ summary: `No attached open PR matched ${workspaceTargetLabel}.`,
289
+ branch: resolvedBranch,
290
+ attachedPrUrls
291
+ });
292
+ }
293
+ return buildResult({
294
+ attemptedAt,
295
+ issueId: input.issueId,
296
+ issueIdentifier,
297
+ workspacePath: input.workspacePath,
298
+ status: 'succeeded',
299
+ summary: `Closed ${closedPrUrls.length} attached PR(s) for ${formatWorkspaceTargetLabel(resolvedBranch, workspaceHeadOid)}.`,
300
+ branch: resolvedBranch,
301
+ attachedPrUrls,
302
+ matchingOpenPrUrls,
303
+ closedPrUrls
304
+ });
305
+ }
306
+ async function runProviderTerminalCleanupCommand(input) {
307
+ try {
308
+ const { stdout, stderr } = await execFileAsync(input.command, input.args, {
309
+ cwd: input.cwd,
310
+ timeout: PROVIDER_TERMINAL_CLEANUP_COMMAND_TIMEOUT_MS
311
+ });
312
+ return {
313
+ ok: true,
314
+ exitCode: 0,
315
+ stdout,
316
+ stderr
317
+ };
318
+ }
319
+ catch (error) {
320
+ const execError = error;
321
+ const timedOut = execError.killed === true && execError.signal === 'SIGTERM';
322
+ return {
323
+ ok: false,
324
+ exitCode: typeof execError.code === 'number' && Number.isInteger(execError.code)
325
+ ? execError.code
326
+ : null,
327
+ stdout: typeof execError.stdout === 'string' ? execError.stdout : '',
328
+ stderr: typeof execError.stderr === 'string' && execError.stderr.length > 0
329
+ ? execError.stderr
330
+ : timedOut
331
+ ? `command timed out after ${PROVIDER_TERMINAL_CLEANUP_COMMAND_TIMEOUT_MS}ms`
332
+ : (execError.message ?? ''),
333
+ };
334
+ }
335
+ }
336
+ function buildResult(input) {
337
+ return {
338
+ attemptedAt: input.attemptedAt,
339
+ status: input.status,
340
+ summary: input.summary,
341
+ error: input.error ?? null,
342
+ issueId: input.issueId,
343
+ issueIdentifier: input.issueIdentifier,
344
+ workspacePath: input.workspacePath,
345
+ branch: input.branch,
346
+ attachedPrUrls: [...(input.attachedPrUrls ?? [])],
347
+ matchingOpenPrUrls: [...(input.matchingOpenPrUrls ?? [])],
348
+ closedPrUrls: [...(input.closedPrUrls ?? [])]
349
+ };
350
+ }
351
+ function collectAttachedGitHubPrUrls(attachments) {
352
+ const seen = new Set();
353
+ const urls = [];
354
+ for (const attachment of attachments) {
355
+ const parsed = parseGitHubPullRequestUrl(attachment.url);
356
+ if (!parsed || seen.has(parsed.canonicalUrl)) {
357
+ continue;
358
+ }
359
+ seen.add(parsed.canonicalUrl);
360
+ urls.push(parsed.canonicalUrl);
361
+ }
362
+ return urls;
363
+ }
364
+ function parsePrViewResponse(value) {
365
+ try {
366
+ const parsed = JSON.parse(value);
367
+ const parsedUrl = parseGitHubPullRequestUrl(typeof parsed.url === 'string' ? parsed.url : null);
368
+ const headRepository = asRecord(parsed.headRepository);
369
+ const headRepositoryOwner = asRecord(parsed.headRepositoryOwner);
370
+ const headRepositoryName = normalizeOptionalString(headRepository?.name);
371
+ const headRepositoryOwnerLogin = normalizeOptionalString(headRepositoryOwner?.login);
372
+ const isCrossRepository = typeof parsed.isCrossRepository === 'boolean' ? parsed.isCrossRepository : null;
373
+ const headRepoKey = headRepositoryName && headRepositoryOwnerLogin
374
+ ? `${headRepositoryOwnerLogin.toLowerCase()}/${headRepositoryName.toLowerCase()}`
375
+ : isCrossRepository === false
376
+ ? parsedUrl?.repoKey ?? null
377
+ : null;
378
+ return {
379
+ state: normalizeOptionalString(typeof parsed.state === 'string' ? parsed.state : null)?.toUpperCase() ?? null,
380
+ headRefName: normalizeOptionalString(typeof parsed.headRefName === 'string' ? parsed.headRefName : null),
381
+ headRefOid: normalizeOptionalString(typeof parsed.headRefOid === 'string' ? parsed.headRefOid : null),
382
+ url: parsedUrl?.canonicalUrl ?? null,
383
+ repoKey: parsedUrl?.repoKey ?? null,
384
+ headRepoKey
385
+ };
386
+ }
387
+ catch {
388
+ return null;
389
+ }
390
+ }
391
+ function renderClosingComment(template, branch) {
392
+ return template.replaceAll('{{branch}}', branch);
393
+ }
394
+ function formatCommandFailure(command, result) {
395
+ const parts = [`${command} exited ${result.exitCode ?? 'unknown'}`];
396
+ const stderr = result.stderr.trim();
397
+ const stdout = result.stdout.trim();
398
+ if (stderr) {
399
+ parts.push(`stderr=${JSON.stringify(stderr)}`);
400
+ }
401
+ else if (stdout) {
402
+ parts.push(`stdout=${JSON.stringify(stdout)}`);
403
+ }
404
+ return parts.join(' ');
405
+ }
406
+ async function workspaceExists(workspacePath) {
407
+ try {
408
+ await access(workspacePath);
409
+ return true;
410
+ }
411
+ catch {
412
+ return false;
413
+ }
414
+ }
415
+ function formatWorkspaceTargetLabel(branch, headOid) {
416
+ if (branch) {
417
+ return `branch ${branch}`;
418
+ }
419
+ if (headOid) {
420
+ return `detached HEAD ${shortOid(headOid)}`;
421
+ }
422
+ return 'the workspace target';
423
+ }
424
+ function shortOid(value) {
425
+ const normalized = normalizeOptionalString(value);
426
+ return normalized ? normalized.slice(0, 12) : 'unknown';
427
+ }
428
+ function parseGitHubPullRequestUrl(value) {
429
+ const normalized = normalizeOptionalString(value);
430
+ if (!normalized) {
431
+ return null;
432
+ }
433
+ let parsed;
434
+ try {
435
+ parsed = new URL(normalized);
436
+ }
437
+ catch {
438
+ return null;
439
+ }
440
+ const hostname = parsed.hostname.toLowerCase();
441
+ if (hostname !== 'github.com' && hostname !== 'www.github.com') {
442
+ return null;
443
+ }
444
+ const segments = parsed.pathname.split('/').filter(Boolean);
445
+ const owner = normalizeOptionalString(segments[0] ?? null);
446
+ const repo = normalizeOptionalString(segments[1] ?? null);
447
+ const resource = normalizeOptionalString(segments[2] ?? null)?.toLowerCase() ?? null;
448
+ const pullNumber = normalizePullRequestNumber(segments[3] ?? null);
449
+ if (!owner || !repo || resource !== 'pull' || !pullNumber) {
450
+ return null;
451
+ }
452
+ return {
453
+ canonicalUrl: `https://github.com/${owner}/${repo}/pull/${pullNumber}`,
454
+ repoKey: `${owner.toLowerCase()}/${repo.toLowerCase()}`
455
+ };
456
+ }
457
+ function parseGitHubRepositoryUrl(value) {
458
+ const normalized = normalizeOptionalString(value);
459
+ if (!normalized) {
460
+ return null;
461
+ }
462
+ const scpMatch = normalized.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
463
+ if (scpMatch) {
464
+ return `${scpMatch[1].toLowerCase()}/${scpMatch[2].toLowerCase()}`;
465
+ }
466
+ let parsed;
467
+ try {
468
+ parsed = new URL(normalized);
469
+ }
470
+ catch {
471
+ return null;
472
+ }
473
+ const hostname = parsed.hostname.toLowerCase();
474
+ if (hostname !== 'github.com' && hostname !== 'www.github.com') {
475
+ return null;
476
+ }
477
+ const segments = parsed.pathname.split('/').filter(Boolean);
478
+ const owner = normalizeOptionalString(segments[0] ?? null);
479
+ const repo = normalizeOptionalString((segments[1] ?? '').replace(/\.git$/iu, ''));
480
+ if (!owner || !repo || segments.length !== 2) {
481
+ return null;
482
+ }
483
+ return `${owner.toLowerCase()}/${repo.toLowerCase()}`;
484
+ }
485
+ function normalizePullRequestNumber(value) {
486
+ const normalized = normalizeOptionalString(value);
487
+ if (!normalized || !/^\d+$/u.test(normalized)) {
488
+ return null;
489
+ }
490
+ return String(Number.parseInt(normalized, 10));
491
+ }
492
+ function asRecord(value) {
493
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
494
+ return null;
495
+ }
496
+ return value;
497
+ }
498
+ function readBoolean(record, key) {
499
+ const value = record?.[key];
500
+ if (typeof value === 'boolean') {
501
+ return value;
502
+ }
503
+ return null;
504
+ }
505
+ function readNonEmptyString(record, ...keys) {
506
+ for (const key of keys) {
507
+ const value = normalizeOptionalString(record?.[key]);
508
+ if (value) {
509
+ return value;
510
+ }
511
+ }
512
+ return null;
513
+ }
514
+ function normalizeOptionalString(value) {
515
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
516
+ }
@@ -0,0 +1,191 @@
1
+ export const PROVIDER_WORKER_HOST_ENV_KEY = 'CODEX_ORCHESTRATOR_PROVIDER_WORKER_HOST';
2
+ const DEFAULT_PROVIDER_WORKER_HOST_MAX_CONCURRENT_AGENTS = 1;
3
+ const ACTIVE_PROVIDER_WORKER_HOST_CLAIM_STATES = new Set(['starting', 'running', 'resuming']);
4
+ export function resolveProviderWorkerHostConfig(metadata) {
5
+ const record = asRecord(metadata);
6
+ const workerHostsRecord = asRecord(record?.worker_hosts ?? record?.workerHosts);
7
+ if (workerHostsRecord && workerHostsRecord.hosts !== undefined && !Array.isArray(workerHostsRecord.hosts)) {
8
+ throw new Error('provider worker hosts metadata "hosts" must be an array.');
9
+ }
10
+ const hostEntries = Array.isArray(workerHostsRecord?.hosts)
11
+ ? workerHostsRecord.hosts
12
+ : Array.isArray(record?.worker_hosts)
13
+ ? record.worker_hosts
14
+ : Array.isArray(record?.workerHosts)
15
+ ? record.workerHosts
16
+ : [];
17
+ if (hostEntries.length === 0) {
18
+ return [];
19
+ }
20
+ const names = new Set();
21
+ return hostEntries.map((entry, index) => {
22
+ const host = asRecord(entry);
23
+ if (!host) {
24
+ throw new Error(`provider worker host entry #${index + 1} must be an object.`);
25
+ }
26
+ const name = readRequiredString(host, 'name', `provider worker host entry #${index + 1}`);
27
+ if (names.has(name)) {
28
+ throw new Error(`provider worker host "${name}" is duplicated.`);
29
+ }
30
+ names.add(name);
31
+ const transport = readOptionalString(host, 'transport') ?? 'ssh';
32
+ if (transport !== 'ssh') {
33
+ throw new Error(`provider worker host "${name}" transport must be "ssh" for the current distributed worker-host lane.`);
34
+ }
35
+ const sshDestination = readRequiredString(host, 'ssh_destination', `provider worker host "${name}"`);
36
+ return {
37
+ name,
38
+ transport: 'ssh',
39
+ ssh_destination: sshDestination,
40
+ ssh_options: readStringArray(host, 'ssh_options'),
41
+ max_concurrent_agents: readOptionalPositiveInteger(host, 'max_concurrent_agents', `provider worker host "${name}"`) ??
42
+ DEFAULT_PROVIDER_WORKER_HOST_MAX_CONCURRENT_AGENTS,
43
+ node_path: readOptionalString(host, 'node_path')
44
+ };
45
+ });
46
+ }
47
+ export function cloneProviderWorkerHostConfigs(hosts) {
48
+ return Array.isArray(hosts)
49
+ ? hosts.map((host) => ({
50
+ ...host,
51
+ ssh_options: [...host.ssh_options]
52
+ }))
53
+ : [];
54
+ }
55
+ export function normalizeProviderWorkerHostName(value) {
56
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
57
+ }
58
+ export function findProviderWorkerHost(hosts, name) {
59
+ const normalizedName = normalizeProviderWorkerHostName(name);
60
+ if (!normalizedName || !Array.isArray(hosts)) {
61
+ return null;
62
+ }
63
+ return hosts.find((host) => host.name === normalizedName) ?? null;
64
+ }
65
+ export function selectProviderWorkerHost(input) {
66
+ const hosts = cloneProviderWorkerHostConfigs(input.hosts);
67
+ const occupancy = buildProviderWorkerHostOccupancy({
68
+ claims: input.claims,
69
+ currentProviderKey: input.currentProviderKey
70
+ });
71
+ if (hosts.length === 0) {
72
+ return {
73
+ kind: 'local',
74
+ host: null,
75
+ occupancy
76
+ };
77
+ }
78
+ const preferred = findProviderWorkerHost(hosts, input.preferredHost);
79
+ if (preferred && hasProviderWorkerHostCapacity(preferred, occupancy)) {
80
+ return {
81
+ kind: 'remote',
82
+ host: preferred,
83
+ occupancy
84
+ };
85
+ }
86
+ const selected = [...hosts]
87
+ .map((host, index) => ({
88
+ host,
89
+ index,
90
+ used: occupancy.get(host.name) ?? 0,
91
+ remaining: host.max_concurrent_agents - (occupancy.get(host.name) ?? 0)
92
+ }))
93
+ .filter((entry) => entry.remaining > 0)
94
+ .sort((left, right) => {
95
+ if (right.remaining !== left.remaining) {
96
+ return right.remaining - left.remaining;
97
+ }
98
+ if (left.used !== right.used) {
99
+ return left.used - right.used;
100
+ }
101
+ return left.index - right.index;
102
+ })[0]?.host ?? null;
103
+ if (!selected) {
104
+ return {
105
+ kind: 'exhausted',
106
+ host: null,
107
+ occupancy
108
+ };
109
+ }
110
+ return {
111
+ kind: 'remote',
112
+ host: selected,
113
+ occupancy
114
+ };
115
+ }
116
+ function buildProviderWorkerHostOccupancy(input) {
117
+ const occupancy = new Map();
118
+ if (!Array.isArray(input.claims)) {
119
+ return occupancy;
120
+ }
121
+ for (const claim of input.claims) {
122
+ if (claim.provider_key && input.currentProviderKey && claim.provider_key === input.currentProviderKey) {
123
+ continue;
124
+ }
125
+ if (!ACTIVE_PROVIDER_WORKER_HOST_CLAIM_STATES.has(normalizeClaimState(claim.state))) {
126
+ continue;
127
+ }
128
+ const workerHost = normalizeProviderWorkerHostName(claim.worker_host);
129
+ if (!workerHost) {
130
+ continue;
131
+ }
132
+ occupancy.set(workerHost, (occupancy.get(workerHost) ?? 0) + 1);
133
+ }
134
+ return occupancy;
135
+ }
136
+ function hasProviderWorkerHostCapacity(host, occupancy) {
137
+ return (occupancy.get(host.name) ?? 0) < host.max_concurrent_agents;
138
+ }
139
+ function normalizeClaimState(value) {
140
+ return typeof value === 'string' ? value : '';
141
+ }
142
+ function asRecord(value) {
143
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
144
+ ? value
145
+ : null;
146
+ }
147
+ function readOptionalString(record, key) {
148
+ const value = record[key];
149
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
150
+ }
151
+ function readRequiredString(record, key, source) {
152
+ const value = readOptionalString(record, key);
153
+ if (!value) {
154
+ throw new Error(`${source} requires a non-empty ${key}.`);
155
+ }
156
+ return value;
157
+ }
158
+ function readStringArray(record, key) {
159
+ const value = record[key];
160
+ if (value === undefined || value === null) {
161
+ return [];
162
+ }
163
+ if (!Array.isArray(value)) {
164
+ throw new Error(`${key} must be an array of strings.`);
165
+ }
166
+ return value.map((entry, index) => {
167
+ if (typeof entry !== 'string' || entry.trim().length === 0) {
168
+ throw new Error(`${key}[${index}] must be a non-empty string.`);
169
+ }
170
+ return entry;
171
+ });
172
+ }
173
+ function readPositiveInteger(record, key, source) {
174
+ const value = record[key];
175
+ if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
176
+ return value;
177
+ }
178
+ if (typeof value === 'string' && /^\d+$/u.test(value.trim())) {
179
+ const parsed = Number.parseInt(value.trim(), 10);
180
+ if (parsed > 0) {
181
+ return parsed;
182
+ }
183
+ }
184
+ throw new Error(`${source} requires ${key} to be a positive integer.`);
185
+ }
186
+ function readOptionalPositiveInteger(record, key, source) {
187
+ if (record[key] === undefined || record[key] === null) {
188
+ return null;
189
+ }
190
+ return readPositiveInteger(record, key, source);
191
+ }