phase-loop-runtime 0.2.0__py3-none-any.whl

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 (370) hide show
  1. phase_loop_runtime/__init__.py +20 -0
  2. phase_loop_runtime/_contract_docs/phase-loop/extraction-readiness.md +90 -0
  3. phase_loop_runtime/_contract_docs/phase-loop/granular-execution-policy.md +137 -0
  4. phase_loop_runtime/_contract_docs/phase-loop/harness-capability-matrix.md +510 -0
  5. phase_loop_runtime/_contract_docs/phase-loop/harness-skill-matrix.md +125 -0
  6. phase_loop_runtime/_contract_docs/phase-loop/harness-substrate-manifest.md +232 -0
  7. phase_loop_runtime/_contract_docs/phase-loop/pi-loop-control.md +247 -0
  8. phase_loop_runtime/_contract_docs/phase-loop/protocol.md +2759 -0
  9. phase_loop_runtime/_contract_docs/phase-loop/runtime-boundary.md +289 -0
  10. phase_loop_runtime/_contract_docs/phase-loop/spec-discovery-roots.md +47 -0
  11. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-governed-pipeline.md +42 -0
  12. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-portal-projection.md +34 -0
  13. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-regenesis.md +29 -0
  14. phase_loop_runtime/_contract_docs/phase-loop/substrate-soak-report.md +32 -0
  15. phase_loop_runtime/_contract_docs/runtime/verification-evidence-contract.md +52 -0
  16. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/commands.txt +2 -0
  17. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/manifest.json +16 -0
  18. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-mixed-signals/mixed.json +5 -0
  19. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/manifest.json +16 -0
  20. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-provider-timeout/timeout-note.txt +2 -0
  21. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/manifest.json +16 -0
  22. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-redacted-summary/redacted-summary.json +6 -0
  23. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/manifest.json +16 -0
  24. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-small-sample/sample.json +7 -0
  25. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/edge.txt +2 -0
  26. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/borderline/borderline-threshold-edge/manifest.json +16 -0
  27. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/manifest.json +16 -0
  28. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-a.txt +1 -0
  29. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-b.txt +1 -0
  30. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-duplicate-png/shot-c.txt +1 -0
  31. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/claimed-results.json +5 -0
  32. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-missing-references/manifest.json +16 -0
  33. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/manifest.json +16 -0
  34. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-subtle-low-variance/metrics.json +9 -0
  35. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/findings.md +10 -0
  36. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-templated-prose/manifest.json +16 -0
  37. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/manifest.json +16 -0
  38. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/scores.json +4 -0
  39. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-fake/fake-uniform-scores/summary.txt +1 -0
  40. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/bundle-check.json +8 -0
  41. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/manifest.json +16 -0
  42. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-adoption-bundle/operator-note.txt +2 -0
  43. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/events-sample.jsonl +3 -0
  44. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/manifest.json +16 -0
  45. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-handoff-ledger/receipt.txt +1 -0
  46. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/evidence.json +13 -0
  47. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/manifest.json +16 -0
  48. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-runner-diff/summary.txt +2 -0
  49. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/manifest.json +16 -0
  50. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/matrix.json +9 -0
  51. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-test-matrix/notes.md +4 -0
  52. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/console-summary.txt +2 -0
  53. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/manifest.json +16 -0
  54. phase_loop_runtime/_test_fixtures/evidence-audit-calibration/known-real/real-ui-screenshot-standin/screenshot-standin.ppm +5 -0
  55. phase_loop_runtime/adoption_bundle.py +273 -0
  56. phase_loop_runtime/advisor_board/CONTRACTS.md +257 -0
  57. phase_loop_runtime/advisor_board/__init__.py +288 -0
  58. phase_loop_runtime/advisor_board/backing.py +137 -0
  59. phase_loop_runtime/advisor_board/backing_omnigent.py +498 -0
  60. phase_loop_runtime/advisor_board/config.py +225 -0
  61. phase_loop_runtime/advisor_board/events.py +115 -0
  62. phase_loop_runtime/advisor_board/fixtures/advisor-boards.example.toml +59 -0
  63. phase_loop_runtime/advisor_board/fixtures.py +91 -0
  64. phase_loop_runtime/advisor_board/harness_mapping.py +125 -0
  65. phase_loop_runtime/advisor_board/matrix.py +146 -0
  66. phase_loop_runtime/advisor_board/observability.py +481 -0
  67. phase_loop_runtime/advisor_board/presets.py +188 -0
  68. phase_loop_runtime/advisor_board/registries.py +309 -0
  69. phase_loop_runtime/advisor_board/resolver.py +253 -0
  70. phase_loop_runtime/advisor_board/schema.py +269 -0
  71. phase_loop_runtime/advisor_board/standin.py +166 -0
  72. phase_loop_runtime/advisor_board/validation.py +159 -0
  73. phase_loop_runtime/agent_runtime_provider.py +676 -0
  74. phase_loop_runtime/baml_modular.py +656 -0
  75. phase_loop_runtime/baml_src/dotfiles_adoption_manifest.baml +37 -0
  76. phase_loop_runtime/baml_src/dotfiles_c4_document.baml +14 -0
  77. phase_loop_runtime/baml_src/dotfiles_plan_manifest.baml +50 -0
  78. phase_loop_runtime/baml_src/dotfiles_runtime_projection.baml +16 -0
  79. phase_loop_runtime/baml_src/dotfiles_task_catalog.baml +20 -0
  80. phase_loop_runtime/baml_src/emit_phase_closeout.baml +86 -0
  81. phase_loop_runtime/baml_src/evaluate_suspected_fake_evidence.baml +36 -0
  82. phase_loop_runtime/baml_src/verification_evidence.baml +40 -0
  83. phase_loop_runtime/broker.py +181 -0
  84. phase_loop_runtime/build_bundle.py +294 -0
  85. phase_loop_runtime/capability_registry.py +835 -0
  86. phase_loop_runtime/classifier.py +87 -0
  87. phase_loop_runtime/claude_agent_view.py +625 -0
  88. phase_loop_runtime/claude_channel_sidecar.py +1086 -0
  89. phase_loop_runtime/cli.py +2599 -0
  90. phase_loop_runtime/closeout.py +865 -0
  91. phase_loop_runtime/closeout_classifier.py +120 -0
  92. phase_loop_runtime/closeout_evidence_audit.py +112 -0
  93. phase_loop_runtime/closeout_validation.py +144 -0
  94. phase_loop_runtime/closeout_validators.py +203 -0
  95. phase_loop_runtime/consiliency_gates.py +374 -0
  96. phase_loop_runtime/consiliency_ingest.py +399 -0
  97. phase_loop_runtime/consiliency_layout.py +176 -0
  98. phase_loop_runtime/consiliency_scaffold.py +305 -0
  99. phase_loop_runtime/cross_repo_channel.py +331 -0
  100. phase_loop_runtime/discovery.py +2368 -0
  101. phase_loop_runtime/dispatch_lock.py +167 -0
  102. phase_loop_runtime/doc_delta_validator.py +98 -0
  103. phase_loop_runtime/docs_audit.py +285 -0
  104. phase_loop_runtime/docs_freshness.py +468 -0
  105. phase_loop_runtime/docs_surfaces.py +150 -0
  106. phase_loop_runtime/dotfiles_profile_plugin.py +96 -0
  107. phase_loop_runtime/events.py +154 -0
  108. phase_loop_runtime/events_migration.py +96 -0
  109. phase_loop_runtime/evidence_audit.py +1082 -0
  110. phase_loop_runtime/evidence_audit_config.py +172 -0
  111. phase_loop_runtime/fleet_map.py +426 -0
  112. phase_loop_runtime/fleet_metrics.py +298 -0
  113. phase_loop_runtime/fleet_metrics_export.py +196 -0
  114. phase_loop_runtime/git_discipline.py +479 -0
  115. phase_loop_runtime/git_ops.py +122 -0
  116. phase_loop_runtime/git_topology.py +252 -0
  117. phase_loop_runtime/governed_bundle.py +137 -0
  118. phase_loop_runtime/governed_premerge.py +219 -0
  119. phase_loop_runtime/governed_review.py +264 -0
  120. phase_loop_runtime/handoff.py +932 -0
  121. phase_loop_runtime/handoff_path.py +14 -0
  122. phase_loop_runtime/injection.py +740 -0
  123. phase_loop_runtime/install_status.py +88 -0
  124. phase_loop_runtime/lane_scheduler.py +317 -0
  125. phase_loop_runtime/launcher.py +2760 -0
  126. phase_loop_runtime/lease_store.py +553 -0
  127. phase_loop_runtime/maintenance.py +401 -0
  128. phase_loop_runtime/migrate_handoffs.py +201 -0
  129. phase_loop_runtime/models.py +2144 -0
  130. phase_loop_runtime/observability.py +1290 -0
  131. phase_loop_runtime/panel_invoker.py +1638 -0
  132. phase_loop_runtime/phase_loop_drift_audit.py +388 -0
  133. phase_loop_runtime/phase_worktree_executor.py +433 -0
  134. phase_loop_runtime/pipeline_adapter/__init__.py +12 -0
  135. phase_loop_runtime/pipeline_adapter/branch_ops.py +274 -0
  136. phase_loop_runtime/pipeline_adapter/flag.py +55 -0
  137. phase_loop_runtime/pipeline_adapter/markers.py +13 -0
  138. phase_loop_runtime/pipeline_adapter/merge_policy.py +72 -0
  139. phase_loop_runtime/pipeline_adapter/ratification.py +100 -0
  140. phase_loop_runtime/pipeline_adapter/sibling_matcher.py +123 -0
  141. phase_loop_runtime/plan_ir.py +523 -0
  142. phase_loop_runtime/plan_manifest.py +415 -0
  143. phase_loop_runtime/planner_validation.py +176 -0
  144. phase_loop_runtime/profiles.py +462 -0
  145. phase_loop_runtime/prompts.py +365 -0
  146. phase_loop_runtime/provenance.py +165 -0
  147. phase_loop_runtime/publishing.py +339 -0
  148. phase_loop_runtime/py.typed +1 -0
  149. phase_loop_runtime/reconcile.py +1550 -0
  150. phase_loop_runtime/redaction.py +184 -0
  151. phase_loop_runtime/reflection_sync.py +293 -0
  152. phase_loop_runtime/release_guard.py +186 -0
  153. phase_loop_runtime/render.py +432 -0
  154. phase_loop_runtime/repo_validation.py +427 -0
  155. phase_loop_runtime/review_summary.py +150 -0
  156. phase_loop_runtime/roadmap_lint.py +425 -0
  157. phase_loop_runtime/route_log.py +59 -0
  158. phase_loop_runtime/route_policy/__init__.py +11 -0
  159. phase_loop_runtime/route_policy/fixtures/route_selection.golden.json +263 -0
  160. phase_loop_runtime/runner.py +8400 -0
  161. phase_loop_runtime/runtime_paths.py +225 -0
  162. phase_loop_runtime/runtime_projection.py +113 -0
  163. phase_loop_runtime/runtime_resources.py +36 -0
  164. phase_loop_runtime/schema_export.py +292 -0
  165. phase_loop_runtime/schemas/phase_loop_closeout.schema.json +366 -0
  166. phase_loop_runtime/skill_install.py +176 -0
  167. phase_loop_runtime/skill_inventory.py +662 -0
  168. phase_loop_runtime/skill_paths.py +42 -0
  169. phase_loop_runtime/skill_sources_plugin.py +37 -0
  170. phase_loop_runtime/skills_bundle/claude-advisor-board/README.md +1 -0
  171. phase_loop_runtime/skills_bundle/claude-advisor-board/SKILL.md +38 -0
  172. phase_loop_runtime/skills_bundle/claude-advisor-panel/README.md +1 -0
  173. phase_loop_runtime/skills_bundle/claude-advisor-panel/SKILL.md +38 -0
  174. phase_loop_runtime/skills_bundle/claude-execute-detailed/README.md +1 -0
  175. phase_loop_runtime/skills_bundle/claude-execute-detailed/SKILL.md +152 -0
  176. phase_loop_runtime/skills_bundle/claude-execute-phase/README.md +1 -0
  177. phase_loop_runtime/skills_bundle/claude-execute-phase/SKILL.md +745 -0
  178. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  179. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  180. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  181. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  182. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  183. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  184. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  185. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/state.py +274 -0
  186. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  187. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/team_teardown.sh +32 -0
  188. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/validate_plan_doc.py +642 -0
  189. phase_loop_runtime/skills_bundle/claude-execute-phase/scripts/verify_harness.sh +96 -0
  190. phase_loop_runtime/skills_bundle/claude-phase-loop/README.md +1 -0
  191. phase_loop_runtime/skills_bundle/claude-phase-loop/SKILL.md +115 -0
  192. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/README.md +1 -0
  193. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/SKILL.md +439 -0
  194. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/assets/review_prompt.md +19 -0
  195. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  196. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/references/roadmap-template.md +193 -0
  197. phase_loop_runtime/skills_bundle/claude-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  198. phase_loop_runtime/skills_bundle/claude-plan-detailed/README.md +1 -0
  199. phase_loop_runtime/skills_bundle/claude-plan-detailed/SKILL.md +334 -0
  200. phase_loop_runtime/skills_bundle/claude-plan-detailed/assets/review_prompt.md +25 -0
  201. phase_loop_runtime/skills_bundle/claude-plan-phase/README.md +1 -0
  202. phase_loop_runtime/skills_bundle/claude-plan-phase/SKILL.md +776 -0
  203. phase_loop_runtime/skills_bundle/claude-plan-phase/assets/review_prompt.md +17 -0
  204. phase_loop_runtime/skills_bundle/claude-plan-phase/scripts/validate_plan_doc.py +1092 -0
  205. phase_loop_runtime/skills_bundle/claude-run-train/README.md +1 -0
  206. phase_loop_runtime/skills_bundle/claude-run-train/SKILL.md +75 -0
  207. phase_loop_runtime/skills_bundle/claude-skill-editor/README.md +1 -0
  208. phase_loop_runtime/skills_bundle/claude-skill-editor/SKILL.md +243 -0
  209. phase_loop_runtime/skills_bundle/claude-skill-editor/assets/editor_prompt.md +72 -0
  210. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/README.md +1 -0
  211. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/SKILL.md +249 -0
  212. phase_loop_runtime/skills_bundle/claude-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  213. phase_loop_runtime/skills_bundle/claude-task-contextualizer/README.md +1 -0
  214. phase_loop_runtime/skills_bundle/claude-task-contextualizer/SKILL.md +74 -0
  215. phase_loop_runtime/skills_bundle/claude-task-contextualizer/references/task-templates.md +103 -0
  216. phase_loop_runtime/skills_bundle/codex-advisor-board/README.md +1 -0
  217. phase_loop_runtime/skills_bundle/codex-advisor-board/SKILL.md +38 -0
  218. phase_loop_runtime/skills_bundle/codex-advisor-panel/README.md +1 -0
  219. phase_loop_runtime/skills_bundle/codex-advisor-panel/SKILL.md +38 -0
  220. phase_loop_runtime/skills_bundle/codex-execute-detailed/README.md +1 -0
  221. phase_loop_runtime/skills_bundle/codex-execute-detailed/SKILL.md +154 -0
  222. phase_loop_runtime/skills_bundle/codex-execute-phase/README.md +1 -0
  223. phase_loop_runtime/skills_bundle/codex-execute-phase/SKILL.md +231 -0
  224. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  225. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  226. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  227. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  228. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  229. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  230. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  231. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/state.py +274 -0
  232. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  233. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/team_teardown.sh +32 -0
  234. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/validate_plan_doc.py +642 -0
  235. phase_loop_runtime/skills_bundle/codex-execute-phase/scripts/verify_harness.sh +96 -0
  236. phase_loop_runtime/skills_bundle/codex-phase-loop/README.md +1 -0
  237. phase_loop_runtime/skills_bundle/codex-phase-loop/SKILL.md +248 -0
  238. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/README.md +1 -0
  239. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/SKILL.md +150 -0
  240. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/assets/review_prompt.md +19 -0
  241. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  242. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/references/roadmap-template.md +193 -0
  243. phase_loop_runtime/skills_bundle/codex-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  244. phase_loop_runtime/skills_bundle/codex-plan-detailed/README.md +1 -0
  245. phase_loop_runtime/skills_bundle/codex-plan-detailed/SKILL.md +119 -0
  246. phase_loop_runtime/skills_bundle/codex-plan-detailed/assets/review_prompt.md +25 -0
  247. phase_loop_runtime/skills_bundle/codex-plan-phase/README.md +1 -0
  248. phase_loop_runtime/skills_bundle/codex-plan-phase/SKILL.md +307 -0
  249. phase_loop_runtime/skills_bundle/codex-plan-phase/assets/review_prompt.md +17 -0
  250. phase_loop_runtime/skills_bundle/codex-plan-phase/scripts/validate_plan_doc.py +1092 -0
  251. phase_loop_runtime/skills_bundle/codex-run-train/README.md +1 -0
  252. phase_loop_runtime/skills_bundle/codex-run-train/SKILL.md +75 -0
  253. phase_loop_runtime/skills_bundle/codex-skill-editor/README.md +1 -0
  254. phase_loop_runtime/skills_bundle/codex-skill-editor/SKILL.md +94 -0
  255. phase_loop_runtime/skills_bundle/codex-skill-editor/assets/editor_prompt.md +72 -0
  256. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/README.md +1 -0
  257. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/SKILL.md +114 -0
  258. phase_loop_runtime/skills_bundle/codex-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  259. phase_loop_runtime/skills_bundle/codex-task-contextualizer/README.md +1 -0
  260. phase_loop_runtime/skills_bundle/codex-task-contextualizer/SKILL.md +102 -0
  261. phase_loop_runtime/skills_bundle/codex-task-contextualizer/references/task-templates.md +103 -0
  262. phase_loop_runtime/skills_bundle/gemini-advisor-board/README.md +1 -0
  263. phase_loop_runtime/skills_bundle/gemini-advisor-board/SKILL.md +38 -0
  264. phase_loop_runtime/skills_bundle/gemini-advisor-panel/README.md +1 -0
  265. phase_loop_runtime/skills_bundle/gemini-advisor-panel/SKILL.md +38 -0
  266. phase_loop_runtime/skills_bundle/gemini-execute-detailed/README.md +1 -0
  267. phase_loop_runtime/skills_bundle/gemini-execute-detailed/SKILL.md +144 -0
  268. phase_loop_runtime/skills_bundle/gemini-execute-phase/README.md +1 -0
  269. phase_loop_runtime/skills_bundle/gemini-execute-phase/SKILL.md +198 -0
  270. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  271. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  272. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  273. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  274. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  275. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  276. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  277. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/state.py +274 -0
  278. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  279. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/team_teardown.sh +32 -0
  280. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/validate_plan_doc.py +642 -0
  281. phase_loop_runtime/skills_bundle/gemini-execute-phase/scripts/verify_harness.sh +96 -0
  282. phase_loop_runtime/skills_bundle/gemini-phase-loop/README.md +1 -0
  283. phase_loop_runtime/skills_bundle/gemini-phase-loop/SKILL.md +111 -0
  284. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/README.md +1 -0
  285. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/SKILL.md +147 -0
  286. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/assets/review_prompt.md +19 -0
  287. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  288. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/references/roadmap-template.md +193 -0
  289. phase_loop_runtime/skills_bundle/gemini-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  290. phase_loop_runtime/skills_bundle/gemini-plan-detailed/README.md +1 -0
  291. phase_loop_runtime/skills_bundle/gemini-plan-detailed/SKILL.md +119 -0
  292. phase_loop_runtime/skills_bundle/gemini-plan-detailed/assets/review_prompt.md +25 -0
  293. phase_loop_runtime/skills_bundle/gemini-plan-phase/README.md +1 -0
  294. phase_loop_runtime/skills_bundle/gemini-plan-phase/SKILL.md +217 -0
  295. phase_loop_runtime/skills_bundle/gemini-plan-phase/assets/review_prompt.md +17 -0
  296. phase_loop_runtime/skills_bundle/gemini-plan-phase/scripts/validate_plan_doc.py +1092 -0
  297. phase_loop_runtime/skills_bundle/gemini-run-train/README.md +1 -0
  298. phase_loop_runtime/skills_bundle/gemini-run-train/SKILL.md +75 -0
  299. phase_loop_runtime/skills_bundle/gemini-skill-editor/README.md +1 -0
  300. phase_loop_runtime/skills_bundle/gemini-skill-editor/SKILL.md +87 -0
  301. phase_loop_runtime/skills_bundle/gemini-skill-editor/assets/editor_prompt.md +72 -0
  302. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/README.md +1 -0
  303. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/SKILL.md +109 -0
  304. phase_loop_runtime/skills_bundle/gemini-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  305. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/README.md +1 -0
  306. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/SKILL.md +98 -0
  307. phase_loop_runtime/skills_bundle/gemini-task-contextualizer/references/task-templates.md +103 -0
  308. phase_loop_runtime/skills_bundle/opencode-advisor-board/README.md +1 -0
  309. phase_loop_runtime/skills_bundle/opencode-advisor-board/SKILL.md +38 -0
  310. phase_loop_runtime/skills_bundle/opencode-advisor-panel/README.md +1 -0
  311. phase_loop_runtime/skills_bundle/opencode-advisor-panel/SKILL.md +38 -0
  312. phase_loop_runtime/skills_bundle/opencode-execute-detailed/README.md +1 -0
  313. phase_loop_runtime/skills_bundle/opencode-execute-detailed/SKILL.md +144 -0
  314. phase_loop_runtime/skills_bundle/opencode-execute-phase/README.md +1 -0
  315. phase_loop_runtime/skills_bundle/opencode-execute-phase/SKILL.md +196 -0
  316. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/allocate_worktree_name.sh +33 -0
  317. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/audit_lane_file_touches.py +197 -0
  318. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/cleanup_lane_worktrees.sh +103 -0
  319. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/parent_tree_leakage_check.sh +125 -0
  320. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/post_merge_import_smoke.sh +62 -0
  321. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/pre_merge_destructiveness_check.sh +127 -0
  322. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/resolve_branch_from_sha.sh +51 -0
  323. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/state.py +274 -0
  324. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/sweep_stale_worktrees.sh +62 -0
  325. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/team_teardown.sh +32 -0
  326. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/validate_plan_doc.py +642 -0
  327. phase_loop_runtime/skills_bundle/opencode-execute-phase/scripts/verify_harness.sh +96 -0
  328. phase_loop_runtime/skills_bundle/opencode-phase-loop/README.md +1 -0
  329. phase_loop_runtime/skills_bundle/opencode-phase-loop/SKILL.md +113 -0
  330. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/README.md +1 -0
  331. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/SKILL.md +147 -0
  332. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/assets/review_prompt.md +19 -0
  333. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/parallelization-heuristics.md +111 -0
  334. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/references/roadmap-template.md +193 -0
  335. phase_loop_runtime/skills_bundle/opencode-phase-roadmap-builder/scripts/validate_roadmap.py +32 -0
  336. phase_loop_runtime/skills_bundle/opencode-plan-detailed/README.md +1 -0
  337. phase_loop_runtime/skills_bundle/opencode-plan-detailed/SKILL.md +119 -0
  338. phase_loop_runtime/skills_bundle/opencode-plan-detailed/assets/review_prompt.md +25 -0
  339. phase_loop_runtime/skills_bundle/opencode-plan-phase/README.md +1 -0
  340. phase_loop_runtime/skills_bundle/opencode-plan-phase/SKILL.md +211 -0
  341. phase_loop_runtime/skills_bundle/opencode-plan-phase/assets/review_prompt.md +17 -0
  342. phase_loop_runtime/skills_bundle/opencode-plan-phase/scripts/validate_plan_doc.py +1092 -0
  343. phase_loop_runtime/skills_bundle/opencode-run-train/README.md +1 -0
  344. phase_loop_runtime/skills_bundle/opencode-run-train/SKILL.md +75 -0
  345. phase_loop_runtime/skills_bundle/opencode-skill-editor/README.md +1 -0
  346. phase_loop_runtime/skills_bundle/opencode-skill-editor/SKILL.md +87 -0
  347. phase_loop_runtime/skills_bundle/opencode-skill-editor/assets/editor_prompt.md +72 -0
  348. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/README.md +1 -0
  349. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/SKILL.md +109 -0
  350. phase_loop_runtime/skills_bundle/opencode-skill-improvement-planner/assets/aggregator_prompt.md +79 -0
  351. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/README.md +1 -0
  352. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/SKILL.md +98 -0
  353. phase_loop_runtime/skills_bundle/opencode-task-contextualizer/references/task-templates.md +103 -0
  354. phase_loop_runtime/state.py +156 -0
  355. phase_loop_runtime/state_degradation.py +136 -0
  356. phase_loop_runtime/state_ops.py +676 -0
  357. phase_loop_runtime/train_ledger.py +215 -0
  358. phase_loop_runtime/train_roadmap.py +339 -0
  359. phase_loop_runtime/train_runner.py +1197 -0
  360. phase_loop_runtime/verification_evidence.py +650 -0
  361. phase_loop_runtime/verification_evidence_validator.py +75 -0
  362. phase_loop_runtime/visual_evidence_validator.py +36 -0
  363. phase_loop_runtime/worker_pool.py +123 -0
  364. phase_loop_runtime/worktree_index.py +270 -0
  365. phase_loop_runtime-0.2.0.data/data/share/phase-loop-runtime/protocol/protocol.md +2475 -0
  366. phase_loop_runtime-0.2.0.dist-info/METADATA +106 -0
  367. phase_loop_runtime-0.2.0.dist-info/RECORD +370 -0
  368. phase_loop_runtime-0.2.0.dist-info/WHEEL +5 -0
  369. phase_loop_runtime-0.2.0.dist-info/entry_points.txt +9 -0
  370. phase_loop_runtime-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,498 @@
