okstra 0.174.0 → 0.176.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +18 -9
  2. package/bin/okstra +8 -4
  3. package/dist/cli-registry.d.mts +16 -0
  4. package/dist/cli-registry.mjs +560 -0
  5. package/dist/cli-registry.mjs.map +1 -0
  6. package/dist/commands/execute/agent-prompt.d.mts +1 -0
  7. package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
  8. package/dist/commands/execute/agent-prompt.mjs.map +1 -0
  9. package/dist/commands/execute/codex-dispatch.d.mts +3 -0
  10. package/dist/commands/execute/codex-dispatch.mjs +6 -0
  11. package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
  12. package/dist/commands/execute/codex-run.d.mts +3 -0
  13. package/{src → dist}/commands/execute/codex-run.mjs +30 -37
  14. package/dist/commands/execute/codex-run.mjs.map +1 -0
  15. package/dist/commands/execute/convergence.d.mts +1 -0
  16. package/{src → dist}/commands/execute/convergence.mjs +15 -16
  17. package/dist/commands/execute/convergence.mjs.map +1 -0
  18. package/dist/commands/execute/error-log.d.mts +1 -0
  19. package/{src → dist}/commands/execute/error-log.mjs +2 -3
  20. package/dist/commands/execute/error-log.mjs.map +1 -0
  21. package/dist/commands/execute/git-reconcile.d.mts +1 -0
  22. package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
  23. package/dist/commands/execute/git-reconcile.mjs.map +1 -0
  24. package/dist/commands/execute/handoff.d.mts +1 -0
  25. package/{src → dist}/commands/execute/handoff.mjs +10 -11
  26. package/dist/commands/execute/handoff.mjs.map +1 -0
  27. package/dist/commands/execute/incremental-carry.d.mts +1 -0
  28. package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
  29. package/dist/commands/execute/incremental-carry.mjs.map +1 -0
  30. package/dist/commands/execute/incremental-scope.d.mts +1 -0
  31. package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
  32. package/dist/commands/execute/incremental-scope.mjs.map +1 -0
  33. package/dist/commands/execute/integrate-stages.d.mts +1 -0
  34. package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
  35. package/dist/commands/execute/integrate-stages.mjs.map +1 -0
  36. package/dist/commands/execute/pane-title.d.mts +1 -0
  37. package/dist/commands/execute/pane-title.mjs +20 -0
  38. package/dist/commands/execute/pane-title.mjs.map +1 -0
  39. package/dist/commands/execute/plan-items.d.mts +1 -0
  40. package/dist/commands/execute/plan-items.mjs +9 -0
  41. package/dist/commands/execute/plan-items.mjs.map +1 -0
  42. package/dist/commands/execute/plan-validate.d.mts +1 -0
  43. package/dist/commands/execute/plan-validate.mjs +68 -0
  44. package/dist/commands/execute/plan-validate.mjs.map +1 -0
  45. package/dist/commands/execute/plan-verify.d.mts +1 -0
  46. package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
  47. package/dist/commands/execute/plan-verify.mjs.map +1 -0
  48. package/dist/commands/execute/render-bundle.d.mts +3 -0
  49. package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
  50. package/dist/commands/execute/render-bundle.mjs.map +1 -0
  51. package/dist/commands/execute/run.d.mts +24 -0
  52. package/dist/commands/execute/run.mjs +196 -0
  53. package/dist/commands/execute/run.mjs.map +1 -0
  54. package/dist/commands/execute/spawn-followups.d.mts +1 -0
  55. package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
  56. package/dist/commands/execute/spawn-followups.mjs.map +1 -0
  57. package/dist/commands/execute/team.d.mts +3 -0
  58. package/{src → dist}/commands/execute/team.mjs +29 -36
  59. package/dist/commands/execute/team.mjs.map +1 -0
  60. package/dist/commands/execute/token-usage.d.mts +1 -0
  61. package/{src → dist}/commands/execute/token-usage.mjs +2 -3
  62. package/dist/commands/execute/token-usage.mjs.map +1 -0
  63. package/dist/commands/execute/wizard.d.mts +8 -0
  64. package/dist/commands/execute/wizard.mjs +191 -0
  65. package/dist/commands/execute/wizard.mjs.map +1 -0
  66. package/dist/commands/execute/worker-audit-check.d.mts +1 -0
  67. package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
  68. package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
  69. package/dist/commands/execute/worker-dispatch.d.mts +7 -0
  70. package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
  71. package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
  72. package/dist/commands/execute/worker-state.d.mts +1 -0
  73. package/{src → dist}/commands/execute/worker-state.mjs +15 -16
  74. package/dist/commands/execute/worker-state.mjs.map +1 -0
  75. package/dist/commands/execute/worktree-lookup.d.mts +1 -0
  76. package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
  77. package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
  78. package/dist/commands/execute/worktree-status.d.mts +1 -0
  79. package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
  80. package/dist/commands/execute/worktree-status.mjs.map +1 -0
  81. package/dist/commands/inspect/code-review.d.mts +1 -0
  82. package/{src → dist}/commands/inspect/code-review.mjs +16 -19
  83. package/dist/commands/inspect/code-review.mjs.map +1 -0
  84. package/dist/commands/inspect/container.d.mts +1 -0
  85. package/{src → dist}/commands/inspect/container.mjs +11 -13
  86. package/dist/commands/inspect/container.mjs.map +1 -0
  87. package/dist/commands/inspect/context-cost.d.mts +1 -0
  88. package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
  89. package/dist/commands/inspect/context-cost.mjs.map +1 -0
  90. package/dist/commands/inspect/design-prep.d.mts +1 -0
  91. package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
  92. package/dist/commands/inspect/design-prep.mjs.map +1 -0
  93. package/dist/commands/inspect/error-report.d.mts +1 -0
  94. package/{src → dist}/commands/inspect/error-report.mjs +11 -13
  95. package/dist/commands/inspect/error-report.mjs.map +1 -0
  96. package/dist/commands/inspect/error-zip.d.mts +1 -0
  97. package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
  98. package/dist/commands/inspect/error-zip.mjs.map +1 -0
  99. package/dist/commands/inspect/log-report.d.mts +1 -0
  100. package/{src → dist}/commands/inspect/log-report.mjs +11 -13
  101. package/dist/commands/inspect/log-report.mjs.map +1 -0
  102. package/dist/commands/inspect/profile-show.d.mts +1 -0
  103. package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
  104. package/dist/commands/inspect/profile-show.mjs.map +1 -0
  105. package/dist/commands/inspect/recap.d.mts +1 -0
  106. package/{src → dist}/commands/inspect/recap.mjs +11 -13
  107. package/dist/commands/inspect/recap.mjs.map +1 -0
  108. package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
  109. package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
  110. package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
  111. package/dist/commands/inspect/rollup.d.mts +1 -0
  112. package/{src → dist}/commands/inspect/rollup.mjs +11 -13
  113. package/dist/commands/inspect/rollup.mjs.map +1 -0
  114. package/dist/commands/inspect/run-audit.d.mts +1 -0
  115. package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
  116. package/dist/commands/inspect/run-audit.mjs.map +1 -0
  117. package/dist/commands/inspect/set-work-status.d.mts +1 -0
  118. package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
  119. package/dist/commands/inspect/set-work-status.mjs.map +1 -0
  120. package/dist/commands/inspect/stage-map.d.mts +1 -0
  121. package/dist/commands/inspect/stage-map.mjs +110 -0
  122. package/dist/commands/inspect/stage-map.mjs.map +1 -0
  123. package/dist/commands/inspect/task-list.d.mts +1 -0
  124. package/dist/commands/inspect/task-list.mjs +103 -0
  125. package/dist/commands/inspect/task-list.mjs.map +1 -0
  126. package/dist/commands/inspect/task-show.d.mts +1 -0
  127. package/dist/commands/inspect/task-show.mjs +108 -0
  128. package/dist/commands/inspect/task-show.mjs.map +1 -0
  129. package/dist/commands/inspect/time-report.d.mts +1 -0
  130. package/{src → dist}/commands/inspect/time-report.mjs +11 -13
  131. package/dist/commands/inspect/time-report.mjs.map +1 -0
  132. package/dist/commands/inspect/usage-report.d.mts +1 -0
  133. package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
  134. package/dist/commands/inspect/usage-report.mjs.map +1 -0
  135. package/dist/commands/inspect/user-response.d.mts +1 -0
  136. package/{src → dist}/commands/inspect/user-response.mjs +10 -11
  137. package/dist/commands/inspect/user-response.mjs.map +1 -0
  138. package/dist/commands/inspect/worker-liveness.d.mts +1 -0
  139. package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
  140. package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
  141. package/dist/commands/lifecycle/check-project.d.mts +21 -0
  142. package/dist/commands/lifecycle/check-project.mjs +175 -0
  143. package/dist/commands/lifecycle/check-project.mjs.map +1 -0
  144. package/dist/commands/lifecycle/config.d.mts +1 -0
  145. package/dist/commands/lifecycle/config.mjs +369 -0
  146. package/dist/commands/lifecycle/config.mjs.map +1 -0
  147. package/dist/commands/lifecycle/doctor.d.mts +10 -0
  148. package/dist/commands/lifecycle/doctor.mjs +317 -0
  149. package/dist/commands/lifecycle/doctor.mjs.map +1 -0
  150. package/dist/commands/lifecycle/install.d.mts +17 -0
  151. package/dist/commands/lifecycle/install.mjs +1077 -0
  152. package/dist/commands/lifecycle/install.mjs.map +1 -0
  153. package/dist/commands/lifecycle/migrate.d.mts +1 -0
  154. package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
  155. package/dist/commands/lifecycle/migrate.mjs.map +1 -0
  156. package/dist/commands/lifecycle/model.d.mts +1 -0
  157. package/dist/commands/lifecycle/model.mjs +22 -0
  158. package/dist/commands/lifecycle/model.mjs.map +1 -0
  159. package/dist/commands/lifecycle/paths.d.mts +1 -0
  160. package/dist/commands/lifecycle/paths.mjs +102 -0
  161. package/dist/commands/lifecycle/paths.mjs.map +1 -0
  162. package/dist/commands/lifecycle/preflight.d.mts +1 -0
  163. package/dist/commands/lifecycle/preflight.mjs +124 -0
  164. package/dist/commands/lifecycle/preflight.mjs.map +1 -0
  165. package/dist/commands/lifecycle/setup.d.mts +1 -0
  166. package/dist/commands/lifecycle/setup.mjs +297 -0
  167. package/dist/commands/lifecycle/setup.mjs.map +1 -0
  168. package/dist/commands/lifecycle/uninstall.d.mts +4 -0
  169. package/dist/commands/lifecycle/uninstall.mjs +262 -0
  170. package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
  171. package/dist/commands/manager.d.mts +3 -0
  172. package/{src → dist}/commands/manager.mjs +25 -32
  173. package/dist/commands/manager.mjs.map +1 -0
  174. package/dist/commands/memory/memory.d.mts +1 -0
  175. package/dist/commands/memory/memory.mjs +569 -0
  176. package/dist/commands/memory/memory.mjs.map +1 -0
  177. package/dist/commands/pr/pr.d.mts +33 -0
  178. package/dist/commands/pr/pr.mjs +296 -0
  179. package/dist/commands/pr/pr.mjs.map +1 -0
  180. package/dist/commands/report/agent-activity.d.mts +1 -0
  181. package/{src → dist}/commands/report/agent-activity.mjs +11 -12
  182. package/dist/commands/report/agent-activity.mjs.map +1 -0
  183. package/dist/commands/report/finalize.d.mts +4 -0
  184. package/{src → dist}/commands/report/finalize.mjs +25 -32
  185. package/dist/commands/report/finalize.mjs.map +1 -0
  186. package/dist/commands/report/inject-report-index.d.mts +1 -0
  187. package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
  188. package/dist/commands/report/inject-report-index.mjs.map +1 -0
  189. package/dist/commands/report/render-final-report.d.mts +1 -0
  190. package/{src → dist}/commands/report/render-final-report.mjs +6 -7
  191. package/dist/commands/report/render-final-report.mjs.map +1 -0
  192. package/dist/commands/report/render-views.d.mts +1 -0
  193. package/{src → dist}/commands/report/render-views.mjs +6 -7
  194. package/dist/commands/report/render-views.mjs.map +1 -0
  195. package/dist/commands/report/translate.d.mts +1 -0
  196. package/{src → dist}/commands/report/translate.mjs +6 -7
  197. package/dist/commands/report/translate.mjs.map +1 -0
  198. package/dist/lib/helper-scripts.d.mts +3 -0
  199. package/{src → dist}/lib/helper-scripts.mjs +11 -14
  200. package/dist/lib/helper-scripts.mjs.map +1 -0
  201. package/dist/lib/host-registry-client.d.mts +25 -0
  202. package/dist/lib/host-registry-client.mjs +152 -0
  203. package/dist/lib/host-registry-client.mjs.map +1 -0
  204. package/dist/lib/install-assets.d.mts +8 -0
  205. package/{src → dist}/lib/install-assets.mjs +17 -20
  206. package/dist/lib/install-assets.mjs.map +1 -0
  207. package/dist/lib/okstra-dirs.d.mts +4 -0
  208. package/{src → dist}/lib/okstra-dirs.mjs +3 -6
  209. package/dist/lib/okstra-dirs.mjs.map +1 -0
  210. package/dist/lib/paths.d.mts +4 -0
  211. package/dist/lib/paths.mjs +78 -0
  212. package/dist/lib/paths.mjs.map +1 -0
  213. package/dist/lib/proc.d.mts +3 -0
  214. package/dist/lib/proc.mjs +31 -0
  215. package/dist/lib/proc.mjs.map +1 -0
  216. package/dist/lib/python-helper.d.mts +8 -0
  217. package/dist/lib/python-helper.mjs +123 -0
  218. package/dist/lib/python-helper.mjs.map +1 -0
  219. package/dist/lib/runtime-manifest.d.mts +7 -0
  220. package/dist/lib/runtime-manifest.mjs +73 -0
  221. package/dist/lib/runtime-manifest.mjs.map +1 -0
  222. package/dist/lib/skill-catalog.d.mts +4 -0
  223. package/dist/lib/skill-catalog.mjs +50 -0
  224. package/dist/lib/skill-catalog.mjs.map +1 -0
  225. package/dist/lib/types.d.mts +129 -0
  226. package/dist/lib/types.mjs +2 -0
  227. package/dist/lib/types.mjs.map +1 -0
  228. package/dist/lib/version.d.mts +2 -0
  229. package/dist/lib/version.mjs +27 -0
  230. package/dist/lib/version.mjs.map +1 -0
  231. package/docs/architecture/storage-model.md +6 -2
  232. package/docs/architecture.md +29 -20
  233. package/docs/cli.md +40 -16
  234. package/docs/contributor-change-matrix.md +2 -2
  235. package/docs/for-ai/skills/okstra-inspect.md +2 -3
  236. package/docs/for-ai/skills/okstra-rollup.md +1 -0
  237. package/docs/project-structure-overview.md +72 -62
  238. package/docs/task-process/README.md +12 -8
  239. package/docs/task-process/common-flow.md +13 -16
  240. package/docs/task-process/error-analysis.md +9 -10
  241. package/docs/task-process/final-verification.md +7 -7
  242. package/docs/task-process/implementation-planning.md +9 -9
  243. package/docs/task-process/implementation.md +6 -6
  244. package/docs/task-process/release-handoff.md +8 -7
  245. package/docs/task-process/requirements-discovery.md +8 -8
  246. package/package.json +11 -5
  247. package/runtime/BUILD.json +2 -2
  248. package/runtime/agents/workers/claude-worker.md +2 -1
  249. package/runtime/agents/workers/report-writer-worker.md +3 -1
  250. package/runtime/agents/workers/translator-worker.md +3 -1
  251. package/runtime/bin/lib/okstra/cli.sh +12 -0
  252. package/runtime/bin/lib/okstra/globals.sh +3 -0
  253. package/runtime/bin/lib/okstra/interactive.sh +12 -6
  254. package/runtime/bin/lib/okstra/usage.sh +7 -6
  255. package/runtime/bin/okstra-error-log.py +27 -0
  256. package/runtime/bin/okstra-provider-exec.py +217 -5
  257. package/runtime/bin/okstra-spawn-followups.py +4 -2
  258. package/runtime/bin/okstra.sh +10 -0
  259. package/runtime/prompts/launch.template.md +2 -2
  260. package/runtime/prompts/lead/context-loader.md +1 -2
  261. package/runtime/prompts/lead/convergence.md +27 -18
  262. package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
  263. package/runtime/prompts/lead/plan-body-verification.md +10 -3
  264. package/runtime/prompts/lead/report-writer.md +15 -1
  265. package/runtime/prompts/lead/team-contract.md +17 -13
  266. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  267. package/runtime/prompts/profiles/_implementation-executor.md +0 -3
  268. package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
  269. package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
  270. package/runtime/prompts/profiles/error-analysis.md +27 -1
  271. package/runtime/prompts/profiles/feature-analysis.md +20 -0
  272. package/runtime/prompts/profiles/final-verification.md +20 -1
  273. package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
  274. package/runtime/prompts/profiles/implementation-planning.md +26 -0
  275. package/runtime/prompts/profiles/implementation.md +19 -0
  276. package/runtime/prompts/profiles/improvement-discovery.md +21 -1
  277. package/runtime/prompts/profiles/project-analysis.md +20 -0
  278. package/runtime/prompts/profiles/release-handoff.md +4 -0
  279. package/runtime/prompts/profiles/requirements-discovery.md +27 -1
  280. package/runtime/prompts/wizard/prompts.ko.json +35 -0
  281. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
  282. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
  283. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  284. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
  285. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
  286. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  287. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
  288. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  289. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
  290. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
  291. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
  292. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
  293. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
  294. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
  295. package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
  296. package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
  297. package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
  298. package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
  299. package/runtime/python/okstra_ctl/agent_activity.py +5 -1
  300. package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
  301. package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
  302. package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
  303. package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
  304. package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
  305. package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
  306. package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
  307. package/runtime/python/okstra_ctl/convergence.py +409 -3
  308. package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
  309. package/runtime/python/okstra_ctl/convergence_store.py +170 -1
  310. package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
  311. package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
  312. package/runtime/python/okstra_ctl/doctor.py +14 -0
  313. package/runtime/python/okstra_ctl/domain/host.py +34 -1
  314. package/runtime/python/okstra_ctl/domain/provider.py +212 -6
  315. package/runtime/python/okstra_ctl/domain/role.py +125 -0
  316. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
  317. package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
  318. package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
  319. package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
  320. package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
  321. package/runtime/python/okstra_ctl/error_log_write.py +9 -0
  322. package/runtime/python/okstra_ctl/error_report.py +21 -1
  323. package/runtime/python/okstra_ctl/execution_identity.py +529 -0
  324. package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
  325. package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
  326. package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
  327. package/runtime/python/okstra_ctl/index.py +6 -2
  328. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
  329. package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
  330. package/runtime/python/okstra_ctl/manager_sync.py +4 -1
  331. package/runtime/python/okstra_ctl/model_cli.py +278 -0
  332. package/runtime/python/okstra_ctl/model_defaults.py +46 -0
  333. package/runtime/python/okstra_ctl/model_discovery.py +26 -7
  334. package/runtime/python/okstra_ctl/model_pool.py +501 -0
  335. package/runtime/python/okstra_ctl/models.py +47 -27
  336. package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
  337. package/runtime/python/okstra_ctl/next_phase.py +236 -0
  338. package/runtime/python/okstra_ctl/pane_title.py +151 -0
  339. package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
  340. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
  341. package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
  342. package/runtime/python/okstra_ctl/recap.py +4 -1
  343. package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
  344. package/runtime/python/okstra_ctl/render.py +124 -57
  345. package/runtime/python/okstra_ctl/render_final_report.py +19 -0
  346. package/runtime/python/okstra_ctl/report_contract.py +39 -0
  347. package/runtime/python/okstra_ctl/report_finalize.py +16 -0
  348. package/runtime/python/okstra_ctl/report_html/render.py +1 -0
  349. package/runtime/python/okstra_ctl/role_requirements.py +210 -0
  350. package/runtime/python/okstra_ctl/rollup.py +4 -1
  351. package/runtime/python/okstra_ctl/run.py +1210 -258
  352. package/runtime/python/okstra_ctl/run_index_row.py +7 -1
  353. package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
  354. package/runtime/python/okstra_ctl/team.py +14 -18
  355. package/runtime/python/okstra_ctl/time_report.py +38 -4
  356. package/runtime/python/okstra_ctl/usage_identity.py +86 -0
  357. package/runtime/python/okstra_ctl/usage_report.py +15 -3
  358. package/runtime/python/okstra_ctl/wizard.py +1449 -66
  359. package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
  360. package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
  361. package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
  362. package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
  363. package/runtime/python/okstra_ctl/worker_request.py +7 -1
  364. package/runtime/python/okstra_ctl/worker_runner.py +130 -5
  365. package/runtime/python/okstra_ctl/workers.py +7 -5
  366. package/runtime/python/okstra_ctl/workflow.py +18 -32
  367. package/runtime/python/okstra_ctl/worktree.py +3 -3
  368. package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
  369. package/runtime/python/okstra_ctl/write_policy.py +571 -0
  370. package/runtime/python/okstra_project/state.py +54 -6
  371. package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
  372. package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
  373. package/runtime/schemas/final-report-v2.0.schema.json +68 -5
  374. package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
  375. package/runtime/skills/okstra-inspect/facets/report.md +1 -1
  376. package/runtime/skills/okstra-inspect/facets/status.md +15 -13
  377. package/runtime/skills/okstra-rollup/SKILL.md +1 -0
  378. package/runtime/skills/okstra-run/SKILL.md +14 -14
  379. package/runtime/templates/implementation-worker-preamble.md +3 -18
  380. package/runtime/templates/project-docs/task-index.template.md +0 -1
  381. package/runtime/templates/report-writer-prompt-preamble.md +2 -0
  382. package/runtime/templates/reports/final-report-v2.template.md +7 -1
  383. package/runtime/templates/reports/html/base.template.html +21 -0
  384. package/runtime/templates/reports/html/i18n/en.json +4 -0
  385. package/runtime/templates/reports/html/i18n/ko.json +4 -0
  386. package/runtime/templates/reports/html/macros/forms.html +5 -4
  387. package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
  388. package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
  389. package/runtime/templates/worker-prompt-preamble.md +6 -35
  390. package/runtime/validators/lib/fixtures.sh +49 -16
  391. package/runtime/validators/validate-run.py +486 -134
  392. package/runtime/validators/validate_session_conformance.py +25 -7
  393. package/src/cli-registry.mjs +0 -539
  394. package/src/commands/execute/codex-dispatch.mjs +0 -10
  395. package/src/commands/execute/plan-items.mjs +0 -9
  396. package/src/commands/execute/plan-validate.mjs +0 -68
  397. package/src/commands/execute/run.mjs +0 -208
  398. package/src/commands/execute/wizard.mjs +0 -167
  399. package/src/commands/inspect/stage-map.mjs +0 -98
  400. package/src/commands/inspect/task-list.mjs +0 -97
  401. package/src/commands/inspect/task-show.mjs +0 -100
  402. package/src/commands/lifecycle/check-project.mjs +0 -187
  403. package/src/commands/lifecycle/config.mjs +0 -385
  404. package/src/commands/lifecycle/doctor.mjs +0 -308
  405. package/src/commands/lifecycle/install.mjs +0 -1170
  406. package/src/commands/lifecycle/paths.mjs +0 -104
  407. package/src/commands/lifecycle/preflight.mjs +0 -125
  408. package/src/commands/lifecycle/setup.mjs +0 -333
  409. package/src/commands/lifecycle/uninstall.mjs +0 -279
  410. package/src/commands/memory/memory.mjs +0 -524
  411. package/src/commands/pr/pr.mjs +0 -261
  412. package/src/lib/host-registry-client.mjs +0 -176
  413. package/src/lib/paths.mjs +0 -86
  414. package/src/lib/proc.mjs +0 -31
  415. package/src/lib/python-helper.mjs +0 -142
  416. package/src/lib/runtime-manifest.mjs +0 -70
  417. package/src/lib/skill-catalog.mjs +0 -53
  418. package/src/lib/version.mjs +0 -20
  419. /package/{src → dist}/commands/pr/default.md +0 -0
