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.
- package/README.md +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/{src → dist}/commands/execute/team.mjs +29 -36
- package/dist/commands/execute/team.mjs.map +1 -0
- package/dist/commands/execute/token-usage.d.mts +1 -0
- package/{src → dist}/commands/execute/token-usage.mjs +2 -3
- package/dist/commands/execute/token-usage.mjs.map +1 -0
- package/dist/commands/execute/wizard.d.mts +8 -0
- package/dist/commands/execute/wizard.mjs +191 -0
- package/dist/commands/execute/wizard.mjs.map +1 -0
- package/dist/commands/execute/worker-audit-check.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
- package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
- package/dist/commands/execute/worker-dispatch.d.mts +7 -0
- package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
- package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
- package/dist/commands/execute/worker-state.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-state.mjs +15 -16
- package/dist/commands/execute/worker-state.mjs.map +1 -0
- package/dist/commands/execute/worktree-lookup.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
- package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
- package/dist/commands/execute/worktree-status.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
- package/dist/commands/execute/worktree-status.mjs.map +1 -0
- package/dist/commands/inspect/code-review.d.mts +1 -0
- package/{src → dist}/commands/inspect/code-review.mjs +16 -19
- package/dist/commands/inspect/code-review.mjs.map +1 -0
- package/dist/commands/inspect/container.d.mts +1 -0
- package/{src → dist}/commands/inspect/container.mjs +11 -13
- package/dist/commands/inspect/container.mjs.map +1 -0
- package/dist/commands/inspect/context-cost.d.mts +1 -0
- package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
- package/dist/commands/inspect/context-cost.mjs.map +1 -0
- package/dist/commands/inspect/design-prep.d.mts +1 -0
- package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
- package/dist/commands/inspect/design-prep.mjs.map +1 -0
- package/dist/commands/inspect/error-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-report.mjs +11 -13
- package/dist/commands/inspect/error-report.mjs.map +1 -0
- package/dist/commands/inspect/error-zip.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
- package/dist/commands/inspect/error-zip.mjs.map +1 -0
- package/dist/commands/inspect/log-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/log-report.mjs +11 -13
- package/dist/commands/inspect/log-report.mjs.map +1 -0
- package/dist/commands/inspect/profile-show.d.mts +1 -0
- package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
- package/dist/commands/inspect/profile-show.mjs.map +1 -0
- package/dist/commands/inspect/recap.d.mts +1 -0
- package/{src → dist}/commands/inspect/recap.mjs +11 -13
- package/dist/commands/inspect/recap.mjs.map +1 -0
- package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
- package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
- package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
- package/dist/commands/inspect/rollup.d.mts +1 -0
- package/{src → dist}/commands/inspect/rollup.mjs +11 -13
- package/dist/commands/inspect/rollup.mjs.map +1 -0
- package/dist/commands/inspect/run-audit.d.mts +1 -0
- package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
- package/dist/commands/inspect/run-audit.mjs.map +1 -0
- package/dist/commands/inspect/set-work-status.d.mts +1 -0
- package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
- package/dist/commands/inspect/set-work-status.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.d.mts +1 -0
- package/dist/commands/inspect/stage-map.mjs +110 -0
- package/dist/commands/inspect/stage-map.mjs.map +1 -0
- package/dist/commands/inspect/task-list.d.mts +1 -0
- package/dist/commands/inspect/task-list.mjs +103 -0
- package/dist/commands/inspect/task-list.mjs.map +1 -0
- package/dist/commands/inspect/task-show.d.mts +1 -0
- package/dist/commands/inspect/task-show.mjs +108 -0
- package/dist/commands/inspect/task-show.mjs.map +1 -0
- package/dist/commands/inspect/time-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/time-report.mjs +11 -13
- package/dist/commands/inspect/time-report.mjs.map +1 -0
- package/dist/commands/inspect/usage-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
- package/dist/commands/inspect/usage-report.mjs.map +1 -0
- package/dist/commands/inspect/user-response.d.mts +1 -0
- package/{src → dist}/commands/inspect/user-response.mjs +10 -11
- package/dist/commands/inspect/user-response.mjs.map +1 -0
- package/dist/commands/inspect/worker-liveness.d.mts +1 -0
- package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
- package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
- package/dist/commands/lifecycle/check-project.d.mts +21 -0
- package/dist/commands/lifecycle/check-project.mjs +175 -0
- package/dist/commands/lifecycle/check-project.mjs.map +1 -0
- package/dist/commands/lifecycle/config.d.mts +1 -0
- package/dist/commands/lifecycle/config.mjs +369 -0
- package/dist/commands/lifecycle/config.mjs.map +1 -0
- package/dist/commands/lifecycle/doctor.d.mts +10 -0
- package/dist/commands/lifecycle/doctor.mjs +317 -0
- package/dist/commands/lifecycle/doctor.mjs.map +1 -0
- package/dist/commands/lifecycle/install.d.mts +17 -0
- package/dist/commands/lifecycle/install.mjs +1077 -0
- package/dist/commands/lifecycle/install.mjs.map +1 -0
- package/dist/commands/lifecycle/migrate.d.mts +1 -0
- package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
- package/dist/commands/lifecycle/migrate.mjs.map +1 -0
- package/dist/commands/lifecycle/model.d.mts +1 -0
- package/dist/commands/lifecycle/model.mjs +22 -0
- package/dist/commands/lifecycle/model.mjs.map +1 -0
- package/dist/commands/lifecycle/paths.d.mts +1 -0
- package/dist/commands/lifecycle/paths.mjs +102 -0
- package/dist/commands/lifecycle/paths.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.d.mts +1 -0
- package/dist/commands/lifecycle/preflight.mjs +124 -0
- package/dist/commands/lifecycle/preflight.mjs.map +1 -0
- package/dist/commands/lifecycle/setup.d.mts +1 -0
- package/dist/commands/lifecycle/setup.mjs +297 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -0
- package/dist/commands/lifecycle/uninstall.d.mts +4 -0
- package/dist/commands/lifecycle/uninstall.mjs +262 -0
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
- package/dist/commands/manager.d.mts +3 -0
- package/{src → dist}/commands/manager.mjs +25 -32
- package/dist/commands/manager.mjs.map +1 -0
- package/dist/commands/memory/memory.d.mts +1 -0
- package/dist/commands/memory/memory.mjs +569 -0
- package/dist/commands/memory/memory.mjs.map +1 -0
- package/dist/commands/pr/pr.d.mts +33 -0
- package/dist/commands/pr/pr.mjs +296 -0
- package/dist/commands/pr/pr.mjs.map +1 -0
- package/dist/commands/report/agent-activity.d.mts +1 -0
- package/{src → dist}/commands/report/agent-activity.mjs +11 -12
- package/dist/commands/report/agent-activity.mjs.map +1 -0
- package/dist/commands/report/finalize.d.mts +4 -0
- package/{src → dist}/commands/report/finalize.mjs +25 -32
- package/dist/commands/report/finalize.mjs.map +1 -0
- package/dist/commands/report/inject-report-index.d.mts +1 -0
- package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
- package/dist/commands/report/inject-report-index.mjs.map +1 -0
- package/dist/commands/report/render-final-report.d.mts +1 -0
- package/{src → dist}/commands/report/render-final-report.mjs +6 -7
- package/dist/commands/report/render-final-report.mjs.map +1 -0
- package/dist/commands/report/render-views.d.mts +1 -0
- package/{src → dist}/commands/report/render-views.mjs +6 -7
- package/dist/commands/report/render-views.mjs.map +1 -0
- package/dist/commands/report/translate.d.mts +1 -0
- package/{src → dist}/commands/report/translate.mjs +6 -7
- package/dist/commands/report/translate.mjs.map +1 -0
- package/dist/lib/helper-scripts.d.mts +3 -0
- package/{src → dist}/lib/helper-scripts.mjs +11 -14
- package/dist/lib/helper-scripts.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +25 -0
- package/dist/lib/host-registry-client.mjs +152 -0
- package/dist/lib/host-registry-client.mjs.map +1 -0
- package/dist/lib/install-assets.d.mts +8 -0
- package/{src → dist}/lib/install-assets.mjs +17 -20
- package/dist/lib/install-assets.mjs.map +1 -0
- package/dist/lib/okstra-dirs.d.mts +4 -0
- package/{src → dist}/lib/okstra-dirs.mjs +3 -6
- package/dist/lib/okstra-dirs.mjs.map +1 -0
- package/dist/lib/paths.d.mts +4 -0
- package/dist/lib/paths.mjs +78 -0
- package/dist/lib/paths.mjs.map +1 -0
- package/dist/lib/proc.d.mts +3 -0
- package/dist/lib/proc.mjs +31 -0
- package/dist/lib/proc.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +8 -0
- package/dist/lib/python-helper.mjs +123 -0
- package/dist/lib/python-helper.mjs.map +1 -0
- package/dist/lib/runtime-manifest.d.mts +7 -0
- package/dist/lib/runtime-manifest.mjs +73 -0
- package/dist/lib/runtime-manifest.mjs.map +1 -0
- package/dist/lib/skill-catalog.d.mts +4 -0
- package/dist/lib/skill-catalog.mjs +50 -0
- package/dist/lib/skill-catalog.mjs.map +1 -0
- package/dist/lib/types.d.mts +129 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/lib/version.d.mts +2 -0
- package/dist/lib/version.mjs +27 -0
- package/dist/lib/version.mjs.map +1 -0
- package/docs/architecture/storage-model.md +6 -2
- package/docs/architecture.md +29 -20
- package/docs/cli.md +40 -16
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +72 -62
- package/docs/task-process/README.md +12 -8
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/agents/workers/translator-worker.md +3 -1
- package/runtime/bin/lib/okstra/cli.sh +12 -0
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +7 -6
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +17 -13
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
- package/runtime/prompts/profiles/error-analysis.md +27 -1
- package/runtime/prompts/profiles/feature-analysis.md +20 -0
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
- package/runtime/prompts/profiles/implementation-planning.md +26 -0
- package/runtime/prompts/profiles/implementation.md +19 -0
- package/runtime/prompts/profiles/improvement-discovery.md +21 -1
- package/runtime/prompts/profiles/project-analysis.md +20 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +27 -1
- package/runtime/prompts/wizard/prompts.ko.json +35 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/convergence.py +409 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
- package/runtime/python/okstra_ctl/convergence_store.py +170 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
- package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
- package/runtime/python/okstra_ctl/doctor.py +14 -0
- package/runtime/python/okstra_ctl/domain/host.py +34 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/next_phase.py +236 -0
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +124 -57
- package/runtime/python/okstra_ctl/render_final_report.py +19 -0
- package/runtime/python/okstra_ctl/report_contract.py +39 -0
- package/runtime/python/okstra_ctl/report_finalize.py +16 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +210 -0
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +1210 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1449 -66
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/worker_request.py +7 -1
- package/runtime/python/okstra_ctl/worker_runner.py +130 -5
- package/runtime/python/okstra_ctl/workers.py +7 -5
- package/runtime/python/okstra_ctl/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_project/state.py +54 -6
- package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
- package/runtime/schemas/final-report-v2.0.schema.json +68 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +14 -14
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- package/runtime/templates/report-writer-prompt-preamble.md +2 -0
- package/runtime/templates/reports/final-report-v2.template.md +7 -1
- package/runtime/templates/reports/html/base.template.html +21 -0
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +6 -35
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +486 -134
- package/runtime/validators/validate_session_conformance.py +25 -7
- package/src/cli-registry.mjs +0 -539
- package/src/commands/execute/codex-dispatch.mjs +0 -10
- package/src/commands/execute/plan-items.mjs +0 -9
- package/src/commands/execute/plan-validate.mjs +0 -68
- package/src/commands/execute/run.mjs +0 -208
- package/src/commands/execute/wizard.mjs +0 -167
- package/src/commands/inspect/stage-map.mjs +0 -98
- package/src/commands/inspect/task-list.mjs +0 -97
- package/src/commands/inspect/task-show.mjs +0 -100
- package/src/commands/lifecycle/check-project.mjs +0 -187
- package/src/commands/lifecycle/config.mjs +0 -385
- package/src/commands/lifecycle/doctor.mjs +0 -308
- package/src/commands/lifecycle/install.mjs +0 -1170
- package/src/commands/lifecycle/paths.mjs +0 -104
- package/src/commands/lifecycle/preflight.mjs +0 -125
- package/src/commands/lifecycle/setup.mjs +0 -333
- package/src/commands/lifecycle/uninstall.mjs +0 -279
- package/src/commands/memory/memory.mjs +0 -524
- package/src/commands/pr/pr.mjs +0 -261
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""Unified, validated model lookup over registered provider adapters."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from .domain.provider import ModelSpec, UnknownModelError
|
|
10
|
+
from .domain.role import ROLE_DEFINITIONS, RoleCatalogError, normalize_role
|
|
11
|
+
from .domain.worker_exec import WorkerWriteCapability
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .domain.provider import ProviderSpec
|
|
15
|
+
from .registry.provider_registry import ProviderRegistry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_BUNDLED_PROVIDER_ORDER = ("claude", "antigravity", "codex", "grok", "kimi")
|
|
19
|
+
_TOKEN = re.compile(r"[a-z0-9][a-z0-9._-]*\Z")
|
|
20
|
+
_VERSION_KINDS = frozenset({"pinned", "channel"})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ModelRef(str):
|
|
24
|
+
"""A canonical `<provider>/<model>` reference."""
|
|
25
|
+
|
|
26
|
+
def __new__(cls, provider_id: str, model_id: str) -> "ModelRef":
|
|
27
|
+
provider = provider_id.strip().lower()
|
|
28
|
+
model = model_id.strip().lower()
|
|
29
|
+
if not provider or not model:
|
|
30
|
+
raise ValueError("model ref requires a provider and model id")
|
|
31
|
+
value = str.__new__(cls, f"{provider}/{model}")
|
|
32
|
+
value.provider_id = provider
|
|
33
|
+
value.model_id = model
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def parse(cls, raw: str) -> "ModelRef":
|
|
38
|
+
provider, separator, model = raw.strip().partition("/")
|
|
39
|
+
if not separator:
|
|
40
|
+
raise ValueError(f"invalid model ref: {raw!r}")
|
|
41
|
+
return cls(provider, model)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ResolvedModel:
|
|
46
|
+
model_ref: ModelRef
|
|
47
|
+
provider_id: str
|
|
48
|
+
display_name: str
|
|
49
|
+
execution_value: str
|
|
50
|
+
aliases: tuple[str, ...]
|
|
51
|
+
version_kind: str
|
|
52
|
+
channel_family: str | None
|
|
53
|
+
selectable: bool
|
|
54
|
+
pricing: tuple[float, float, float] | None
|
|
55
|
+
supported_roles: frozenset[str] | None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ModelAvailability:
|
|
60
|
+
available: bool
|
|
61
|
+
reason: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class ProviderRuntimeFacts:
|
|
66
|
+
"""Immutable provider facts required by assignment and rendering."""
|
|
67
|
+
|
|
68
|
+
provider: str
|
|
69
|
+
display_label: str
|
|
70
|
+
wrapper: str
|
|
71
|
+
supported_roles: frozenset[str]
|
|
72
|
+
cli_worker_write_capability: WorkerWriteCapability | None
|
|
73
|
+
execution_normalization_required: bool
|
|
74
|
+
|
|
75
|
+
def supports_role(self, role: str) -> bool:
|
|
76
|
+
try:
|
|
77
|
+
return normalize_role(role) in self.supported_roles
|
|
78
|
+
except RoleCatalogError:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class ModelPoolValidationFailure:
|
|
84
|
+
code: str
|
|
85
|
+
message: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ModelPoolValidationError(ValueError):
|
|
89
|
+
"""Raised with every invalid adapter-catalog contract row."""
|
|
90
|
+
|
|
91
|
+
def __init__(self, failures: tuple[ModelPoolValidationFailure, ...]) -> None:
|
|
92
|
+
self.failures = failures
|
|
93
|
+
super().__init__("; ".join(f"{row.code}: {row.message}" for row in failures))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ModelPool:
|
|
97
|
+
"""One model-query surface composed from provider-owned catalog entries."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, registry: "ProviderRegistry") -> None:
|
|
100
|
+
self._registry = registry
|
|
101
|
+
self._providers = self._ordered_providers()
|
|
102
|
+
self._provider_defaults = self._snapshot_provider_defaults()
|
|
103
|
+
self._provider_facts = self._snapshot_provider_facts()
|
|
104
|
+
self._models, self._aliases = self._index_models()
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_registry(cls, registry: "ProviderRegistry") -> "ModelPool":
|
|
108
|
+
pool = cls(registry)
|
|
109
|
+
pool.validate()
|
|
110
|
+
return pool
|
|
111
|
+
|
|
112
|
+
def list(self, *, role: str | None = None) -> tuple[ResolvedModel, ...]:
|
|
113
|
+
canonical_role = self._normalize_role(role) if role else None
|
|
114
|
+
return tuple(
|
|
115
|
+
model
|
|
116
|
+
for model in self._models.values()
|
|
117
|
+
if model.selectable and self._supports_role(model, canonical_role)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def list_catalog(self, *, role: str | None = None) -> tuple[ResolvedModel, ...]:
|
|
121
|
+
canonical_role = self._normalize_role(role) if role else None
|
|
122
|
+
return tuple(
|
|
123
|
+
model
|
|
124
|
+
for model in self._models.values()
|
|
125
|
+
if self._supports_role(model, canonical_role)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def resolve(self, model_ref: str) -> ResolvedModel:
|
|
129
|
+
try:
|
|
130
|
+
return self._models[ModelRef.parse(model_ref)]
|
|
131
|
+
except (KeyError, ValueError) as exc:
|
|
132
|
+
raise UnknownModelError(f"unknown model ref {model_ref!r}") from exc
|
|
133
|
+
|
|
134
|
+
def resolve_alias(self, provider: str, alias: str) -> ResolvedModel:
|
|
135
|
+
provider_id = provider.strip().lower()
|
|
136
|
+
alias_id = alias.strip().lower()
|
|
137
|
+
try:
|
|
138
|
+
model_ref = self._aliases[(provider_id, alias_id)]
|
|
139
|
+
except KeyError as exc:
|
|
140
|
+
allowed = ", ".join(self.aliases_for(provider_id))
|
|
141
|
+
raise UnknownModelError(
|
|
142
|
+
f"{provider} model {alias!r} is not a supported alias. "
|
|
143
|
+
f"Allowed values: {allowed}"
|
|
144
|
+
) from exc
|
|
145
|
+
return self.resolve(model_ref)
|
|
146
|
+
|
|
147
|
+
def resolve_provider(self, provider_id: str) -> ProviderRuntimeFacts:
|
|
148
|
+
"""Return frozen assignment facts without leaking registry objects."""
|
|
149
|
+
normalized = provider_id.strip().lower()
|
|
150
|
+
try:
|
|
151
|
+
return self._provider_facts[normalized]
|
|
152
|
+
except KeyError:
|
|
153
|
+
self._registry.resolve(provider_id)
|
|
154
|
+
raise AssertionError("unreachable")
|
|
155
|
+
|
|
156
|
+
def default_candidates(self, role: str) -> tuple[ResolvedModel, ...]:
|
|
157
|
+
canonical_role = self._normalize_role(role)
|
|
158
|
+
candidates: list[ResolvedModel] = []
|
|
159
|
+
for provider_id in self._providers:
|
|
160
|
+
default = self._default_for_role(
|
|
161
|
+
self._provider_defaults[provider_id],
|
|
162
|
+
canonical_role,
|
|
163
|
+
)
|
|
164
|
+
if default is not None:
|
|
165
|
+
candidates.append(self.resolve_alias(provider_id, default))
|
|
166
|
+
return tuple(candidates)
|
|
167
|
+
|
|
168
|
+
def default_candidate(
|
|
169
|
+
self,
|
|
170
|
+
provider_id: str,
|
|
171
|
+
role: str,
|
|
172
|
+
) -> ResolvedModel | None:
|
|
173
|
+
"""Return one provider's role default without scanning other providers."""
|
|
174
|
+
canonical_role = self._normalize_role(role)
|
|
175
|
+
provider = self.resolve_provider(provider_id)
|
|
176
|
+
default = self._default_for_role(
|
|
177
|
+
self._provider_defaults[provider.provider],
|
|
178
|
+
canonical_role,
|
|
179
|
+
)
|
|
180
|
+
if default is None:
|
|
181
|
+
return None
|
|
182
|
+
return self.resolve_alias(provider.provider, default)
|
|
183
|
+
|
|
184
|
+
def availability(
|
|
185
|
+
self,
|
|
186
|
+
model_ref: str,
|
|
187
|
+
role: str,
|
|
188
|
+
host_runtime: str | None = None,
|
|
189
|
+
entry_mode: str | None = None,
|
|
190
|
+
) -> ModelAvailability:
|
|
191
|
+
del host_runtime, entry_mode
|
|
192
|
+
try:
|
|
193
|
+
model = self.resolve(model_ref)
|
|
194
|
+
except UnknownModelError:
|
|
195
|
+
return ModelAvailability(False, "unknown-model")
|
|
196
|
+
if not model.selectable:
|
|
197
|
+
return ModelAvailability(False, "not-selectable")
|
|
198
|
+
try:
|
|
199
|
+
self._normalize_role(role)
|
|
200
|
+
except RoleCatalogError:
|
|
201
|
+
return ModelAvailability(False, "unknown-role")
|
|
202
|
+
return ModelAvailability(True, "available")
|
|
203
|
+
|
|
204
|
+
def validate(self) -> None:
|
|
205
|
+
failures = self._validation_failures()
|
|
206
|
+
if failures:
|
|
207
|
+
raise ModelPoolValidationError(tuple(failures))
|
|
208
|
+
|
|
209
|
+
def aliases_for(self, provider: str) -> tuple[str, ...]:
|
|
210
|
+
provider_id = provider.strip().lower()
|
|
211
|
+
return tuple(
|
|
212
|
+
alias
|
|
213
|
+
for indexed_provider, alias in self._aliases
|
|
214
|
+
if indexed_provider == provider_id
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _ordered_providers(self) -> tuple[str, ...]:
|
|
218
|
+
providers = self._registry.providers
|
|
219
|
+
bundled = tuple(provider for provider in _BUNDLED_PROVIDER_ORDER if provider in providers)
|
|
220
|
+
users = tuple(sorted(provider for provider in providers if provider not in bundled))
|
|
221
|
+
return (*bundled, *users)
|
|
222
|
+
|
|
223
|
+
def _snapshot_provider_facts(self) -> dict[str, ProviderRuntimeFacts]:
|
|
224
|
+
facts: dict[str, ProviderRuntimeFacts] = {}
|
|
225
|
+
canonical_roles = tuple(row.id for row in ROLE_DEFINITIONS)
|
|
226
|
+
for provider_id in self._providers:
|
|
227
|
+
spec = self._registry.resolve(provider_id)
|
|
228
|
+
capability = (
|
|
229
|
+
spec.exec_strategy.policy_support().worker_write_capability()
|
|
230
|
+
if spec.exec_strategy is not None
|
|
231
|
+
else None
|
|
232
|
+
)
|
|
233
|
+
facts[provider_id] = ProviderRuntimeFacts(
|
|
234
|
+
provider=spec.provider,
|
|
235
|
+
display_label=spec.display_label,
|
|
236
|
+
wrapper=spec.wrapper,
|
|
237
|
+
supported_roles=frozenset(
|
|
238
|
+
role for role in canonical_roles if spec.supports_role(role)
|
|
239
|
+
),
|
|
240
|
+
cli_worker_write_capability=capability,
|
|
241
|
+
execution_normalization_required=(
|
|
242
|
+
spec.execution_normalizer is not None
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
return facts
|
|
246
|
+
|
|
247
|
+
def _snapshot_provider_defaults(self):
|
|
248
|
+
return {
|
|
249
|
+
provider_id: MappingProxyType(dict(
|
|
250
|
+
self._registry.resolve(provider_id).default_models
|
|
251
|
+
))
|
|
252
|
+
for provider_id in self._providers
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
def _index_models(
|
|
256
|
+
self,
|
|
257
|
+
) -> tuple[dict[ModelRef, ResolvedModel], dict[tuple[str, str], ModelRef]]:
|
|
258
|
+
models: dict[ModelRef, ResolvedModel] = {}
|
|
259
|
+
aliases: dict[tuple[str, str], ModelRef] = {}
|
|
260
|
+
for provider_id in self._providers:
|
|
261
|
+
for mapping_key, spec in self._registry.resolve(provider_id).models.items():
|
|
262
|
+
model_ref = self._model_ref(provider_id, spec.model_id)
|
|
263
|
+
if model_ref is None:
|
|
264
|
+
continue
|
|
265
|
+
models.setdefault(model_ref, self._resolved_model(model_ref, spec))
|
|
266
|
+
for alias in (spec.model_id, mapping_key, *spec.aliases):
|
|
267
|
+
aliases.setdefault((provider_id, alias.strip().lower()), model_ref)
|
|
268
|
+
return models, aliases
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def _resolved_model(model_ref: ModelRef, spec: ModelSpec) -> ResolvedModel:
|
|
272
|
+
return ResolvedModel(
|
|
273
|
+
model_ref=model_ref,
|
|
274
|
+
provider_id=model_ref.provider_id,
|
|
275
|
+
display_name=spec.display_name,
|
|
276
|
+
execution_value=spec.execution_value,
|
|
277
|
+
aliases=spec.aliases,
|
|
278
|
+
version_kind=spec.version_kind,
|
|
279
|
+
channel_family=spec.channel_family,
|
|
280
|
+
selectable=spec.selectable,
|
|
281
|
+
pricing=spec.pricing,
|
|
282
|
+
supported_roles=spec.supported_roles,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _validation_failures(self) -> list[ModelPoolValidationFailure]:
|
|
286
|
+
failures: list[ModelPoolValidationFailure] = []
|
|
287
|
+
seen_refs: set[ModelRef] = set()
|
|
288
|
+
aliases: dict[tuple[str, str], ModelRef] = {}
|
|
289
|
+
for provider_id in self._providers:
|
|
290
|
+
spec = self._registry.resolve(provider_id)
|
|
291
|
+
self._validate_provider_token(provider_id, failures)
|
|
292
|
+
self._validate_provider_roles(spec.supported_roles, provider_id, failures)
|
|
293
|
+
for role in spec.default_models:
|
|
294
|
+
self._validate_role(role, provider_id, failures)
|
|
295
|
+
channel_families: set[str] = set()
|
|
296
|
+
pinned_families: list[str] = []
|
|
297
|
+
for mapping_key, model in spec.models.items():
|
|
298
|
+
model_ref = self._model_ref(provider_id, model.model_id)
|
|
299
|
+
raw_ref = self._raw_model_ref(provider_id, model.model_id)
|
|
300
|
+
if model_ref is not None and model_ref in seen_refs:
|
|
301
|
+
failures.append(ModelPoolValidationFailure(
|
|
302
|
+
"duplicate-model-ref", str(model_ref)
|
|
303
|
+
))
|
|
304
|
+
if model_ref is not None:
|
|
305
|
+
seen_refs.add(model_ref)
|
|
306
|
+
self._validate_model_structure(
|
|
307
|
+
provider_id, model, raw_ref, channel_families, pinned_families,
|
|
308
|
+
failures,
|
|
309
|
+
)
|
|
310
|
+
self._validate_model_roles(model, provider_id, raw_ref, failures)
|
|
311
|
+
if model_ref is None:
|
|
312
|
+
continue
|
|
313
|
+
for alias in (model.model_id, mapping_key, *model.aliases):
|
|
314
|
+
key = (provider_id, alias.strip().lower())
|
|
315
|
+
previous = aliases.get(key)
|
|
316
|
+
if previous is not None and previous != model_ref:
|
|
317
|
+
failures.append(ModelPoolValidationFailure(
|
|
318
|
+
"alias-collision", f"{provider_id}/{alias}"
|
|
319
|
+
))
|
|
320
|
+
aliases[key] = model_ref
|
|
321
|
+
self._validate_pinned_families(
|
|
322
|
+
provider_id, pinned_families, channel_families, failures
|
|
323
|
+
)
|
|
324
|
+
self._validate_defaults(provider_id, failures)
|
|
325
|
+
return failures
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def _validate_provider_token(
|
|
329
|
+
provider_id: str,
|
|
330
|
+
failures: list[ModelPoolValidationFailure],
|
|
331
|
+
) -> None:
|
|
332
|
+
if not _TOKEN.fullmatch(provider_id):
|
|
333
|
+
failures.append(ModelPoolValidationFailure(
|
|
334
|
+
"invalid-model-ref", f"invalid provider token: {provider_id!r}"
|
|
335
|
+
))
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def _validate_model_structure(
|
|
339
|
+
provider_id: str,
|
|
340
|
+
model: ModelSpec,
|
|
341
|
+
model_ref: str,
|
|
342
|
+
channel_families: set[str],
|
|
343
|
+
pinned_families: list[str],
|
|
344
|
+
failures: list[ModelPoolValidationFailure],
|
|
345
|
+
) -> None:
|
|
346
|
+
if not _TOKEN.fullmatch(provider_id) or not _TOKEN.fullmatch(model.model_id):
|
|
347
|
+
failures.append(ModelPoolValidationFailure(
|
|
348
|
+
"invalid-model-ref", f"invalid model token: {model_ref!r}"
|
|
349
|
+
))
|
|
350
|
+
if not model.execution_value.strip():
|
|
351
|
+
failures.append(ModelPoolValidationFailure(
|
|
352
|
+
"missing-execution-value", str(model_ref)
|
|
353
|
+
))
|
|
354
|
+
ModelPool._validate_model_family(
|
|
355
|
+
provider_id, model, model_ref, channel_families, pinned_families, failures
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def _validate_model_family(
|
|
360
|
+
provider_id: str,
|
|
361
|
+
model: ModelSpec,
|
|
362
|
+
model_ref: str,
|
|
363
|
+
channel_families: set[str],
|
|
364
|
+
pinned_families: list[str],
|
|
365
|
+
failures: list[ModelPoolValidationFailure],
|
|
366
|
+
) -> None:
|
|
367
|
+
if model.version_kind not in _VERSION_KINDS:
|
|
368
|
+
failures.append(ModelPoolValidationFailure(
|
|
369
|
+
"invalid-version-kind", f"{model_ref}: {model.version_kind}"
|
|
370
|
+
))
|
|
371
|
+
return
|
|
372
|
+
family = model.channel_family
|
|
373
|
+
if model.version_kind == "channel" and not family:
|
|
374
|
+
failures.append(ModelPoolValidationFailure(
|
|
375
|
+
"missing-channel-family", str(model_ref)
|
|
376
|
+
))
|
|
377
|
+
return
|
|
378
|
+
if family and not _TOKEN.fullmatch(family):
|
|
379
|
+
failures.append(ModelPoolValidationFailure(
|
|
380
|
+
"invalid-channel-family", f"{model_ref}: {family!r}"
|
|
381
|
+
))
|
|
382
|
+
return
|
|
383
|
+
if not family:
|
|
384
|
+
return
|
|
385
|
+
if model.version_kind == "channel":
|
|
386
|
+
channel_families.add(family)
|
|
387
|
+
else:
|
|
388
|
+
pinned_families.append(family)
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def _validate_pinned_families(
|
|
392
|
+
provider_id: str,
|
|
393
|
+
pinned_families: list[str],
|
|
394
|
+
channel_families: set[str],
|
|
395
|
+
failures: list[ModelPoolValidationFailure],
|
|
396
|
+
) -> None:
|
|
397
|
+
for family in pinned_families:
|
|
398
|
+
if family not in channel_families:
|
|
399
|
+
failures.append(ModelPoolValidationFailure(
|
|
400
|
+
"orphan-channel-family", f"{provider_id}: {family}"
|
|
401
|
+
))
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def _validate_provider_roles(
|
|
405
|
+
roles: frozenset[str], provider_id: str, failures: list[ModelPoolValidationFailure]
|
|
406
|
+
) -> None:
|
|
407
|
+
for role in roles:
|
|
408
|
+
ModelPool._validate_role(role, provider_id, failures)
|
|
409
|
+
|
|
410
|
+
def _validate_model_roles(
|
|
411
|
+
self,
|
|
412
|
+
model: ModelSpec,
|
|
413
|
+
provider_id: str,
|
|
414
|
+
model_ref: str,
|
|
415
|
+
failures: list[ModelPoolValidationFailure],
|
|
416
|
+
) -> None:
|
|
417
|
+
provider = self._registry.resolve(provider_id)
|
|
418
|
+
for role in model.supported_roles or ():
|
|
419
|
+
ModelPool._validate_role(role, str(model_ref), failures)
|
|
420
|
+
try:
|
|
421
|
+
canonical_role = normalize_role(role)
|
|
422
|
+
except RoleCatalogError:
|
|
423
|
+
continue
|
|
424
|
+
if not provider.supports_role(canonical_role):
|
|
425
|
+
failures.append(ModelPoolValidationFailure(
|
|
426
|
+
"model-role-exceeds-provider", f"{model_ref}: {canonical_role}"
|
|
427
|
+
))
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def _validate_role(
|
|
431
|
+
role: str, owner: str, failures: list[ModelPoolValidationFailure]
|
|
432
|
+
) -> None:
|
|
433
|
+
try:
|
|
434
|
+
normalize_role(role)
|
|
435
|
+
except RoleCatalogError:
|
|
436
|
+
failures.append(ModelPoolValidationFailure("unknown-role", f"{owner}: {role}"))
|
|
437
|
+
|
|
438
|
+
def _validate_defaults(
|
|
439
|
+
self,
|
|
440
|
+
provider_id: str,
|
|
441
|
+
failures: list[ModelPoolValidationFailure],
|
|
442
|
+
) -> None:
|
|
443
|
+
provider = self._registry.resolve(provider_id)
|
|
444
|
+
for role in self._supported_roles(provider):
|
|
445
|
+
default = self._default_for_role(provider.default_models, role)
|
|
446
|
+
if default is None:
|
|
447
|
+
failures.append(ModelPoolValidationFailure(
|
|
448
|
+
"missing-default", f"{provider_id}: {role}"
|
|
449
|
+
))
|
|
450
|
+
continue
|
|
451
|
+
try:
|
|
452
|
+
model = self.resolve_alias(provider_id, default)
|
|
453
|
+
except UnknownModelError:
|
|
454
|
+
failures.append(ModelPoolValidationFailure(
|
|
455
|
+
"missing-default", f"{provider_id}: {role} -> {default}"
|
|
456
|
+
))
|
|
457
|
+
continue
|
|
458
|
+
if not model.selectable or not self._supports_role(model, role):
|
|
459
|
+
failures.append(ModelPoolValidationFailure(
|
|
460
|
+
"missing-default", f"{provider_id}: {role} -> {default}"
|
|
461
|
+
))
|
|
462
|
+
|
|
463
|
+
@staticmethod
|
|
464
|
+
def _supported_roles(spec: object) -> tuple[str, ...]:
|
|
465
|
+
from .domain.role import ROLE_DEFINITIONS
|
|
466
|
+
|
|
467
|
+
return tuple(
|
|
468
|
+
definition.id
|
|
469
|
+
for definition in ROLE_DEFINITIONS
|
|
470
|
+
if spec.supports_role(definition.id)
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _default_for_role(defaults: object, canonical_role: str) -> str | None:
|
|
475
|
+
for role, model_id in defaults.items():
|
|
476
|
+
try:
|
|
477
|
+
if normalize_role(role) == canonical_role:
|
|
478
|
+
return model_id
|
|
479
|
+
except RoleCatalogError:
|
|
480
|
+
continue
|
|
481
|
+
return None
|
|
482
|
+
|
|
483
|
+
def _supports_role(self, model: ResolvedModel, role: str | None) -> bool:
|
|
484
|
+
if role is None:
|
|
485
|
+
return True
|
|
486
|
+
return self.availability(model.model_ref, role).available
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def _normalize_role(role: str) -> str:
|
|
490
|
+
return normalize_role(role)
|
|
491
|
+
|
|
492
|
+
@staticmethod
|
|
493
|
+
def _model_ref(provider_id: str, model_id: str) -> ModelRef | None:
|
|
494
|
+
try:
|
|
495
|
+
return ModelRef(provider_id, model_id)
|
|
496
|
+
except ValueError:
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def _raw_model_ref(provider_id: str, model_id: str) -> str:
|
|
501
|
+
return f"{provider_id}/{model_id}"
|
|
@@ -15,10 +15,14 @@ from .domain.provider import (
|
|
|
15
15
|
UnknownModelError,
|
|
16
16
|
UnknownProviderError,
|
|
17
17
|
)
|
|
18
|
+
from .domain.role import RoleCatalogError, normalize_role
|
|
19
|
+
from .model_pool import ModelPool
|
|
18
20
|
from .registry.provider_registry import default_provider_registry
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
_REGISTRY = default_provider_registry()
|
|
24
|
+
_MODEL_POOL = ModelPool.from_registry(_REGISTRY)
|
|
25
|
+
PROVIDERS = _REGISTRY.providers
|
|
22
26
|
CLAUDE = PROVIDERS["claude"].models
|
|
23
27
|
ANTIGRAVITY = PROVIDERS["antigravity"].models
|
|
24
28
|
CODEX = PROVIDERS["codex"].models
|
|
@@ -95,7 +99,7 @@ def provider_ids(role: str | None = None) -> list[str]:
|
|
|
95
99
|
|
|
96
100
|
def provider_supports_role(provider: str, role: str) -> bool:
|
|
97
101
|
"""Whether the provider has passed the registry gate for a role."""
|
|
98
|
-
return
|
|
102
|
+
return provider_spec(provider).supports_role(role)
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
def provider_wrappers(role: str | None = None) -> dict[str, str]:
|
|
@@ -110,7 +114,14 @@ def provider_wrappers(role: str | None = None) -> dict[str, str]:
|
|
|
110
114
|
|
|
111
115
|
def provider_default_model(provider: str, role: str) -> Optional[str]:
|
|
112
116
|
"""Resolve a provider explicit default for one functional role."""
|
|
113
|
-
|
|
117
|
+
try:
|
|
118
|
+
canonical_role = normalize_role(role)
|
|
119
|
+
except RoleCatalogError:
|
|
120
|
+
return None
|
|
121
|
+
for model in _model_pool().default_candidates(canonical_role):
|
|
122
|
+
if model.provider_id == provider.strip().lower():
|
|
123
|
+
return model.model_ref.model_id
|
|
124
|
+
return None
|
|
114
125
|
|
|
115
126
|
|
|
116
127
|
def resolve_model_metadata(
|
|
@@ -118,40 +129,49 @@ def resolve_model_metadata(
|
|
|
118
129
|
) -> ModelAssignment:
|
|
119
130
|
"""Resolve a model alias to its display and execution values."""
|
|
120
131
|
raw_value = (raw_value or "").strip()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if raw_value and
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
allowed = ", ".join(sorted(models))
|
|
127
|
-
raise UnknownModelError(
|
|
128
|
-
f"{provider} model {raw_value!r} is not a supported alias. "
|
|
129
|
-
f"Allowed values: {allowed}"
|
|
130
|
-
)
|
|
132
|
+
registry = default_provider_registry()
|
|
133
|
+
mapping = registry.providers.get(provider)
|
|
134
|
+
if raw_value and mapping is not None:
|
|
135
|
+
model = _model_pool().resolve_alias(provider, raw_value)
|
|
136
|
+
return ModelAssignment(display=model.display_name, execution=model.execution_value)
|
|
131
137
|
value = raw_value or default_display
|
|
132
|
-
display =
|
|
133
|
-
execution = raw_value or default_execution
|
|
134
|
-
normalized = value.strip().lower()
|
|
135
|
-
if models and normalized in models:
|
|
136
|
-
spec = models[normalized]
|
|
137
|
-
display, execution = spec.display, spec.execution
|
|
138
|
-
return ModelAssignment(display=display, execution=execution)
|
|
138
|
+
return ModelAssignment(display=value, execution=raw_value or default_execution)
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def picker_options(provider: str) -> list[str]:
|
|
142
142
|
"""Picker-exposed aliases for a provider, in catalog insertion order."""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
provider_id = provider.strip().lower()
|
|
144
|
+
return [
|
|
145
|
+
model.model_ref.model_id
|
|
146
|
+
for model in _model_pool().list()
|
|
147
|
+
if model.provider_id == provider_id
|
|
148
|
+
]
|
|
146
149
|
|
|
147
150
|
|
|
148
151
|
def default_model(role: str) -> Optional[str]:
|
|
149
152
|
"""Role-to-default model alias; environment overrides belong to the caller."""
|
|
150
|
-
|
|
153
|
+
if role in default_provider_registry().providers:
|
|
154
|
+
return provider_default_model(role, "leader")
|
|
155
|
+
try:
|
|
156
|
+
candidates = _model_pool().default_candidates(role)
|
|
157
|
+
except RoleCatalogError:
|
|
158
|
+
return None
|
|
159
|
+
return candidates[0].model_ref.model_id if candidates else None
|
|
151
160
|
|
|
152
161
|
|
|
153
162
|
def catalog_pricing(provider: str) -> dict:
|
|
154
163
|
"""Selectable-model pricing as input, cached-input, output USD per million."""
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
provider_id = provider.strip().lower()
|
|
165
|
+
return {
|
|
166
|
+
model.model_ref.model_id: model.pricing
|
|
167
|
+
for model in _model_pool().list()
|
|
168
|
+
if model.provider_id == provider_id and model.pricing is not None
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _model_pool() -> ModelPool:
|
|
173
|
+
"""Return the pool built from the registry identity cached for this home."""
|
|
174
|
+
registry = default_provider_registry()
|
|
175
|
+
if registry is _REGISTRY:
|
|
176
|
+
return _MODEL_POOL
|
|
177
|
+
return ModelPool.from_registry(registry)
|