1
+ """Omnigent provider backing (ABDOMNI, Phase 5).
2
+
3
+ The ``omnigent`` transport for breadth harnesses (opencode / pi, and cursor / amp
4
+ when the live catalog reports them). It routes a board seat through
5
+ omniagent-plus -> Omnigent **v0.4.0** over the frozen HTTP surface, opt-in and
6
+ fail-closed. It is the sibling of ABDHOME's ``homebrew`` backing (the built-3 +
7
+ native host leg): homebrew is hand-written and stays byte-for-byte the legacy
8
+ panel; omnigent is *not* hand-written per harness — it reuses the Omnigent
9
+ launcher, which is the whole point of the maintenance-offload (ABDHOME/ABDOMNI
10
+ non-goal: never hand-write a breadth adapter).
11
+
12
+ **We do not fork the transport.** This module mirrors the method set of
13
+ omniagent-plus ``core-contracts/src/provider.ts`` and issues requests ONLY against
14
+ the frozen v0.4.0 endpoint contract
15
+ (``fixtures/omnigent/discovery/http-surface.json``, vendored into the test tree at
16
+ ``tests/fixtures/omnigent_contract/``). ``SESSION_ENDPOINTS`` / ``HARNESS_ENDPOINT``
17
+ below are this module's declared endpoint set;
18
+ ``tests/test_advisor_board_backing_omnigent.py`` asserts every one appears in that
19
+ frozen contract and that the freeze target is ``0.4.0`` — so "faithful to v0.4.0"
20
+ is a checked invariant, not a claim.
21
+
22
+ **Auth = the same frozen no-silent-key contract as homebrew.** ``run_seat`` resolves
23
+ the seat's env through ``backing.resolve_seat_env`` (subscription scrubs EVERY
24
+ vendor key; an api-key seat, only behind the board opt-in, keeps ONLY the seat
25
+ vendor's key), then transmits vendor-key material to the gateway *derived from that
26
+ resolved env*. A subscription seat therefore puts zero vendor-key material on the
27
+ wire; an api-key opt-in seat transmits exactly the seat vendor's key. The gateway
28
+ **reports which auth lane it resolved** (``session.metadata.auth_lane``); ``run_seat``
29
+ reads it back, so no-silent-key is *observable* for omnigent seats — testable on the
30
+ recorded request, not merely asserted.
31
+
32
+ **Two distinct fail-closed skips** (they satisfy two different exit criteria):
33
+
34
+ * gateway unreachable -> ``select_backing`` degrades the seat skip-with-warning
35
+ (``gateway_available=False``); ``OmnigentBacking.gateway_available()`` is the real
36
+ probe the seam feeds it.
37
+ * harness absent from the live ``GET /v1/harnesses`` catalog -> a SEPARATE per-seat
38
+ gate (``catalog_harnesses``). This is the dynamic cursor/amp gate: they route only
39
+ when the live catalog exposes them, with no code gate.
40
+ """
41
+ from __future__ import annotations
42
+
43
+ import json
44
+ import urllib.error
45
+ import urllib.parse
46
+ import urllib.request
47
+ import uuid
48
+ from collections.abc import Mapping
49
+ from dataclasses import dataclass, field
50
+ from typing import Any, Callable
51
+
52
+ from .backing import VENDOR_API_KEY_VARS, resolve_seat_env
53
+ from .schema import Seat, seat_vendor_family
54
+
55
+
56
+ # --- frozen v0.4.0 endpoint contract (checked against http-surface.json) ------
57
+
58
+ OMNIGENT_FREEZE_TARGET = "0.4.0"
59
+
60
+ # (method, path-template) set this client issues. Every entry MUST appear in the
61
+ # frozen ``http-surface.json`` (asserted by the conformance test). We use only the
62
+ # subset a single-turn advisory board leg needs.
63
+ HARNESS_ENDPOINT: tuple[str, str] = ("GET", "/v1/harnesses")
64
+ SESSION_ENDPOINTS: tuple[tuple[str, str], ...] = (
65
+ ("POST", "/v1/sessions"),
66
+ ("GET", "/v1/sessions/{session_id}"),
67
+ ("GET", "/v1/sessions/{session_id}/items"),
68
+ ("POST", "/v1/sessions/{session_id}/events"),
69
+ ("DELETE", "/v1/sessions/{session_id}"),
70
+ )
71
+
72
+ # The per-seat-vendor key header prefix. A subscription seat sends NONE of these;
73
+ # an api-key opt-in seat sends exactly the seat vendor's key var(s). The gateway
74
+ # derives ``auth_lane`` from their presence (never from a client-asserted claim).
75
+ VENDOR_KEY_HEADER_PREFIX = "x-omnigent-key-"
76
+
77
+
78
+ # --- errors -----------------------------------------------------------------
79
+
80
+
81
+ class OmnigentHttpError(RuntimeError):
82
+ """A non-2xx response from the Omnigent gateway. Mirrors
83
+ ``http-client.ts``'s ``OmnigentHttpError`` (method/path/status/body)."""
84
+
85
+ def __init__(self, *, method: str, path: str, status_code: int, body: Any) -> None:
86
+ super().__init__(f"{method} {path} failed with {status_code}")
87
+ self.method = method
88
+ self.path = path
89
+ self.status_code = status_code
90
+ self.body = body
91
+
92
+
93
+ class OmnigentGatewayUnavailable(RuntimeError):
94
+ """The gateway could not be reached at all (connection refused / DNS / reset).
95
+
96
+ Distinct from ``OmnigentHttpError`` (a reached-but-refusing gateway): this is
97
+ the gateway-DOWN condition that degrades an omnigent seat skip-with-warning.
98
+ """
99
+
100
+
101
+ # --- HTTP client (faithful, synchronous port of http-client.ts) --------------
102
+
103
+ # Injection seam for tests: a callable mirroring ``urlopen`` semantics enough for
104
+ # the client. Default is the stdlib opener.
105
+ Opener = Callable[[urllib.request.Request, float | None], Any]
106
+
107
+
108
+ @dataclass
109
+ class OmnigentHttpClient:
110
+ """Synchronous Omnigent v0.4.0 HTTP client (stdlib only, no new deps).
111
+
112
+ Mirrors the ``core-contracts`` request/response shapes used by a single-turn
113
+ board leg. ``base_url`` is the gateway; ``headers`` carries the gateway-bearer
114
+ (the transport auth, NOT a vendor key). ``opener`` is injectable so tests can
115
+ exercise the mapping without a socket, but the shipped tests run a real
116
+ in-process fake server so the wire path is genuinely exercised.
117
+ """
118
+
119
+ base_url: str
120
+ headers: Mapping[str, str] = field(default_factory=dict)
121
+ timeout: float | None = 30.0
122
+ opener: Opener | None = None
123
+
124
+ def _url(self, path: str) -> str:
125
+ return self.base_url.rstrip("/") + path
126
+
127
+ def _request(
128
+ self,
129
+ method: str,
130
+ path: str,
131
+ *,
132
+ body: Any = None,
133
+ extra_headers: Mapping[str, str] | None = None,
134
+ ) -> Any:
135
+ url = self._url(path)
136
+ data = None
137
+ headers = {**self.headers, **(extra_headers or {})}
138
+ if body is not None:
139
+ data = json.dumps(body).encode("utf-8")
140
+ headers["content-type"] = "application/json"
141
+ request = urllib.request.Request(url, data=data, method=method, headers=headers)
142
+ opener = self.opener or (lambda req, timeout: urllib.request.urlopen(req, timeout=timeout))
143
+ try:
144
+ response = opener(request, self.timeout)
145
+ except urllib.error.HTTPError as exc: # reached the gateway; it refused
146
+ raw = exc.read()
147
+ try:
148
+ parsed = json.loads(raw.decode("utf-8")) if raw else None
149
+ except (ValueError, UnicodeDecodeError):
150
+ parsed = raw.decode("utf-8", "replace") if raw else None
151
+ raise OmnigentHttpError(
152
+ method=method, path=path, status_code=exc.code, body=parsed
153
+ ) from exc
154
+ except (urllib.error.URLError, ConnectionError, OSError) as exc: # gateway down
155
+ raise OmnigentGatewayUnavailable(f"{method} {path}: {exc}") from exc
156
+ with response:
157
+ status = getattr(response, "status", 200)
158
+ payload = response.read()
159
+ if status == 204 or not payload:
160
+ return None
161
+ return json.loads(payload.decode("utf-8"))
162
+
163
+ # -- endpoint methods (each anchored to an SESSION_ENDPOINTS/HARNESS_ENDPOINT) --
164
+
165
+ def list_harnesses(self) -> Mapping[str, Any]:
166
+ return self._request("GET", "/v1/harnesses") or {}
167
+
168
+ def create_session(
169
+ self,
170
+ *,
171
+ target_harness: str,
172
+ idempotency_key: str,
173
+ title: str,
174
+ initial_message: str | None = None,
175
+ vendor_key_headers: Mapping[str, str] | None = None,
176
+ ) -> Mapping[str, Any]:
177
+ body = {
178
+ "idempotencyKey": idempotency_key,
179
+ "targetHarness": target_harness,
180
+ "targetProvider": "omnigent",
181
+ "title": title,
182
+ }
183
+ if initial_message is not None:
184
+ body["initialMessage"] = initial_message
185
+ return self._request("POST", "/v1/sessions", body=body, extra_headers=vendor_key_headers)
186
+
187
+ def send_turn(self, session_id: str, message: str) -> Mapping[str, Any]:
188
+ path = f"/v1/sessions/{_quote(session_id)}/events"
189
+ return self._request("POST", path, body={"type": "message", "data": {"message": message}})
190
+
191
+ def interrupt(self, session_id: str, reason: str = "user_request") -> Mapping[str, Any]:
192
+ path = f"/v1/sessions/{_quote(session_id)}/events"
193
+ return self._request("POST", path, body={"type": "interrupt", "data": {"reason": reason}})
194
+
195
+ def get_session(self, session_id: str) -> Mapping[str, Any]:
196
+ return self._request("GET", f"/v1/sessions/{_quote(session_id)}")
197
+
198
+ def get_history(self, session_id: str) -> list[Any]:
199
+ return self._request("GET", f"/v1/sessions/{_quote(session_id)}/items") or []
200
+
201
+ def delete_session(self, session_id: str) -> None:
202
+ self._request("DELETE", f"/v1/sessions/{_quote(session_id)}")
203
+
204
+
205
+ def _quote(value: str) -> str:
206
+ return urllib.parse.quote(str(value), safe="")
207
+
208
+
209
+ # --- outcome + status mapping ------------------------------------------------
210
+
211
+
212
+ @dataclass(frozen=True)
213
+ class SeatRunOutcome:
214
+ """Result of running one seat through Omnigent, in ``PanelLegResult`` terms.
215
+
216
+ ``auth_lane`` is the lane the GATEWAY reported it resolved (read back from the
217
+ session snapshot), so no-silent-key is testable for omnigent seats.
218
+ """
219
+
220
+ status: str # a panel LEG_STATUS: OK | EMPTY | ERROR | DEGRADED | UNAVAILABLE
221
+ text: str = ""
222
+ detail: str = ""
223
+ auth_lane: str | None = None
224
+
225
+
226
+ # response-stream event families (types.ts ``omnigentStreamEventTypes``).
227
+ _TEXT_DELTA = "response.output_text.delta"
228
+ _COMPLETED = {"response.completed", "turn.completed"}
229
+ _FAILED = {"response.failed", "turn.failed"}
230
+ _CANCELLED = {"response.cancelled", "turn.cancelled"}
231
+
232
+
233
+ def _extract_text_and_status(events: "tuple[Any, ...]") -> tuple[str, str]:
234
+ """Fold a seam ``SessionHistory.events`` stream into ``(text, leg_status)``.
235
+
236
+ Operates on the ``AgentRuntimeProvider`` history (``RuntimeEvent`` with ``.type``
237
+ + ``.payload``), so the mapping runs on what the SEAM returns — not raw HTTP.
238
+ Text is the concatenation of ``response.output_text.delta`` deltas plus any
239
+ ``response.completed`` ``outputText`` (mirrors event-mapper.ts's text handling).
240
+ Status keys on the terminal marker: completed -> OK/EMPTY, failed -> ERROR,
241
+ cancelled -> DEGRADED, none -> ERROR (no terminal event, treated as a broken turn).
242
+ """
243
+ parts: list[str] = []
244
+ completed = failed = cancelled = False
245
+ for event in events:
246
+ etype = event.type
247
+ payload = event.payload
248
+ if etype == _TEXT_DELTA and payload.get("delta"):
249
+ parts.append(str(payload["delta"]))
250
+ elif etype == "response.completed" and payload.get("outputText"):
251
+ parts.append(str(payload["outputText"]))
252
+ if etype in _COMPLETED:
253
+ completed = True
254
+ elif etype in _FAILED:
255
+ failed = True
256
+ elif etype in _CANCELLED:
257
+ cancelled = True
258
+ text = _dedupe_join(parts)
259
+ if failed:
260
+ return text, "ERROR"
261
+ if cancelled:
262
+ return text, "DEGRADED"
263
+ if completed:
264
+ return text, ("OK" if text.strip() else "EMPTY")
265
+ return text, "ERROR"
266
+
267
+
268
+ def _dedupe_join(parts: list[str]) -> str:
269
+ """Join text fragments, collapsing an exact delta/outputText duplicate (the
270
+ fake emits the same ``Echo: …`` as both a delta and the completed outputText)."""
271
+ out: list[str] = []
272
+ for part in parts:
273
+ if not out or out[-1] != part:
274
+ out.append(part)
275
+ return "\n".join(out)
276
+
277
+
278
+ def classify_http_failure(status_code: int, body_text: str) -> tuple[str, str]:
279
+ """Map a gateway HTTP rejection to ``(leg_status, category)``.
280
+
281
+ Mirrors omniagent-plus ``failure-mapper.ts``: 429 -> rate_limit, 403 with
282
+ auth/billing in the body -> auth/billing, other 403 -> policy_denied, else
283
+ backend_unavailable. Every reached-but-refused rejection degrades the SEAT
284
+ (DEGRADED) — it never blocks the board and never silently downgrades auth.
285
+ """
286
+ text = (body_text or "").lower()
287
+ if status_code == 429:
288
+ return "DEGRADED", "rate_limit"
289
+ if status_code == 403 and ("billing" in text or "quota" in text):
290
+ return "DEGRADED", "billing"
291
+ if status_code == 403 and "auth" in text:
292
+ return "DEGRADED", "auth"
293
+ if status_code == 403:
294
+ return "DEGRADED", "policy_denied"
295
+ return "DEGRADED", "backend_unavailable"
296
+
297
+
298
+ # --- the backing ------------------------------------------------------------
299
+
300
+
301
+ @dataclass
302
+ class OmnigentBacking:
303
+ """The ``omnigent`` backing the board seam (``invoke_board``) routes through.
304
+
305
+ Construct with a ready :class:`OmnigentHttpClient`, or via :meth:`from_env` /
306
+ :meth:`from_config` so a production caller (ABDVERIFY) can wire it without
307
+ reshaping this API. ``title`` is the session title prefix for board legs.
308
+ """
309
+
310
+ client: OmnigentHttpClient
311
+ title_prefix: str = "advisor-board"
312
+
313
+ # -- availability probes ---------------------------------------------------
314
+
315
+ def catalog_harnesses(self) -> frozenset[str]:
316
+ """The set of harness names the LIVE ``GET /v1/harnesses`` catalog reports.
317
+
318
+ The v0.4.0 catalog is ``{category: [ {name, ...}, ... ]}`` (types.ts
319
+ ``OmnigentHarnessCatalogResponse``); we flatten the ``name`` of every entry.
320
+ This is the dynamic gate: cursor/amp route only when they appear here.
321
+ """
322
+ catalog = self.client.list_harnesses()
323
+ names: set[str] = set()
324
+ for entries in catalog.values():
325
+ if not isinstance(entries, (list, tuple)):
326
+ continue
327
+ for entry in entries:
328
+ if isinstance(entry, Mapping) and entry.get("name"):
329
+ names.add(str(entry["name"]).lower())
330
+ return frozenset(names)
331
+
332
+ def gateway_available(self) -> bool:
333
+ """Real gateway-reachability probe the seam feeds ``select_backing``.
334
+
335
+ A reachable gateway (even one returning a catalog) is available; a
336
+ connection failure (gateway down) is not. A gateway that is up but returns
337
+ an HTTP error is still 'reachable' — availability is about the transport,
338
+ not the catalog contents (harness presence is the SEPARATE catalog gate)."""
339
+ try:
340
+ self.client.list_harnesses()
341
+ return True
342
+ except OmnigentGatewayUnavailable:
343
+ return False
344
+ except OmnigentHttpError:
345
+ return True
346
+
347
+ # -- run one seat ----------------------------------------------------------
348
+
349
+ def run_seat(
350
+ self,
351
+ seat: Seat,
352
+ artifact: str,
353
+ *,
354
+ base_env: Mapping[str, str],
355
+ allow_api_key_fallback: bool = False,
356
+ ) -> SeatRunOutcome:
357
+ """Route one seat through Omnigent: create session -> send one turn ->
358
+ read history -> map to a leg result, enforcing no-silent-key on the wire.
359
+
360
+ Raises :class:`OmnigentGatewayUnavailable` if the gateway drops mid-run
361
+ (the caller degrades it skip-with-warning) and ``ValueError`` for a
362
+ never-silent-key violation (an api-key seat without the board opt-in — the
363
+ caller maps it to DEGRADED, exactly like the homebrew path).
364
+
365
+ Routes through the SHARED provider seam: it drives an
366
+ :class:`~phase_loop_runtime.agent_runtime_provider.OmnigentAgentRuntimeProvider`
367
+ (an ``AgentRuntimeProvider``) via ``create_session`` -> ``send_turn`` ->
368
+ ``get_session_info`` -> ``read_history`` -> ``close_session`` — exactly as
369
+ the homebrew path drives ``HomebrewAgentRuntimeProvider``.
370
+ """
371
+ # Import here to avoid a module-load cycle (agent_runtime_provider TYPE-imports
372
+ # this module's client for annotations only).
373
+ from ..agent_runtime_provider import (
374
+ OMNIGENT_VENDOR_KEY_HEADERS_META,
375
+ RUNTIME_OMNIGENT,
376
+ CreateSessionRequest,
377
+ OmnigentAgentRuntimeProvider,
378
+ SendTurnRequest,
379
+ )
380
+
381
+ # 1) Frozen no-silent-key contract: the SAME env-scrub that governs the
382
+ # homebrew subprocess env governs what rides to the gateway. A
383
+ # subscription seat ends with zero vendor keys; an api-key opt-in seat
384
+ # keeps ONLY the seat vendor's key. (ValueError propagates: never-silent.)
385
+ seat_env = resolve_seat_env(seat, base_env, allow_api_key_fallback=allow_api_key_fallback)
386
+ vendor = seat_vendor_family(seat)
387
+ vendor_key_headers = {
388
+ f"{VENDOR_KEY_HEADER_PREFIX}{var}": seat_env[var]
389
+ for var in VENDOR_API_KEY_VARS.get(vendor, ())
390
+ if var in seat_env
391
+ }
392
+
393
+ provider = OmnigentAgentRuntimeProvider(self.client)
394
+ title = f"{self.title_prefix}:{seat.seat_key}"
395
+ metadata: dict[str, Any] = (
396
+ {OMNIGENT_VENDOR_KEY_HEADERS_META: vendor_key_headers} if vendor_key_headers else {}
397
+ )
398
+ try:
399
+ info = provider.create_session(
400
+ CreateSessionRequest(
401
+ target_harness=seat.harness or "",
402
+ idempotency_key=uuid.uuid4().hex,
403
+ title=title,
404
+ runtime=RUNTIME_OMNIGENT,
405
+ metadata=metadata,
406
+ )
407
+ )
408
+ provider.send_turn(
409
+ SendTurnRequest(
410
+ session_id=info.id, idempotency_key=uuid.uuid4().hex, message=artifact
411
+ )
412
+ )
413
+ # Re-read the snapshot through the seam: the gateway reports the resolved
414
+ # auth lane in session metadata (so no-silent-key is observable).
415
+ info = provider.get_session_info(info.id)
416
+ history = provider.read_history(info.id)
417
+ except OmnigentHttpError as exc:
418
+ status, category = classify_http_failure(exc.status_code, _body_text(exc.body))
419
+ return SeatRunOutcome(status=status, detail=f"omnigent {category}: HTTP {exc.status_code}")
420
+
421
+ auth_lane = _reported_auth_lane(info.metadata)
422
+ text, status = _extract_text_and_status(history.events)
423
+ detail = f"omnigent v{OMNIGENT_FREEZE_TARGET} lane={auth_lane}" if auth_lane else ""
424
+ # Best-effort close through the seam; never let cleanup change the verdict.
425
+ try:
426
+ provider.close_session(info.id)
427
+ except (OmnigentHttpError, OmnigentGatewayUnavailable):
428
+ pass
429
+ return SeatRunOutcome(status=status, text=text, detail=detail, auth_lane=auth_lane)
430
+
431
+ # -- factories -------------------------------------------------------------
432
+
433
+ @classmethod
434
+ def from_env(
435
+ cls,
436
+ env: Mapping[str, str],
437
+ *,
438
+ opener: Opener | None = None,
439
+ title_prefix: str = "advisor-board",
440
+ ) -> "OmnigentBacking | None":
441
+ """Build from ``OMNIGENT_GATEWAY_URL`` (+ optional ``OMNIGENT_GATEWAY_TOKEN``
442
+ gateway-bearer). Returns ``None`` when no gateway URL is configured, so the
443
+ seam degrades omnigent seats skip-with-warning rather than erroring."""
444
+ base_url = env.get("OMNIGENT_GATEWAY_URL")
445
+ if not base_url:
446
+ return None
447
+ headers: dict[str, str] = {}
448
+ token = env.get("OMNIGENT_GATEWAY_TOKEN")
449
+ if token:
450
+ headers["authorization"] = f"Bearer {token}"
451
+ client = OmnigentHttpClient(base_url=base_url, headers=headers, opener=opener)
452
+ return cls(client=client, title_prefix=title_prefix)
453
+
454
+ @classmethod
455
+ def from_config(
456
+ cls,
457
+ *,
458
+ base_url: str,
459
+ gateway_token: str | None = None,
460
+ opener: Opener | None = None,
461
+ title_prefix: str = "advisor-board",
462
+ ) -> "OmnigentBacking":
463
+ headers = {"authorization": f"Bearer {gateway_token}"} if gateway_token else {}
464
+ client = OmnigentHttpClient(base_url=base_url, headers=headers, opener=opener)
465
+ return cls(client=client, title_prefix=title_prefix)
466
+
467
+
468
+ def _body_text(body: Any) -> str:
469
+ if isinstance(body, str):
470
+ return body
471
+ if body is None:
472
+ return ""
473
+ try:
474
+ return json.dumps(body)
475
+ except (TypeError, ValueError):
476
+ return str(body)
477
+
478
+
479
+ def _reported_auth_lane(metadata: Mapping[str, Any] | None) -> str | None:
480
+ """The auth lane the gateway reports it resolved (``metadata.auth_lane``)."""
481
+ if isinstance(metadata, Mapping):
482
+ lane = metadata.get("auth_lane")
483
+ return str(lane) if lane is not None else None
484
+ return None
485
+
486
+
487
+ __all__ = [
488
+ "OMNIGENT_FREEZE_TARGET",
489
+ "HARNESS_ENDPOINT",
490
+ "SESSION_ENDPOINTS",
491
+ "VENDOR_KEY_HEADER_PREFIX",
492
+ "OmnigentHttpError",
493
+ "OmnigentGatewayUnavailable",
494
+ "OmnigentHttpClient",
495
+ "SeatRunOutcome",
496
+ "OmnigentBacking",
497
+ "classify_http_failure",
498
+ ]