okstra 0.174.0 → 0.175.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +4 -0
- package/docs/architecture.md +7 -1
- package/docs/cli.md +26 -6
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/project-structure-overview.md +16 -7
- package/docs/task-process/README.md +2 -0
- package/docs/task-process/implementation-planning.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- 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/usage.sh +4 -4
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +3 -1
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/change-impact-analysis.md +16 -0
- package/runtime/prompts/profiles/error-analysis.md +20 -0
- package/runtime/prompts/profiles/feature-analysis.md +16 -0
- package/runtime/prompts/profiles/final-verification.md +16 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +16 -0
- package/runtime/prompts/profiles/implementation-planning.md +20 -0
- package/runtime/prompts/profiles/implementation.md +15 -0
- package/runtime/prompts/profiles/improvement-discovery.md +16 -0
- package/runtime/prompts/profiles/project-analysis.md +16 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +20 -0
- package/runtime/prompts/wizard/prompts.ko.json +18 -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/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/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 +1092 -63
- 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 +32 -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 +13 -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_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/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +490 -0
- 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/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/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +78 -10
- 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 +217 -0
- package/runtime/python/okstra_ctl/run.py +1207 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- 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 +1253 -62
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- 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/write_policy.py +571 -0
- 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 +25 -0
- package/runtime/skills/okstra-run/SKILL.md +9 -9
- 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/worker-prompt-preamble.md +6 -2
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +429 -35
- 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
|
@@ -26,7 +26,7 @@ import tempfile
|
|
|
26
26
|
from dataclasses import dataclass, field
|
|
27
27
|
from datetime import datetime, timezone
|
|
28
28
|
from pathlib import Path
|
|
29
|
-
from typing import Sequence
|
|
29
|
+
from typing import Mapping, Sequence
|
|
30
30
|
|
|
31
31
|
from okstra_project import project_json_path, upsert_project_json
|
|
32
32
|
from okstra_project.state import slugify
|
|
@@ -68,19 +68,44 @@ from .material import (
|
|
|
68
68
|
from .final_report_schema import load_schema_version
|
|
69
69
|
from .final_report_paths import final_report_data_path as _final_report_data_path
|
|
70
70
|
from .lead_events import LeadEvent, append_lead_event
|
|
71
|
-
from .
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
from .assignment_environment import load_assignment_context
|
|
72
|
+
from .assignment_resolver import (
|
|
73
|
+
AssignmentContext,
|
|
74
|
+
AssignmentPlan,
|
|
75
|
+
AssignmentResolutionError,
|
|
76
|
+
ResolvedAssignment,
|
|
77
|
+
TerminalBackend,
|
|
78
|
+
resolve_assignments,
|
|
79
|
+
resolve_context_lead_provider,
|
|
80
|
+
resolve_dispatch_assignment,
|
|
81
|
+
resolve_model_selection,
|
|
74
82
|
)
|
|
75
83
|
from .domain.host import (
|
|
84
|
+
CurrentSessionModelAttestation,
|
|
76
85
|
HostCapabilityMismatch,
|
|
77
86
|
HostNotRegistered,
|
|
87
|
+
HostSessionContext,
|
|
78
88
|
ProviderUnavailable,
|
|
79
89
|
)
|
|
80
|
-
from .
|
|
90
|
+
from .domain.provider import ServedModelAttestation
|
|
91
|
+
from .execution_identity import (
|
|
92
|
+
ExecutionManifest,
|
|
93
|
+
ParticipantAssignment,
|
|
94
|
+
RoleExecution,
|
|
95
|
+
execution_label,
|
|
96
|
+
model_spec_digest,
|
|
97
|
+
)
|
|
98
|
+
from .execution_manifest import persist_static_execution_identity
|
|
99
|
+
from .legacy_model_selection import (
|
|
100
|
+
CanonicalModelSelection,
|
|
101
|
+
ModelSelectionInputError,
|
|
102
|
+
deserialize_host_session_context,
|
|
103
|
+
normalize_model_selection_inputs,
|
|
104
|
+
serialize_host_session_context,
|
|
105
|
+
)
|
|
106
|
+
from .model_defaults import ModelDefaultScopes
|
|
81
107
|
from .worker_prompt_policy import ANALYSIS_DUTY_BY_TASK_TYPE
|
|
82
108
|
from .models import (
|
|
83
|
-
ModelAssignment,
|
|
84
109
|
UnknownProviderError,
|
|
85
110
|
default_model,
|
|
86
111
|
lead_launch_argv,
|
|
@@ -89,12 +114,11 @@ from .models import (
|
|
|
89
114
|
provider_ids,
|
|
90
115
|
provider_spec,
|
|
91
116
|
provider_supports_role,
|
|
92
|
-
resolve_model_metadata,
|
|
93
117
|
)
|
|
94
118
|
from .schema_excerpt import build_schema_excerpt
|
|
95
119
|
from .registry.host_registry import default_host_registry
|
|
96
120
|
from .registry.provider_registry import default_provider_registry
|
|
97
|
-
from .
|
|
121
|
+
from .role_requirements import RoleProfile, RoleProfileError, load_role_profile
|
|
98
122
|
from .agent_invocation import (
|
|
99
123
|
AgentInstruction,
|
|
100
124
|
AgentInstructionSource,
|
|
@@ -103,6 +127,7 @@ from .agent_invocation import (
|
|
|
103
127
|
AgentInvocationError,
|
|
104
128
|
agent_model_assignment_from_payload,
|
|
105
129
|
digest_duty_catalog,
|
|
130
|
+
invocation_execution_identity_from_manifest,
|
|
106
131
|
prepare_agent_invocation,
|
|
107
132
|
verify_agent_invocation,
|
|
108
133
|
)
|
|
@@ -422,6 +447,17 @@ class PrepareInputs:
|
|
|
422
447
|
evidence_inputs_raw: str = ""
|
|
423
448
|
directive: str = ""
|
|
424
449
|
workers_override: str = ""
|
|
450
|
+
role_counts_raw: tuple[str, ...] = ()
|
|
451
|
+
role_models_raw: tuple[str, ...] = ()
|
|
452
|
+
host_session_context: HostSessionContext = field(
|
|
453
|
+
default_factory=lambda: HostSessionContext(
|
|
454
|
+
host_id="",
|
|
455
|
+
entry_mode="spawn-process",
|
|
456
|
+
available_functions=frozenset(),
|
|
457
|
+
interaction_surface="unavailable",
|
|
458
|
+
current_model=CurrentSessionModelAttestation.unknown(""),
|
|
459
|
+
)
|
|
460
|
+
)
|
|
425
461
|
lead_provider: str = ""
|
|
426
462
|
lead_model: str = ""
|
|
427
463
|
claude_model: str = ""
|
|
@@ -706,6 +742,18 @@ def _ensure_task_directories(ctx: dict) -> None:
|
|
|
706
742
|
Path(ctx[key]).mkdir(parents=True, exist_ok=True)
|
|
707
743
|
|
|
708
744
|
|
|
745
|
+
def _role_execution_refs_from_ctx(ctx: dict) -> list[str]:
|
|
746
|
+
try:
|
|
747
|
+
payload = json.loads(ctx.get("EXECUTION_IDENTITY_JSON") or "{}")
|
|
748
|
+
except (TypeError, ValueError):
|
|
749
|
+
return []
|
|
750
|
+
return [
|
|
751
|
+
str(row.get("roleExecutionRef"))
|
|
752
|
+
for row in payload.get("roleExecutions") or []
|
|
753
|
+
if isinstance(row, dict) and row.get("roleExecutionRef")
|
|
754
|
+
]
|
|
755
|
+
|
|
756
|
+
|
|
709
757
|
def _record_start(
|
|
710
758
|
*,
|
|
711
759
|
workspace_root: Path,
|
|
@@ -762,6 +810,8 @@ def _record_start(
|
|
|
762
810
|
okstra_version=ctx.get("OKSTRA_VERSION", ""),
|
|
763
811
|
initial_status=initial_status,
|
|
764
812
|
brief_sha256=brief_sha256,
|
|
813
|
+
execution_manifest_path=ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
814
|
+
role_execution_refs=_role_execution_refs_from_ctx(ctx),
|
|
765
815
|
)
|
|
766
816
|
|
|
767
817
|
|
|
@@ -826,7 +876,15 @@ def _record_artifact_runtime_render_only_event(inp: PrepareInputs, ctx: dict) ->
|
|
|
826
876
|
|
|
827
877
|
def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
828
878
|
"""rerun 충실 재현을 위한 canonical argv 재구성."""
|
|
829
|
-
|
|
879
|
+
has_role_selection = bool(inp.role_counts_raw or inp.role_models_raw)
|
|
880
|
+
legacy_fallbacks = {} if has_role_selection else ctx
|
|
881
|
+
lead_fallbacks = (
|
|
882
|
+
{} if inp.host_session_context.entry_mode == "current-session"
|
|
883
|
+
else legacy_fallbacks
|
|
884
|
+
)
|
|
885
|
+
workers = inp.workers_override or (
|
|
886
|
+
legacy_fallbacks.get("RECOMMENDED_ANALYSERS", "")
|
|
887
|
+
)
|
|
830
888
|
pairs = [
|
|
831
889
|
("--task-type", inp.task_type),
|
|
832
890
|
("--project-id", inp.project_id),
|
|
@@ -841,20 +899,44 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
841
899
|
("--clarification-response", inp.clarification_response_path),
|
|
842
900
|
("--selected-direction", inp.selected_direction_path),
|
|
843
901
|
("--workers", workers),
|
|
844
|
-
(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
(
|
|
902
|
+
(
|
|
903
|
+
"--lead-provider",
|
|
904
|
+
inp.lead_provider or lead_fallbacks.get("LEAD_PROVIDER", ""),
|
|
905
|
+
),
|
|
906
|
+
(
|
|
907
|
+
"--lead-model",
|
|
908
|
+
inp.lead_model or lead_fallbacks.get("LEAD_MODEL", ""),
|
|
909
|
+
),
|
|
910
|
+
(
|
|
911
|
+
"--claude-model",
|
|
912
|
+
inp.claude_model or legacy_fallbacks.get("CLAUDE_WORKER_MODEL", ""),
|
|
913
|
+
),
|
|
914
|
+
(
|
|
915
|
+
"--codex-model",
|
|
916
|
+
inp.codex_model or legacy_fallbacks.get("CODEX_WORKER_MODEL", ""),
|
|
917
|
+
),
|
|
918
|
+
(
|
|
919
|
+
"--antigravity-model",
|
|
920
|
+
inp.antigravity_model
|
|
921
|
+
or legacy_fallbacks.get("ANTIGRAVITY_WORKER_MODEL", ""),
|
|
922
|
+
),
|
|
849
923
|
("--worker-model", inp.worker_models_raw),
|
|
850
924
|
(
|
|
851
925
|
"--report-writer-provider",
|
|
852
|
-
inp.report_writer_provider
|
|
926
|
+
inp.report_writer_provider
|
|
927
|
+
or legacy_fallbacks.get("REPORT_WRITER_PROVIDER", ""),
|
|
928
|
+
),
|
|
929
|
+
(
|
|
930
|
+
"--report-writer-model",
|
|
931
|
+
inp.report_writer_model
|
|
932
|
+
or legacy_fallbacks.get("REPORT_WRITER_MODEL", ""),
|
|
853
933
|
),
|
|
854
|
-
("--report-writer-model", inp.report_writer_model or ctx.get("REPORT_WRITER_MODEL", "")),
|
|
855
934
|
("--lead-runtime", inp.lead_runtime if inp.lead_runtime != "claude-code" else ""),
|
|
856
|
-
(
|
|
857
|
-
|
|
935
|
+
(
|
|
936
|
+
"--executor",
|
|
937
|
+
inp.executor or legacy_fallbacks.get("EXECUTOR_PROVIDER", ""),
|
|
938
|
+
),
|
|
939
|
+
("--critic", inp.critic or legacy_fallbacks.get("CRITIC_CHOICE", "")),
|
|
858
940
|
("--related-tasks", inp.related_tasks_raw),
|
|
859
941
|
("--work-category", inp.work_category),
|
|
860
942
|
]
|
|
@@ -862,6 +944,15 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
862
944
|
for flag, val in pairs:
|
|
863
945
|
if val:
|
|
864
946
|
argv.extend([flag, val])
|
|
947
|
+
for role_count in inp.role_counts_raw:
|
|
948
|
+
argv.extend(["--role-count", role_count])
|
|
949
|
+
for role_model in inp.role_models_raw:
|
|
950
|
+
argv.extend(["--role-model", role_model])
|
|
951
|
+
if inp.host_session_context.host_id:
|
|
952
|
+
argv.extend([
|
|
953
|
+
"--host-session-context-json",
|
|
954
|
+
serialize_host_session_context(inp.host_session_context),
|
|
955
|
+
])
|
|
865
956
|
if inp.render_only:
|
|
866
957
|
argv.append("--render-only")
|
|
867
958
|
if not inp.plan_verification_enabled:
|
|
@@ -1686,12 +1777,484 @@ def _resolve_roster(inp: PrepareInputs, profile_file: Path) -> tuple[list[str],
|
|
|
1686
1777
|
workers = normalize_workers(inp.workers_override or profile_workers_csv)
|
|
1687
1778
|
if inp.workers_override.strip():
|
|
1688
1779
|
validate_workers_against_profile(workers, profile_workers, optional_workers)
|
|
1780
|
+
try:
|
|
1781
|
+
role_profile = load_role_profile(profile_file)
|
|
1782
|
+
except RoleProfileError as exc:
|
|
1783
|
+
raise PrepareError(str(exc)) from exc
|
|
1784
|
+
requires_report_writer = any(
|
|
1785
|
+
row.role == "report-writer" and row.count == 1
|
|
1786
|
+
for row in role_profile.roles
|
|
1787
|
+
)
|
|
1788
|
+
if requires_report_writer and "report-writer" not in workers:
|
|
1789
|
+
workers.append("report-writer")
|
|
1689
1790
|
if not workers:
|
|
1690
1791
|
raise PrepareError(f"no workers resolved for profile: {inp.task_type}")
|
|
1691
1792
|
_validate_option_selection_roster(inp.task_type, workers)
|
|
1692
1793
|
return workers, ",".join(workers)
|
|
1693
1794
|
|
|
1694
1795
|
|
|
1796
|
+
def _profile_requires_executor(profile_file: Path) -> bool:
|
|
1797
|
+
try:
|
|
1798
|
+
profile = load_role_profile(profile_file)
|
|
1799
|
+
except RoleProfileError as exc:
|
|
1800
|
+
raise PrepareError(str(exc)) from exc
|
|
1801
|
+
return any(
|
|
1802
|
+
requirement.role == "implementer" and requirement.count > 0
|
|
1803
|
+
for requirement in profile.roles
|
|
1804
|
+
)
|
|
1805
|
+
|
|
1806
|
+
|
|
1807
|
+
def _normalize_prepare_model_selection(
|
|
1808
|
+
inp: PrepareInputs,
|
|
1809
|
+
profile_file: Path,
|
|
1810
|
+
) -> tuple[CanonicalModelSelection, RoleProfile]:
|
|
1811
|
+
try:
|
|
1812
|
+
_validate_critic_choice(inp.critic)
|
|
1813
|
+
profile = load_role_profile(profile_file)
|
|
1814
|
+
profile_workers = resolve_profile_workers(profile_file)
|
|
1815
|
+
has_worker_models = bool(
|
|
1816
|
+
inp.worker_models_raw.strip()
|
|
1817
|
+
or inp.claude_model.strip()
|
|
1818
|
+
or inp.codex_model.strip()
|
|
1819
|
+
or inp.antigravity_model.strip()
|
|
1820
|
+
)
|
|
1821
|
+
workers = inp.workers_override
|
|
1822
|
+
if not workers and has_worker_models:
|
|
1823
|
+
workers = ",".join(profile_workers)
|
|
1824
|
+
selection = normalize_model_selection_inputs(
|
|
1825
|
+
profile=profile,
|
|
1826
|
+
role_counts_raw=inp.role_counts_raw,
|
|
1827
|
+
role_models_raw=inp.role_models_raw,
|
|
1828
|
+
workers=workers,
|
|
1829
|
+
worker_model=inp.worker_models_raw,
|
|
1830
|
+
critic=inp.critic,
|
|
1831
|
+
executor=inp.executor,
|
|
1832
|
+
lead_provider=inp.lead_provider,
|
|
1833
|
+
lead_model=inp.lead_model,
|
|
1834
|
+
report_writer_provider=inp.report_writer_provider,
|
|
1835
|
+
report_writer_model=inp.report_writer_model,
|
|
1836
|
+
provider_models={
|
|
1837
|
+
"claude": inp.claude_model,
|
|
1838
|
+
"codex": inp.codex_model,
|
|
1839
|
+
"antigravity": inp.antigravity_model,
|
|
1840
|
+
},
|
|
1841
|
+
)
|
|
1842
|
+
except (ModelSelectionInputError, RoleProfileError) as exc:
|
|
1843
|
+
raise PrepareError(str(exc)) from exc
|
|
1844
|
+
return selection, profile
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
def _effective_host_session_context(
|
|
1848
|
+
inp: PrepareInputs,
|
|
1849
|
+
lead_runtime: str,
|
|
1850
|
+
) -> HostSessionContext:
|
|
1851
|
+
context = inp.host_session_context
|
|
1852
|
+
if context.host_id:
|
|
1853
|
+
return context
|
|
1854
|
+
descriptor = default_host_registry().resolve(lead_runtime).descriptor
|
|
1855
|
+
current_model = context.current_model
|
|
1856
|
+
if not current_model.provider_id:
|
|
1857
|
+
current_model = CurrentSessionModelAttestation.unknown(
|
|
1858
|
+
descriptor.native_provider_id
|
|
1859
|
+
)
|
|
1860
|
+
return HostSessionContext(
|
|
1861
|
+
host_id=descriptor.id,
|
|
1862
|
+
entry_mode=context.entry_mode,
|
|
1863
|
+
available_functions=context.available_functions,
|
|
1864
|
+
interaction_surface=context.interaction_surface,
|
|
1865
|
+
current_model=current_model,
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
|
|
1869
|
+
def _resolve_prepare_assignments(
|
|
1870
|
+
*,
|
|
1871
|
+
inp: PrepareInputs,
|
|
1872
|
+
profile: RoleProfile,
|
|
1873
|
+
selection: CanonicalModelSelection,
|
|
1874
|
+
workers: list[str],
|
|
1875
|
+
lead_runtime: str,
|
|
1876
|
+
terminal_backend: TerminalBackend,
|
|
1877
|
+
requires_executor: bool,
|
|
1878
|
+
) -> tuple[AssignmentContext, AssignmentPlan]:
|
|
1879
|
+
scopes = _model_default_scopes(Path(inp.project_root))
|
|
1880
|
+
provider_ids_for_snapshot = _selection_provider_ids(
|
|
1881
|
+
inp,
|
|
1882
|
+
profile,
|
|
1883
|
+
selection,
|
|
1884
|
+
workers,
|
|
1885
|
+
scopes,
|
|
1886
|
+
requires_executor,
|
|
1887
|
+
)
|
|
1888
|
+
context = load_assignment_context(
|
|
1889
|
+
host_runtime=lead_runtime,
|
|
1890
|
+
terminal_backend=terminal_backend,
|
|
1891
|
+
execution_provider_ids=provider_ids_for_snapshot,
|
|
1892
|
+
)
|
|
1893
|
+
host = _effective_host_session_context(inp, lead_runtime)
|
|
1894
|
+
role_models = _materialize_selection_models(selection, context, host)
|
|
1895
|
+
role_counts = _resolver_role_counts(profile, selection.role_counts)
|
|
1896
|
+
try:
|
|
1897
|
+
plan = resolve_assignments(
|
|
1898
|
+
profile=profile,
|
|
1899
|
+
role_counts=role_counts,
|
|
1900
|
+
role_models=role_models,
|
|
1901
|
+
scopes=scopes,
|
|
1902
|
+
pool=context.pool,
|
|
1903
|
+
host=host,
|
|
1904
|
+
environment=context.environment,
|
|
1905
|
+
)
|
|
1906
|
+
except AssignmentResolutionError as exc:
|
|
1907
|
+
raise PrepareError(_assignment_resolution_message(exc)) from exc
|
|
1908
|
+
except (ValueError, RuntimeError) as exc:
|
|
1909
|
+
raise PrepareError(str(exc)) from exc
|
|
1910
|
+
return context, plan
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
def _build_static_execution_manifest(
|
|
1914
|
+
plan: AssignmentPlan,
|
|
1915
|
+
context: AssignmentContext,
|
|
1916
|
+
lead: ResolvedAssignment,
|
|
1917
|
+
) -> ExecutionManifest:
|
|
1918
|
+
participants: list[ParticipantAssignment] = []
|
|
1919
|
+
roles: list[RoleExecution] = []
|
|
1920
|
+
assignments = (
|
|
1921
|
+
lead,
|
|
1922
|
+
*(row for row in plan.assignments if row.role != "leader"),
|
|
1923
|
+
)
|
|
1924
|
+
for index, assignment in enumerate(assignments, start=1):
|
|
1925
|
+
participant_ref = f"participant-{index:03d}"
|
|
1926
|
+
participants.append(_participant_assignment(assignment, participant_ref))
|
|
1927
|
+
roles.append(
|
|
1928
|
+
_static_role_execution(
|
|
1929
|
+
assignment,
|
|
1930
|
+
participant_ref,
|
|
1931
|
+
context,
|
|
1932
|
+
)
|
|
1933
|
+
)
|
|
1934
|
+
return ExecutionManifest(
|
|
1935
|
+
entry_mode=lead.entry_mode,
|
|
1936
|
+
participant_assignments=tuple(participants),
|
|
1937
|
+
role_executions=tuple(roles),
|
|
1938
|
+
)
|
|
1939
|
+
|
|
1940
|
+
|
|
1941
|
+
def _participant_assignment(
|
|
1942
|
+
assignment: ResolvedAssignment,
|
|
1943
|
+
participant_ref: str,
|
|
1944
|
+
) -> ParticipantAssignment:
|
|
1945
|
+
binding = assignment.binding
|
|
1946
|
+
return ParticipantAssignment(
|
|
1947
|
+
participant_ref=participant_ref,
|
|
1948
|
+
provider=assignment.provider_id,
|
|
1949
|
+
model_ref=assignment.model_ref,
|
|
1950
|
+
model_id=assignment.model_id,
|
|
1951
|
+
runner=binding.runner if binding is not None else "current-session",
|
|
1952
|
+
host_runtime=assignment.host_runtime,
|
|
1953
|
+
host_model_value=binding.host_model_value if binding is not None else None,
|
|
1954
|
+
session_ref=None,
|
|
1955
|
+
window_ref=None,
|
|
1956
|
+
worker_write_capability=(
|
|
1957
|
+
binding.worker_write_capability if binding is not None else None
|
|
1958
|
+
),
|
|
1959
|
+
entry_mode=assignment.entry_mode,
|
|
1960
|
+
status="prepared",
|
|
1961
|
+
)
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
def _static_role_execution(
|
|
1965
|
+
assignment: ResolvedAssignment,
|
|
1966
|
+
participant_ref: str,
|
|
1967
|
+
context: AssignmentContext,
|
|
1968
|
+
) -> RoleExecution:
|
|
1969
|
+
binding = assignment.binding
|
|
1970
|
+
digest = None
|
|
1971
|
+
if assignment.model_ref is not None and binding is not None:
|
|
1972
|
+
digest = model_spec_digest(context.pool.resolve(assignment.model_ref), binding)
|
|
1973
|
+
return RoleExecution(
|
|
1974
|
+
role_execution_ref=(
|
|
1975
|
+
f"role-exec-{assignment.role}-{assignment.ordinal:03d}"
|
|
1976
|
+
),
|
|
1977
|
+
participant_ref=participant_ref,
|
|
1978
|
+
source_role_execution_ref=None,
|
|
1979
|
+
role=assignment.role,
|
|
1980
|
+
provider=assignment.provider_id,
|
|
1981
|
+
model_ref=assignment.model_ref,
|
|
1982
|
+
model_id=assignment.model_id,
|
|
1983
|
+
ordinal=assignment.ordinal,
|
|
1984
|
+
execution_label=execution_label(
|
|
1985
|
+
assignment.role,
|
|
1986
|
+
assignment.provider_id,
|
|
1987
|
+
assignment.model_id,
|
|
1988
|
+
assignment.ordinal,
|
|
1989
|
+
),
|
|
1990
|
+
model_spec_digest=digest,
|
|
1991
|
+
binding=binding,
|
|
1992
|
+
served_model_attestation=ServedModelAttestation.unknown(),
|
|
1993
|
+
status="prepared",
|
|
1994
|
+
)
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
def _assignment_resolution_message(exc: AssignmentResolutionError) -> str:
|
|
1998
|
+
lines = [str(exc)]
|
|
1999
|
+
for candidate in exc.candidates:
|
|
2000
|
+
if candidate.available:
|
|
2001
|
+
continue
|
|
2002
|
+
lines.append(
|
|
2003
|
+
f"- {candidate.role}#{candidate.ordinal} {candidate.model_ref}: "
|
|
2004
|
+
f"{candidate.reason}"
|
|
2005
|
+
)
|
|
2006
|
+
for constraint in exc.unsatisfied_constraints:
|
|
2007
|
+
lines.append(
|
|
2008
|
+
f"- {constraint.role} {constraint.constraint}: "
|
|
2009
|
+
f"required={constraint.required}, available={constraint.available}"
|
|
2010
|
+
)
|
|
2011
|
+
return "\n".join(lines)
|
|
2012
|
+
|
|
2013
|
+
|
|
2014
|
+
def _selection_provider_ids(
|
|
2015
|
+
inp: PrepareInputs,
|
|
2016
|
+
profile: RoleProfile,
|
|
2017
|
+
selection: CanonicalModelSelection,
|
|
2018
|
+
workers: list[str],
|
|
2019
|
+
scopes: ModelDefaultScopes,
|
|
2020
|
+
requires_executor: bool,
|
|
2021
|
+
) -> tuple[str, ...]:
|
|
2022
|
+
default_roles = _roles_requiring_defaults(
|
|
2023
|
+
inp,
|
|
2024
|
+
profile,
|
|
2025
|
+
selection,
|
|
2026
|
+
)
|
|
2027
|
+
has_canonical_role_selection = bool(
|
|
2028
|
+
inp.role_counts_raw or inp.role_models_raw
|
|
2029
|
+
)
|
|
2030
|
+
if has_canonical_role_selection:
|
|
2031
|
+
selected = _canonical_selection_provider_ids(
|
|
2032
|
+
inp,
|
|
2033
|
+
profile,
|
|
2034
|
+
selection,
|
|
2035
|
+
workers,
|
|
2036
|
+
scopes,
|
|
2037
|
+
default_roles,
|
|
2038
|
+
requires_executor,
|
|
2039
|
+
)
|
|
2040
|
+
else:
|
|
2041
|
+
selected = list(
|
|
2042
|
+
_selected_execution_provider_ids(inp, workers, requires_executor)
|
|
2043
|
+
)
|
|
2044
|
+
selected.extend(
|
|
2045
|
+
provider
|
|
2046
|
+
for providers in selection.provider_constraints.values()
|
|
2047
|
+
for provider in providers
|
|
2048
|
+
if provider
|
|
2049
|
+
)
|
|
2050
|
+
selected.extend(_providers_from_model_refs(selection.role_models.values()))
|
|
2051
|
+
selected.extend(
|
|
2052
|
+
_providers_from_model_refs(
|
|
2053
|
+
_selected_scoped_default_groups(default_roles, scopes)
|
|
2054
|
+
)
|
|
2055
|
+
)
|
|
2056
|
+
current_provider = inp.host_session_context.current_model.provider_id
|
|
2057
|
+
if current_provider:
|
|
2058
|
+
selected.append(current_provider)
|
|
2059
|
+
return tuple(dict.fromkeys(selected))
|
|
2060
|
+
|
|
2061
|
+
|
|
2062
|
+
def _selected_scoped_default_groups(
|
|
2063
|
+
roles: Sequence[str],
|
|
2064
|
+
scopes: ModelDefaultScopes,
|
|
2065
|
+
) -> tuple[tuple[str, ...], ...]:
|
|
2066
|
+
groups: list[tuple[str, ...]] = []
|
|
2067
|
+
for role in roles:
|
|
2068
|
+
if role in scopes.project:
|
|
2069
|
+
groups.append(scopes.project[role])
|
|
2070
|
+
elif role in scopes.global_:
|
|
2071
|
+
groups.append(scopes.global_[role])
|
|
2072
|
+
return tuple(groups)
|
|
2073
|
+
|
|
2074
|
+
|
|
2075
|
+
def _roles_requiring_defaults(
|
|
2076
|
+
inp: PrepareInputs,
|
|
2077
|
+
profile: RoleProfile,
|
|
2078
|
+
selection: CanonicalModelSelection,
|
|
2079
|
+
) -> tuple[str, ...]:
|
|
2080
|
+
roles: list[str] = []
|
|
2081
|
+
if (
|
|
2082
|
+
inp.host_session_context.entry_mode != "current-session"
|
|
2083
|
+
and not _explicit_selection_count(selection, "leader")
|
|
2084
|
+
):
|
|
2085
|
+
roles.append("leader")
|
|
2086
|
+
for requirement in profile.roles:
|
|
2087
|
+
if requirement.dynamic:
|
|
2088
|
+
continue
|
|
2089
|
+
desired = selection.role_counts.get(
|
|
2090
|
+
requirement.role,
|
|
2091
|
+
requirement.count,
|
|
2092
|
+
)
|
|
2093
|
+
explicit = _explicit_selection_count(selection, requirement.role)
|
|
2094
|
+
if explicit < desired:
|
|
2095
|
+
roles.append(requirement.role)
|
|
2096
|
+
return tuple(roles)
|
|
2097
|
+
|
|
2098
|
+
|
|
2099
|
+
def _explicit_selection_count(
|
|
2100
|
+
selection: CanonicalModelSelection,
|
|
2101
|
+
role: str,
|
|
2102
|
+
) -> int:
|
|
2103
|
+
return max(
|
|
2104
|
+
len(selection.role_models.get(role, ())),
|
|
2105
|
+
len(selection.provider_constraints.get(role, ())),
|
|
2106
|
+
)
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
def _canonical_selection_provider_ids(
|
|
2110
|
+
inp: PrepareInputs,
|
|
2111
|
+
profile: RoleProfile,
|
|
2112
|
+
selection: CanonicalModelSelection,
|
|
2113
|
+
workers: list[str],
|
|
2114
|
+
scopes: ModelDefaultScopes,
|
|
2115
|
+
default_roles: Sequence[str],
|
|
2116
|
+
requires_executor: bool,
|
|
2117
|
+
) -> list[str]:
|
|
2118
|
+
selected = [inp.lead_provider]
|
|
2119
|
+
report_writer_uses_bundled_default = (
|
|
2120
|
+
"report-writer" in default_roles
|
|
2121
|
+
and "report-writer" not in scopes.project
|
|
2122
|
+
and "report-writer" not in scopes.global_
|
|
2123
|
+
)
|
|
2124
|
+
if report_writer_uses_bundled_default:
|
|
2125
|
+
selected.append("claude")
|
|
2126
|
+
implementer_uses_bundled_default = (
|
|
2127
|
+
requires_executor
|
|
2128
|
+
and "implementer" in default_roles
|
|
2129
|
+
and "implementer" not in scopes.project
|
|
2130
|
+
and "implementer" not in scopes.global_
|
|
2131
|
+
)
|
|
2132
|
+
if implementer_uses_bundled_default:
|
|
2133
|
+
selected.append(
|
|
2134
|
+
_default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
2135
|
+
)
|
|
2136
|
+
if _needs_profile_worker_candidates(profile, selection, scopes):
|
|
2137
|
+
selected.extend(
|
|
2138
|
+
worker for worker in workers if worker != "report-writer"
|
|
2139
|
+
)
|
|
2140
|
+
return selected
|
|
2141
|
+
|
|
2142
|
+
|
|
2143
|
+
def _needs_profile_worker_candidates(
|
|
2144
|
+
profile: RoleProfile,
|
|
2145
|
+
selection: CanonicalModelSelection,
|
|
2146
|
+
scopes: ModelDefaultScopes,
|
|
2147
|
+
) -> bool:
|
|
2148
|
+
for requirement in profile.roles:
|
|
2149
|
+
if requirement.dynamic or requirement.role == "report-writer":
|
|
2150
|
+
continue
|
|
2151
|
+
desired = selection.role_counts.get(
|
|
2152
|
+
requirement.role,
|
|
2153
|
+
requirement.count,
|
|
2154
|
+
)
|
|
2155
|
+
explicit = _explicit_selection_count(selection, requirement.role)
|
|
2156
|
+
has_scoped_default = (
|
|
2157
|
+
requirement.role in scopes.project
|
|
2158
|
+
or requirement.role in scopes.global_
|
|
2159
|
+
)
|
|
2160
|
+
if explicit < desired and not has_scoped_default:
|
|
2161
|
+
return True
|
|
2162
|
+
return False
|
|
2163
|
+
|
|
2164
|
+
|
|
2165
|
+
def _providers_from_model_refs(
|
|
2166
|
+
model_groups: Sequence[Sequence[str]],
|
|
2167
|
+
) -> tuple[str, ...]:
|
|
2168
|
+
return tuple(
|
|
2169
|
+
model_ref.split("/", 1)[0]
|
|
2170
|
+
for models in model_groups
|
|
2171
|
+
for model_ref in models
|
|
2172
|
+
if "/" in model_ref
|
|
2173
|
+
)
|
|
2174
|
+
|
|
2175
|
+
|
|
2176
|
+
def _materialize_selection_models(
|
|
2177
|
+
selection: CanonicalModelSelection,
|
|
2178
|
+
context: AssignmentContext,
|
|
2179
|
+
host: HostSessionContext,
|
|
2180
|
+
) -> dict[str, tuple[str, ...]]:
|
|
2181
|
+
role_models = dict(selection.role_models)
|
|
2182
|
+
for role, providers in selection.provider_constraints.items():
|
|
2183
|
+
model_values = selection.legacy_model_values.get(role, {})
|
|
2184
|
+
resolved: list[str] = []
|
|
2185
|
+
for raw_provider in providers:
|
|
2186
|
+
provider = raw_provider or _fallback_legacy_provider(role, context, host)
|
|
2187
|
+
raw_model = model_values.get(provider, model_values.get("", ""))
|
|
2188
|
+
try:
|
|
2189
|
+
model = resolve_model_selection(
|
|
2190
|
+
context.pool,
|
|
2191
|
+
provider,
|
|
2192
|
+
role,
|
|
2193
|
+
raw_model,
|
|
2194
|
+
)
|
|
2195
|
+
except (ValueError, RuntimeError) as exc:
|
|
2196
|
+
raise PrepareError(str(exc)) from exc
|
|
2197
|
+
resolved.append(str(model.model_ref))
|
|
2198
|
+
role_models[role] = tuple(resolved)
|
|
2199
|
+
return role_models
|
|
2200
|
+
|
|
2201
|
+
|
|
2202
|
+
def _fallback_legacy_provider(
|
|
2203
|
+
role: str,
|
|
2204
|
+
context: AssignmentContext,
|
|
2205
|
+
host: HostSessionContext,
|
|
2206
|
+
) -> str:
|
|
2207
|
+
if role == "leader":
|
|
2208
|
+
return (
|
|
2209
|
+
host.current_model.provider_id
|
|
2210
|
+
or context.environment.host_descriptor.native_provider_id
|
|
2211
|
+
)
|
|
2212
|
+
return "claude"
|
|
2213
|
+
|
|
2214
|
+
|
|
2215
|
+
def _resolver_role_counts(
|
|
2216
|
+
profile: RoleProfile,
|
|
2217
|
+
counts: Mapping[str, int],
|
|
2218
|
+
) -> dict[str, int]:
|
|
2219
|
+
adjustable = {
|
|
2220
|
+
requirement.role
|
|
2221
|
+
for requirement in profile.roles
|
|
2222
|
+
if not requirement.dynamic and requirement.optional_count > 0
|
|
2223
|
+
}
|
|
2224
|
+
return {role: count for role, count in counts.items() if role in adjustable}
|
|
2225
|
+
|
|
2226
|
+
|
|
2227
|
+
def _model_default_scopes(project_root: Path) -> ModelDefaultScopes:
|
|
2228
|
+
return ModelDefaultScopes(
|
|
2229
|
+
project=_read_model_defaults(project_json_path(project_root)),
|
|
2230
|
+
global_=_read_model_defaults(okstra_home() / "config.json"),
|
|
2231
|
+
bundled={},
|
|
2232
|
+
)
|
|
2233
|
+
|
|
2234
|
+
|
|
2235
|
+
def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
|
|
2236
|
+
if not path.is_file():
|
|
2237
|
+
return {}
|
|
2238
|
+
try:
|
|
2239
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
2240
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
2241
|
+
raise PrepareError(f"model defaults file is invalid: {path}: {exc}") from exc
|
|
2242
|
+
raw_defaults = payload.get("modelDefaults", {}) if isinstance(payload, dict) else {}
|
|
2243
|
+
if not isinstance(raw_defaults, dict):
|
|
2244
|
+
raise PrepareError(f"modelDefaults must be an object: {path}")
|
|
2245
|
+
defaults: dict[str, tuple[str, ...]] = {}
|
|
2246
|
+
for role, models in raw_defaults.items():
|
|
2247
|
+
if (
|
|
2248
|
+
not isinstance(role, str)
|
|
2249
|
+
or not isinstance(models, list)
|
|
2250
|
+
or not models
|
|
2251
|
+
or not all(isinstance(model, str) and model for model in models)
|
|
2252
|
+
):
|
|
2253
|
+
raise PrepareError(f"modelDefaults entries must be non-empty string arrays: {path}")
|
|
2254
|
+
defaults[role] = tuple(models)
|
|
2255
|
+
return defaults
|
|
2256
|
+
|
|
2257
|
+
|
|
1695
2258
|
def _validate_option_selection_roster(
|
|
1696
2259
|
task_type: str, workers: Sequence[str]
|
|
1697
2260
|
) -> None:
|
|
@@ -1756,23 +2319,43 @@ class _ModelBindings:
|
|
|
1756
2319
|
해소한다 — prepare 가 run seq 를 할당하거나 worktree 를 만들기 전에.
|
|
1757
2320
|
"""
|
|
1758
2321
|
|
|
1759
|
-
lead:
|
|
1760
|
-
cw:
|
|
1761
|
-
co:
|
|
1762
|
-
ge:
|
|
1763
|
-
rw:
|
|
2322
|
+
lead: ResolvedAssignment
|
|
2323
|
+
cw: "_LegacyModelProjection"
|
|
2324
|
+
co: "_LegacyModelProjection"
|
|
2325
|
+
ge: "_LegacyModelProjection"
|
|
2326
|
+
rw: ResolvedAssignment
|
|
1764
2327
|
critic_choice: str
|
|
1765
2328
|
critic_model_execution: str
|
|
1766
2329
|
executor_display_name: str
|
|
1767
2330
|
codex_worker_execution: str
|
|
1768
2331
|
antigravity_worker_execution: str
|
|
1769
|
-
executor_assignment: RoleAssignment
|
|
2332
|
+
executor_assignment: RoleAssignment | None
|
|
1770
2333
|
lead_assignment: RoleAssignment
|
|
1771
2334
|
worker_assignments: tuple[RoleAssignment, ...]
|
|
1772
2335
|
invocation_assignments: dict[str, dict[str, object]]
|
|
1773
2336
|
|
|
1774
2337
|
|
|
1775
|
-
|
|
2338
|
+
@dataclass(frozen=True)
|
|
2339
|
+
class _LegacyModelProjection:
|
|
2340
|
+
"""Display/catalog values retained for the fixed v1 manifest fields."""
|
|
2341
|
+
|
|
2342
|
+
model_id: str
|
|
2343
|
+
display: str
|
|
2344
|
+
execution: str
|
|
2345
|
+
|
|
2346
|
+
|
|
2347
|
+
def _legacy_claude_model_override(provider: str, env_name: str) -> str:
|
|
2348
|
+
"""Read a legacy Claude model override only for a Claude assignment."""
|
|
2349
|
+
if provider.strip().lower() != "claude":
|
|
2350
|
+
return ""
|
|
2351
|
+
return _default(env_name, "")
|
|
2352
|
+
|
|
2353
|
+
|
|
2354
|
+
def recommended_role_models(
|
|
2355
|
+
*,
|
|
2356
|
+
lead_provider: str = "",
|
|
2357
|
+
report_writer_provider: str = "",
|
|
2358
|
+
) -> dict[str, str]:
|
|
1776
2359
|
"""역할 → 추천 모델 display 값 (env override 반영). prepare 의 모델 해소와
|
|
1777
2360
|
wizard 의 안내 표기가 공유하는 단일 기준점.
|
|
1778
2361
|
|
|
@@ -1782,18 +2365,28 @@ def recommended_role_models(*, lead_provider: str = "") -> dict[str, str]:
|
|
|
1782
2365
|
role 별 레거시 기본값(claude 계열)으로 떨어지는데, 그 값을 codex 호스트
|
|
1783
2366
|
화면에 그대로 쓰면 안내는 `opus` 인데 prepare 는 `gpt-5.6-sol` 을 배정한다.
|
|
1784
2367
|
"""
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2368
|
+
lead_provider_id = lead_provider or "claude"
|
|
2369
|
+
report_writer_provider_id = report_writer_provider or "claude"
|
|
2370
|
+
lead_default = (
|
|
2371
|
+
_legacy_claude_model_override(
|
|
2372
|
+
lead_provider_id,
|
|
2373
|
+
"OKSTRA_DEFAULT_LEAD_MODEL",
|
|
2374
|
+
)
|
|
2375
|
+
or provider_default_model(lead_provider_id, "lead")
|
|
2376
|
+
)
|
|
2377
|
+
report_writer_default = (
|
|
2378
|
+
_legacy_claude_model_override(
|
|
2379
|
+
report_writer_provider_id,
|
|
2380
|
+
"OKSTRA_DEFAULT_REPORT_WRITER_MODEL",
|
|
2381
|
+
)
|
|
2382
|
+
or provider_default_model(report_writer_provider_id, "report-writer")
|
|
1790
2383
|
)
|
|
1791
2384
|
recommendations = {
|
|
1792
2385
|
"lead": lead_default,
|
|
1793
2386
|
"claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", default_model("claude")),
|
|
1794
2387
|
"codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", default_model("codex")),
|
|
1795
2388
|
"antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", default_model("antigravity")),
|
|
1796
|
-
"report-writer":
|
|
2389
|
+
"report-writer": report_writer_default,
|
|
1797
2390
|
}
|
|
1798
2391
|
for provider in provider_ids("analyser"):
|
|
1799
2392
|
recommendations.setdefault(
|
|
@@ -1802,7 +2395,10 @@ def recommended_role_models(*, lead_provider: str = "") -> dict[str, str]:
|
|
|
1802
2395
|
return recommendations
|
|
1803
2396
|
|
|
1804
2397
|
|
|
1805
|
-
def _parse_worker_model_overrides(
|
|
2398
|
+
def _parse_worker_model_overrides(
|
|
2399
|
+
raw_value: str,
|
|
2400
|
+
context: AssignmentContext,
|
|
2401
|
+
) -> dict[str, str]:
|
|
1806
2402
|
overrides: dict[str, str] = {}
|
|
1807
2403
|
for item in (raw_value or "").split(","):
|
|
1808
2404
|
item = item.strip()
|
|
@@ -1817,65 +2413,71 @@ def _parse_worker_model_overrides(raw_value: str) -> dict[str, str]:
|
|
|
1817
2413
|
)
|
|
1818
2414
|
if provider in overrides:
|
|
1819
2415
|
raise PrepareError(f"duplicate --worker-model provider: {provider}")
|
|
1820
|
-
|
|
2416
|
+
context.pool.resolve_provider(provider)
|
|
1821
2417
|
overrides[provider] = model
|
|
1822
2418
|
return overrides
|
|
1823
2419
|
|
|
1824
2420
|
|
|
1825
2421
|
def _model_for_role(
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
default_display=default_value,
|
|
1835
|
-
default_execution=default_value,
|
|
1836
|
-
)
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
def _assignment_runner(lead_runtime: str, provider: str, role: str) -> str:
|
|
1840
|
-
host_registry = default_host_registry()
|
|
1841
|
-
provider_registry = default_provider_registry()
|
|
2422
|
+
*,
|
|
2423
|
+
context: AssignmentContext,
|
|
2424
|
+
provider: str,
|
|
2425
|
+
raw_value: str,
|
|
2426
|
+
role: str,
|
|
2427
|
+
default_override: str = "",
|
|
2428
|
+
duty_id: str = "",
|
|
2429
|
+
) -> ResolvedAssignment:
|
|
1842
2430
|
try:
|
|
1843
|
-
return
|
|
1844
|
-
|
|
1845
|
-
|
|
2431
|
+
return resolve_dispatch_assignment(
|
|
2432
|
+
context=context,
|
|
2433
|
+
host_runtime=context.environment.host_descriptor.id,
|
|
1846
2434
|
role=role,
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
UnknownProviderError,
|
|
1853
|
-
) as exc:
|
|
2435
|
+
duty_id=duty_id or role,
|
|
2436
|
+
provider=provider,
|
|
2437
|
+
model=raw_value or default_override,
|
|
2438
|
+
)
|
|
2439
|
+
except (ValueError, RuntimeError) as exc:
|
|
1854
2440
|
raise PrepareError(str(exc)) from exc
|
|
1855
2441
|
|
|
1856
2442
|
|
|
1857
|
-
def _resolve_worker_models(
|
|
2443
|
+
def _resolve_worker_models(
|
|
2444
|
+
inp: PrepareInputs,
|
|
2445
|
+
workers: list[str],
|
|
2446
|
+
context: AssignmentContext,
|
|
2447
|
+
) -> dict:
|
|
1858
2448
|
"""Resolve provider models while preserving legacy fixed-provider projections."""
|
|
1859
|
-
|
|
1860
|
-
overrides = _parse_worker_model_overrides(inp.worker_models_raw)
|
|
2449
|
+
overrides = _parse_worker_model_overrides(inp.worker_models_raw, context)
|
|
1861
2450
|
legacy = {
|
|
1862
2451
|
"claude": inp.claude_model,
|
|
1863
2452
|
"codex": inp.codex_model,
|
|
1864
2453
|
"antigravity": inp.antigravity_model,
|
|
1865
2454
|
}
|
|
2455
|
+
legacy_default_env = {
|
|
2456
|
+
"claude": "OKSTRA_DEFAULT_CLAUDE_MODEL",
|
|
2457
|
+
"codex": "OKSTRA_DEFAULT_CODEX_MODEL",
|
|
2458
|
+
"antigravity": "OKSTRA_DEFAULT_ANTIGRAVITY_MODEL",
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
def worker_model_input(provider: str) -> str:
|
|
2462
|
+
selected = overrides.get(provider, legacy.get(provider, ""))
|
|
2463
|
+
env_name = legacy_default_env.get(provider)
|
|
2464
|
+
return selected or (_default(env_name, "") if env_name else "")
|
|
2465
|
+
|
|
2466
|
+
selected_worker_providers = tuple(dict.fromkeys(
|
|
2467
|
+
worker for worker in workers if worker != "report-writer"
|
|
2468
|
+
))
|
|
1866
2469
|
provider_models = {}
|
|
1867
|
-
for provider in
|
|
1868
|
-
default_override = rec.get(provider, "")
|
|
2470
|
+
for provider in selected_worker_providers:
|
|
1869
2471
|
provider_models[provider] = _model_for_role(
|
|
1870
|
-
|
|
1871
|
-
|
|
2472
|
+
context=context,
|
|
2473
|
+
provider=provider,
|
|
2474
|
+
raw_value=worker_model_input(provider),
|
|
2475
|
+
role="analyser",
|
|
1872
2476
|
)
|
|
1873
2477
|
try:
|
|
1874
|
-
lead_assignment =
|
|
1875
|
-
|
|
2478
|
+
lead_assignment = resolve_context_lead_provider(
|
|
2479
|
+
context=context,
|
|
1876
2480
|
requested_provider=inp.lead_provider,
|
|
1877
|
-
host_registry=default_host_registry(),
|
|
1878
|
-
provider_registry=default_provider_registry(),
|
|
1879
2481
|
)
|
|
1880
2482
|
except (
|
|
1881
2483
|
HostCapabilityMismatch,
|
|
@@ -1886,119 +2488,127 @@ def _resolve_worker_models(inp: PrepareInputs) -> dict:
|
|
|
1886
2488
|
raise PrepareError(str(exc)) from exc
|
|
1887
2489
|
lead_provider = lead_assignment.provider
|
|
1888
2490
|
report_writer_provider = inp.report_writer_provider.strip().lower() or "claude"
|
|
1889
|
-
if not
|
|
2491
|
+
if not context.pool.resolve_provider(report_writer_provider).supports_role(
|
|
2492
|
+
"report-writer"
|
|
2493
|
+
):
|
|
1890
2494
|
raise PrepareError(
|
|
1891
2495
|
f"provider {report_writer_provider!r} does not support the report-writer role"
|
|
1892
2496
|
)
|
|
2497
|
+
|
|
2498
|
+
def legacy_projection(provider: str) -> _LegacyModelProjection:
|
|
2499
|
+
assigned = provider_models.get(provider)
|
|
2500
|
+
if assigned is not None:
|
|
2501
|
+
return _LegacyModelProjection(
|
|
2502
|
+
model_id=assigned.model_id,
|
|
2503
|
+
display=assigned.display,
|
|
2504
|
+
execution=assigned.execution,
|
|
2505
|
+
)
|
|
2506
|
+
try:
|
|
2507
|
+
selected = resolve_model_selection(
|
|
2508
|
+
context.pool,
|
|
2509
|
+
provider,
|
|
2510
|
+
"analyser",
|
|
2511
|
+
worker_model_input(provider),
|
|
2512
|
+
)
|
|
2513
|
+
except (ValueError, RuntimeError) as exc:
|
|
2514
|
+
raise PrepareError(str(exc)) from exc
|
|
2515
|
+
return _LegacyModelProjection(
|
|
2516
|
+
model_id=selected.model_ref.model_id,
|
|
2517
|
+
display=selected.display_name,
|
|
2518
|
+
execution=selected.execution_value,
|
|
2519
|
+
)
|
|
2520
|
+
|
|
1893
2521
|
return {
|
|
1894
2522
|
"lead_provider": lead_provider,
|
|
1895
2523
|
"report_writer_provider": report_writer_provider,
|
|
1896
2524
|
"lead": _model_for_role(
|
|
1897
|
-
|
|
1898
|
-
|
|
2525
|
+
context=context,
|
|
2526
|
+
provider=lead_provider,
|
|
2527
|
+
raw_value=inp.lead_model,
|
|
2528
|
+
role="leader",
|
|
2529
|
+
default_override=_legacy_claude_model_override(
|
|
2530
|
+
lead_provider,
|
|
2531
|
+
"OKSTRA_DEFAULT_LEAD_MODEL",
|
|
2532
|
+
),
|
|
2533
|
+
duty_id="lead",
|
|
1899
2534
|
),
|
|
1900
|
-
"cw":
|
|
1901
|
-
"co":
|
|
1902
|
-
"ge":
|
|
2535
|
+
"cw": legacy_projection("claude"),
|
|
2536
|
+
"co": legacy_projection("codex"),
|
|
2537
|
+
"ge": legacy_projection("antigravity"),
|
|
1903
2538
|
"rw": _model_for_role(
|
|
1904
|
-
|
|
1905
|
-
|
|
2539
|
+
context=context,
|
|
2540
|
+
provider=report_writer_provider,
|
|
2541
|
+
raw_value=inp.report_writer_model,
|
|
2542
|
+
role="report-writer",
|
|
2543
|
+
default_override=_legacy_claude_model_override(
|
|
2544
|
+
report_writer_provider,
|
|
2545
|
+
"OKSTRA_DEFAULT_REPORT_WRITER_MODEL",
|
|
2546
|
+
),
|
|
1906
2547
|
),
|
|
1907
2548
|
"providers": provider_models,
|
|
1908
2549
|
"worker_model_inputs": {
|
|
1909
|
-
provider:
|
|
1910
|
-
for provider in
|
|
2550
|
+
provider: worker_model_input(provider)
|
|
2551
|
+
for provider in dict.fromkeys((
|
|
2552
|
+
*selected_worker_providers,
|
|
2553
|
+
*overrides,
|
|
2554
|
+
*legacy,
|
|
2555
|
+
))
|
|
1911
2556
|
},
|
|
1912
2557
|
}
|
|
1913
2558
|
|
|
1914
2559
|
|
|
1915
|
-
def _resolve_model_bindings(
|
|
2560
|
+
def _resolve_model_bindings(
|
|
2561
|
+
inp: PrepareInputs,
|
|
2562
|
+
workers: list[str],
|
|
2563
|
+
context: AssignmentContext,
|
|
2564
|
+
requires_executor: bool,
|
|
2565
|
+
) -> _ModelBindings:
|
|
1916
2566
|
"""worker 모델 + critic 선택 + executor 바인딩을 한 묶음으로 해소·검증한다."""
|
|
1917
|
-
m = _resolve_worker_models(inp)
|
|
1918
|
-
critic_choice = (inp.critic
|
|
1919
|
-
allowed_critics = ["", "off", *provider_ids("critic")]
|
|
1920
|
-
if critic_choice not in allowed_critics:
|
|
1921
|
-
raise PrepareError(
|
|
1922
|
-
f"--critic must be one of: {', '.join(allowed_critics[1:])} "
|
|
1923
|
-
f"(got: {critic_choice!r})"
|
|
1924
|
-
)
|
|
2567
|
+
m = _resolve_worker_models(inp, workers, context)
|
|
2568
|
+
critic_choice = _validate_critic_choice(inp.critic)
|
|
1925
2569
|
critic_model_execution = ""
|
|
1926
2570
|
critic_meta = None
|
|
1927
2571
|
if critic_choice in provider_ids("critic"):
|
|
1928
2572
|
critic_meta = _model_for_role(
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
"
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
critic_meta, provider=critic_choice, role="critic",
|
|
1935
|
-
)
|
|
1936
|
-
executor_default = _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
1937
|
-
executor_provider = (inp.executor or executor_default).strip().lower()
|
|
1938
|
-
allowed_executors = provider_ids("executor")
|
|
1939
|
-
if executor_provider not in allowed_executors:
|
|
1940
|
-
raise PrepareError(
|
|
1941
|
-
f"--executor must be one of: {', '.join(allowed_executors)} "
|
|
1942
|
-
f"(got: {executor_provider!r})"
|
|
1943
|
-
)
|
|
1944
|
-
if inp.task_type == "implementation" and executor_provider not in workers:
|
|
1945
|
-
raise PrepareError(
|
|
1946
|
-
f"--executor {executor_provider} requires {executor_provider!r} in "
|
|
1947
|
-
f"--workers, but resolved roster is {workers!r}. "
|
|
1948
|
-
f"Add it explicitly, e.g. --workers {','.join(sorted(set(workers + [executor_provider])))}."
|
|
2573
|
+
context=context,
|
|
2574
|
+
provider=critic_choice,
|
|
2575
|
+
raw_value=m["worker_model_inputs"].get(critic_choice, ""),
|
|
2576
|
+
role="critic",
|
|
2577
|
+
duty_id="scope-critic",
|
|
1949
2578
|
)
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2579
|
+
critic_model_execution = critic_meta.execution
|
|
2580
|
+
executor_assignment = None
|
|
2581
|
+
display_name = ""
|
|
2582
|
+
if requires_executor:
|
|
2583
|
+
executor_assignment, display_name = _resolve_executor_assignment(
|
|
2584
|
+
inp,
|
|
2585
|
+
workers,
|
|
2586
|
+
context,
|
|
2587
|
+
m["worker_model_inputs"],
|
|
1958
2588
|
)
|
|
1959
2589
|
lead_assignment = _role_assignment(
|
|
1960
|
-
|
|
2590
|
+
resolved=m["lead"],
|
|
1961
2591
|
role="lead",
|
|
1962
|
-
provider=lead_provider,
|
|
1963
|
-
model=m["lead"],
|
|
1964
|
-
execution=lead_execution,
|
|
1965
|
-
runner_role="lead",
|
|
1966
2592
|
)
|
|
1967
|
-
worker_assignments = _build_worker_assignments(
|
|
2593
|
+
worker_assignments = _build_worker_assignments(workers, m)
|
|
1968
2594
|
critic_assignment = (
|
|
1969
2595
|
_role_assignment(
|
|
1970
|
-
|
|
2596
|
+
resolved=critic_meta,
|
|
1971
2597
|
role="critic",
|
|
1972
|
-
provider=critic_choice,
|
|
1973
|
-
model=critic_meta,
|
|
1974
|
-
execution=critic_model_execution,
|
|
1975
|
-
runner_role="critic",
|
|
1976
2598
|
)
|
|
1977
2599
|
if critic_meta is not None
|
|
1978
2600
|
else None
|
|
1979
2601
|
)
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
role="translator",
|
|
2602
|
+
translator_meta = _model_for_role(
|
|
2603
|
+
context=context,
|
|
1983
2604
|
provider=m["report_writer_provider"],
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
provider=m["report_writer_provider"],
|
|
1988
|
-
role="report-writer",
|
|
1989
|
-
),
|
|
1990
|
-
runner_role="report-writer",
|
|
2605
|
+
raw_value=m["rw"].model_id,
|
|
2606
|
+
role="translator",
|
|
2607
|
+
duty_id="translator",
|
|
1991
2608
|
)
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
role="
|
|
1995
|
-
provider=executor_provider,
|
|
1996
|
-
model=model_meta,
|
|
1997
|
-
execution=_normalized_execution(
|
|
1998
|
-
model_meta, provider=executor_provider, role="executor"
|
|
1999
|
-
),
|
|
2000
|
-
runner_role="executor",
|
|
2001
|
-
worker_id=executor_provider,
|
|
2609
|
+
translator_assignment = _role_assignment(
|
|
2610
|
+
resolved=translator_meta,
|
|
2611
|
+
role="translator",
|
|
2002
2612
|
)
|
|
2003
2613
|
invocation_assignments = _build_invocation_assignments(
|
|
2004
2614
|
lead_assignment,
|
|
@@ -2006,15 +2616,24 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
2006
2616
|
critic_assignment,
|
|
2007
2617
|
translator_assignment,
|
|
2008
2618
|
)
|
|
2619
|
+
worker_by_provider = {
|
|
2620
|
+
row.provider: row for row in worker_assignments
|
|
2621
|
+
}
|
|
2009
2622
|
return _ModelBindings(
|
|
2010
2623
|
lead=m["lead"], cw=m["cw"], co=m["co"], ge=m["ge"], rw=m["rw"],
|
|
2011
2624
|
critic_choice=critic_choice,
|
|
2012
2625
|
critic_model_execution=critic_model_execution,
|
|
2013
2626
|
executor_display_name=display_name,
|
|
2014
|
-
codex_worker_execution=
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
m["
|
|
2627
|
+
codex_worker_execution=(
|
|
2628
|
+
worker_by_provider["codex"].model_execution_value
|
|
2629
|
+
if "codex" in worker_by_provider
|
|
2630
|
+
else m["co"].execution
|
|
2631
|
+
),
|
|
2632
|
+
antigravity_worker_execution=(
|
|
2633
|
+
worker_by_provider["antigravity"].model_execution_value
|
|
2634
|
+
if "antigravity" in worker_by_provider
|
|
2635
|
+
else m["ge"].execution
|
|
2636
|
+
),
|
|
2018
2637
|
executor_assignment=executor_assignment,
|
|
2019
2638
|
lead_assignment=lead_assignment,
|
|
2020
2639
|
worker_assignments=worker_assignments,
|
|
@@ -2022,8 +2641,245 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
2022
2641
|
)
|
|
2023
2642
|
|
|
2024
2643
|
|
|
2644
|
+
def _validate_critic_choice(raw_value: str) -> str:
|
|
2645
|
+
critic_choice = (raw_value or "").strip().lower()
|
|
2646
|
+
allowed_critics = ["", "off", *provider_ids("critic")]
|
|
2647
|
+
if critic_choice not in allowed_critics:
|
|
2648
|
+
raise PrepareError(
|
|
2649
|
+
f"--critic must be one of: {', '.join(allowed_critics[1:])} "
|
|
2650
|
+
f"(got: {critic_choice!r})"
|
|
2651
|
+
)
|
|
2652
|
+
return critic_choice
|
|
2653
|
+
|
|
2654
|
+
|
|
2655
|
+
def _uses_canonical_assignment_projection(inp: PrepareInputs) -> bool:
|
|
2656
|
+
return bool(
|
|
2657
|
+
inp.role_counts_raw
|
|
2658
|
+
or inp.role_models_raw
|
|
2659
|
+
or inp.host_session_context.entry_mode == "current-session"
|
|
2660
|
+
)
|
|
2661
|
+
|
|
2662
|
+
|
|
2663
|
+
def _project_assignment_plan(
|
|
2664
|
+
inp: PrepareInputs,
|
|
2665
|
+
plan: AssignmentPlan,
|
|
2666
|
+
context: AssignmentContext,
|
|
2667
|
+
) -> tuple[list[str], _ModelBindings]:
|
|
2668
|
+
lead = plan.by_role("leader")[0]
|
|
2669
|
+
report_rows = plan.by_role("report-writer")
|
|
2670
|
+
report_writer = (
|
|
2671
|
+
report_rows[0]
|
|
2672
|
+
if report_rows
|
|
2673
|
+
else _model_for_role(
|
|
2674
|
+
context=context,
|
|
2675
|
+
provider=inp.report_writer_provider.strip().lower() or "claude",
|
|
2676
|
+
raw_value=inp.report_writer_model,
|
|
2677
|
+
role="report-writer",
|
|
2678
|
+
)
|
|
2679
|
+
)
|
|
2680
|
+
worker_rows = tuple(
|
|
2681
|
+
row for row in plan.assignments
|
|
2682
|
+
if row.role not in {"leader", "critic", "implementer", "report-writer"}
|
|
2683
|
+
)
|
|
2684
|
+
worker_assignments = tuple(
|
|
2685
|
+
_role_assignment(
|
|
2686
|
+
resolved=row,
|
|
2687
|
+
role=row.role,
|
|
2688
|
+
worker_id=_canonical_worker_id(row, worker_rows),
|
|
2689
|
+
)
|
|
2690
|
+
for row in worker_rows
|
|
2691
|
+
)
|
|
2692
|
+
if report_rows:
|
|
2693
|
+
worker_assignments = (
|
|
2694
|
+
*worker_assignments,
|
|
2695
|
+
_role_assignment(
|
|
2696
|
+
resolved=report_writer,
|
|
2697
|
+
role="report-writer",
|
|
2698
|
+
worker_id="report-writer",
|
|
2699
|
+
),
|
|
2700
|
+
)
|
|
2701
|
+
models = _project_model_bindings(
|
|
2702
|
+
inp,
|
|
2703
|
+
plan,
|
|
2704
|
+
context,
|
|
2705
|
+
lead,
|
|
2706
|
+
report_writer,
|
|
2707
|
+
worker_assignments,
|
|
2708
|
+
)
|
|
2709
|
+
return [row.worker_id for row in worker_assignments], models
|
|
2710
|
+
|
|
2711
|
+
|
|
2712
|
+
def _canonical_worker_id(
|
|
2713
|
+
assignment: ResolvedAssignment,
|
|
2714
|
+
rows: Sequence[ResolvedAssignment],
|
|
2715
|
+
) -> str:
|
|
2716
|
+
prior = sum(
|
|
2717
|
+
row.provider_id == assignment.provider_id and row.ordinal < assignment.ordinal
|
|
2718
|
+
for row in rows
|
|
2719
|
+
)
|
|
2720
|
+
return assignment.provider_id if prior == 0 else f"{assignment.provider_id}-{prior + 1}"
|
|
2721
|
+
|
|
2722
|
+
|
|
2723
|
+
def _project_model_bindings(
|
|
2724
|
+
inp: PrepareInputs,
|
|
2725
|
+
plan: AssignmentPlan,
|
|
2726
|
+
context: AssignmentContext,
|
|
2727
|
+
lead: ResolvedAssignment,
|
|
2728
|
+
report_writer: ResolvedAssignment,
|
|
2729
|
+
workers: tuple[RoleAssignment, ...],
|
|
2730
|
+
) -> _ModelBindings:
|
|
2731
|
+
lead_assignment = _role_assignment(resolved=lead, role="lead")
|
|
2732
|
+
critic_rows = plan.by_role("critic")
|
|
2733
|
+
critic_assignment = (
|
|
2734
|
+
_role_assignment(resolved=critic_rows[0], role="critic")
|
|
2735
|
+
if critic_rows
|
|
2736
|
+
else None
|
|
2737
|
+
)
|
|
2738
|
+
executor_rows = plan.by_role("implementer")
|
|
2739
|
+
executor_assignment = (
|
|
2740
|
+
_role_assignment(
|
|
2741
|
+
resolved=executor_rows[0],
|
|
2742
|
+
role="executor",
|
|
2743
|
+
worker_id=executor_rows[0].provider_id,
|
|
2744
|
+
)
|
|
2745
|
+
if executor_rows
|
|
2746
|
+
else None
|
|
2747
|
+
)
|
|
2748
|
+
translator = _model_for_role(
|
|
2749
|
+
context=context,
|
|
2750
|
+
provider=report_writer.provider_id,
|
|
2751
|
+
raw_value=report_writer.model_id,
|
|
2752
|
+
role="translator",
|
|
2753
|
+
duty_id="translator",
|
|
2754
|
+
)
|
|
2755
|
+
projections = {
|
|
2756
|
+
provider: _legacy_projection_from_plan(provider, plan, context)
|
|
2757
|
+
for provider in ("claude", "codex", "antigravity")
|
|
2758
|
+
}
|
|
2759
|
+
invocation_assignments = _build_invocation_assignments(
|
|
2760
|
+
lead_assignment,
|
|
2761
|
+
workers,
|
|
2762
|
+
critic_assignment,
|
|
2763
|
+
_role_assignment(resolved=translator, role="translator"),
|
|
2764
|
+
)
|
|
2765
|
+
worker_by_provider = {row.provider: row for row in workers}
|
|
2766
|
+
return _ModelBindings(
|
|
2767
|
+
lead=lead,
|
|
2768
|
+
cw=projections["claude"],
|
|
2769
|
+
co=projections["codex"],
|
|
2770
|
+
ge=projections["antigravity"],
|
|
2771
|
+
rw=report_writer,
|
|
2772
|
+
critic_choice=(critic_rows[0].provider_id if critic_rows else "off"),
|
|
2773
|
+
critic_model_execution=(
|
|
2774
|
+
critic_assignment.model_execution_value if critic_assignment else ""
|
|
2775
|
+
),
|
|
2776
|
+
executor_display_name=(
|
|
2777
|
+
f"{executor_rows[0].display_name} executor" if executor_rows else ""
|
|
2778
|
+
),
|
|
2779
|
+
codex_worker_execution=(
|
|
2780
|
+
worker_by_provider["codex"].model_execution_value
|
|
2781
|
+
if "codex" in worker_by_provider
|
|
2782
|
+
else projections["codex"].execution
|
|
2783
|
+
),
|
|
2784
|
+
antigravity_worker_execution=(
|
|
2785
|
+
worker_by_provider["antigravity"].model_execution_value
|
|
2786
|
+
if "antigravity" in worker_by_provider
|
|
2787
|
+
else projections["antigravity"].execution
|
|
2788
|
+
),
|
|
2789
|
+
executor_assignment=executor_assignment,
|
|
2790
|
+
lead_assignment=lead_assignment,
|
|
2791
|
+
worker_assignments=workers,
|
|
2792
|
+
invocation_assignments=invocation_assignments,
|
|
2793
|
+
)
|
|
2794
|
+
|
|
2795
|
+
|
|
2796
|
+
def _legacy_projection_from_plan(
|
|
2797
|
+
provider: str,
|
|
2798
|
+
plan: AssignmentPlan,
|
|
2799
|
+
context: AssignmentContext,
|
|
2800
|
+
) -> _LegacyModelProjection:
|
|
2801
|
+
selected = next(
|
|
2802
|
+
(
|
|
2803
|
+
row for row in plan.assignments
|
|
2804
|
+
if row.provider_id == provider and row.role != "leader"
|
|
2805
|
+
),
|
|
2806
|
+
None,
|
|
2807
|
+
)
|
|
2808
|
+
if selected is not None:
|
|
2809
|
+
return _LegacyModelProjection(
|
|
2810
|
+
selected.model_id,
|
|
2811
|
+
selected.display_name,
|
|
2812
|
+
selected.execution,
|
|
2813
|
+
)
|
|
2814
|
+
model = resolve_model_selection(context.pool, provider, "analyser", "")
|
|
2815
|
+
return _LegacyModelProjection(
|
|
2816
|
+
model.model_ref.model_id,
|
|
2817
|
+
model.display_name,
|
|
2818
|
+
model.execution_value,
|
|
2819
|
+
)
|
|
2820
|
+
|
|
2821
|
+
|
|
2822
|
+
def _selected_execution_provider_ids(
|
|
2823
|
+
inp: PrepareInputs,
|
|
2824
|
+
workers: list[str],
|
|
2825
|
+
requires_executor: bool,
|
|
2826
|
+
) -> tuple[str, ...]:
|
|
2827
|
+
selected = [
|
|
2828
|
+
inp.lead_provider,
|
|
2829
|
+
*(worker for worker in workers if worker != "report-writer"),
|
|
2830
|
+
inp.report_writer_provider.strip().lower() or "claude",
|
|
2831
|
+
]
|
|
2832
|
+
critic = inp.critic.strip().lower()
|
|
2833
|
+
if critic not in {"", "off"}:
|
|
2834
|
+
selected.append(critic)
|
|
2835
|
+
if requires_executor:
|
|
2836
|
+
selected.append(
|
|
2837
|
+
inp.executor.strip().lower()
|
|
2838
|
+
or _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
2839
|
+
)
|
|
2840
|
+
return tuple(dict.fromkeys(selected))
|
|
2841
|
+
|
|
2842
|
+
|
|
2843
|
+
def _resolve_executor_assignment(
|
|
2844
|
+
inp: PrepareInputs,
|
|
2845
|
+
workers: list[str],
|
|
2846
|
+
context: AssignmentContext,
|
|
2847
|
+
worker_model_inputs: dict[str, str],
|
|
2848
|
+
) -> tuple[RoleAssignment, str]:
|
|
2849
|
+
executor_default = _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
2850
|
+
executor_provider = (inp.executor or executor_default).strip().lower()
|
|
2851
|
+
allowed_executors = provider_ids("executor")
|
|
2852
|
+
if executor_provider not in allowed_executors:
|
|
2853
|
+
raise PrepareError(
|
|
2854
|
+
f"--executor must be one of: {', '.join(allowed_executors)} "
|
|
2855
|
+
f"(got: {executor_provider!r})"
|
|
2856
|
+
)
|
|
2857
|
+
if executor_provider not in workers:
|
|
2858
|
+
raise PrepareError(
|
|
2859
|
+
f"--executor {executor_provider} requires {executor_provider!r} in "
|
|
2860
|
+
f"--workers, but resolved roster is {workers!r}. "
|
|
2861
|
+
f"Add it explicitly, e.g. --workers {','.join(sorted(set(workers + [executor_provider])))}."
|
|
2862
|
+
)
|
|
2863
|
+
model = _model_for_role(
|
|
2864
|
+
context=context,
|
|
2865
|
+
provider=executor_provider,
|
|
2866
|
+
raw_value=worker_model_inputs.get(executor_provider, ""),
|
|
2867
|
+
role="implementer",
|
|
2868
|
+
duty_id="implementation-executor",
|
|
2869
|
+
)
|
|
2870
|
+
assignment = _role_assignment(
|
|
2871
|
+
resolved=model,
|
|
2872
|
+
role="executor",
|
|
2873
|
+
worker_id=executor_provider,
|
|
2874
|
+
)
|
|
2875
|
+
display_name = (
|
|
2876
|
+
f"{context.pool.resolve_provider(executor_provider).display_label} executor"
|
|
2877
|
+
)
|
|
2878
|
+
return assignment, display_name
|
|
2879
|
+
|
|
2880
|
+
|
|
2025
2881
|
def _build_worker_assignments(
|
|
2026
|
-
|
|
2882
|
+
workers: list[str], models: dict,
|
|
2027
2883
|
) -> tuple[RoleAssignment, ...]:
|
|
2028
2884
|
assignments: list[RoleAssignment] = []
|
|
2029
2885
|
for worker_id in workers:
|
|
@@ -2031,18 +2887,12 @@ def _build_worker_assignments(
|
|
|
2031
2887
|
provider = (
|
|
2032
2888
|
models["report_writer_provider"] if is_report_writer else worker_id
|
|
2033
2889
|
)
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
execution = _worker_execution_value(
|
|
2037
|
-
meta, provider, "worker", [provider],
|
|
2890
|
+
selected = (
|
|
2891
|
+
models["rw"] if is_report_writer else models["providers"][provider]
|
|
2038
2892
|
)
|
|
2039
2893
|
assignments.append(_role_assignment(
|
|
2040
|
-
|
|
2041
|
-
role=
|
|
2042
|
-
provider=provider,
|
|
2043
|
-
model=meta,
|
|
2044
|
-
execution=execution,
|
|
2045
|
-
runner_role=role,
|
|
2894
|
+
resolved=selected,
|
|
2895
|
+
role="report-writer" if is_report_writer else "analyser",
|
|
2046
2896
|
worker_id=worker_id,
|
|
2047
2897
|
))
|
|
2048
2898
|
return tuple(assignments)
|
|
@@ -2050,55 +2900,40 @@ def _build_worker_assignments(
|
|
|
2050
2900
|
|
|
2051
2901
|
def _role_assignment(
|
|
2052
2902
|
*,
|
|
2053
|
-
|
|
2903
|
+
resolved: ResolvedAssignment,
|
|
2054
2904
|
role: str,
|
|
2055
|
-
provider: str,
|
|
2056
|
-
model: ModelAssignment,
|
|
2057
|
-
execution: str,
|
|
2058
|
-
runner_role: str,
|
|
2059
2905
|
worker_id: str = "",
|
|
2060
2906
|
) -> RoleAssignment:
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2907
|
+
binding = resolved.binding
|
|
2908
|
+
if binding is None:
|
|
2909
|
+
if resolved.role != "leader" or resolved.entry_mode != "current-session":
|
|
2910
|
+
raise PrepareError("legacy assignment projection requires a model binding")
|
|
2911
|
+
return RoleAssignment(
|
|
2912
|
+
role=role,
|
|
2913
|
+
provider=resolved.provider_id,
|
|
2914
|
+
model_display=resolved.display_name,
|
|
2915
|
+
model_execution_value="unknown",
|
|
2916
|
+
runner="cli-wrapper",
|
|
2917
|
+
host_runtime=resolved.host_runtime,
|
|
2918
|
+
host_model_value=None,
|
|
2919
|
+
worker_id=worker_id,
|
|
2920
|
+
)
|
|
2069
2921
|
return RoleAssignment(
|
|
2070
2922
|
role=role,
|
|
2071
|
-
provider=
|
|
2072
|
-
model_display=
|
|
2073
|
-
model_execution_value=
|
|
2074
|
-
runner=
|
|
2075
|
-
|
|
2076
|
-
|
|
2923
|
+
provider=resolved.provider_id,
|
|
2924
|
+
model_display=resolved.display_name,
|
|
2925
|
+
model_execution_value=binding.resolved_execution_value,
|
|
2926
|
+
runner=(
|
|
2927
|
+
"native-session"
|
|
2928
|
+
if binding.runner == "current-session"
|
|
2929
|
+
else binding.runner
|
|
2930
|
+
),
|
|
2931
|
+
host_runtime=resolved.host_runtime,
|
|
2932
|
+
host_model_value=binding.host_model_value,
|
|
2077
2933
|
worker_id=worker_id,
|
|
2078
2934
|
)
|
|
2079
2935
|
|
|
2080
2936
|
|
|
2081
|
-
def _resolve_host_model_value(
|
|
2082
|
-
*,
|
|
2083
|
-
host_runtime: str,
|
|
2084
|
-
provider: str,
|
|
2085
|
-
model: ModelAssignment,
|
|
2086
|
-
execution: str,
|
|
2087
|
-
runner: str,
|
|
2088
|
-
) -> str | None:
|
|
2089
|
-
try:
|
|
2090
|
-
port = default_host_registry().resolve(host_runtime).host_model()
|
|
2091
|
-
return port.resolve(HostModelBindingRequest(
|
|
2092
|
-
host_runtime=host_runtime,
|
|
2093
|
-
provider=provider,
|
|
2094
|
-
model=model.display,
|
|
2095
|
-
model_execution_value=execution,
|
|
2096
|
-
runner=runner,
|
|
2097
|
-
))
|
|
2098
|
-
except (HostModelBindingError, HostNotRegistered) as exc:
|
|
2099
|
-
raise PrepareError(str(exc)) from exc
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
2937
|
def _build_invocation_assignments(
|
|
2103
2938
|
lead: RoleAssignment,
|
|
2104
2939
|
workers: tuple[RoleAssignment, ...],
|
|
@@ -2120,27 +2955,6 @@ def _build_invocation_assignments(
|
|
|
2120
2955
|
return assignments
|
|
2121
2956
|
|
|
2122
2957
|
|
|
2123
|
-
def _normalized_execution(meta: ModelAssignment, provider: str, role: str) -> str:
|
|
2124
|
-
"""Route a role's catalog execution value through the CLI-identity normalizer
|
|
2125
|
-
so the manifest carries the dispatch-correct spelling. SSOT for the mapping
|
|
2126
|
-
lives in model_discovery; this is only the run.py call seam."""
|
|
2127
|
-
return normalize_execution_for_dispatch(
|
|
2128
|
-
provider=provider, execution=meta.execution, role=role,
|
|
2129
|
-
)
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
def _worker_execution_value(
|
|
2133
|
-
meta: ModelAssignment, provider: str, role: str, workers: list[str],
|
|
2134
|
-
) -> str:
|
|
2135
|
-
"""Normalize a worker role's execution value only when that provider's
|
|
2136
|
-
worker is in the resolved roster; otherwise return the raw catalog value.
|
|
2137
|
-
A worker that will not be dispatched must not probe its CLI or hard-fail
|
|
2138
|
-
the whole prep (the CLI-identity check belongs only to workers that run)."""
|
|
2139
|
-
if provider in workers:
|
|
2140
|
-
return _normalized_execution(meta, provider=provider, role=role)
|
|
2141
|
-
return meta.execution
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
2958
|
StageRunClaim = _implementation_stage.StageRunClaim
|
|
2145
2959
|
|
|
2146
2960
|
|
|
@@ -2529,11 +3343,18 @@ def _render_lead_prompt_and_snapshot(
|
|
|
2529
3343
|
assignment = _agent_model_assignment(
|
|
2530
3344
|
json.loads(ctx["INVOCATION_ASSIGNMENTS_JSON"])["lead"]
|
|
2531
3345
|
)
|
|
3346
|
+
identity = invocation_execution_identity_from_manifest(
|
|
3347
|
+
json.loads(Path(ctx["RUN_MANIFEST_PATH"]).read_text(encoding="utf-8")),
|
|
3348
|
+
assignment=assignment,
|
|
3349
|
+
assignment_ref="lead",
|
|
3350
|
+
duty_id="lead",
|
|
3351
|
+
)
|
|
3352
|
+
if identity is None:
|
|
3353
|
+
raise PrepareError("lead v2 execution identity is missing")
|
|
3354
|
+
invocation_id = f"{ctx['TASK_TYPE_SEGMENT']}-{ctx['RUN_PROMPTS_SEQ']}-lead"
|
|
2532
3355
|
prepared = prepare_agent_invocation(AgentInvocationRequest(
|
|
2533
|
-
invocation_id=
|
|
2534
|
-
|
|
2535
|
-
),
|
|
2536
|
-
worker_id="lead",
|
|
3356
|
+
invocation_id=invocation_id,
|
|
3357
|
+
worker_id=None,
|
|
2537
3358
|
audience="lead",
|
|
2538
3359
|
assignment_ref="lead",
|
|
2539
3360
|
purpose=None,
|
|
@@ -2557,6 +3378,11 @@ def _render_lead_prompt_and_snapshot(
|
|
|
2557
3378
|
prompt_path=Path(ctx["RUN_PROMPT_SNAPSHOT_FILE"]),
|
|
2558
3379
|
metadata_path=Path(ctx["LEAD_PROMPT_METADATA_PATH"]),
|
|
2559
3380
|
dispatch_kind="lead",
|
|
3381
|
+
participant_ref=identity.participant_ref,
|
|
3382
|
+
role_execution_ref=identity.role_execution_ref,
|
|
3383
|
+
duty_id=identity.duty_id,
|
|
3384
|
+
invocation_ref=invocation_id,
|
|
3385
|
+
attempt=1,
|
|
2560
3386
|
))
|
|
2561
3387
|
return prepared.prompt_path.read_text(encoding="utf-8")
|
|
2562
3388
|
|
|
@@ -2581,12 +3407,7 @@ def _persist_run_inputs(
|
|
|
2581
3407
|
if inp.approved_plan_path
|
|
2582
3408
|
else ""
|
|
2583
3409
|
)
|
|
2584
|
-
|
|
2585
|
-
project_root=Path(inp.project_root),
|
|
2586
|
-
run_manifests_dir=Path(ctx["RUN_MANIFESTS_DIR"]),
|
|
2587
|
-
task_type_segment=ctx["TASK_TYPE_SEGMENT"],
|
|
2588
|
-
seq=ctx["RUN_MANIFESTS_SEQ"],
|
|
2589
|
-
inputs={
|
|
3410
|
+
inputs = {
|
|
2590
3411
|
"taskBriefPath": brief_relative,
|
|
2591
3412
|
"taskBriefAbsolutePath": str(inp.brief_path),
|
|
2592
3413
|
"directive": inp.directive,
|
|
@@ -2608,7 +3429,11 @@ def _persist_run_inputs(
|
|
|
2608
3429
|
inp.report_writer_provider or "claude",
|
|
2609
3430
|
),
|
|
2610
3431
|
"reportWriterModel": models.rw.display,
|
|
2611
|
-
"executor":
|
|
3432
|
+
"executor": (
|
|
3433
|
+
models.executor_assignment.provider
|
|
3434
|
+
if models.executor_assignment is not None
|
|
3435
|
+
else ""
|
|
3436
|
+
),
|
|
2612
3437
|
"relatedTasks": inp.related_tasks_raw,
|
|
2613
3438
|
"approvedPlanPath": approved_plan_path,
|
|
2614
3439
|
"clarificationResponsePath": inp.clarification_response_path,
|
|
@@ -2618,7 +3443,13 @@ def _persist_run_inputs(
|
|
|
2618
3443
|
),
|
|
2619
3444
|
"evidenceInputs": json.loads(ctx.get("EVIDENCE_INPUTS_JSON", "[]")),
|
|
2620
3445
|
"renderOnly": inp.render_only,
|
|
2621
|
-
|
|
3446
|
+
}
|
|
3447
|
+
run_inputs_path = _write_run_inputs_once(
|
|
3448
|
+
project_root=Path(inp.project_root),
|
|
3449
|
+
run_manifests_dir=Path(ctx["RUN_MANIFESTS_DIR"]),
|
|
3450
|
+
task_type_segment=ctx["TASK_TYPE_SEGMENT"],
|
|
3451
|
+
seq=ctx["RUN_MANIFESTS_SEQ"],
|
|
3452
|
+
inputs=inputs,
|
|
2622
3453
|
)
|
|
2623
3454
|
ctx["RUN_INPUTS_PATH"] = str(run_inputs_path)
|
|
2624
3455
|
ctx["RUN_INPUTS_RELATIVE_PATH"] = relative_to_project_root(
|
|
@@ -2626,6 +3457,29 @@ def _persist_run_inputs(
|
|
|
2626
3457
|
)
|
|
2627
3458
|
|
|
2628
3459
|
|
|
3460
|
+
def _write_run_inputs_once(
|
|
3461
|
+
*,
|
|
3462
|
+
project_root: Path,
|
|
3463
|
+
run_manifests_dir: Path,
|
|
3464
|
+
task_type_segment: str,
|
|
3465
|
+
seq: str,
|
|
3466
|
+
inputs: dict,
|
|
3467
|
+
) -> Path:
|
|
3468
|
+
path = run_manifests_dir / f"run-inputs-{task_type_segment}-{seq}.json"
|
|
3469
|
+
existing = _read_existing_manifest(path)
|
|
3470
|
+
if existing.get("schemaVersion") == "2.0":
|
|
3471
|
+
if existing.get("inputs") != inputs:
|
|
3472
|
+
raise PrepareError("input snapshot is immutable after v2 creation")
|
|
3473
|
+
return path
|
|
3474
|
+
return write_run_inputs(
|
|
3475
|
+
project_root=project_root,
|
|
3476
|
+
run_manifests_dir=run_manifests_dir,
|
|
3477
|
+
task_type_segment=task_type_segment,
|
|
3478
|
+
seq=seq,
|
|
3479
|
+
inputs=inputs,
|
|
3480
|
+
)
|
|
3481
|
+
|
|
3482
|
+
|
|
2629
3483
|
def _persist_pre_dispatch_run_manifest(ctx: dict) -> None:
|
|
2630
3484
|
"""Publish and re-read the complete invocation authority before prompts."""
|
|
2631
3485
|
render_run_manifest(ctx["RUN_MANIFEST_PATH"], ctx)
|
|
@@ -3023,24 +3877,38 @@ def _model_ctx(models: "_ModelBindings") -> dict[str, str]:
|
|
|
3023
3877
|
),
|
|
3024
3878
|
"",
|
|
3025
3879
|
),
|
|
3026
|
-
|
|
3027
|
-
"
|
|
3880
|
+
**_executor_model_ctx(models),
|
|
3881
|
+
"CRITIC_CHOICE": models.critic_choice,
|
|
3882
|
+
"CRITIC_MODEL_EXECUTION_VALUE": models.critic_model_execution,
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
|
|
3886
|
+
def _executor_model_ctx(models: "_ModelBindings") -> dict[str, str]:
|
|
3887
|
+
assignment = models.executor_assignment
|
|
3888
|
+
if assignment is None:
|
|
3889
|
+
return {
|
|
3890
|
+
"EXECUTOR_WORKER_ID": "",
|
|
3891
|
+
"EXECUTOR_PROVIDER": "",
|
|
3892
|
+
"EXECUTOR_DISPLAY_NAME": "",
|
|
3893
|
+
"EXECUTOR_MODEL_DISPLAY": "",
|
|
3894
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE": "",
|
|
3895
|
+
"EXECUTOR_HOST_MODEL_VALUE": "",
|
|
3896
|
+
"EXECUTOR_RUNNER": "",
|
|
3897
|
+
"EXECUTOR_DISPATCH_MODE": "",
|
|
3898
|
+
}
|
|
3899
|
+
return {
|
|
3900
|
+
"EXECUTOR_WORKER_ID": assignment.worker_id,
|
|
3901
|
+
"EXECUTOR_PROVIDER": assignment.provider,
|
|
3028
3902
|
"EXECUTOR_DISPLAY_NAME": models.executor_display_name,
|
|
3029
|
-
"EXECUTOR_MODEL_DISPLAY":
|
|
3030
|
-
"EXECUTOR_MODEL_EXECUTION_VALUE":
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
"EXECUTOR_HOST_MODEL_VALUE": (
|
|
3034
|
-
models.executor_assignment.host_model_value or ""
|
|
3035
|
-
),
|
|
3036
|
-
"EXECUTOR_RUNNER": models.executor_assignment.runner,
|
|
3903
|
+
"EXECUTOR_MODEL_DISPLAY": assignment.model_display,
|
|
3904
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE": assignment.model_execution_value,
|
|
3905
|
+
"EXECUTOR_HOST_MODEL_VALUE": assignment.host_model_value or "",
|
|
3906
|
+
"EXECUTOR_RUNNER": assignment.runner,
|
|
3037
3907
|
"EXECUTOR_DISPATCH_MODE": (
|
|
3038
3908
|
"host-native"
|
|
3039
|
-
if
|
|
3909
|
+
if assignment.runner == "native-session"
|
|
3040
3910
|
else "worker-dispatch"
|
|
3041
3911
|
),
|
|
3042
|
-
"CRITIC_CHOICE": models.critic_choice,
|
|
3043
|
-
"CRITIC_MODEL_EXECUTION_VALUE": models.critic_model_execution,
|
|
3044
3912
|
}
|
|
3045
3913
|
|
|
3046
3914
|
|
|
@@ -3147,6 +4015,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3147
4015
|
# 조립 코드가 단일 흐름으로 읽히도록 유지한다.
|
|
3148
4016
|
assets = _resolve_runtime_assets(workspace_root, inp)
|
|
3149
4017
|
profile_file = assets.profile_file
|
|
4018
|
+
requires_executor = _profile_requires_executor(profile_file)
|
|
4019
|
+
selection, role_profile = _normalize_prepare_model_selection(inp, profile_file)
|
|
3150
4020
|
prompt_template = assets.prompt_template
|
|
3151
4021
|
task_index_template = assets.task_index_template
|
|
3152
4022
|
final_report_template = assets.final_report_template
|
|
@@ -3168,13 +4038,45 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3168
4038
|
handoff_tokens = _materialize_release_handoff_input(
|
|
3169
4039
|
workspace_root, project_root, inp)
|
|
3170
4040
|
|
|
3171
|
-
verify_installation(workspace_root)
|
|
3172
|
-
_register_and_check_project(project_root, inp)
|
|
3173
|
-
|
|
3174
4041
|
workers, selected_reviewers = _resolve_roster(inp, profile_file)
|
|
3175
4042
|
pr_template_path_str, pr_template_source = _resolve_pr_template(inp)
|
|
3176
4043
|
|
|
3177
|
-
|
|
4044
|
+
assignment_context, assignment_plan = _resolve_prepare_assignments(
|
|
4045
|
+
inp=inp,
|
|
4046
|
+
profile=role_profile,
|
|
4047
|
+
selection=selection,
|
|
4048
|
+
workers=workers,
|
|
4049
|
+
lead_runtime=lead_runtime,
|
|
4050
|
+
terminal_backend=terminal_backend,
|
|
4051
|
+
requires_executor=requires_executor,
|
|
4052
|
+
)
|
|
4053
|
+
if _uses_canonical_assignment_projection(inp):
|
|
4054
|
+
workers, models = _project_assignment_plan(
|
|
4055
|
+
inp,
|
|
4056
|
+
assignment_plan,
|
|
4057
|
+
assignment_context,
|
|
4058
|
+
)
|
|
4059
|
+
selected_reviewers = ",".join(workers)
|
|
4060
|
+
else:
|
|
4061
|
+
models = _resolve_model_bindings(
|
|
4062
|
+
inp,
|
|
4063
|
+
workers,
|
|
4064
|
+
assignment_context,
|
|
4065
|
+
requires_executor,
|
|
4066
|
+
)
|
|
4067
|
+
execution_manifest = _build_static_execution_manifest(
|
|
4068
|
+
assignment_plan,
|
|
4069
|
+
assignment_context,
|
|
4070
|
+
models.lead,
|
|
4071
|
+
)
|
|
4072
|
+
dynamic_roles = tuple(
|
|
4073
|
+
requirement.role
|
|
4074
|
+
for requirement in role_profile.roles
|
|
4075
|
+
if requirement.dynamic
|
|
4076
|
+
)
|
|
4077
|
+
|
|
4078
|
+
verify_installation(workspace_root)
|
|
4079
|
+
_register_and_check_project(project_root, inp)
|
|
3178
4080
|
|
|
3179
4081
|
# ---- paths under per-task mutex (writes run-context-*.json) ----
|
|
3180
4082
|
# OKSTRA_RUN_SEQ_OVERRIDE: okstra-ctl rerun / 테스트 hook 이 미리 reserve
|
|
@@ -3327,6 +4229,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3327
4229
|
"LATEST_REPORT_RELATIVE_PATH": "",
|
|
3328
4230
|
"RENDER_ONLY": "true" if inp.render_only else "false",
|
|
3329
4231
|
"OKSTRA_VERSION": installed_version(),
|
|
4232
|
+
"EXECUTION_IDENTITY_JSON": json.dumps(
|
|
4233
|
+
execution_manifest.to_payload(), ensure_ascii=False,
|
|
4234
|
+
),
|
|
4235
|
+
"DYNAMIC_EXECUTION_ROLES_JSON": json.dumps(
|
|
4236
|
+
dynamic_roles, ensure_ascii=False,
|
|
4237
|
+
),
|
|
3330
4238
|
})
|
|
3331
4239
|
refresh_run_context_snapshot(ctx)
|
|
3332
4240
|
if lead_runtime == "codex":
|
|
@@ -3366,6 +4274,15 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3366
4274
|
_finalize_status_and_render_manifests(
|
|
3367
4275
|
inp, ctx, task_index_template, forbidden_by_phase
|
|
3368
4276
|
)
|
|
4277
|
+
persist_static_execution_identity(
|
|
4278
|
+
run_manifest_path=Path(ctx["RUN_MANIFEST_PATH"]),
|
|
4279
|
+
task_manifest_path=Path(ctx["TASK_MANIFEST_PATH"]),
|
|
4280
|
+
input_snapshot_path=Path(ctx["RUN_INPUTS_PATH"]),
|
|
4281
|
+
manifest=execution_manifest,
|
|
4282
|
+
task_key=ctx["TASK_KEY"],
|
|
4283
|
+
run_ref=ctx["RUN_MANIFEST_RELATIVE_PATH"],
|
|
4284
|
+
dynamic_roles=dynamic_roles,
|
|
4285
|
+
)
|
|
3369
4286
|
_record_artifact_runtime_render_only_event(inp, ctx)
|
|
3370
4287
|
|
|
3371
4288
|
_record_run_in_central_index(inp, ctx, workspace_root, run_seq_override)
|
|
@@ -3389,10 +4306,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3389
4306
|
)
|
|
3390
4307
|
|
|
3391
4308
|
|
|
3392
|
-
def
|
|
3393
|
-
"""
|
|
3394
|
-
(the same flags `okstra.sh` accepts), runs prepare_task_bundle, prints
|
|
3395
|
-
summary lines + prompt to stdout."""
|
|
4309
|
+
def build_prepare_argument_parser():
|
|
4310
|
+
"""Build the shared flat-argument parser used by public prepare shims."""
|
|
3396
4311
|
import argparse
|
|
3397
4312
|
|
|
3398
4313
|
p = argparse.ArgumentParser()
|
|
@@ -3422,6 +4337,13 @@ def main(argv: list[str]) -> int:
|
|
|
3422
4337
|
),
|
|
3423
4338
|
)
|
|
3424
4339
|
p.add_argument("--workers", default="", dest="workers_override")
|
|
4340
|
+
p.add_argument("--role-count", action="append", default=[], dest="role_counts_raw")
|
|
4341
|
+
p.add_argument("--role-model", action="append", default=[], dest="role_models_raw")
|
|
4342
|
+
p.add_argument(
|
|
4343
|
+
"--host-session-context-json",
|
|
4344
|
+
default="",
|
|
4345
|
+
dest="host_session_context_json",
|
|
4346
|
+
)
|
|
3425
4347
|
p.add_argument("--lead-provider", default="")
|
|
3426
4348
|
p.add_argument("--lead-model", default="")
|
|
3427
4349
|
p.add_argument("--claude-model", default="")
|
|
@@ -3565,7 +4487,12 @@ def main(argv: list[str]) -> int:
|
|
|
3565
4487
|
"`main` / `dev` / `staging` / `preprod` / `prod` or any local ref."
|
|
3566
4488
|
),
|
|
3567
4489
|
)
|
|
3568
|
-
|
|
4490
|
+
return p
|
|
4491
|
+
|
|
4492
|
+
|
|
4493
|
+
def main(argv: list[str]) -> int:
|
|
4494
|
+
"""Parse one public prepare invocation and emit its rendered bundle."""
|
|
4495
|
+
args = build_prepare_argument_parser().parse_args(argv)
|
|
3569
4496
|
|
|
3570
4497
|
project_root = Path(args.project_root).expanduser().resolve()
|
|
3571
4498
|
if args.task_type == "release-handoff":
|
|
@@ -3609,6 +4536,25 @@ def main(argv: list[str]) -> int:
|
|
|
3609
4536
|
return 1
|
|
3610
4537
|
selected_direction_abs = str(selected)
|
|
3611
4538
|
|
|
4539
|
+
try:
|
|
4540
|
+
host_session_context = deserialize_host_session_context(
|
|
4541
|
+
args.host_session_context_json
|
|
4542
|
+
)
|
|
4543
|
+
except ModelSelectionInputError as exc:
|
|
4544
|
+
print(str(exc), file=__import__("sys").stderr)
|
|
4545
|
+
return 1
|
|
4546
|
+
if not args.host_session_context_json:
|
|
4547
|
+
adapter = default_host_registry().resolve(args.lead_runtime)
|
|
4548
|
+
host_session_context = HostSessionContext(
|
|
4549
|
+
host_id=adapter.descriptor.id,
|
|
4550
|
+
entry_mode="spawn-process",
|
|
4551
|
+
available_functions=frozenset(),
|
|
4552
|
+
interaction_surface="unavailable",
|
|
4553
|
+
current_model=CurrentSessionModelAttestation.unknown(
|
|
4554
|
+
adapter.descriptor.native_provider_id
|
|
4555
|
+
),
|
|
4556
|
+
)
|
|
4557
|
+
|
|
3612
4558
|
inputs = PrepareInputs(
|
|
3613
4559
|
workspace_root=Path(args.workspace_root).resolve(),
|
|
3614
4560
|
project_root=project_root,
|
|
@@ -3621,6 +4567,9 @@ def main(argv: list[str]) -> int:
|
|
|
3621
4567
|
evidence_inputs_raw=args.evidence_inputs_raw,
|
|
3622
4568
|
directive=args.directive,
|
|
3623
4569
|
workers_override=args.workers_override,
|
|
4570
|
+
role_counts_raw=tuple(args.role_counts_raw),
|
|
4571
|
+
role_models_raw=tuple(args.role_models_raw),
|
|
4572
|
+
host_session_context=host_session_context,
|
|
3624
4573
|
lead_provider=args.lead_provider,
|
|
3625
4574
|
lead_model=args.lead_model,
|
|
3626
4575
|
claude_model=args.claude_model,
|