@@ -2,7 +2,9 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import argparse
5
+ from dataclasses import replace
5
6
  from datetime import datetime, timezone
7
+ import hashlib
6
8
  import json
7
9
  import os
8
10
  from pathlib import Path
@@ -21,24 +23,33 @@ from .agent_invocation import (
21
23
  AgentModelAssignment,
22
24
  PreparedAgentInvocation,
23
25
  agent_model_assignment_from_payload,
26
+ compose_agent_prompt,
27
+ compose_unbound_run_prompt,
24
28
  digest_duty_catalog,
29
+ invocation_execution_identity_from_manifest,
25
30
  materialize_standalone_result,
26
31
  prepare_agent_invocation,
27
32
  publish_standalone_completion,
28
33
  verify_standalone_completion,
29
34
  verify_agent_invocation,
35
+ v2_role_assignment_authority_errors,
30
36
  )
31
- from .application.resolve_assignment import resolve_assignment_runner
32
- from .model_discovery import normalize_execution_for_dispatch
33
- from .models import provider_default_model, resolve_model_metadata
37
+ from .convergence_store import (
38
+ DYNAMIC_VERIFIER_SOURCE_ROLES,
39
+ reserve_dynamic_verifier,
40
+ )
41
+ from .assignment_environment import load_assignment_context
42
+ from .assignment_resolver import AssignmentContext, resolve_dispatch_assignment
34
43
  from .path_hints import hydrate_active_run_context
