okstra 0.174.0 → 0.176.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/{src → dist}/commands/execute/team.mjs +29 -36
- package/dist/commands/execute/team.mjs.map +1 -0
- package/dist/commands/execute/token-usage.d.mts +1 -0
- package/{src → dist}/commands/execute/token-usage.mjs +2 -3
- package/dist/commands/execute/token-usage.mjs.map +1 -0
- package/dist/commands/execute/wizard.d.mts +8 -0
- package/dist/commands/execute/wizard.mjs +191 -0
- package/dist/commands/execute/wizard.mjs.map +1 -0
- package/dist/commands/execute/worker-audit-check.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-audit-check.mjs +11 -12
- package/dist/commands/execute/worker-audit-check.mjs.map +1 -0
- package/dist/commands/execute/worker-dispatch.d.mts +7 -0
- package/{src → dist}/commands/execute/worker-dispatch.mjs +34 -46
- package/dist/commands/execute/worker-dispatch.mjs.map +1 -0
- package/dist/commands/execute/worker-state.d.mts +1 -0
- package/{src → dist}/commands/execute/worker-state.mjs +15 -16
- package/dist/commands/execute/worker-state.mjs.map +1 -0
- package/dist/commands/execute/worktree-lookup.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-lookup.mjs +38 -37
- package/dist/commands/execute/worktree-lookup.mjs.map +1 -0
- package/dist/commands/execute/worktree-status.d.mts +1 -0
- package/{src → dist}/commands/execute/worktree-status.mjs +52 -46
- package/dist/commands/execute/worktree-status.mjs.map +1 -0
- package/dist/commands/inspect/code-review.d.mts +1 -0
- package/{src → dist}/commands/inspect/code-review.mjs +16 -19
- package/dist/commands/inspect/code-review.mjs.map +1 -0
- package/dist/commands/inspect/container.d.mts +1 -0
- package/{src → dist}/commands/inspect/container.mjs +11 -13
- package/dist/commands/inspect/container.mjs.map +1 -0
- package/dist/commands/inspect/context-cost.d.mts +1 -0
- package/{src → dist}/commands/inspect/context-cost.mjs +11 -13
- package/dist/commands/inspect/context-cost.mjs.map +1 -0
- package/dist/commands/inspect/design-prep.d.mts +1 -0
- package/{src → dist}/commands/inspect/design-prep.mjs +10 -11
- package/dist/commands/inspect/design-prep.mjs.map +1 -0
- package/dist/commands/inspect/error-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-report.mjs +11 -13
- package/dist/commands/inspect/error-report.mjs.map +1 -0
- package/dist/commands/inspect/error-zip.d.mts +1 -0
- package/{src → dist}/commands/inspect/error-zip.mjs +11 -12
- package/dist/commands/inspect/error-zip.mjs.map +1 -0
- package/dist/commands/inspect/log-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/log-report.mjs +11 -13
- package/dist/commands/inspect/log-report.mjs.map +1 -0
- package/dist/commands/inspect/profile-show.d.mts +1 -0
- package/{src → dist}/commands/inspect/profile-show.mjs +10 -11
- package/dist/commands/inspect/profile-show.mjs.map +1 -0
- package/dist/commands/inspect/recap.d.mts +1 -0
- package/{src → dist}/commands/inspect/recap.mjs +11 -13
- package/dist/commands/inspect/recap.mjs.map +1 -0
- package/dist/commands/inspect/resolve-task-key.d.mts +1 -0
- package/{src → dist}/commands/inspect/resolve-task-key.mjs +11 -13
- package/dist/commands/inspect/resolve-task-key.mjs.map +1 -0
- package/dist/commands/inspect/rollup.d.mts +1 -0
- package/{src → dist}/commands/inspect/rollup.mjs +11 -13
- package/dist/commands/inspect/rollup.mjs.map +1 -0
- package/dist/commands/inspect/run-audit.d.mts +1 -0
- package/{src → dist}/commands/inspect/run-audit.mjs +11 -12
- package/dist/commands/inspect/run-audit.mjs.map +1 -0
- package/dist/commands/inspect/set-work-status.d.mts +1 -0
- package/{src → dist}/commands/inspect/set-work-status.mjs +11 -13
- package/dist/commands/inspect/set-work-status.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.d.mts +1 -0
- package/dist/commands/inspect/stage-map.mjs +110 -0
- package/dist/commands/inspect/stage-map.mjs.map +1 -0
- package/dist/commands/inspect/task-list.d.mts +1 -0
- package/dist/commands/inspect/task-list.mjs +103 -0
- package/dist/commands/inspect/task-list.mjs.map +1 -0
- package/dist/commands/inspect/task-show.d.mts +1 -0
- package/dist/commands/inspect/task-show.mjs +108 -0
- package/dist/commands/inspect/task-show.mjs.map +1 -0
- package/dist/commands/inspect/time-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/time-report.mjs +11 -13
- package/dist/commands/inspect/time-report.mjs.map +1 -0
- package/dist/commands/inspect/usage-report.d.mts +1 -0
- package/{src → dist}/commands/inspect/usage-report.mjs +11 -13
- package/dist/commands/inspect/usage-report.mjs.map +1 -0
- package/dist/commands/inspect/user-response.d.mts +1 -0
- package/{src → dist}/commands/inspect/user-response.mjs +10 -11
- package/dist/commands/inspect/user-response.mjs.map +1 -0
- package/dist/commands/inspect/worker-liveness.d.mts +1 -0
- package/{src → dist}/commands/inspect/worker-liveness.mjs +11 -13
- package/dist/commands/inspect/worker-liveness.mjs.map +1 -0
- package/dist/commands/lifecycle/check-project.d.mts +21 -0
- package/dist/commands/lifecycle/check-project.mjs +175 -0
- package/dist/commands/lifecycle/check-project.mjs.map +1 -0
- package/dist/commands/lifecycle/config.d.mts +1 -0
- package/dist/commands/lifecycle/config.mjs +369 -0
- package/dist/commands/lifecycle/config.mjs.map +1 -0
- package/dist/commands/lifecycle/doctor.d.mts +10 -0
- package/dist/commands/lifecycle/doctor.mjs +317 -0
- package/dist/commands/lifecycle/doctor.mjs.map +1 -0
- package/dist/commands/lifecycle/install.d.mts +17 -0
- package/dist/commands/lifecycle/install.mjs +1077 -0
- package/dist/commands/lifecycle/install.mjs.map +1 -0
- package/dist/commands/lifecycle/migrate.d.mts +1 -0
- package/{src → dist}/commands/lifecycle/migrate.mjs +10 -11
- package/dist/commands/lifecycle/migrate.mjs.map +1 -0
- package/dist/commands/lifecycle/model.d.mts +1 -0
- package/dist/commands/lifecycle/model.mjs +22 -0
- package/dist/commands/lifecycle/model.mjs.map +1 -0
- package/dist/commands/lifecycle/paths.d.mts +1 -0
- package/dist/commands/lifecycle/paths.mjs +102 -0
- package/dist/commands/lifecycle/paths.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.d.mts +1 -0
- package/dist/commands/lifecycle/preflight.mjs +124 -0
- package/dist/commands/lifecycle/preflight.mjs.map +1 -0
- package/dist/commands/lifecycle/setup.d.mts +1 -0
- package/dist/commands/lifecycle/setup.mjs +297 -0
- package/dist/commands/lifecycle/setup.mjs.map +1 -0
- package/dist/commands/lifecycle/uninstall.d.mts +4 -0
- package/dist/commands/lifecycle/uninstall.mjs +262 -0
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -0
- package/dist/commands/manager.d.mts +3 -0
- package/{src → dist}/commands/manager.mjs +25 -32
- package/dist/commands/manager.mjs.map +1 -0
- package/dist/commands/memory/memory.d.mts +1 -0
- package/dist/commands/memory/memory.mjs +569 -0
- package/dist/commands/memory/memory.mjs.map +1 -0
- package/dist/commands/pr/pr.d.mts +33 -0
- package/dist/commands/pr/pr.mjs +296 -0
- package/dist/commands/pr/pr.mjs.map +1 -0
- package/dist/commands/report/agent-activity.d.mts +1 -0
- package/{src → dist}/commands/report/agent-activity.mjs +11 -12
- package/dist/commands/report/agent-activity.mjs.map +1 -0
- package/dist/commands/report/finalize.d.mts +4 -0
- package/{src → dist}/commands/report/finalize.mjs +25 -32
- package/dist/commands/report/finalize.mjs.map +1 -0
- package/dist/commands/report/inject-report-index.d.mts +1 -0
- package/{src → dist}/commands/report/inject-report-index.mjs +6 -7
- package/dist/commands/report/inject-report-index.mjs.map +1 -0
- package/dist/commands/report/render-final-report.d.mts +1 -0
- package/{src → dist}/commands/report/render-final-report.mjs +6 -7
- package/dist/commands/report/render-final-report.mjs.map +1 -0
- package/dist/commands/report/render-views.d.mts +1 -0
- package/{src → dist}/commands/report/render-views.mjs +6 -7
- package/dist/commands/report/render-views.mjs.map +1 -0
- package/dist/commands/report/translate.d.mts +1 -0
- package/{src → dist}/commands/report/translate.mjs +6 -7
- package/dist/commands/report/translate.mjs.map +1 -0
- package/dist/lib/helper-scripts.d.mts +3 -0
- package/{src → dist}/lib/helper-scripts.mjs +11 -14
- package/dist/lib/helper-scripts.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +25 -0
- package/dist/lib/host-registry-client.mjs +152 -0
- package/dist/lib/host-registry-client.mjs.map +1 -0
- package/dist/lib/install-assets.d.mts +8 -0
- package/{src → dist}/lib/install-assets.mjs +17 -20
- package/dist/lib/install-assets.mjs.map +1 -0
- package/dist/lib/okstra-dirs.d.mts +4 -0
- package/{src → dist}/lib/okstra-dirs.mjs +3 -6
- package/dist/lib/okstra-dirs.mjs.map +1 -0
- package/dist/lib/paths.d.mts +4 -0
- package/dist/lib/paths.mjs +78 -0
- package/dist/lib/paths.mjs.map +1 -0
- package/dist/lib/proc.d.mts +3 -0
- package/dist/lib/proc.mjs +31 -0
- package/dist/lib/proc.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +8 -0
- package/dist/lib/python-helper.mjs +123 -0
- package/dist/lib/python-helper.mjs.map +1 -0
- package/dist/lib/runtime-manifest.d.mts +7 -0
- package/dist/lib/runtime-manifest.mjs +73 -0
- package/dist/lib/runtime-manifest.mjs.map +1 -0
- package/dist/lib/skill-catalog.d.mts +4 -0
- package/dist/lib/skill-catalog.mjs +50 -0
- package/dist/lib/skill-catalog.mjs.map +1 -0
- package/dist/lib/types.d.mts +129 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/lib/types.mjs.map +1 -0
- package/dist/lib/version.d.mts +2 -0
- package/dist/lib/version.mjs +27 -0
- package/dist/lib/version.mjs.map +1 -0
- package/docs/architecture/storage-model.md +6 -2
- package/docs/architecture.md +29 -20
- package/docs/cli.md +40 -16
- package/docs/contributor-change-matrix.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +72 -62
- package/docs/task-process/README.md +12 -8
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +2 -1
- package/runtime/agents/workers/report-writer-worker.md +3 -1
- package/runtime/agents/workers/translator-worker.md +3 -1
- package/runtime/bin/lib/okstra/cli.sh +12 -0
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +7 -6
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +217 -5
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +17 -13
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +20 -0
- package/runtime/prompts/profiles/error-analysis.md +27 -1
- package/runtime/prompts/profiles/feature-analysis.md +20 -0
- package/runtime/prompts/profiles/final-verification.md +20 -1
- package/runtime/prompts/profiles/implementation-option-selection.md +20 -0
- package/runtime/prompts/profiles/implementation-planning.md +26 -0
- package/runtime/prompts/profiles/implementation.md +19 -0
- package/runtime/prompts/profiles/improvement-discovery.md +21 -1
- package/runtime/prompts/profiles/project-analysis.md +20 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -0
- package/runtime/prompts/profiles/requirements-discovery.md +27 -1
- package/runtime/prompts/wizard/prompts.ko.json +35 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +8 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +25 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +4 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +79 -11
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +95 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/convergence.py +409 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +248 -15
- package/runtime/python/okstra_ctl/convergence_store.py +170 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +1204 -155
- package/runtime/python/okstra_ctl/dispatch_state.py +696 -11
- package/runtime/python/okstra_ctl/doctor.py +14 -0
- package/runtime/python/okstra_ctl/domain/host.py +34 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +18 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +34 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +578 -0
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/next_phase.py +236 -0
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +124 -57
- package/runtime/python/okstra_ctl/render_final_report.py +19 -0
- package/runtime/python/okstra_ctl/report_contract.py +39 -0
- package/runtime/python/okstra_ctl/report_finalize.py +16 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +210 -0
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +1210 -258
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1449 -66
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/worker_request.py +7 -1
- package/runtime/python/okstra_ctl/worker_runner.py +130 -5
- package/runtime/python/okstra_ctl/workers.py +7 -5
- package/runtime/python/okstra_ctl/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_project/state.py +54 -6
- package/runtime/schemas/convergence-groups-v2.0.schema.json +128 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +340 -0
- package/runtime/schemas/final-report-v2.0.schema.json +68 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +14 -14
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- package/runtime/templates/report-writer-prompt-preamble.md +2 -0
- package/runtime/templates/reports/final-report-v2.template.md +7 -1
- package/runtime/templates/reports/html/base.template.html +21 -0
- package/runtime/templates/reports/html/i18n/en.json +4 -0
- package/runtime/templates/reports/html/i18n/ko.json +4 -0
- package/runtime/templates/reports/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +6 -35
- package/runtime/validators/lib/fixtures.sh +49 -16
- package/runtime/validators/validate-run.py +486 -134
- package/runtime/validators/validate_session_conformance.py +25 -7
- package/src/cli-registry.mjs +0 -539
- package/src/commands/execute/codex-dispatch.mjs +0 -10
- package/src/commands/execute/plan-items.mjs +0 -9
- package/src/commands/execute/plan-validate.mjs +0 -68
- package/src/commands/execute/run.mjs +0 -208
- package/src/commands/execute/wizard.mjs +0 -167
- package/src/commands/inspect/stage-map.mjs +0 -98
- package/src/commands/inspect/task-list.mjs +0 -97
- package/src/commands/inspect/task-show.mjs +0 -100
- package/src/commands/lifecycle/check-project.mjs +0 -187
- package/src/commands/lifecycle/config.mjs +0 -385
- package/src/commands/lifecycle/doctor.mjs +0 -308
- package/src/commands/lifecycle/install.mjs +0 -1170
- package/src/commands/lifecycle/paths.mjs +0 -104
- package/src/commands/lifecycle/preflight.mjs +0 -125
- package/src/commands/lifecycle/setup.mjs +0 -333
- package/src/commands/lifecycle/uninstall.mjs +0 -279
- package/src/commands/memory/memory.mjs +0 -524
- package/src/commands/pr/pr.mjs +0 -261
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -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,487 @@ 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.min_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.min_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.recommended_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.recommended_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 (
|
|
2223
|
+
not requirement.dynamic
|
|
2224
|
+
and requirement.min_count < requirement.max_count
|
|
2225
|
+
)
|
|
2226
|
+
}
|
|
2227
|
+
return {role: count for role, count in counts.items() if role in adjustable}
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
def _model_default_scopes(project_root: Path) -> ModelDefaultScopes:
|
|
2231
|
+
return ModelDefaultScopes(
|
|
2232
|
+
project=_read_model_defaults(project_json_path(project_root)),
|
|
2233
|
+
global_=_read_model_defaults(okstra_home() / "config.json"),
|
|
2234
|
+
bundled={},
|
|
2235
|
+
)
|
|
2236
|
+
|
|
2237
|
+
|
|
2238
|
+
def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
|
|
2239
|
+
if not path.is_file():
|
|
2240
|
+
return {}
|
|
2241
|
+
try:
|
|
2242
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
2243
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
2244
|
+
raise PrepareError(f"model defaults file is invalid: {path}: {exc}") from exc
|
|
2245
|
+
raw_defaults = payload.get("modelDefaults", {}) if isinstance(payload, dict) else {}
|
|
2246
|
+
if not isinstance(raw_defaults, dict):
|
|
2247
|
+
raise PrepareError(f"modelDefaults must be an object: {path}")
|
|
2248
|
+
defaults: dict[str, tuple[str, ...]] = {}
|
|
2249
|
+
for role, models in raw_defaults.items():
|
|
2250
|
+
if (
|
|
2251
|
+
not isinstance(role, str)
|
|
2252
|
+
or not isinstance(models, list)
|
|
2253
|
+
or not models
|
|
2254
|
+
or not all(isinstance(model, str) and model for model in models)
|
|
2255
|
+
):
|
|
2256
|
+
raise PrepareError(f"modelDefaults entries must be non-empty string arrays: {path}")
|
|
2257
|
+
defaults[role] = tuple(models)
|
|
2258
|
+
return defaults
|
|
2259
|
+
|
|
2260
|
+
|
|
1695
2261
|
def _validate_option_selection_roster(
|
|
1696
2262
|
task_type: str, workers: Sequence[str]
|
|
1697
2263
|
) -> None:
|
|
@@ -1756,23 +2322,43 @@ class _ModelBindings:
|
|
|
1756
2322
|
해소한다 — prepare 가 run seq 를 할당하거나 worktree 를 만들기 전에.
|
|
1757
2323
|
"""
|
|
1758
2324
|
|
|
1759
|
-
lead:
|
|
1760
|
-
cw:
|
|
1761
|
-
co:
|
|
1762
|
-
ge:
|
|
1763
|
-
rw:
|
|
2325
|
+
lead: ResolvedAssignment
|
|
2326
|
+
cw: "_LegacyModelProjection"
|
|
2327
|
+
co: "_LegacyModelProjection"
|
|
2328
|
+
ge: "_LegacyModelProjection"
|
|
2329
|
+
rw: ResolvedAssignment
|
|
1764
2330
|
critic_choice: str
|
|
1765
2331
|
critic_model_execution: str
|
|
1766
2332
|
executor_display_name: str
|
|
1767
2333
|
codex_worker_execution: str
|
|
1768
2334
|
antigravity_worker_execution: str
|
|
1769
|
-
executor_assignment: RoleAssignment
|
|
2335
|
+
executor_assignment: RoleAssignment | None
|
|
1770
2336
|
lead_assignment: RoleAssignment
|
|
1771
2337
|
worker_assignments: tuple[RoleAssignment, ...]
|
|
1772
2338
|
invocation_assignments: dict[str, dict[str, object]]
|
|
1773
2339
|
|
|
1774
2340
|
|
|
1775
|
-
|
|
2341
|
+
@dataclass(frozen=True)
|
|
2342
|
+
class _LegacyModelProjection:
|
|
2343
|
+
"""Display/catalog values retained for the fixed v1 manifest fields."""
|
|
2344
|
+
|
|
2345
|
+
model_id: str
|
|
2346
|
+
display: str
|
|
2347
|
+
execution: str
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
def _legacy_claude_model_override(provider: str, env_name: str) -> str:
|
|
2351
|
+
"""Read a legacy Claude model override only for a Claude assignment."""
|
|
2352
|
+
if provider.strip().lower() != "claude":
|
|
2353
|
+
return ""
|
|
2354
|
+
return _default(env_name, "")
|
|
2355
|
+
|
|
2356
|
+
|
|
2357
|
+
def recommended_role_models(
|
|
2358
|
+
*,
|
|
2359
|
+
lead_provider: str = "",
|
|
2360
|
+
report_writer_provider: str = "",
|
|
2361
|
+
) -> dict[str, str]:
|
|
1776
2362
|
"""역할 → 추천 모델 display 값 (env override 반영). prepare 의 모델 해소와
|
|
1777
2363
|
wizard 의 안내 표기가 공유하는 단일 기준점.
|
|
1778
2364
|
|
|
@@ -1782,18 +2368,28 @@ def recommended_role_models(*, lead_provider: str = "") -> dict[str, str]:
|
|
|
1782
2368
|
role 별 레거시 기본값(claude 계열)으로 떨어지는데, 그 값을 codex 호스트
|
|
1783
2369
|
화면에 그대로 쓰면 안내는 `opus` 인데 prepare 는 `gpt-5.6-sol` 을 배정한다.
|
|
1784
2370
|
"""
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2371
|
+
lead_provider_id = lead_provider or "claude"
|
|
2372
|
+
report_writer_provider_id = report_writer_provider or "claude"
|
|
2373
|
+
lead_default = (
|
|
2374
|
+
_legacy_claude_model_override(
|
|
2375
|
+
lead_provider_id,
|
|
2376
|
+
"OKSTRA_DEFAULT_LEAD_MODEL",
|
|
2377
|
+
)
|
|
2378
|
+
or provider_default_model(lead_provider_id, "lead")
|
|
2379
|
+
)
|
|
2380
|
+
report_writer_default = (
|
|
2381
|
+
_legacy_claude_model_override(
|
|
2382
|
+
report_writer_provider_id,
|
|
2383
|
+
"OKSTRA_DEFAULT_REPORT_WRITER_MODEL",
|
|
2384
|
+
)
|
|
2385
|
+
or provider_default_model(report_writer_provider_id, "report-writer")
|
|
1790
2386
|
)
|
|
1791
2387
|
recommendations = {
|
|
1792
2388
|
"lead": lead_default,
|
|
1793
2389
|
"claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", default_model("claude")),
|
|
1794
2390
|
"codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", default_model("codex")),
|
|
1795
2391
|
"antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", default_model("antigravity")),
|
|
1796
|
-
"report-writer":
|
|
2392
|
+
"report-writer": report_writer_default,
|
|
1797
2393
|
}
|
|
1798
2394
|
for provider in provider_ids("analyser"):
|
|
1799
2395
|
recommendations.setdefault(
|
|
@@ -1802,7 +2398,10 @@ def recommended_role_models(*, lead_provider: str = "") -> dict[str, str]:
|
|
|
1802
2398
|
return recommendations
|
|
1803
2399
|
|
|
1804
2400
|
|
|
1805
|
-
def _parse_worker_model_overrides(
|
|
2401
|
+
def _parse_worker_model_overrides(
|
|
2402
|
+
raw_value: str,
|
|
2403
|
+
context: AssignmentContext,
|
|
2404
|
+
) -> dict[str, str]:
|
|
1806
2405
|
overrides: dict[str, str] = {}
|
|
1807
2406
|
for item in (raw_value or "").split(","):
|
|
1808
2407
|
item = item.strip()
|
|
@@ -1817,65 +2416,71 @@ def _parse_worker_model_overrides(raw_value: str) -> dict[str, str]:
|
|
|
1817
2416
|
)
|
|
1818
2417
|
if provider in overrides:
|
|
1819
2418
|
raise PrepareError(f"duplicate --worker-model provider: {provider}")
|
|
1820
|
-
|
|
2419
|
+
context.pool.resolve_provider(provider)
|
|
1821
2420
|
overrides[provider] = model
|
|
1822
2421
|
return overrides
|
|
1823
2422
|
|
|
1824
2423
|
|
|
1825
2424
|
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()
|
|
2425
|
+
*,
|
|
2426
|
+
context: AssignmentContext,
|
|
2427
|
+
provider: str,
|
|
2428
|
+
raw_value: str,
|
|
2429
|
+
role: str,
|
|
2430
|
+
default_override: str = "",
|
|
2431
|
+
duty_id: str = "",
|
|
2432
|
+
) -> ResolvedAssignment:
|
|
1842
2433
|
try:
|
|
1843
|
-
return
|
|
1844
|
-
|
|
1845
|
-
|
|
2434
|
+
return resolve_dispatch_assignment(
|
|
2435
|
+
context=context,
|
|
2436
|
+
host_runtime=context.environment.host_descriptor.id,
|
|
1846
2437
|
role=role,
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
UnknownProviderError,
|
|
1853
|
-
) as exc:
|
|
2438
|
+
duty_id=duty_id or role,
|
|
2439
|
+
provider=provider,
|
|
2440
|
+
model=raw_value or default_override,
|
|
2441
|
+
)
|
|
2442
|
+
except (ValueError, RuntimeError) as exc:
|
|
1854
2443
|
raise PrepareError(str(exc)) from exc
|
|
1855
2444
|
|
|
1856
2445
|
|
|
1857
|
-
def _resolve_worker_models(
|
|
2446
|
+
def _resolve_worker_models(
|
|
2447
|
+
inp: PrepareInputs,
|
|
2448
|
+
workers: list[str],
|
|
2449
|
+
context: AssignmentContext,
|
|
2450
|
+
) -> dict:
|
|
1858
2451
|
"""Resolve provider models while preserving legacy fixed-provider projections."""
|
|
1859
|
-
|
|
1860
|
-
overrides = _parse_worker_model_overrides(inp.worker_models_raw)
|
|
2452
|
+
overrides = _parse_worker_model_overrides(inp.worker_models_raw, context)
|
|
1861
2453
|
legacy = {
|
|
1862
2454
|
"claude": inp.claude_model,
|
|
1863
2455
|
"codex": inp.codex_model,
|
|
1864
2456
|
"antigravity": inp.antigravity_model,
|
|
1865
2457
|
}
|
|
2458
|
+
legacy_default_env = {
|
|
2459
|
+
"claude": "OKSTRA_DEFAULT_CLAUDE_MODEL",
|
|
2460
|
+
"codex": "OKSTRA_DEFAULT_CODEX_MODEL",
|
|
2461
|
+
"antigravity": "OKSTRA_DEFAULT_ANTIGRAVITY_MODEL",
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
def worker_model_input(provider: str) -> str:
|
|
2465
|
+
selected = overrides.get(provider, legacy.get(provider, ""))
|
|
2466
|
+
env_name = legacy_default_env.get(provider)
|
|
2467
|
+
return selected or (_default(env_name, "") if env_name else "")
|
|
2468
|
+
|
|
2469
|
+
selected_worker_providers = tuple(dict.fromkeys(
|
|
2470
|
+
worker for worker in workers if worker != "report-writer"
|
|
2471
|
+
))
|
|
1866
2472
|
provider_models = {}
|
|
1867
|
-
for provider in
|
|
1868
|
-
default_override = rec.get(provider, "")
|
|
2473
|
+
for provider in selected_worker_providers:
|
|
1869
2474
|
provider_models[provider] = _model_for_role(
|
|
1870
|
-
|
|
1871
|
-
|
|
2475
|
+
context=context,
|
|
2476
|
+
provider=provider,
|
|
2477
|
+
raw_value=worker_model_input(provider),
|
|
2478
|
+
role="analyser",
|
|
1872
2479
|
)
|
|
1873
2480
|
try:
|
|
1874
|
-
lead_assignment =
|
|
1875
|
-
|
|
2481
|
+
lead_assignment = resolve_context_lead_provider(
|
|
2482
|
+
context=context,
|
|
1876
2483
|
requested_provider=inp.lead_provider,
|
|
1877
|
-
host_registry=default_host_registry(),
|
|
1878
|
-
provider_registry=default_provider_registry(),
|
|
1879
2484
|
)
|
|
1880
2485
|
except (
|
|
1881
2486
|
HostCapabilityMismatch,
|
|
@@ -1886,119 +2491,127 @@ def _resolve_worker_models(inp: PrepareInputs) -> dict:
|
|
|
1886
2491
|
raise PrepareError(str(exc)) from exc
|
|
1887
2492
|
lead_provider = lead_assignment.provider
|
|
1888
2493
|
report_writer_provider = inp.report_writer_provider.strip().lower() or "claude"
|
|
1889
|
-
if not
|
|
2494
|
+
if not context.pool.resolve_provider(report_writer_provider).supports_role(
|
|
2495
|
+
"report-writer"
|
|
2496
|
+
):
|
|
1890
2497
|
raise PrepareError(
|
|
1891
2498
|
f"provider {report_writer_provider!r} does not support the report-writer role"
|
|
1892
2499
|
)
|
|
2500
|
+
|
|
2501
|
+
def legacy_projection(provider: str) -> _LegacyModelProjection:
|
|
2502
|
+
assigned = provider_models.get(provider)
|
|
2503
|
+
if assigned is not None:
|
|
2504
|
+
return _LegacyModelProjection(
|
|
2505
|
+
model_id=assigned.model_id,
|
|
2506
|
+
display=assigned.display,
|
|
2507
|
+
execution=assigned.execution,
|
|
2508
|
+
)
|
|
2509
|
+
try:
|
|
2510
|
+
selected = resolve_model_selection(
|
|
2511
|
+
context.pool,
|
|
2512
|
+
provider,
|
|
2513
|
+
"analyser",
|
|
2514
|
+
worker_model_input(provider),
|
|
2515
|
+
)
|
|
2516
|
+
except (ValueError, RuntimeError) as exc:
|
|
2517
|
+
raise PrepareError(str(exc)) from exc
|
|
2518
|
+
return _LegacyModelProjection(
|
|
2519
|
+
model_id=selected.model_ref.model_id,
|
|
2520
|
+
display=selected.display_name,
|
|
2521
|
+
execution=selected.execution_value,
|
|
2522
|
+
)
|
|
2523
|
+
|
|
1893
2524
|
return {
|
|
1894
2525
|
"lead_provider": lead_provider,
|
|
1895
2526
|
"report_writer_provider": report_writer_provider,
|
|
1896
2527
|
"lead": _model_for_role(
|
|
1897
|
-
|
|
1898
|
-
|
|
2528
|
+
context=context,
|
|
2529
|
+
provider=lead_provider,
|
|
2530
|
+
raw_value=inp.lead_model,
|
|
2531
|
+
role="leader",
|
|
2532
|
+
default_override=_legacy_claude_model_override(
|
|
2533
|
+
lead_provider,
|
|
2534
|
+
"OKSTRA_DEFAULT_LEAD_MODEL",
|
|
2535
|
+
),
|
|
2536
|
+
duty_id="lead",
|
|
1899
2537
|
),
|
|
1900
|
-
"cw":
|
|
1901
|
-
"co":
|
|
1902
|
-
"ge":
|
|
2538
|
+
"cw": legacy_projection("claude"),
|
|
2539
|
+
"co": legacy_projection("codex"),
|
|
2540
|
+
"ge": legacy_projection("antigravity"),
|
|
1903
2541
|
"rw": _model_for_role(
|
|
1904
|
-
|
|
1905
|
-
|
|
2542
|
+
context=context,
|
|
2543
|
+
provider=report_writer_provider,
|
|
2544
|
+
raw_value=inp.report_writer_model,
|
|
2545
|
+
role="report-writer",
|
|
2546
|
+
default_override=_legacy_claude_model_override(
|
|
2547
|
+
report_writer_provider,
|
|
2548
|
+
"OKSTRA_DEFAULT_REPORT_WRITER_MODEL",
|
|
2549
|
+
),
|
|
1906
2550
|
),
|
|
1907
2551
|
"providers": provider_models,
|
|
1908
2552
|
"worker_model_inputs": {
|
|
1909
|
-
provider:
|
|
1910
|
-
for provider in
|
|
2553
|
+
provider: worker_model_input(provider)
|
|
2554
|
+
for provider in dict.fromkeys((
|
|
2555
|
+
*selected_worker_providers,
|
|
2556
|
+
*overrides,
|
|
2557
|
+
*legacy,
|
|
2558
|
+
))
|
|
1911
2559
|
},
|
|
1912
2560
|
}
|
|
1913
2561
|
|
|
1914
2562
|
|
|
1915
|
-
def _resolve_model_bindings(
|
|
2563
|
+
def _resolve_model_bindings(
|
|
2564
|
+
inp: PrepareInputs,
|
|
2565
|
+
workers: list[str],
|
|
2566
|
+
context: AssignmentContext,
|
|
2567
|
+
requires_executor: bool,
|
|
2568
|
+
) -> _ModelBindings:
|
|
1916
2569
|
"""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
|
-
)
|
|
2570
|
+
m = _resolve_worker_models(inp, workers, context)
|
|
2571
|
+
critic_choice = _validate_critic_choice(inp.critic)
|
|
1925
2572
|
critic_model_execution = ""
|
|
1926
2573
|
critic_meta = None
|
|
1927
2574
|
if critic_choice in provider_ids("critic"):
|
|
1928
2575
|
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])))}."
|
|
2576
|
+
context=context,
|
|
2577
|
+
provider=critic_choice,
|
|
2578
|
+
raw_value=m["worker_model_inputs"].get(critic_choice, ""),
|
|
2579
|
+
role="critic",
|
|
2580
|
+
duty_id="scope-critic",
|
|
1949
2581
|
)
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2582
|
+
critic_model_execution = critic_meta.execution
|
|
2583
|
+
executor_assignment = None
|
|
2584
|
+
display_name = ""
|
|
2585
|
+
if requires_executor:
|
|
2586
|
+
executor_assignment, display_name = _resolve_executor_assignment(
|
|
2587
|
+
inp,
|
|
2588
|
+
workers,
|
|
2589
|
+
context,
|
|
2590
|
+
m["worker_model_inputs"],
|
|
1958
2591
|
)
|
|
1959
2592
|
lead_assignment = _role_assignment(
|
|
1960
|
-
|
|
2593
|
+
resolved=m["lead"],
|
|
1961
2594
|
role="lead",
|
|
1962
|
-
provider=lead_provider,
|
|
1963
|
-
model=m["lead"],
|
|
1964
|
-
execution=lead_execution,
|
|
1965
|
-
runner_role="lead",
|
|
1966
2595
|
)
|
|
1967
|
-
worker_assignments = _build_worker_assignments(
|
|
2596
|
+
worker_assignments = _build_worker_assignments(workers, m)
|
|
1968
2597
|
critic_assignment = (
|
|
1969
2598
|
_role_assignment(
|
|
1970
|
-
|
|
2599
|
+
resolved=critic_meta,
|
|
1971
2600
|
role="critic",
|
|
1972
|
-
provider=critic_choice,
|
|
1973
|
-
model=critic_meta,
|
|
1974
|
-
execution=critic_model_execution,
|
|
1975
|
-
runner_role="critic",
|
|
1976
2601
|
)
|
|
1977
2602
|
if critic_meta is not None
|
|
1978
2603
|
else None
|
|
1979
2604
|
)
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
role="translator",
|
|
2605
|
+
translator_meta = _model_for_role(
|
|
2606
|
+
context=context,
|
|
1983
2607
|
provider=m["report_writer_provider"],
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
provider=m["report_writer_provider"],
|
|
1988
|
-
role="report-writer",
|
|
1989
|
-
),
|
|
1990
|
-
runner_role="report-writer",
|
|
2608
|
+
raw_value=m["rw"].model_id,
|
|
2609
|
+
role="translator",
|
|
2610
|
+
duty_id="translator",
|
|
1991
2611
|
)
|
|
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,
|
|
2612
|
+
translator_assignment = _role_assignment(
|
|
2613
|
+
resolved=translator_meta,
|
|
2614
|
+
role="translator",
|
|
2002
2615
|
)
|
|
2003
2616
|
invocation_assignments = _build_invocation_assignments(
|
|
2004
2617
|
lead_assignment,
|
|
@@ -2006,15 +2619,24 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
2006
2619
|
critic_assignment,
|
|
2007
2620
|
translator_assignment,
|
|
2008
2621
|
)
|
|
2622
|
+
worker_by_provider = {
|
|
2623
|
+
row.provider: row for row in worker_assignments
|
|
2624
|
+
}
|
|
2009
2625
|
return _ModelBindings(
|
|
2010
2626
|
lead=m["lead"], cw=m["cw"], co=m["co"], ge=m["ge"], rw=m["rw"],
|
|
2011
2627
|
critic_choice=critic_choice,
|
|
2012
2628
|
critic_model_execution=critic_model_execution,
|
|
2013
2629
|
executor_display_name=display_name,
|
|
2014
|
-
codex_worker_execution=
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
m["
|
|
2630
|
+
codex_worker_execution=(
|
|
2631
|
+
worker_by_provider["codex"].model_execution_value
|
|
2632
|
+
if "codex" in worker_by_provider
|
|
2633
|
+
else m["co"].execution
|
|
2634
|
+
),
|
|
2635
|
+
antigravity_worker_execution=(
|
|
2636
|
+
worker_by_provider["antigravity"].model_execution_value
|
|
2637
|
+
if "antigravity" in worker_by_provider
|
|
2638
|
+
else m["ge"].execution
|
|
2639
|
+
),
|
|
2018
2640
|
executor_assignment=executor_assignment,
|
|
2019
2641
|
lead_assignment=lead_assignment,
|
|
2020
2642
|
worker_assignments=worker_assignments,
|
|
@@ -2022,8 +2644,245 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
2022
2644
|
)
|
|
2023
2645
|
|
|
2024
2646
|
|
|
2647
|
+
def _validate_critic_choice(raw_value: str) -> str:
|
|
2648
|
+
critic_choice = (raw_value or "").strip().lower()
|
|
2649
|
+
allowed_critics = ["", "off", *provider_ids("critic")]
|
|
2650
|
+
if critic_choice not in allowed_critics:
|
|
2651
|
+
raise PrepareError(
|
|
2652
|
+
f"--critic must be one of: {', '.join(allowed_critics[1:])} "
|
|
2653
|
+
f"(got: {critic_choice!r})"
|
|
2654
|
+
)
|
|
2655
|
+
return critic_choice
|
|
2656
|
+
|
|
2657
|
+
|
|
2658
|
+
def _uses_canonical_assignment_projection(inp: PrepareInputs) -> bool:
|
|
2659
|
+
return bool(
|
|
2660
|
+
inp.role_counts_raw
|
|
2661
|
+
or inp.role_models_raw
|
|
2662
|
+
or inp.host_session_context.entry_mode == "current-session"
|
|
2663
|
+
)
|
|
2664
|
+
|
|
2665
|
+
|
|
2666
|
+
def _project_assignment_plan(
|
|
2667
|
+
inp: PrepareInputs,
|
|
2668
|
+
plan: AssignmentPlan,
|
|
2669
|
+
context: AssignmentContext,
|
|
2670
|
+
) -> tuple[list[str], _ModelBindings]:
|
|
2671
|
+
lead = plan.by_role("leader")[0]
|
|
2672
|
+
report_rows = plan.by_role("report-writer")
|
|
2673
|
+
report_writer = (
|
|
2674
|
+
report_rows[0]
|
|
2675
|
+
if report_rows
|
|
2676
|
+
else _model_for_role(
|
|
2677
|
+
context=context,
|
|
2678
|
+
provider=inp.report_writer_provider.strip().lower() or "claude",
|
|
2679
|
+
raw_value=inp.report_writer_model,
|
|
2680
|
+
role="report-writer",
|
|
2681
|
+
)
|
|
2682
|
+
)
|
|
2683
|
+
worker_rows = tuple(
|
|
2684
|
+
row for row in plan.assignments
|
|
2685
|
+
if row.role not in {"leader", "critic", "implementer", "report-writer"}
|
|
2686
|
+
)
|
|
2687
|
+
worker_assignments = tuple(
|
|
2688
|
+
_role_assignment(
|
|
2689
|
+
resolved=row,
|
|
2690
|
+
role=row.role,
|
|
2691
|
+
worker_id=_canonical_worker_id(row, worker_rows),
|
|
2692
|
+
)
|
|
2693
|
+
for row in worker_rows
|
|
2694
|
+
)
|
|
2695
|
+
if report_rows:
|
|
2696
|
+
worker_assignments = (
|
|
2697
|
+
*worker_assignments,
|
|
2698
|
+
_role_assignment(
|
|
2699
|
+
resolved=report_writer,
|
|
2700
|
+
role="report-writer",
|
|
2701
|
+
worker_id="report-writer",
|
|
2702
|
+
),
|
|
2703
|
+
)
|
|
2704
|
+
models = _project_model_bindings(
|
|
2705
|
+
inp,
|
|
2706
|
+
plan,
|
|
2707
|
+
context,
|
|
2708
|
+
lead,
|
|
2709
|
+
report_writer,
|
|
2710
|
+
worker_assignments,
|
|
2711
|
+
)
|
|
2712
|
+
return [row.worker_id for row in worker_assignments], models
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
def _canonical_worker_id(
|
|
2716
|
+
assignment: ResolvedAssignment,
|
|
2717
|
+
rows: Sequence[ResolvedAssignment],
|
|
2718
|
+
) -> str:
|
|
2719
|
+
prior = sum(
|
|
2720
|
+
row.provider_id == assignment.provider_id and row.ordinal < assignment.ordinal
|
|
2721
|
+
for row in rows
|
|
2722
|
+
)
|
|
2723
|
+
return assignment.provider_id if prior == 0 else f"{assignment.provider_id}-{prior + 1}"
|
|
2724
|
+
|
|
2725
|
+
|
|
2726
|
+
def _project_model_bindings(
|
|
2727
|
+
inp: PrepareInputs,
|
|
2728
|
+
plan: AssignmentPlan,
|
|
2729
|
+
context: AssignmentContext,
|
|
2730
|
+
lead: ResolvedAssignment,
|
|
2731
|
+
report_writer: ResolvedAssignment,
|
|
2732
|
+
workers: tuple[RoleAssignment, ...],
|
|
2733
|
+
) -> _ModelBindings:
|
|
2734
|
+
lead_assignment = _role_assignment(resolved=lead, role="lead")
|
|
2735
|
+
critic_rows = plan.by_role("critic")
|
|
2736
|
+
critic_assignment = (
|
|
2737
|
+
_role_assignment(resolved=critic_rows[0], role="critic")
|
|
2738
|
+
if critic_rows
|
|
2739
|
+
else None
|
|
2740
|
+
)
|
|
2741
|
+
executor_rows = plan.by_role("implementer")
|
|
2742
|
+
executor_assignment = (
|
|
2743
|
+
_role_assignment(
|
|
2744
|
+
resolved=executor_rows[0],
|
|
2745
|
+
role="executor",
|
|
2746
|
+
worker_id=executor_rows[0].provider_id,
|
|
2747
|
+
)
|
|
2748
|
+
if executor_rows
|
|
2749
|
+
else None
|
|
2750
|
+
)
|
|
2751
|
+
translator = _model_for_role(
|
|
2752
|
+
context=context,
|
|
2753
|
+
provider=report_writer.provider_id,
|
|
2754
|
+
raw_value=report_writer.model_id,
|
|
2755
|
+
role="translator",
|
|
2756
|
+
duty_id="translator",
|
|
2757
|
+
)
|
|
2758
|
+
projections = {
|
|
2759
|
+
provider: _legacy_projection_from_plan(provider, plan, context)
|
|
2760
|
+
for provider in ("claude", "codex", "antigravity")
|
|
2761
|
+
}
|
|
2762
|
+
invocation_assignments = _build_invocation_assignments(
|
|
2763
|
+
lead_assignment,
|
|
2764
|
+
workers,
|
|
2765
|
+
critic_assignment,
|
|
2766
|
+
_role_assignment(resolved=translator, role="translator"),
|
|
2767
|
+
)
|
|
2768
|
+
worker_by_provider = {row.provider: row for row in workers}
|
|
2769
|
+
return _ModelBindings(
|
|
2770
|
+
lead=lead,
|
|
2771
|
+
cw=projections["claude"],
|
|
2772
|
+
co=projections["codex"],
|
|
2773
|
+
ge=projections["antigravity"],
|
|
2774
|
+
rw=report_writer,
|
|
2775
|
+
critic_choice=(critic_rows[0].provider_id if critic_rows else "off"),
|
|
2776
|
+
critic_model_execution=(
|
|
2777
|
+
critic_assignment.model_execution_value if critic_assignment else ""
|
|
2778
|
+
),
|
|
2779
|
+
executor_display_name=(
|
|
2780
|
+
f"{executor_rows[0].display_name} executor" if executor_rows else ""
|
|
2781
|
+
),
|
|
2782
|
+
codex_worker_execution=(
|
|
2783
|
+
worker_by_provider["codex"].model_execution_value
|
|
2784
|
+
if "codex" in worker_by_provider
|
|
2785
|
+
else projections["codex"].execution
|
|
2786
|
+
),
|
|
2787
|
+
antigravity_worker_execution=(
|
|
2788
|
+
worker_by_provider["antigravity"].model_execution_value
|
|
2789
|
+
if "antigravity" in worker_by_provider
|
|
2790
|
+
else projections["antigravity"].execution
|
|
2791
|
+
),
|
|
2792
|
+
executor_assignment=executor_assignment,
|
|
2793
|
+
lead_assignment=lead_assignment,
|
|
2794
|
+
worker_assignments=workers,
|
|
2795
|
+
invocation_assignments=invocation_assignments,
|
|
2796
|
+
)
|
|
2797
|
+
|
|
2798
|
+
|
|
2799
|
+
def _legacy_projection_from_plan(
|
|
2800
|
+
provider: str,
|
|
2801
|
+
plan: AssignmentPlan,
|
|
2802
|
+
context: AssignmentContext,
|
|
2803
|
+
) -> _LegacyModelProjection:
|
|
2804
|
+
selected = next(
|
|
2805
|
+
(
|
|
2806
|
+
row for row in plan.assignments
|
|
2807
|
+
if row.provider_id == provider and row.role != "leader"
|
|
2808
|
+
),
|
|
2809
|
+
None,
|
|
2810
|
+
)
|
|
2811
|
+
if selected is not None:
|
|
2812
|
+
return _LegacyModelProjection(
|
|
2813
|
+
selected.model_id,
|
|
2814
|
+
selected.display_name,
|
|
2815
|
+
selected.execution,
|
|
2816
|
+
)
|
|
2817
|
+
model = resolve_model_selection(context.pool, provider, "analyser", "")
|
|
2818
|
+
return _LegacyModelProjection(
|
|
2819
|
+
model.model_ref.model_id,
|
|
2820
|
+
model.display_name,
|
|
2821
|
+
model.execution_value,
|
|
2822
|
+
)
|
|
2823
|
+
|
|
2824
|
+
|
|
2825
|
+
def _selected_execution_provider_ids(
|
|
2826
|
+
inp: PrepareInputs,
|
|
2827
|
+
workers: list[str],
|
|
2828
|
+
requires_executor: bool,
|
|
2829
|
+
) -> tuple[str, ...]:
|
|
2830
|
+
selected = [
|
|
2831
|
+
inp.lead_provider,
|
|
2832
|
+
*(worker for worker in workers if worker != "report-writer"),
|
|
2833
|
+
inp.report_writer_provider.strip().lower() or "claude",
|
|
2834
|
+
]
|
|
2835
|
+
critic = inp.critic.strip().lower()
|
|
2836
|
+
if critic not in {"", "off"}:
|
|
2837
|
+
selected.append(critic)
|
|
2838
|
+
if requires_executor:
|
|
2839
|
+
selected.append(
|
|
2840
|
+
inp.executor.strip().lower()
|
|
2841
|
+
or _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
2842
|
+
)
|
|
2843
|
+
return tuple(dict.fromkeys(selected))
|
|
2844
|
+
|
|
2845
|
+
|
|
2846
|
+
def _resolve_executor_assignment(
|
|
2847
|
+
inp: PrepareInputs,
|
|
2848
|
+
workers: list[str],
|
|
2849
|
+
context: AssignmentContext,
|
|
2850
|
+
worker_model_inputs: dict[str, str],
|
|
2851
|
+
) -> tuple[RoleAssignment, str]:
|
|
2852
|
+
executor_default = _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
2853
|
+
executor_provider = (inp.executor or executor_default).strip().lower()
|
|
2854
|
+
allowed_executors = provider_ids("executor")
|
|
2855
|
+
if executor_provider not in allowed_executors:
|
|
2856
|
+
raise PrepareError(
|
|
2857
|
+
f"--executor must be one of: {', '.join(allowed_executors)} "
|
|
2858
|
+
f"(got: {executor_provider!r})"
|
|
2859
|
+
)
|
|
2860
|
+
if executor_provider not in workers:
|
|
2861
|
+
raise PrepareError(
|
|
2862
|
+
f"--executor {executor_provider} requires {executor_provider!r} in "
|
|
2863
|
+
f"--workers, but resolved roster is {workers!r}. "
|
|
2864
|
+
f"Add it explicitly, e.g. --workers {','.join(sorted(set(workers + [executor_provider])))}."
|
|
2865
|
+
)
|
|
2866
|
+
model = _model_for_role(
|
|
2867
|
+
context=context,
|
|
2868
|
+
provider=executor_provider,
|
|
2869
|
+
raw_value=worker_model_inputs.get(executor_provider, ""),
|
|
2870
|
+
role="implementer",
|
|
2871
|
+
duty_id="implementation-executor",
|
|
2872
|
+
)
|
|
2873
|
+
assignment = _role_assignment(
|
|
2874
|
+
resolved=model,
|
|
2875
|
+
role="executor",
|
|
2876
|
+
worker_id=executor_provider,
|
|
2877
|
+
)
|
|
2878
|
+
display_name = (
|
|
2879
|
+
f"{context.pool.resolve_provider(executor_provider).display_label} executor"
|
|
2880
|
+
)
|
|
2881
|
+
return assignment, display_name
|
|
2882
|
+
|
|
2883
|
+
|
|
2025
2884
|
def _build_worker_assignments(
|
|
2026
|
-
|
|
2885
|
+
workers: list[str], models: dict,
|
|
2027
2886
|
) -> tuple[RoleAssignment, ...]:
|
|
2028
2887
|
assignments: list[RoleAssignment] = []
|
|
2029
2888
|
for worker_id in workers:
|
|
@@ -2031,18 +2890,12 @@ def _build_worker_assignments(
|
|
|
2031
2890
|
provider = (
|
|
2032
2891
|
models["report_writer_provider"] if is_report_writer else worker_id
|
|
2033
2892
|
)
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
execution = _worker_execution_value(
|
|
2037
|
-
meta, provider, "worker", [provider],
|
|
2893
|
+
selected = (
|
|
2894
|
+
models["rw"] if is_report_writer else models["providers"][provider]
|
|
2038
2895
|
)
|
|
2039
2896
|
assignments.append(_role_assignment(
|
|
2040
|
-
|
|
2041
|
-
role=
|
|
2042
|
-
provider=provider,
|
|
2043
|
-
model=meta,
|
|
2044
|
-
execution=execution,
|
|
2045
|
-
runner_role=role,
|
|
2897
|
+
resolved=selected,
|
|
2898
|
+
role="report-writer" if is_report_writer else "analyser",
|
|
2046
2899
|
worker_id=worker_id,
|
|
2047
2900
|
))
|
|
2048
2901
|
return tuple(assignments)
|
|
@@ -2050,55 +2903,40 @@ def _build_worker_assignments(
|
|
|
2050
2903
|
|
|
2051
2904
|
def _role_assignment(
|
|
2052
2905
|
*,
|
|
2053
|
-
|
|
2906
|
+
resolved: ResolvedAssignment,
|
|
2054
2907
|
role: str,
|
|
2055
|
-
provider: str,
|
|
2056
|
-
model: ModelAssignment,
|
|
2057
|
-
execution: str,
|
|
2058
|
-
runner_role: str,
|
|
2059
2908
|
worker_id: str = "",
|
|
2060
2909
|
) -> RoleAssignment:
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2910
|
+
binding = resolved.binding
|
|
2911
|
+
if binding is None:
|
|
2912
|
+
if resolved.role != "leader" or resolved.entry_mode != "current-session":
|
|
2913
|
+
raise PrepareError("legacy assignment projection requires a model binding")
|
|
2914
|
+
return RoleAssignment(
|
|
2915
|
+
role=role,
|
|
2916
|
+
provider=resolved.provider_id,
|
|
2917
|
+
model_display=resolved.display_name,
|
|
2918
|
+
model_execution_value="unknown",
|
|
2919
|
+
runner="cli-wrapper",
|
|
2920
|
+
host_runtime=resolved.host_runtime,
|
|
2921
|
+
host_model_value=None,
|
|
2922
|
+
worker_id=worker_id,
|
|
2923
|
+
)
|
|
2069
2924
|
return RoleAssignment(
|
|
2070
2925
|
role=role,
|
|
2071
|
-
provider=
|
|
2072
|
-
model_display=
|
|
2073
|
-
model_execution_value=
|
|
2074
|
-
runner=
|
|
2075
|
-
|
|
2076
|
-
|
|
2926
|
+
provider=resolved.provider_id,
|
|
2927
|
+
model_display=resolved.display_name,
|
|
2928
|
+
model_execution_value=binding.resolved_execution_value,
|
|
2929
|
+
runner=(
|
|
2930
|
+
"native-session"
|
|
2931
|
+
if binding.runner == "current-session"
|
|
2932
|
+
else binding.runner
|
|
2933
|
+
),
|
|
2934
|
+
host_runtime=resolved.host_runtime,
|
|
2935
|
+
host_model_value=binding.host_model_value,
|
|
2077
2936
|
worker_id=worker_id,
|
|
2078
2937
|
)
|
|
2079
2938
|
|
|
2080
2939
|
|
|
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
2940
|
def _build_invocation_assignments(
|
|
2103
2941
|
lead: RoleAssignment,
|
|
2104
2942
|
workers: tuple[RoleAssignment, ...],
|
|
@@ -2120,27 +2958,6 @@ def _build_invocation_assignments(
|
|
|
2120
2958
|
return assignments
|
|
2121
2959
|
|
|
2122
2960
|
|
|
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
2961
|
StageRunClaim = _implementation_stage.StageRunClaim
|
|
2145
2962
|
|
|
2146
2963
|
|
|
@@ -2529,11 +3346,18 @@ def _render_lead_prompt_and_snapshot(
|
|
|
2529
3346
|
assignment = _agent_model_assignment(
|
|
2530
3347
|
json.loads(ctx["INVOCATION_ASSIGNMENTS_JSON"])["lead"]
|
|
2531
3348
|
)
|
|
3349
|
+
identity = invocation_execution_identity_from_manifest(
|
|
3350
|
+
json.loads(Path(ctx["RUN_MANIFEST_PATH"]).read_text(encoding="utf-8")),
|
|
3351
|
+
assignment=assignment,
|
|
3352
|
+
assignment_ref="lead",
|
|
3353
|
+
duty_id="lead",
|
|
3354
|
+
)
|
|
3355
|
+
if identity is None:
|
|
3356
|
+
raise PrepareError("lead v2 execution identity is missing")
|
|
3357
|
+
invocation_id = f"{ctx['TASK_TYPE_SEGMENT']}-{ctx['RUN_PROMPTS_SEQ']}-lead"
|
|
2532
3358
|
prepared = prepare_agent_invocation(AgentInvocationRequest(
|
|
2533
|
-
invocation_id=
|
|
2534
|
-
|
|
2535
|
-
),
|
|
2536
|
-
worker_id="lead",
|
|
3359
|
+
invocation_id=invocation_id,
|
|
3360
|
+
worker_id=None,
|
|
2537
3361
|
audience="lead",
|
|
2538
3362
|
assignment_ref="lead",
|
|
2539
3363
|
purpose=None,
|
|
@@ -2557,6 +3381,11 @@ def _render_lead_prompt_and_snapshot(
|
|
|
2557
3381
|
prompt_path=Path(ctx["RUN_PROMPT_SNAPSHOT_FILE"]),
|
|
2558
3382
|
metadata_path=Path(ctx["LEAD_PROMPT_METADATA_PATH"]),
|
|
2559
3383
|
dispatch_kind="lead",
|
|
3384
|
+
participant_ref=identity.participant_ref,
|
|
3385
|
+
role_execution_ref=identity.role_execution_ref,
|
|
3386
|
+
duty_id=identity.duty_id,
|
|
3387
|
+
invocation_ref=invocation_id,
|
|
3388
|
+
attempt=1,
|
|
2560
3389
|
))
|
|
2561
3390
|
return prepared.prompt_path.read_text(encoding="utf-8")
|
|
2562
3391
|
|
|
@@ -2581,12 +3410,7 @@ def _persist_run_inputs(
|
|
|
2581
3410
|
if inp.approved_plan_path
|
|
2582
3411
|
else ""
|
|
2583
3412
|
)
|
|
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={
|
|
3413
|
+
inputs = {
|
|
2590
3414
|
"taskBriefPath": brief_relative,
|
|
2591
3415
|
"taskBriefAbsolutePath": str(inp.brief_path),
|
|
2592
3416
|
"directive": inp.directive,
|
|
@@ -2608,7 +3432,11 @@ def _persist_run_inputs(
|
|
|
2608
3432
|
inp.report_writer_provider or "claude",
|
|
2609
3433
|
),
|
|
2610
3434
|
"reportWriterModel": models.rw.display,
|
|
2611
|
-
"executor":
|
|
3435
|
+
"executor": (
|
|
3436
|
+
models.executor_assignment.provider
|
|
3437
|
+
if models.executor_assignment is not None
|
|
3438
|
+
else ""
|
|
3439
|
+
),
|
|
2612
3440
|
"relatedTasks": inp.related_tasks_raw,
|
|
2613
3441
|
"approvedPlanPath": approved_plan_path,
|
|
2614
3442
|
"clarificationResponsePath": inp.clarification_response_path,
|
|
@@ -2618,7 +3446,13 @@ def _persist_run_inputs(
|
|
|
2618
3446
|
),
|
|
2619
3447
|
"evidenceInputs": json.loads(ctx.get("EVIDENCE_INPUTS_JSON", "[]")),
|
|
2620
3448
|
"renderOnly": inp.render_only,
|
|
2621
|
-
|
|
3449
|
+
}
|
|
3450
|
+
run_inputs_path = _write_run_inputs_once(
|
|
3451
|
+
project_root=Path(inp.project_root),
|
|
3452
|
+
run_manifests_dir=Path(ctx["RUN_MANIFESTS_DIR"]),
|
|
3453
|
+
task_type_segment=ctx["TASK_TYPE_SEGMENT"],
|
|
3454
|
+
seq=ctx["RUN_MANIFESTS_SEQ"],
|
|
3455
|
+
inputs=inputs,
|
|
2622
3456
|
)
|
|
2623
3457
|
ctx["RUN_INPUTS_PATH"] = str(run_inputs_path)
|
|
2624
3458
|
ctx["RUN_INPUTS_RELATIVE_PATH"] = relative_to_project_root(
|
|
@@ -2626,6 +3460,29 @@ def _persist_run_inputs(
|
|
|
2626
3460
|
)
|
|
2627
3461
|
|
|
2628
3462
|
|
|
3463
|
+
def _write_run_inputs_once(
|
|
3464
|
+
*,
|
|
3465
|
+
project_root: Path,
|
|
3466
|
+
run_manifests_dir: Path,
|
|
3467
|
+
task_type_segment: str,
|
|
3468
|
+
seq: str,
|
|
3469
|
+
inputs: dict,
|
|
3470
|
+
) -> Path:
|
|
3471
|
+
path = run_manifests_dir / f"run-inputs-{task_type_segment}-{seq}.json"
|
|
3472
|
+
existing = _read_existing_manifest(path)
|
|
3473
|
+
if existing.get("schemaVersion") == "2.0":
|
|
3474
|
+
if existing.get("inputs") != inputs:
|
|
3475
|
+
raise PrepareError("input snapshot is immutable after v2 creation")
|
|
3476
|
+
return path
|
|
3477
|
+
return write_run_inputs(
|
|
3478
|
+
project_root=project_root,
|
|
3479
|
+
run_manifests_dir=run_manifests_dir,
|
|
3480
|
+
task_type_segment=task_type_segment,
|
|
3481
|
+
seq=seq,
|
|
3482
|
+
inputs=inputs,
|
|
3483
|
+
)
|
|
3484
|
+
|
|
3485
|
+
|
|
2629
3486
|
def _persist_pre_dispatch_run_manifest(ctx: dict) -> None:
|
|
2630
3487
|
"""Publish and re-read the complete invocation authority before prompts."""
|
|
2631
3488
|
render_run_manifest(ctx["RUN_MANIFEST_PATH"], ctx)
|
|
@@ -3023,24 +3880,38 @@ def _model_ctx(models: "_ModelBindings") -> dict[str, str]:
|
|
|
3023
3880
|
),
|
|
3024
3881
|
"",
|
|
3025
3882
|
),
|
|
3026
|
-
|
|
3027
|
-
"
|
|
3883
|
+
**_executor_model_ctx(models),
|
|
3884
|
+
"CRITIC_CHOICE": models.critic_choice,
|
|
3885
|
+
"CRITIC_MODEL_EXECUTION_VALUE": models.critic_model_execution,
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
|
|
3889
|
+
def _executor_model_ctx(models: "_ModelBindings") -> dict[str, str]:
|
|
3890
|
+
assignment = models.executor_assignment
|
|
3891
|
+
if assignment is None:
|
|
3892
|
+
return {
|
|
3893
|
+
"EXECUTOR_WORKER_ID": "",
|
|
3894
|
+
"EXECUTOR_PROVIDER": "",
|
|
3895
|
+
"EXECUTOR_DISPLAY_NAME": "",
|
|
3896
|
+
"EXECUTOR_MODEL_DISPLAY": "",
|
|
3897
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE": "",
|
|
3898
|
+
"EXECUTOR_HOST_MODEL_VALUE": "",
|
|
3899
|
+
"EXECUTOR_RUNNER": "",
|
|
3900
|
+
"EXECUTOR_DISPATCH_MODE": "",
|
|
3901
|
+
}
|
|
3902
|
+
return {
|
|
3903
|
+
"EXECUTOR_WORKER_ID": assignment.worker_id,
|
|
3904
|
+
"EXECUTOR_PROVIDER": assignment.provider,
|
|
3028
3905
|
"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,
|
|
3906
|
+
"EXECUTOR_MODEL_DISPLAY": assignment.model_display,
|
|
3907
|
+
"EXECUTOR_MODEL_EXECUTION_VALUE": assignment.model_execution_value,
|
|
3908
|
+
"EXECUTOR_HOST_MODEL_VALUE": assignment.host_model_value or "",
|
|
3909
|
+
"EXECUTOR_RUNNER": assignment.runner,
|
|
3037
3910
|
"EXECUTOR_DISPATCH_MODE": (
|
|
3038
3911
|
"host-native"
|
|
3039
|
-
if
|
|
3912
|
+
if assignment.runner == "native-session"
|
|
3040
3913
|
else "worker-dispatch"
|
|
3041
3914
|
),
|
|
3042
|
-
"CRITIC_CHOICE": models.critic_choice,
|
|
3043
|
-
"CRITIC_MODEL_EXECUTION_VALUE": models.critic_model_execution,
|
|
3044
3915
|
}
|
|
3045
3916
|
|
|
3046
3917
|
|
|
@@ -3147,6 +4018,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3147
4018
|
# 조립 코드가 단일 흐름으로 읽히도록 유지한다.
|
|
3148
4019
|
assets = _resolve_runtime_assets(workspace_root, inp)
|
|
3149
4020
|
profile_file = assets.profile_file
|
|
4021
|
+
requires_executor = _profile_requires_executor(profile_file)
|
|
4022
|
+
selection, role_profile = _normalize_prepare_model_selection(inp, profile_file)
|
|
3150
4023
|
prompt_template = assets.prompt_template
|
|
3151
4024
|
task_index_template = assets.task_index_template
|
|
3152
4025
|
final_report_template = assets.final_report_template
|
|
@@ -3168,13 +4041,45 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3168
4041
|
handoff_tokens = _materialize_release_handoff_input(
|
|
3169
4042
|
workspace_root, project_root, inp)
|
|
3170
4043
|
|
|
3171
|
-
verify_installation(workspace_root)
|
|
3172
|
-
_register_and_check_project(project_root, inp)
|
|
3173
|
-
|
|
3174
4044
|
workers, selected_reviewers = _resolve_roster(inp, profile_file)
|
|
3175
4045
|
pr_template_path_str, pr_template_source = _resolve_pr_template(inp)
|
|
3176
4046
|
|
|
3177
|
-
|
|
4047
|
+
assignment_context, assignment_plan = _resolve_prepare_assignments(
|
|
4048
|
+
inp=inp,
|
|
4049
|
+
profile=role_profile,
|
|
4050
|
+
selection=selection,
|
|
4051
|
+
workers=workers,
|
|
4052
|
+
lead_runtime=lead_runtime,
|
|
4053
|
+
terminal_backend=terminal_backend,
|
|
4054
|
+
requires_executor=requires_executor,
|
|
4055
|
+
)
|
|
4056
|
+
if _uses_canonical_assignment_projection(inp):
|
|
4057
|
+
workers, models = _project_assignment_plan(
|
|
4058
|
+
inp,
|
|
4059
|
+
assignment_plan,
|
|
4060
|
+
assignment_context,
|
|
4061
|
+
)
|
|
4062
|
+
selected_reviewers = ",".join(workers)
|
|
4063
|
+
else:
|
|
4064
|
+
models = _resolve_model_bindings(
|
|
4065
|
+
inp,
|
|
4066
|
+
workers,
|
|
4067
|
+
assignment_context,
|
|
4068
|
+
requires_executor,
|
|
4069
|
+
)
|
|
4070
|
+
execution_manifest = _build_static_execution_manifest(
|
|
4071
|
+
assignment_plan,
|
|
4072
|
+
assignment_context,
|
|
4073
|
+
models.lead,
|
|
4074
|
+
)
|
|
4075
|
+
dynamic_roles = tuple(
|
|
4076
|
+
requirement.role
|
|
4077
|
+
for requirement in role_profile.roles
|
|
4078
|
+
if requirement.dynamic
|
|
4079
|
+
)
|
|
4080
|
+
|
|
4081
|
+
verify_installation(workspace_root)
|
|
4082
|
+
_register_and_check_project(project_root, inp)
|
|
3178
4083
|
|
|
3179
4084
|
# ---- paths under per-task mutex (writes run-context-*.json) ----
|
|
3180
4085
|
# OKSTRA_RUN_SEQ_OVERRIDE: okstra-ctl rerun / 테스트 hook 이 미리 reserve
|
|
@@ -3327,6 +4232,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3327
4232
|
"LATEST_REPORT_RELATIVE_PATH": "",
|
|
3328
4233
|
"RENDER_ONLY": "true" if inp.render_only else "false",
|
|
3329
4234
|
"OKSTRA_VERSION": installed_version(),
|
|
4235
|
+
"EXECUTION_IDENTITY_JSON": json.dumps(
|
|
4236
|
+
execution_manifest.to_payload(), ensure_ascii=False,
|
|
4237
|
+
),
|
|
4238
|
+
"DYNAMIC_EXECUTION_ROLES_JSON": json.dumps(
|
|
4239
|
+
dynamic_roles, ensure_ascii=False,
|
|
4240
|
+
),
|
|
3330
4241
|
})
|
|
3331
4242
|
refresh_run_context_snapshot(ctx)
|
|
3332
4243
|
if lead_runtime == "codex":
|
|
@@ -3366,6 +4277,15 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3366
4277
|
_finalize_status_and_render_manifests(
|
|
3367
4278
|
inp, ctx, task_index_template, forbidden_by_phase
|
|
3368
4279
|
)
|
|
4280
|
+
persist_static_execution_identity(
|
|
4281
|
+
run_manifest_path=Path(ctx["RUN_MANIFEST_PATH"]),
|
|
4282
|
+
task_manifest_path=Path(ctx["TASK_MANIFEST_PATH"]),
|
|
4283
|
+
input_snapshot_path=Path(ctx["RUN_INPUTS_PATH"]),
|
|
4284
|
+
manifest=execution_manifest,
|
|
4285
|
+
task_key=ctx["TASK_KEY"],
|
|
4286
|
+
run_ref=ctx["RUN_MANIFEST_RELATIVE_PATH"],
|
|
4287
|
+
dynamic_roles=dynamic_roles,
|
|
4288
|
+
)
|
|
3369
4289
|
_record_artifact_runtime_render_only_event(inp, ctx)
|
|
3370
4290
|
|
|
3371
4291
|
_record_run_in_central_index(inp, ctx, workspace_root, run_seq_override)
|
|
@@ -3389,10 +4309,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3389
4309
|
)
|
|
3390
4310
|
|
|
3391
4311
|
|
|
3392
|
-
def
|
|
3393
|
-
"""
|
|
3394
|
-
(the same flags `okstra.sh` accepts), runs prepare_task_bundle, prints
|
|
3395
|
-
summary lines + prompt to stdout."""
|
|
4312
|
+
def build_prepare_argument_parser():
|
|
4313
|
+
"""Build the shared flat-argument parser used by public prepare shims."""
|
|
3396
4314
|
import argparse
|
|
3397
4315
|
|
|
3398
4316
|
p = argparse.ArgumentParser()
|
|
@@ -3422,6 +4340,13 @@ def main(argv: list[str]) -> int:
|
|
|
3422
4340
|
),
|
|
3423
4341
|
)
|
|
3424
4342
|
p.add_argument("--workers", default="", dest="workers_override")
|
|
4343
|
+
p.add_argument("--role-count", action="append", default=[], dest="role_counts_raw")
|
|
4344
|
+
p.add_argument("--role-model", action="append", default=[], dest="role_models_raw")
|
|
4345
|
+
p.add_argument(
|
|
4346
|
+
"--host-session-context-json",
|
|
4347
|
+
default="",
|
|
4348
|
+
dest="host_session_context_json",
|
|
4349
|
+
)
|
|
3425
4350
|
p.add_argument("--lead-provider", default="")
|
|
3426
4351
|
p.add_argument("--lead-model", default="")
|
|
3427
4352
|
p.add_argument("--claude-model", default="")
|
|
@@ -3565,7 +4490,12 @@ def main(argv: list[str]) -> int:
|
|
|
3565
4490
|
"`main` / `dev` / `staging` / `preprod` / `prod` or any local ref."
|
|
3566
4491
|
),
|
|
3567
4492
|
)
|
|
3568
|
-
|
|
4493
|
+
return p
|
|
4494
|
+
|
|
4495
|
+
|
|
4496
|
+
def main(argv: list[str]) -> int:
|
|
4497
|
+
"""Parse one public prepare invocation and emit its rendered bundle."""
|
|
4498
|
+
args = build_prepare_argument_parser().parse_args(argv)
|
|
3569
4499
|
|
|
3570
4500
|
project_root = Path(args.project_root).expanduser().resolve()
|
|
3571
4501
|
if args.task_type == "release-handoff":
|
|
@@ -3609,6 +4539,25 @@ def main(argv: list[str]) -> int:
|
|
|
3609
4539
|
return 1
|
|
3610
4540
|
selected_direction_abs = str(selected)
|
|
3611
4541
|
|
|
4542
|
+
try:
|
|
4543
|
+
host_session_context = deserialize_host_session_context(
|
|
4544
|
+
args.host_session_context_json
|
|
4545
|
+
)
|
|
4546
|
+
except ModelSelectionInputError as exc:
|
|
4547
|
+
print(str(exc), file=__import__("sys").stderr)
|
|
4548
|
+
return 1
|
|
4549
|
+
if not args.host_session_context_json:
|
|
4550
|
+
adapter = default_host_registry().resolve(args.lead_runtime)
|
|
4551
|
+
host_session_context = HostSessionContext(
|
|
4552
|
+
host_id=adapter.descriptor.id,
|
|
4553
|
+
entry_mode="spawn-process",
|
|
4554
|
+
available_functions=frozenset(),
|
|
4555
|
+
interaction_surface="unavailable",
|
|
4556
|
+
current_model=CurrentSessionModelAttestation.unknown(
|
|
4557
|
+
adapter.descriptor.native_provider_id
|
|
4558
|
+
),
|
|
4559
|
+
)
|
|
4560
|
+
|
|
3612
4561
|
inputs = PrepareInputs(
|
|
3613
4562
|
workspace_root=Path(args.workspace_root).resolve(),
|
|
3614
4563
|
project_root=project_root,
|
|
@@ -3621,6 +4570,9 @@ def main(argv: list[str]) -> int:
|
|
|
3621
4570
|
evidence_inputs_raw=args.evidence_inputs_raw,
|
|
3622
4571
|
directive=args.directive,
|
|
3623
4572
|
workers_override=args.workers_override,
|
|
4573
|
+
role_counts_raw=tuple(args.role_counts_raw),
|
|
4574
|
+
role_models_raw=tuple(args.role_models_raw),
|
|
4575
|
+
host_session_context=host_session_context,
|
|
3624
4576
|
lead_provider=args.lead_provider,
|
|
3625
4577
|
lead_model=args.lead_model,
|
|
3626
4578
|
claude_model=args.claude_model,
|