35
- from .ports.host_model import HostModelBindingRequest
36
- from .registry.host_registry import default_host_registry
37
- from .registry.provider_registry import default_provider_registry
38
44
  from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
45
+ from .worker_artifact_paths import audit_sidecar_rel
46
+ from .wrapper_status import status_path_for_prompt
39
47
  from .worker_prompt_policy import resolve_prompt_plan_for_manifest
40
48
  from .dispatch_state import (
49
+ BACKEND_CLI_WRAPPER,
50
+ BACKEND_CMUX_PANE,
41
51
  DispatchError,
52
+ detect_terminal_backend,
42
53
  link_agent_dispatch_result,
43
54
  record_verified_agent_dispatch,
44
55
  reject_agent_dispatch_result,
@@ -65,9 +76,14 @@ def _parser() -> argparse.ArgumentParser:
65
76
  materialize.add_argument("--worker-id")
66
77
  materialize.add_argument("--dispatch-kind")
67
78
  materialize.add_argument("--assignment-ref")
79
+ materialize.add_argument("--source-role-execution-ref")
68
80
  materialize.add_argument("--result")
69
81
  materialize.add_argument("--audit-source")
70
82
  materialize.add_argument("--host-runtime")
83
+ materialize.add_argument(
84
+ "--terminal-backend",
85
+ choices=(BACKEND_CLI_WRAPPER, BACKEND_CMUX_PANE),
86
+ )
71
87
  materialize.add_argument("--provider")
72
88
  materialize.add_argument("--model-role")
73
89
  materialize.add_argument("--model", default="")
@@ -314,7 +330,19 @@ def _materialize_run(
314
330
  raise AgentPromptCliError(
315
331
  f"assignment reference is missing from run manifest: {args.assignment_ref}"
316
332
  )
317
- assignment = agent_model_assignment_from_payload(assignments[args.assignment_ref])
333
+ assignment_payload = assignments[args.assignment_ref]
334
+ assignment = agent_model_assignment_from_payload(assignment_payload)
335
+ dynamic_source = _dynamic_verifier_source(args, manifest)
336
+ identity = (
337
+ None
338
+ if dynamic_source is not None
339
+ else invocation_execution_identity_from_manifest(
340
+ manifest,
341
+ assignment=assignment,
342
+ assignment_ref=args.assignment_ref,
343
+ duty_id=args.audience,
344
+ )
345
+ )
318
346
  duty_root = _project_manifest_path(
319
347
  project_root,
320
348
  contract.get("dutyRootPath"),
@@ -345,9 +373,9 @@ def _materialize_run(
345
373
  manifest=manifest,
346
374
  active_context=active_context,
347
375
  ))
348
- return prepare_agent_invocation(AgentInvocationRequest(
376
+ request = AgentInvocationRequest(
349
377
  invocation_id=args.invocation_id,
350
- worker_id=args.worker_id,
378
+ worker_id=args.worker_id if identity is None else None,
351
379
  audience=args.audience,
352
380
  assignment_ref=args.assignment_ref,
353
381
  purpose=None,
@@ -366,8 +394,186 @@ def _materialize_run(
366
394
  prompt_path=prompt_path,
367
395
  metadata_path=prompt_path.with_name(prompt_path.name + ".meta.json"),
368
396
  dispatch_kind=args.dispatch_kind,
397
+ participant_ref=identity.participant_ref if identity is not None else None,
398
+ role_execution_ref=(
399
+ identity.role_execution_ref if identity is not None else None
400
+ ),
401
+ duty_id=identity.duty_id if identity is not None else None,
402
+ invocation_ref=args.invocation_id if identity is not None else None,
403
+ attempt=1,
369
404
  replace_undispatched=args.replace_undispatched,
370
- ))
405
+ )
406
+ if dynamic_source is not None:
407
+ request = _reserve_dynamic_verifier_request(
408
+ request,
409
+ args=args,
410
+ manifest=manifest,
411
+ manifest_path=manifest_path,
412
+ source_role_execution_ref=dynamic_source,
413
+ artifact_paths=_dynamic_verifier_artifact_paths(
414
+ project_root,
415
+ prompt_path,
416
+ result_path,
417
+ audit_source_path,
418
+ audit_rel,
419
+ active_context,
420
+ args.worker_id,
421
+ ),
422
+ )
423
+ return prepare_agent_invocation(request)
424
+
425
+
426
+ def _reserve_dynamic_verifier_request(
427
+ request: AgentInvocationRequest,
428
+ *,
429
+ args: argparse.Namespace,
430
+ manifest: Mapping[str, Any],
431
+ manifest_path: Path,
432
+ source_role_execution_ref: str,
433
+ artifact_paths: tuple[Path, ...],
434
+ ) -> AgentInvocationRequest:
435
+ _validate_dynamic_source_assignment(
436
+ manifest,
437
+ source_role_execution_ref,
438
+ request.assignment,
439
+ )
440
+ prompt_bytes = compose_unbound_run_prompt(request)
441
+ verifier, invocation = reserve_dynamic_verifier(
442
+ manifest_path,
443
+ source_role_execution_ref=source_role_execution_ref,
444
+ duty_id=args.audience,
445
+ round_number=_reverify_round(args.dispatch_kind),
446
+ task_key=_required_manifest_string(manifest, "taskKey"),
447
+ input_digest="sha256:" + hashlib.sha256(prompt_bytes).hexdigest(),
448
+ invocation_ref=args.invocation_id,
449
+ artifact_paths=artifact_paths,
450
+ )
451
+ bound = replace(
452
+ request,
453
+ worker_id=None,
454
+ participant_ref=verifier.participant_ref,
455
+ role_execution_ref=verifier.role_execution_ref,
456
+ duty_id=args.audience,
457
+ invocation_ref=invocation.invocation_ref,
458
+ )
459
+ if compose_agent_prompt(bound).encode("utf-8") != prompt_bytes:
460
+ raise AgentPromptCliError(
461
+ "dynamic verifier identity changed immutable prompt bytes"
462
+ )
463
+ return bound
464
+
465
+
466
+ def _dynamic_verifier_artifact_paths(
467
+ project_root: Path,
468
+ prompt_path: Path,
469
+ result_path: Path,
470
+ audit_source_path: Path,
471
+ audit_rel: str,
472
+ active_context: Mapping[str, Any],
473
+ worker_id: str,
474
+ ) -> tuple[Path, ...]:
475
+ paths = {
476
+ result_path,
477
+ audit_source_path,
478
+ project_root / audit_sidecar_rel(audit_rel),
479
+ status_path_for_prompt(prompt_path),
480
+ prompt_path.with_suffix(prompt_path.suffix + ".log"),
481
+ }
482
+ error_logs = active_context.get("errorLogs")
483
+ sidecars = error_logs.get("sidecarsByWorkerId") if isinstance(error_logs, Mapping) else None
484
+ value = sidecars.get(worker_id) if isinstance(sidecars, Mapping) else None
485
+ if isinstance(value, str) and value:
486
+ path = Path(value)
487
+ paths.add(path if path.is_absolute() else project_root / path)
488
+ return tuple(sorted(paths, key=str))
489
+
490
+
491
+ def _dynamic_verifier_source(
492
+ args: argparse.Namespace,
493
+ manifest: Mapping[str, Any],
494
+ ) -> str | None:
495
+ is_v2 = (
496
+ manifest.get("schemaVersion") == "2.0"
497
+ and manifest.get("executionIdentityVersion") == 2
498
+ )
499
+ is_reverify = (
500
+ args.assignment_ref.startswith("reverify/")
501
+ and args.audience == "reverification-worker"
502
+ )
503
+ source = args.source_role_execution_ref
504
+ if is_v2 and is_reverify:
505
+ if args.replace_undispatched:
506
+ raise AgentPromptCliError(
507
+ "v2 dynamic verifier prompts are append-only; use a fresh "
508
+ "invocation ID and prompt path"
509
+ )
510
+ if not source:
511
+ raise AgentPromptCliError(
512
+ "v2 reverify materialization requires "
513
+ "--source-role-execution-ref"
514
+ )
515
+ return source
516
+ if source:
517
+ raise AgentPromptCliError(
518
+ "--source-role-execution-ref is allowed only for v2 reverify"
519
+ )
520
+ return None
521
+
522
+
523
+ def _reverify_round(dispatch_kind: str) -> int:
524
+ prefix = "reverify-r"
525
+ value = dispatch_kind.removeprefix(prefix)
526
+ if not dispatch_kind.startswith(prefix) or not value.isdigit() or int(value) < 1:
527
+ raise AgentPromptCliError(
528
+ "dynamic verifier dispatch kind must be reverify-r<N>"
529
+ )
530
+ return int(value)
531
+
532
+
533
+ def _validate_dynamic_source_assignment(
534
+ manifest: Mapping[str, Any],
535
+ source_role_execution_ref: str,
536
+ assignment: AgentModelAssignment,
537
+ ) -> None:
538
+ rows = manifest.get("roleExecutions")
539
+ source = next(
540
+ (
541
+ row
542
+ for row in rows
543
+ if isinstance(row, Mapping)
544
+ and row.get("roleExecutionRef") == source_role_execution_ref
545
+ ),
546
+ None,
547
+ ) if isinstance(rows, list) else None
548
+ if source is None:
549
+ raise AgentPromptCliError(
550
+ f"source role execution is unknown: {source_role_execution_ref}"
551
+ )
552
+ if source.get("role") not in DYNAMIC_VERIFIER_SOURCE_ROLES:
553
+ raise AgentPromptCliError(
554
+ "dynamic verifier source role is not eligible for re-verification"
555
+ )
556
+ errors = v2_role_assignment_authority_errors(
557
+ manifest,
558
+ role_execution_ref=source_role_execution_ref,
559
+ participant_ref=str(source.get("participantRef") or ""),
560
+ assignment=assignment,
561
+ )
562
+ if errors:
563
+ raise AgentPromptCliError(
564
+ "dynamic verifier source does not match the selected assignment: "
565
+ + "; ".join(errors)
566
+ )
567
+
568
+
569
+ def _required_manifest_string(
570
+ manifest: Mapping[str, Any],
571
+ key: str,
572
+ ) -> str:
573
+ value = manifest.get(key)
574
+ if not isinstance(value, str) or not value.strip():
575
+ raise AgentPromptCliError(f"run manifest has no {key}")
576
+ return value
371
577
 
372
578
 
373
579
  def _validate_run_identity(
@@ -437,6 +643,7 @@ def _materialize_standalone(
437
643
  "assignment-ref": args.assignment_ref,
438
644
  "result": args.result,
439
645
  "audit-source": args.audit_source,
646
+ "source-role-execution-ref": args.source_role_execution_ref,
440
647
  }
441
648
  present = [name for name, value in forbidden.items() if value]
442
649
  if present:
@@ -459,8 +666,14 @@ def _materialize_standalone(
459
666
  expected_prompt = root / f"{args.invocation_id}.prompt.md"
460
667
  instruction_path = _exact_path(args.instruction, expected_instruction, True, "instruction")
461
668
  prompt_path = _exact_path(args.prompt, expected_prompt, False, "prompt")
462
- assignment = _standalone_assignment(
669
+ assignment_context = load_assignment_context(
463
670
  host_runtime=args.host_runtime,
671
+ terminal_backend=args.terminal_backend or detect_terminal_backend(),
672
+ execution_provider_ids=(args.provider,),
673
+ include_native_provider=False,
674
+ )
675
+ assignment = _standalone_assignment(
676
+ context=assignment_context,
464
677
  provider=args.provider,
465
678
  model_role=args.model_role,
466
679
  model=args.model,
@@ -488,54 +701,40 @@ def _materialize_standalone(
488
701
  prompt_path=prompt_path,
489
702
  metadata_path=prompt_path.with_name(prompt_path.name + ".meta.json"),
490
703
  dispatch_kind="standalone",
704
+ participant_ref=None,
705
+ role_execution_ref=None,
706
+ duty_id=None,
707
+ invocation_ref=None,
708
+ attempt=1,
491
709
  ))
492
710
 
493
711
 
494
712
  def _standalone_assignment(
495
- *, host_runtime: str, provider: str, model_role: str, model: str,
713
+ *,
714
+ context: AssignmentContext,
715
+ provider: str,
716
+ model_role: str,
717
+ model: str,
496
718
  ) -> AgentModelAssignment:
497
- provider_registry = default_provider_registry()
498
- provider_spec = provider_registry.resolve(provider)
499
- default_model = provider_default_model(provider, model_role)
500
- if not default_model:
501
- raise AgentPromptCliError(
502
- f"provider {provider!r} has no default model for role {model_role!r}"
503
- )
504
- metadata = resolve_model_metadata(
505
- provider=provider,
506
- raw_value=model,
507
- default_display=default_model,
508
- default_execution=default_model,
509
- )
510
- host_adapter = default_host_registry(provider_registry).resolve(host_runtime)
511
- runner = resolve_assignment_runner(
512
- host=host_adapter.descriptor,
513
- provider=provider_spec,
514
- role=model_role,
515
- ).runner
516
- execution = (
517
- normalize_execution_for_dispatch(
518
- provider=provider,
519
- execution=metadata.execution,
520
- role=model_role,
521
- )
522
- if runner == "cli-wrapper"
523
- else metadata.execution
524
- )
525
- host_model = host_adapter.host_model().resolve(HostModelBindingRequest(
719
+ host_runtime = context.environment.host_descriptor.id
720
+ resolved = resolve_dispatch_assignment(
721
+ context=context,
526
722
  host_runtime=host_runtime,
723
+ role=model_role,
724
+ duty_id=model_role,
527
725
  provider=provider,
528
- model=metadata.display,
529
- model_execution_value=execution,
530
- runner=runner,
531
- ))
726
+ model=model,
727
+ )
728
+ binding = resolved.binding
729
+ if binding is None:
730
+ raise AgentPromptCliError("standalone assignment requires a model binding")
532
731
  return AgentModelAssignment(
533
- provider=provider,
534
- model=metadata.display,
535
- model_execution_value=execution,
536
- runner=runner,
732
+ provider=resolved.provider_id,
733
+ model=resolved.display_name,
734
+ model_execution_value=binding.resolved_execution_value,
735
+ runner=binding.runner,
537
736
  host_runtime=host_runtime,
538
- host_model_value=host_model,
737
+ host_model_value=binding.host_model_value,
539
738
  )
540
739
 
541
740
 
@@ -0,0 +1,29 @@
1
+ """Choose an execution surface from an injected port chain."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Callable, Sequence
5
+
6
+ from ..domain.worker_runtime import (
7
+ EnvironmentBlocked,
8
+ RuntimeHandle,
9
+ SurfaceUnavailable,
10
+ WorkerSpawnRequest,
11
+ )
12
+ from ..ports.worker_runtime import WorkerRuntimePort
13
+
14
+
15
+ def open_worker(
16
+ chain: Sequence[WorkerRuntimePort],
17
+ request_for: Callable[[str], WorkerSpawnRequest],
18
+ ) -> RuntimeHandle:
19
+ last_unavailable: SurfaceUnavailable | None = None
20
+ for port in chain:
21
+ try:
22
+ return port.spawn(request_for(port.surface))
23
+ except EnvironmentBlocked:
24
+ raise
25
+ except SurfaceUnavailable as exc:
26
+ last_unavailable = exc
27
+ if last_unavailable is None:
28
+ raise SurfaceUnavailable("runtime chain is empty")
29
+ raise last_unavailable
@@ -25,7 +25,7 @@ def resolve_assignment_runner(
25
25
  role: str,
26
26
  requested_runner: str = "",
27
27
  ) -> RunnerResolution:
28
- if role not in provider.supported_roles:
28
+ if not provider.supports_role(role):
29
29
  raise ProviderUnavailable(
30
30
  f"provider {provider.provider!r} does not support role {role!r}"
31
31
  )
@@ -66,20 +66,33 @@ def resolve_lead_provider(
66
66
  provider_registry,
67
67
  ) -> LeadProviderAssignment:
68
68
  host = host_registry.resolve(host_id).descriptor
69
+ provider_id = resolve_lead_provider_id(
70
+ host=host,
71
+ requested_provider=requested_provider,
72
+ )
73
+ provider = provider_registry.resolve(provider_id)
74
+ return resolve_lead_provider_from_specs(
75
+ host=host,
76
+ requested_provider=requested_provider,
77
+ provider=provider,
78
+ )
79
+
80
+
81
+ def resolve_lead_provider_from_specs(
82
+ *,
83
+ host: HostDescriptor,
84
+ requested_provider: str,
85
+ provider: ProviderSpec,
86
+ ) -> LeadProviderAssignment:
87
+ """Resolve a lead from already loaded host and provider facts."""
69
88
  requested = (requested_provider or "").strip().lower()
70
- if not requested and not host.native_provider_id:
71
- raise HostCapabilityMismatch(
72
- f"host {host.id!r} has no native lead provider; select an explicit "
73
- "provider with lead capability"
74
- )
75
- if host.native_provider_id and requested not in {"", host.native_provider_id}:
89
+ _validate_lead_provider_request(host, requested)
90
+ provider_id = requested or host.native_provider_id
91
+ if provider.provider != provider_id:
76
92
  raise HostCapabilityMismatch(
77
- f"{host.agent_label.removesuffix(' CLI')} host requires provider "
78
- f"{host.native_provider_id!r} for the in-session lead; provider "
79
- f"{requested!r} must be selected as a worker instead"
93
+ f"provider fact {provider.provider!r} does not match requested "
94
+ f"lead provider {provider_id!r}"
80
95
  )
81
- provider_id = requested or host.native_provider_id
82
- provider = provider_registry.resolve(provider_id)
83
96
  resolution = resolve_assignment_runner(
84
97
  host=host,
85
98
  provider=provider,
@@ -91,3 +104,31 @@ def resolve_lead_provider(
91
104
  wrapper=resolution.wrapper,
92
105
  reason=resolution.reason,
93
106
  )
107
+
108
+
109
+ def resolve_lead_provider_id(
110
+ *,
111
+ host: HostDescriptor,
112
+ requested_provider: str,
113
+ ) -> str:
114
+ """Validate lead-provider selection and return its canonical id."""
115
+ requested = (requested_provider or "").strip().lower()
116
+ _validate_lead_provider_request(host, requested)
117
+ return requested or host.native_provider_id
118
+
119
+
120
+ def _validate_lead_provider_request(
121
+ host: HostDescriptor,
122
+ requested: str,
123
+ ) -> None:
124
+ if not requested and not host.native_provider_id:
125
+ raise HostCapabilityMismatch(
126
+ f"host {host.id!r} has no native lead provider; select an explicit "
127
+ "provider with lead capability"
128
+ )
129
+ if host.native_provider_id and requested not in {"", host.native_provider_id}:
130
+ raise HostCapabilityMismatch(
131
+ f"{host.agent_label.removesuffix(' CLI')} host requires provider "
132
+ f"{host.native_provider_id!r} for the in-session lead; provider "
133
+ f"{requested!r} must be selected as a worker instead"
134
+ )
@@ -0,0 +1,102 @@
1
+ """Load ambient adapter facts once, outside deterministic assignment logic."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Iterable
5
+
6
+ from .assignment_resolver import (
7
+ AssignmentContext,
8
+ AssignmentEnvironment,
9
+ TerminalBackend,
10
+ )
11
+ from .domain.provider import ExecutionNormalization, UnknownProviderError
12
+ from .domain.role import ROLE_DEFINITIONS
13
+ from .domain.worker_exec import WorkerWriteCapability
14
+ from .model_pool import ModelPool
15
+ from .registry.host_registry import default_host_registry
16
+ from .registry.provider_registry import default_provider_registry
17
+
18
+
19
+ def load_assignment_context(
20
+ *,
21
+ host_runtime: str,
22
+ terminal_backend: TerminalBackend,
23
+ execution_provider_ids: Iterable[str] | None = None,
24
+ include_native_provider: bool = True,
25
+ ) -> AssignmentContext:
26
+ """Snapshot provider, host, terminal, and execution inputs for one caller."""
27
+ provider_registry = default_provider_registry()
28
+ pool = ModelPool.from_registry(provider_registry)
29
+ adapter = default_host_registry(provider_registry).resolve(host_runtime)
30
+ worker_port = adapter.worker_dispatch()
31
+ capability_reader = getattr(worker_port, "worker_write_capability", None)
32
+ native_capability = (
33
+ capability_reader()
34
+ if callable(capability_reader)
35
+ else WorkerWriteCapability("none")
36
+ )
37
+ roles = tuple(row.id for row in ROLE_DEFINITIONS)
38
+ if execution_provider_ids is None:
39
+ provider_ids = provider_registry.ids()
40
+ else:
41
+ requested = [
42
+ provider.strip().lower() for provider in execution_provider_ids
43
+ ]
44
+ if include_native_provider:
45
+ requested.insert(0, adapter.descriptor.native_provider_id)
46
+ requested_ids = dict.fromkeys(requested)
47
+ selected_ids = []
48
+ for provider_id in requested_ids:
49
+ if not provider_id:
50
+ continue
51
+ try:
52
+ provider_registry.resolve(provider_id)
53
+ except UnknownProviderError:
54
+ continue
55
+ selected_ids.append(provider_id)
56
+ provider_ids = tuple(selected_ids)
57
+ execution_resolutions = {}
58
+ for provider_id in provider_ids:
59
+ provider = provider_registry.resolve(provider_id)
60
+ models_by_id = {
61
+ model.model_id: model for model in provider.models.values()
62
+ }
63
+ models = tuple(models_by_id.values())
64
+ normalizer = provider.execution_normalizer
65
+ if normalizer is None:
66
+ rows = {
67
+ (model.model_id, role): ExecutionNormalization(
68
+ model.execution_value
69
+ )
70
+ for model in models
71
+ for role in roles
72
+ }
73
+ else:
74
+ try:
75
+ rows = normalizer.snapshot(models, roles)
76
+ except Exception as exc:
77
+ rows = {
78
+ (model.model_id, role): ExecutionNormalization(None, str(exc))
79
+ for model in models
80
+ for role in roles
81
+ }
82
+ for model in models:
83
+ for role in roles:
84
+ key = (model.model_id, role)
85
+ outcome = rows.get(key)
86
+ if outcome is None:
87
+ outcome = ExecutionNormalization(
88
+ None,
89
+ f"provider {provider_id!r} did not snapshot model "
90
+ f"{model.model_id!r} for role {role!r}",
91
+ )
92
+ execution_resolutions[(f"{provider_id}/{model.model_id}", role)] = (
93
+ outcome
94
+ )
95
+ environment = AssignmentEnvironment(
96
+ host_descriptor=adapter.descriptor,
97
+ host_model_port=adapter.host_model(),
98
+ native_worker_write_capability=native_capability,
99
+ terminal_backend=terminal_backend,
100
+ execution_resolutions=execution_resolutions,
101
+ )
102
+ return AssignmentContext(pool=pool, environment=environment)