okstra 0.173.0 → 0.175.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/bin/okstra +8 -4
- package/dist/cli-registry.d.mts +16 -0
- package/dist/cli-registry.mjs +560 -0
- package/dist/cli-registry.mjs.map +1 -0
- package/dist/commands/execute/agent-prompt.d.mts +1 -0
- package/{src → dist}/commands/execute/agent-prompt.mjs +10 -11
- package/dist/commands/execute/agent-prompt.mjs.map +1 -0
- package/dist/commands/execute/codex-dispatch.d.mts +3 -0
- package/dist/commands/execute/codex-dispatch.mjs +6 -0
- package/dist/commands/execute/codex-dispatch.mjs.map +1 -0
- package/dist/commands/execute/codex-run.d.mts +3 -0
- package/{src → dist}/commands/execute/codex-run.mjs +30 -37
- package/dist/commands/execute/codex-run.mjs.map +1 -0
- package/dist/commands/execute/convergence.d.mts +1 -0
- package/{src → dist}/commands/execute/convergence.mjs +15 -16
- package/dist/commands/execute/convergence.mjs.map +1 -0
- package/dist/commands/execute/error-log.d.mts +1 -0
- package/{src → dist}/commands/execute/error-log.mjs +2 -3
- package/dist/commands/execute/error-log.mjs.map +1 -0
- package/dist/commands/execute/git-reconcile.d.mts +1 -0
- package/{src → dist}/commands/execute/git-reconcile.mjs +10 -11
- package/dist/commands/execute/git-reconcile.mjs.map +1 -0
- package/dist/commands/execute/handoff.d.mts +1 -0
- package/{src → dist}/commands/execute/handoff.mjs +10 -11
- package/dist/commands/execute/handoff.mjs.map +1 -0
- package/dist/commands/execute/incremental-carry.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-carry.mjs +2 -3
- package/dist/commands/execute/incremental-carry.mjs.map +1 -0
- package/dist/commands/execute/incremental-scope.d.mts +1 -0
- package/{src → dist}/commands/execute/incremental-scope.mjs +2 -3
- package/dist/commands/execute/incremental-scope.mjs.map +1 -0
- package/dist/commands/execute/integrate-stages.d.mts +1 -0
- package/{src → dist}/commands/execute/integrate-stages.mjs +10 -11
- package/dist/commands/execute/integrate-stages.mjs.map +1 -0
- package/dist/commands/execute/pane-title.d.mts +1 -0
- package/dist/commands/execute/pane-title.mjs +20 -0
- package/dist/commands/execute/pane-title.mjs.map +1 -0
- package/dist/commands/execute/plan-items.d.mts +1 -0
- package/dist/commands/execute/plan-items.mjs +9 -0
- package/dist/commands/execute/plan-items.mjs.map +1 -0
- package/dist/commands/execute/plan-validate.d.mts +1 -0
- package/dist/commands/execute/plan-validate.mjs +68 -0
- package/dist/commands/execute/plan-validate.mjs.map +1 -0
- package/dist/commands/execute/plan-verify.d.mts +1 -0
- package/{src → dist}/commands/execute/plan-verify.mjs +13 -16
- package/dist/commands/execute/plan-verify.mjs.map +1 -0
- package/dist/commands/execute/render-bundle.d.mts +3 -0
- package/{src → dist}/commands/execute/render-bundle.mjs +65 -61
- package/dist/commands/execute/render-bundle.mjs.map +1 -0
- package/dist/commands/execute/run.d.mts +24 -0
- package/dist/commands/execute/run.mjs +196 -0
- package/dist/commands/execute/run.mjs.map +1 -0
- package/dist/commands/execute/spawn-followups.d.mts +1 -0
- package/{src → dist}/commands/execute/spawn-followups.mjs +6 -7
- package/dist/commands/execute/spawn-followups.mjs.map +1 -0
- package/dist/commands/execute/team.d.mts +3 -0
- package/dist/commands/execute/team.mjs +56 -0
- 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/dist/commands/execute/worktree-status.mjs +115 -0
- 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/dist/commands/report/finalize.mjs +64 -0
- 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 +17 -3
- package/docs/architecture.md +12 -22
- package/docs/cli.md +29 -8
- package/docs/container.md +1 -1
- package/docs/contributor-change-matrix.md +3 -3
- package/docs/project-structure-overview.md +29 -20
- package/docs/task-process/README.md +3 -1
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/package.json +11 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +3 -2
- 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 +4 -1
- package/runtime/bin/lib/okstra/usage.sh +4 -4
- package/runtime/bin/okstra-error-log.py +27 -0
- package/runtime/bin/okstra-provider-exec.py +245 -16
- package/runtime/bin/okstra-trace-cleanup.sh +58 -129
- package/runtime/bin/okstra.sh +10 -0
- package/runtime/prompts/lead/adapters/cmux.md +2 -0
- package/runtime/prompts/lead/convergence.md +27 -18
- package/runtime/prompts/lead/okstra-lead-contract.md +4 -2
- package/runtime/prompts/lead/plan-body-verification.md +13 -6
- package/runtime/prompts/lead/report-writer.md +6 -6
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +2 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/change-impact-analysis.md +16 -0
- package/runtime/prompts/profiles/error-analysis.md +21 -1
- package/runtime/prompts/profiles/feature-analysis.md +16 -0
- package/runtime/prompts/profiles/final-verification.md +16 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +16 -0
- package/runtime/prompts/profiles/implementation-planning.md +32 -9
- package/runtime/prompts/profiles/implementation.md +17 -1
- package/runtime/prompts/profiles/improvement-discovery.md +16 -0
- package/runtime/prompts/profiles/project-analysis.md +16 -0
- package/runtime/prompts/profiles/release-handoff.md +5 -1
- package/runtime/prompts/profiles/requirements-discovery.md +20 -0
- package/runtime/prompts/wizard/prompts.ko.json +18 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +1 -6
- 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/external/relay.md +4 -4
- 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 +100 -25
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +59 -14
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +55 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +51 -12
- package/runtime/python/okstra_ctl/agent_activity.py +5 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +608 -14
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +249 -50
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +53 -12
- package/runtime/python/okstra_ctl/assignment_environment.py +102 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +790 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +342 -0
- package/runtime/python/okstra_ctl/conformance.py +68 -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 +1181 -102
- package/runtime/python/okstra_ctl/dispatch_state.py +835 -22
- package/runtime/python/okstra_ctl/doctor.py +16 -2
- package/runtime/python/okstra_ctl/domain/host.py +32 -1
- package/runtime/python/okstra_ctl/domain/provider.py +212 -6
- package/runtime/python/okstra_ctl/domain/role.py +125 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +13 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +39 -1
- package/runtime/python/okstra_ctl/domain/worker_role.py +13 -3
- package/runtime/python/okstra_ctl/domain/worker_stream.py +24 -0
- package/runtime/python/okstra_ctl/error_log_write.py +9 -0
- package/runtime/python/okstra_ctl/error_report.py +21 -1
- package/runtime/python/okstra_ctl/execution_identity.py +529 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +1281 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +619 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +5 -4
- package/runtime/python/okstra_ctl/index.py +6 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +43 -3
- package/runtime/python/okstra_ctl/legacy_model_selection.py +490 -0
- package/runtime/python/okstra_ctl/model_cli.py +278 -0
- package/runtime/python/okstra_ctl/model_defaults.py +46 -0
- package/runtime/python/okstra_ctl/model_discovery.py +26 -7
- package/runtime/python/okstra_ctl/model_pool.py +501 -0
- package/runtime/python/okstra_ctl/models.py +47 -27
- package/runtime/python/okstra_ctl/mutation_recovery.py +293 -0
- package/runtime/python/okstra_ctl/pane_reclaim.py +13 -22
- package/runtime/python/okstra_ctl/pane_title.py +151 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +48 -17
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/registry/provider_registry.py +8 -4
- package/runtime/python/okstra_ctl/render.py +78 -10
- package/runtime/python/okstra_ctl/render_final_report.py +34 -19
- package/runtime/python/okstra_ctl/report_contract.py +39 -1
- package/runtime/python/okstra_ctl/report_finalize.py +84 -9
- package/runtime/python/okstra_ctl/report_html/render.py +1 -0
- package/runtime/python/okstra_ctl/role_requirements.py +217 -0
- package/runtime/python/okstra_ctl/run.py +1250 -260
- package/runtime/python/okstra_ctl/run_index_row.py +7 -1
- package/runtime/python/okstra_ctl/schema_excerpt.py +1 -1
- package/runtime/python/okstra_ctl/scope_provenance.py +1 -1
- package/runtime/python/okstra_ctl/session.py +69 -12
- package/runtime/python/okstra_ctl/team.py +51 -25
- package/runtime/python/okstra_ctl/time_report.py +38 -4
- package/runtime/python/okstra_ctl/tmux.py +19 -149
- package/runtime/python/okstra_ctl/usage_identity.py +86 -0
- package/runtime/python/okstra_ctl/usage_report.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +1253 -62
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +68 -65
- package/runtime/python/okstra_ctl/worker_request.py +9 -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/worktree.py +69 -3
- package/runtime/python/okstra_ctl/write_policy.py +571 -0
- package/runtime/python/okstra_token_usage/cli.py +1 -1
- package/runtime/python/okstra_token_usage/collect.py +66 -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 +25 -0
- package/runtime/skills/okstra-run/SKILL.md +9 -9
- package/runtime/skills/okstra-setup/references/project-config.md +11 -0
- 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/settings.template.json +0 -24
- package/runtime/templates/worker-prompt-preamble.md +6 -2
- package/runtime/validators/lib/fixtures.sh +98 -33
- package/runtime/validators/validate-implementation-plan-stages.py +63 -3
- package/runtime/validators/validate-run.py +443 -508
- package/runtime/validators/validate_session_conformance.py +26 -8
- package/runtime/bin/okstra-subagent-reclaim.sh +0 -26
- package/runtime/schemas/final-report-v1.0.schema.json +0 -6366
- package/runtime/templates/reports/final-report.template.md +0 -1258
- package/src/cli-registry.mjs +0 -532
- 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/team.mjs +0 -63
- 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 -1172
- 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/commands/report/finalize.mjs +0 -64
- package/src/lib/host-registry-client.mjs +0 -176
- package/src/lib/paths.mjs +0 -86
- package/src/lib/proc.mjs +0 -31
- package/src/lib/python-helper.mjs +0 -142
- package/src/lib/runtime-manifest.mjs +0 -70
- package/src/lib/skill-catalog.mjs +0 -53
- package/src/lib/version.mjs +0 -20
- /package/{src → dist}/commands/pr/default.md +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Mutation recovery decisions after sealed or unsealed implementer attempts."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Literal, Mapping, Sequence
|
|
7
|
+
|
|
8
|
+
from .attempt_evidence import AttemptEvidenceSeal, _sha
|
|
9
|
+
from .dispatch_state import build_dispatch_id
|
|
10
|
+
from .execution_identity import ExecutionManifestError
|
|
11
|
+
from .execution_manifest import (
|
|
12
|
+
_replace_manifest,
|
|
13
|
+
_write_manifest_over_existing,
|
|
14
|
+
read_execution_manifest,
|
|
15
|
+
)
|
|
16
|
+
from .run_context import task_mutex
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MutationRecoveryError(ValueError):
|
|
20
|
+
"""Raised when a recovery decision cannot be recorded or applied."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class MutationRecoveryInput:
|
|
25
|
+
manifest_path: Path
|
|
26
|
+
source_invocation_ref: str
|
|
27
|
+
source_attempt_ref: str
|
|
28
|
+
source_evidence_seal_ref: str | None
|
|
29
|
+
original_baseline_digest: str
|
|
30
|
+
terminal_source_diff_digest: str
|
|
31
|
+
terminal_git_chain_digest: str
|
|
32
|
+
verifier_role_execution_ref: str | None
|
|
33
|
+
verifier_invocation_ref: str | None
|
|
34
|
+
verifier_participant_ref: str | None
|
|
35
|
+
implementer_participant_ref: str
|
|
36
|
+
recovered_evidence_refs: tuple[Mapping[str, Any], ...]
|
|
37
|
+
recovered_out_of_plan_edits: tuple[Mapping[str, Any], ...]
|
|
38
|
+
current_source_diff_digest: str
|
|
39
|
+
current_git_chain_digest: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class MutationRecoveryDecision:
|
|
44
|
+
recovery_ref: str
|
|
45
|
+
source_invocation_ref: str
|
|
46
|
+
source_attempt_ref: str
|
|
47
|
+
source_evidence_seal_ref: str | None
|
|
48
|
+
original_baseline_digest: str
|
|
49
|
+
terminal_source_diff_digest: str
|
|
50
|
+
terminal_git_chain_digest: str
|
|
51
|
+
verifier_role_execution_ref: str | None
|
|
52
|
+
verifier_invocation_ref: str | None
|
|
53
|
+
recovered_evidence_refs: tuple[Mapping[str, Any], ...]
|
|
54
|
+
recovered_out_of_plan_edits: tuple[Mapping[str, Any], ...]
|
|
55
|
+
unmet_obligations: tuple[str, ...]
|
|
56
|
+
decision: Literal["carry-forward", "reject", "cleanup-confirmed"]
|
|
57
|
+
reason: str = ""
|
|
58
|
+
|
|
59
|
+
def to_payload(self) -> dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"recoveryRef": self.recovery_ref,
|
|
62
|
+
"sourceInvocationRef": self.source_invocation_ref,
|
|
63
|
+
"sourceAttemptRef": self.source_attempt_ref,
|
|
64
|
+
"sourceEvidenceSealRef": self.source_evidence_seal_ref,
|
|
65
|
+
"originalBaselineDigest": self.original_baseline_digest,
|
|
66
|
+
"terminalSourceDiffDigest": self.terminal_source_diff_digest,
|
|
67
|
+
"terminalGitChainDigest": self.terminal_git_chain_digest,
|
|
68
|
+
"verifierRoleExecutionRef": self.verifier_role_execution_ref,
|
|
69
|
+
"verifierInvocationRef": self.verifier_invocation_ref,
|
|
70
|
+
"recoveredEvidenceRefs": [dict(row) for row in self.recovered_evidence_refs],
|
|
71
|
+
"recoveredOutOfPlanEdits": [
|
|
72
|
+
dict(row) for row in self.recovered_out_of_plan_edits
|
|
73
|
+
],
|
|
74
|
+
"unmetObligations": list(self.unmet_obligations),
|
|
75
|
+
"decision": self.decision,
|
|
76
|
+
"reason": self.reason,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_payload(cls, payload: Mapping[str, Any]) -> "MutationRecoveryDecision":
|
|
81
|
+
return cls(
|
|
82
|
+
recovery_ref=str(payload["recoveryRef"]),
|
|
83
|
+
source_invocation_ref=str(payload["sourceInvocationRef"]),
|
|
84
|
+
source_attempt_ref=str(payload["sourceAttemptRef"]),
|
|
85
|
+
source_evidence_seal_ref=payload.get("sourceEvidenceSealRef"),
|
|
86
|
+
original_baseline_digest=str(payload.get("originalBaselineDigest") or ""),
|
|
87
|
+
terminal_source_diff_digest=str(
|
|
88
|
+
payload.get("terminalSourceDiffDigest") or ""
|
|
89
|
+
),
|
|
90
|
+
terminal_git_chain_digest=str(payload.get("terminalGitChainDigest") or ""),
|
|
91
|
+
verifier_role_execution_ref=payload.get("verifierRoleExecutionRef"),
|
|
92
|
+
verifier_invocation_ref=payload.get("verifierInvocationRef"),
|
|
93
|
+
recovered_evidence_refs=tuple(payload.get("recoveredEvidenceRefs") or ()),
|
|
94
|
+
recovered_out_of_plan_edits=tuple(
|
|
95
|
+
payload.get("recoveredOutOfPlanEdits") or ()
|
|
96
|
+
),
|
|
97
|
+
unmet_obligations=tuple(payload.get("unmetObligations") or ()),
|
|
98
|
+
decision=str(payload["decision"]),
|
|
99
|
+
reason=str(payload.get("reason") or ""),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _seal_for(manifest, seal_ref: str | None) -> AttemptEvidenceSeal | None:
|
|
104
|
+
if not seal_ref:
|
|
105
|
+
return None
|
|
106
|
+
row = next(
|
|
107
|
+
(
|
|
108
|
+
item
|
|
109
|
+
for item in manifest.attempt_evidence_seals
|
|
110
|
+
if item.get("sealRef") == seal_ref
|
|
111
|
+
),
|
|
112
|
+
None,
|
|
113
|
+
)
|
|
114
|
+
return AttemptEvidenceSeal.from_payload(row) if row is not None else None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _verify_sealed_bytes(seal: AttemptEvidenceSeal) -> None:
|
|
118
|
+
stream_path = seal.host_event_stream.get("path")
|
|
119
|
+
if isinstance(stream_path, str) and stream_path:
|
|
120
|
+
data = Path(stream_path).read_bytes()
|
|
121
|
+
if _sha(data) != seal.host_event_stream.get("sha256"):
|
|
122
|
+
raise MutationRecoveryError("sealed bytes changed")
|
|
123
|
+
if len(data) != seal.host_event_stream.get("byteLength"):
|
|
124
|
+
raise MutationRecoveryError("sealed bytes changed")
|
|
125
|
+
for artifact in seal.artifacts:
|
|
126
|
+
path = Path(str(artifact["normalizedPath"]))
|
|
127
|
+
data = path.read_bytes() if path.is_file() else b""
|
|
128
|
+
if _sha(data) != artifact.get("sha256"):
|
|
129
|
+
raise MutationRecoveryError("sealed bytes changed")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def evaluate_mutation_recovery(
|
|
133
|
+
recovery: MutationRecoveryInput,
|
|
134
|
+
) -> MutationRecoveryDecision:
|
|
135
|
+
"""Decide carry-forward or reject from sealed evidence."""
|
|
136
|
+
manifest = read_execution_manifest(recovery.manifest_path)
|
|
137
|
+
attempt = next(
|
|
138
|
+
(
|
|
139
|
+
row
|
|
140
|
+
for row in manifest.attempts
|
|
141
|
+
if build_dispatch_id(row.invocation_ref, row.attempt)
|
|
142
|
+
== recovery.source_attempt_ref
|
|
143
|
+
),
|
|
144
|
+
None,
|
|
145
|
+
)
|
|
146
|
+
if attempt is None:
|
|
147
|
+
raise MutationRecoveryError("source attempt is unknown")
|
|
148
|
+
if attempt.status == "evidence-unsealed":
|
|
149
|
+
return MutationRecoveryDecision(
|
|
150
|
+
recovery_ref=f"recovery-reject-{attempt.attempt}",
|
|
151
|
+
source_invocation_ref=recovery.source_invocation_ref,
|
|
152
|
+
source_attempt_ref=recovery.source_attempt_ref,
|
|
153
|
+
source_evidence_seal_ref=None,
|
|
154
|
+
original_baseline_digest=recovery.original_baseline_digest,
|
|
155
|
+
terminal_source_diff_digest=recovery.terminal_source_diff_digest,
|
|
156
|
+
terminal_git_chain_digest=recovery.terminal_git_chain_digest,
|
|
157
|
+
verifier_role_execution_ref=None,
|
|
158
|
+
verifier_invocation_ref=None,
|
|
159
|
+
recovered_evidence_refs=(),
|
|
160
|
+
recovered_out_of_plan_edits=(),
|
|
161
|
+
unmet_obligations=("evidence-unsealed",),
|
|
162
|
+
decision="reject",
|
|
163
|
+
reason="evidence-unsealed",
|
|
164
|
+
)
|
|
165
|
+
unmet: list[str] = []
|
|
166
|
+
if not recovery.source_evidence_seal_ref:
|
|
167
|
+
unmet.append("missing-source-seal")
|
|
168
|
+
if not recovery.recovered_evidence_refs:
|
|
169
|
+
unmet.append("missing-host-tdd-events")
|
|
170
|
+
if (
|
|
171
|
+
recovery.verifier_participant_ref
|
|
172
|
+
and recovery.verifier_participant_ref == recovery.implementer_participant_ref
|
|
173
|
+
):
|
|
174
|
+
unmet.append("verifier-not-independent")
|
|
175
|
+
if not recovery.verifier_invocation_ref:
|
|
176
|
+
unmet.append("missing-verifier-invocation")
|
|
177
|
+
recovered_paths = {
|
|
178
|
+
str(row.get("path"))
|
|
179
|
+
for row in recovery.recovered_out_of_plan_edits
|
|
180
|
+
if isinstance(row, Mapping) and row.get("path")
|
|
181
|
+
}
|
|
182
|
+
sealed = _seal_for(manifest, recovery.source_evidence_seal_ref)
|
|
183
|
+
if sealed is not None:
|
|
184
|
+
_verify_sealed_bytes(sealed)
|
|
185
|
+
if recovered_paths - set(sealed.changed_paths):
|
|
186
|
+
raise MutationRecoveryError("out-of-plan path set differs")
|
|
187
|
+
if recovered_paths:
|
|
188
|
+
raise MutationRecoveryError("out-of-plan path set differs")
|
|
189
|
+
if unmet:
|
|
190
|
+
return MutationRecoveryDecision(
|
|
191
|
+
recovery_ref=f"recovery-reject-{attempt.attempt}",
|
|
192
|
+
source_invocation_ref=recovery.source_invocation_ref,
|
|
193
|
+
source_attempt_ref=recovery.source_attempt_ref,
|
|
194
|
+
source_evidence_seal_ref=recovery.source_evidence_seal_ref,
|
|
195
|
+
original_baseline_digest=recovery.original_baseline_digest,
|
|
196
|
+
terminal_source_diff_digest=recovery.terminal_source_diff_digest,
|
|
197
|
+
terminal_git_chain_digest=recovery.terminal_git_chain_digest,
|
|
198
|
+
verifier_role_execution_ref=recovery.verifier_role_execution_ref,
|
|
199
|
+
verifier_invocation_ref=recovery.verifier_invocation_ref,
|
|
200
|
+
recovered_evidence_refs=recovery.recovered_evidence_refs,
|
|
201
|
+
recovered_out_of_plan_edits=recovery.recovered_out_of_plan_edits,
|
|
202
|
+
unmet_obligations=tuple(unmet),
|
|
203
|
+
decision="reject",
|
|
204
|
+
reason="unmet-obligations",
|
|
205
|
+
)
|
|
206
|
+
return MutationRecoveryDecision(
|
|
207
|
+
recovery_ref=f"recovery-carry-{attempt.attempt}",
|
|
208
|
+
source_invocation_ref=recovery.source_invocation_ref,
|
|
209
|
+
source_attempt_ref=recovery.source_attempt_ref,
|
|
210
|
+
source_evidence_seal_ref=recovery.source_evidence_seal_ref,
|
|
211
|
+
original_baseline_digest=recovery.original_baseline_digest,
|
|
212
|
+
terminal_source_diff_digest=recovery.terminal_source_diff_digest,
|
|
213
|
+
terminal_git_chain_digest=recovery.terminal_git_chain_digest,
|
|
214
|
+
verifier_role_execution_ref=recovery.verifier_role_execution_ref,
|
|
215
|
+
verifier_invocation_ref=recovery.verifier_invocation_ref,
|
|
216
|
+
recovered_evidence_refs=recovery.recovered_evidence_refs,
|
|
217
|
+
recovered_out_of_plan_edits=recovery.recovered_out_of_plan_edits,
|
|
218
|
+
unmet_obligations=(),
|
|
219
|
+
decision="carry-forward",
|
|
220
|
+
reason="",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def confirm_mutation_cleanup(
|
|
225
|
+
manifest_path: Path,
|
|
226
|
+
recovery_ref: str,
|
|
227
|
+
*,
|
|
228
|
+
original_baseline_digest: str,
|
|
229
|
+
current_baseline_digest: str,
|
|
230
|
+
task_key: str,
|
|
231
|
+
) -> MutationRecoveryDecision:
|
|
232
|
+
"""Allow a new invocation after user cleanup matches the original baseline."""
|
|
233
|
+
if current_baseline_digest != original_baseline_digest:
|
|
234
|
+
raise MutationRecoveryError("cleanup does not restore the original baseline")
|
|
235
|
+
with task_mutex(task_key):
|
|
236
|
+
manifest = read_execution_manifest(manifest_path)
|
|
237
|
+
source = next(
|
|
238
|
+
(
|
|
239
|
+
MutationRecoveryDecision.from_payload(row)
|
|
240
|
+
for row in manifest.mutation_recovery_decisions
|
|
241
|
+
if row.get("recoveryRef") == recovery_ref
|
|
242
|
+
),
|
|
243
|
+
None,
|
|
244
|
+
)
|
|
245
|
+
if source is None or source.decision != "reject":
|
|
246
|
+
raise MutationRecoveryError("cleanup requires a prior reject decision")
|
|
247
|
+
confirmed = MutationRecoveryDecision(
|
|
248
|
+
recovery_ref=f"{recovery_ref}-cleanup",
|
|
249
|
+
source_invocation_ref=source.source_invocation_ref,
|
|
250
|
+
source_attempt_ref=source.source_attempt_ref,
|
|
251
|
+
source_evidence_seal_ref=None,
|
|
252
|
+
original_baseline_digest=original_baseline_digest,
|
|
253
|
+
terminal_source_diff_digest="",
|
|
254
|
+
terminal_git_chain_digest="",
|
|
255
|
+
verifier_role_execution_ref=None,
|
|
256
|
+
verifier_invocation_ref=None,
|
|
257
|
+
recovered_evidence_refs=(),
|
|
258
|
+
recovered_out_of_plan_edits=(),
|
|
259
|
+
unmet_obligations=(),
|
|
260
|
+
decision="cleanup-confirmed",
|
|
261
|
+
reason="cleanup-confirmed",
|
|
262
|
+
)
|
|
263
|
+
updated = _replace_manifest(
|
|
264
|
+
manifest,
|
|
265
|
+
mutation_recovery_decisions=(
|
|
266
|
+
*manifest.mutation_recovery_decisions,
|
|
267
|
+
confirmed.to_payload(),
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
_write_manifest_over_existing(manifest_path, updated)
|
|
271
|
+
return confirmed
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def start_new_invocation(recovery: MutationRecoveryInput) -> None:
|
|
275
|
+
"""Refuse a new invocation while an unresolved mutation has no decision."""
|
|
276
|
+
manifest = read_execution_manifest(recovery.manifest_path)
|
|
277
|
+
unresolved = [
|
|
278
|
+
row
|
|
279
|
+
for row in manifest.attempts
|
|
280
|
+
if row.status in {"mutation-present-unresolved", "evidence-unsealed"}
|
|
281
|
+
]
|
|
282
|
+
confirmed = {
|
|
283
|
+
row.get("sourceAttemptRef")
|
|
284
|
+
for row in manifest.mutation_recovery_decisions
|
|
285
|
+
if row.get("decision") in {"carry-forward", "cleanup-confirmed"}
|
|
286
|
+
}
|
|
287
|
+
pending = [
|
|
288
|
+
row
|
|
289
|
+
for row in unresolved
|
|
290
|
+
if build_dispatch_id(row.invocation_ref, row.attempt) not in confirmed
|
|
291
|
+
]
|
|
292
|
+
if pending:
|
|
293
|
+
raise MutationRecoveryError("recovery decision required")
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""활성 run 스코프 조회 — pane 정리 의무를 어느 run 에 걸지 고르는 데 쓴다.
|
|
2
|
+
|
|
3
|
+
호출자는 `SessionStart(compact)` 훅(`okstra-compact-reminder.sh`) 하나다. 압축
|
|
4
|
+
직후 리드에게 "이 run 의 완료 teammate pane 을 라운드 경계마다 회수하라"는 의무를
|
|
5
|
+
재주입할 때, 그 대상이 되는 이 프로젝트의 진행 중 run 을 여기서 찾는다.
|
|
6
|
+
|
|
7
|
+
완료 판정(`is_completed_status`)과 프로젝트 무관 전체 조회(`active_run_dirs`)도
|
|
8
|
+
여기 있었으나, 그것을 쓰던 `okstra-trace-cleanup.sh --reclaim-completed` 와
|
|
9
|
+
`okstra-subagent-reclaim.sh` 가 제거되면서 함께 사라졌다.
|
|
10
|
+
"""
|
|
3
11
|
from __future__ import annotations
|
|
4
12
|
|
|
5
13
|
import json
|
|
@@ -8,12 +16,6 @@ from pathlib import Path
|
|
|
8
16
|
|
|
9
17
|
from .paths import resolve_under_root
|
|
10
18
|
from .reconcile import NON_TERMINAL_RECENT_STATUSES
|
|
11
|
-
from .wrapper_status import read_wrapper_status
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def is_completed_status(status_path: str) -> bool:
|
|
15
|
-
status = read_wrapper_status(Path(status_path))
|
|
16
|
-
return status is not None and status.is_terminal
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def _iter_active_run_dirs(home: Path):
|
|
@@ -30,9 +32,9 @@ def _iter_active_run_dirs(home: Path):
|
|
|
30
32
|
row = json.loads(line)
|
|
31
33
|
except json.JSONDecodeError:
|
|
32
34
|
continue
|
|
33
|
-
#
|
|
34
|
-
# SSOT. reserving 은 아직 run-dir 산출물이 없어
|
|
35
|
-
#
|
|
35
|
+
# 대상(진행 중) 집합은 reconcile 의 NON_TERMINAL_RECENT_STATUSES 가
|
|
36
|
+
# SSOT. reserving 은 아직 run-dir 산출물이 없어 이 집합에 들어있지
|
|
37
|
+
# 않으므로 자연히 제외된다(allowlist).
|
|
36
38
|
if row.get("status") not in NON_TERMINAL_RECENT_STATUSES:
|
|
37
39
|
continue
|
|
38
40
|
run_dir = resolve_under_root(row.get("projectRoot"), row.get("runDirRel"))
|
|
@@ -40,11 +42,6 @@ def _iter_active_run_dirs(home: Path):
|
|
|
40
42
|
yield row.get("projectRoot"), run_dir
|
|
41
43
|
|
|
42
44
|
|
|
43
|
-
def active_run_dirs(home: Path) -> list[Path]:
|
|
44
|
-
"""active.jsonl 의 진행 중 run 들의 절대 run-dir 목록."""
|
|
45
|
-
return [run_dir for _project_root, run_dir in _iter_active_run_dirs(home)]
|
|
46
|
-
|
|
47
|
-
|
|
48
45
|
def active_run_dirs_for_project(home: Path, cwd: Path) -> list[Path]:
|
|
49
46
|
"""진행 중 run 중 projectRoot 이 cwd(또는 그 상위)인 것의 절대 run-dir 목록.
|
|
50
47
|
SessionStart(compact) 훅이 이 프로젝트의 run 에만 리마인더를 걸도록 스코프한다."""
|
|
@@ -63,16 +60,10 @@ def active_run_dirs_for_project(home: Path, cwd: Path) -> list[Path]:
|
|
|
63
60
|
|
|
64
61
|
|
|
65
62
|
def main(argv: list[str]) -> int:
|
|
66
|
-
if len(argv) == 2 and argv[0] == "--active-dirs":
|
|
67
|
-
for run_dir in active_run_dirs(Path(argv[1])):
|
|
68
|
-
print(run_dir)
|
|
69
|
-
return 0
|
|
70
63
|
if len(argv) == 3 and argv[0] == "--active-dirs-for":
|
|
71
64
|
for run_dir in active_run_dirs_for_project(Path(argv[1]), Path(argv[2])):
|
|
72
65
|
print(run_dir)
|
|
73
66
|
return 0
|
|
74
|
-
if len(argv) == 1:
|
|
75
|
-
return 0 if is_completed_status(argv[0]) else 1
|
|
76
67
|
return 1
|
|
77
68
|
|
|
78
69
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Lease pane titles from stored execution labels."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import secrets
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PaneTitleError(ValueError):
|
|
12
|
+
"""Raised when a title lease cannot be acquired or released."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PaneTitleBackend(Protocol):
|
|
16
|
+
def get_title(self, pane_id: str) -> str: ...
|
|
17
|
+
def set_title(self, pane_id: str, title: str) -> None: ...
|
|
18
|
+
def available(self) -> bool: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MemoryPaneBackend:
|
|
23
|
+
titles: dict[str, str]
|
|
24
|
+
capable: bool = True
|
|
25
|
+
|
|
26
|
+
def get_title(self, pane_id: str) -> str:
|
|
27
|
+
return self.titles.get(pane_id, "")
|
|
28
|
+
|
|
29
|
+
def set_title(self, pane_id: str, title: str) -> None:
|
|
30
|
+
self.titles[pane_id] = title
|
|
31
|
+
|
|
32
|
+
def available(self) -> bool:
|
|
33
|
+
return self.capable
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class PaneTitleLease:
|
|
38
|
+
run_ref: str
|
|
39
|
+
participant_ref: str
|
|
40
|
+
role_execution_ref: str
|
|
41
|
+
invocation_ref: str | None
|
|
42
|
+
pane_backend: str
|
|
43
|
+
pane_id: str
|
|
44
|
+
previous_title: str
|
|
45
|
+
applied_title: str
|
|
46
|
+
owner_token: str
|
|
47
|
+
status: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UnavailablePaneBackend:
|
|
51
|
+
def get_title(self, pane_id: str) -> str:
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
def set_title(self, pane_id: str, title: str) -> None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def available(self) -> bool:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def acquire_pane_title(
|
|
62
|
+
*,
|
|
63
|
+
backend: PaneTitleBackend,
|
|
64
|
+
pane_id: str,
|
|
65
|
+
execution_label: str,
|
|
66
|
+
run_ref: str,
|
|
67
|
+
participant_ref: str,
|
|
68
|
+
role_execution_ref: str,
|
|
69
|
+
invocation_ref: str | None = None,
|
|
70
|
+
pane_backend: str = "cmux",
|
|
71
|
+
) -> PaneTitleLease:
|
|
72
|
+
if not backend.available():
|
|
73
|
+
return PaneTitleLease(
|
|
74
|
+
run_ref=run_ref,
|
|
75
|
+
participant_ref=participant_ref,
|
|
76
|
+
role_execution_ref=role_execution_ref,
|
|
77
|
+
invocation_ref=invocation_ref,
|
|
78
|
+
pane_backend=pane_backend,
|
|
79
|
+
pane_id=pane_id,
|
|
80
|
+
previous_title="",
|
|
81
|
+
applied_title=execution_label,
|
|
82
|
+
owner_token="",
|
|
83
|
+
status="not-applicable",
|
|
84
|
+
)
|
|
85
|
+
previous = backend.get_title(pane_id)
|
|
86
|
+
backend.set_title(pane_id, execution_label)
|
|
87
|
+
return PaneTitleLease(
|
|
88
|
+
run_ref=run_ref,
|
|
89
|
+
participant_ref=participant_ref,
|
|
90
|
+
role_execution_ref=role_execution_ref,
|
|
91
|
+
invocation_ref=invocation_ref,
|
|
92
|
+
pane_backend=pane_backend,
|
|
93
|
+
pane_id=pane_id,
|
|
94
|
+
previous_title=previous,
|
|
95
|
+
applied_title=execution_label,
|
|
96
|
+
owner_token=secrets.token_hex(8),
|
|
97
|
+
status="acquired",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def release_pane_title(
|
|
102
|
+
lease: PaneTitleLease,
|
|
103
|
+
backend: PaneTitleBackend,
|
|
104
|
+
*,
|
|
105
|
+
owner_token: str,
|
|
106
|
+
) -> PaneTitleLease:
|
|
107
|
+
if lease.status == "not-applicable":
|
|
108
|
+
return lease
|
|
109
|
+
if owner_token != lease.owner_token:
|
|
110
|
+
raise PaneTitleError("owner token does not match")
|
|
111
|
+
current = backend.get_title(lease.pane_id)
|
|
112
|
+
if current != lease.applied_title:
|
|
113
|
+
return PaneTitleLease(**{**lease.__dict__, "status": "manual-change-preserved"})
|
|
114
|
+
backend.set_title(lease.pane_id, lease.previous_title)
|
|
115
|
+
return PaneTitleLease(**{**lease.__dict__, "status": "released"})
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cleanup_pane_title(
|
|
119
|
+
lease: PaneTitleLease, backend: PaneTitleBackend
|
|
120
|
+
) -> PaneTitleLease:
|
|
121
|
+
if lease.status == "not-applicable":
|
|
122
|
+
return lease
|
|
123
|
+
backend.set_title(lease.pane_id, lease.previous_title)
|
|
124
|
+
return PaneTitleLease(**{**lease.__dict__, "status": "cleaned"})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main(argv: list[str] | None = None) -> int:
|
|
128
|
+
import argparse
|
|
129
|
+
|
|
130
|
+
parser = argparse.ArgumentParser(prog="okstra pane-title")
|
|
131
|
+
parser.add_argument("action", choices=("acquire", "release", "cleanup"))
|
|
132
|
+
parser.add_argument("--execution-label", default="")
|
|
133
|
+
parser.add_argument("--pane-id", default="")
|
|
134
|
+
parser.add_argument("--json", action="store_true")
|
|
135
|
+
args = parser.parse_args(argv)
|
|
136
|
+
backend = UnavailablePaneBackend()
|
|
137
|
+
lease = acquire_pane_title(
|
|
138
|
+
backend=backend,
|
|
139
|
+
pane_id=args.pane_id or "none",
|
|
140
|
+
execution_label=args.execution_label or "unused",
|
|
141
|
+
run_ref="run",
|
|
142
|
+
participant_ref="participant",
|
|
143
|
+
role_execution_ref="role",
|
|
144
|
+
)
|
|
145
|
+
payload = {"status": lease.status, "appliedTitle": lease.applied_title}
|
|
146
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
147
|
+
return 0
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
raise SystemExit(main())
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Protocol
|
|
5
|
+
from typing import Literal, Protocol
|
|
6
|
+
|
|
7
|
+
from okstra_ctl.domain.provider import HostModelBinding
|
|
8
|
+
from okstra_ctl.domain.worker_exec import WorkerWriteCapability
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class HostModelBindingError(ValueError):
|
|
@@ -13,48 +16,76 @@ class HostModelBindingError(ValueError):
|
|
|
13
16
|
class HostModelBindingRequest:
|
|
14
17
|
host_runtime: str
|
|
15
18
|
provider: str
|
|
16
|
-
|
|
19
|
+
model_ref: str
|
|
20
|
+
version_kind: Literal["pinned", "channel"]
|
|
17
21
|
model_execution_value: str
|
|
18
22
|
runner: str
|
|
23
|
+
resolved_execution_value: str | None = None
|
|
24
|
+
worker_write_capability: WorkerWriteCapability | None = None
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def execution_value(self) -> str:
|
|
28
|
+
return self.resolved_execution_value or self.model_execution_value
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class HostModelBindingPort(Protocol):
|
|
22
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
32
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cli_binding(request: HostModelBindingRequest) -> HostModelBinding:
|
|
36
|
+
return HostModelBinding(
|
|
37
|
+
runner=request.runner,
|
|
38
|
+
catalog_execution_value=request.model_execution_value,
|
|
39
|
+
resolved_execution_value=request.execution_value,
|
|
40
|
+
host_model_value=None,
|
|
41
|
+
binding_fidelity="exact",
|
|
42
|
+
worker_write_capability=request.worker_write_capability,
|
|
43
|
+
)
|
|
23
44
|
|
|
24
45
|
|
|
25
46
|
class FailClosedHostModelBindingPort:
|
|
26
47
|
def __init__(self, host_runtime: str) -> None:
|
|
27
48
|
self._host_runtime = host_runtime
|
|
28
49
|
|
|
29
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
50
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
|
|
30
51
|
if request.runner == "cli-wrapper":
|
|
31
|
-
return
|
|
52
|
+
return _cli_binding(request)
|
|
32
53
|
raise HostModelBindingError(
|
|
33
54
|
"unsupported host model: "
|
|
34
55
|
f"host={self._host_runtime!r}, provider={request.provider!r}, "
|
|
35
|
-
f"model={request.
|
|
56
|
+
f"model={request.execution_value!r}"
|
|
36
57
|
)
|
|
37
58
|
|
|
38
59
|
|
|
39
60
|
class NativeExecutionValueHostModelBindingPort:
|
|
40
|
-
"""
|
|
61
|
+
"""Binding for hosts whose native API accepts provider execution values."""
|
|
41
62
|
|
|
42
63
|
def __init__(self, host_runtime: str, native_provider: str) -> None:
|
|
43
64
|
self._host_runtime = host_runtime
|
|
44
65
|
self._native_provider = native_provider
|
|
45
66
|
|
|
46
|
-
def resolve(self, request: HostModelBindingRequest) ->
|
|
67
|
+
def resolve(self, request: HostModelBindingRequest) -> HostModelBinding:
|
|
47
68
|
if request.runner == "cli-wrapper":
|
|
48
|
-
return
|
|
49
|
-
if (
|
|
50
|
-
request.runner != "native-session"
|
|
51
|
-
or request.host_runtime != self._host_runtime
|
|
52
|
-
or request.provider != self._native_provider
|
|
53
|
-
or not request.model_execution_value.strip()
|
|
54
|
-
):
|
|
69
|
+
return _cli_binding(request)
|
|
70
|
+
if not self._supports_native(request):
|
|
55
71
|
raise HostModelBindingError(
|
|
56
72
|
"unsupported host model: "
|
|
57
73
|
f"host={request.host_runtime!r}, provider={request.provider!r}, "
|
|
58
|
-
f"model={request.
|
|
74
|
+
f"model={request.execution_value!r}"
|
|
59
75
|
)
|
|
60
|
-
return
|
|
76
|
+
return HostModelBinding(
|
|
77
|
+
runner=request.runner,
|
|
78
|
+
catalog_execution_value=request.model_execution_value,
|
|
79
|
+
resolved_execution_value=request.execution_value,
|
|
80
|
+
host_model_value=request.execution_value,
|
|
81
|
+
binding_fidelity="exact",
|
|
82
|
+
worker_write_capability=request.worker_write_capability,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def _supports_native(self, request: HostModelBindingRequest) -> bool:
|
|
86
|
+
return (
|
|
87
|
+
request.runner == "native-session"
|
|
88
|
+
and request.host_runtime == self._host_runtime
|
|
89
|
+
and request.provider == self._native_provider
|
|
90
|
+
and bool(request.execution_value.strip())
|
|
91
|
+
)
|
|
@@ -5,6 +5,8 @@ from dataclasses import dataclass
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Literal, Protocol
|
|
7
7
|
|
|
8
|
+
from okstra_ctl.domain.worker_exec import WorkerWriteCapability
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
@dataclass(frozen=True)
|
|
10
12
|
class WorkerDispatchRequest:
|
|
@@ -30,3 +32,5 @@ class WorkerDispatchPlan:
|
|
|
30
32
|
|
|
31
33
|
class WorkerDispatchPort(Protocol):
|
|
32
34
|
def build_plan(self, request: WorkerDispatchRequest) -> WorkerDispatchPlan: ...
|
|
35
|
+
|
|
36
|
+
def worker_write_capability(self) -> WorkerWriteCapability: ...
|
|
@@ -46,7 +46,7 @@ class ProviderRegistry:
|
|
|
46
46
|
return tuple(
|
|
47
47
|
provider_id
|
|
48
48
|
for provider_id, spec in self._providers.items()
|
|
49
|
-
if
|
|
49
|
+
if spec.supports_role(role)
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
@property
|
|
@@ -72,10 +72,14 @@ def _registry_for_roots(
|
|
|
72
72
|
) -> ProviderRegistry:
|
|
73
73
|
discovered = ProviderRegistry.from_roots((bundled_root, user_root)).providers
|
|
74
74
|
bundled_order = ("claude", "antigravity", "codex", "grok", "kimi")
|
|
75
|
-
ordered = {
|
|
75
|
+
ordered = {
|
|
76
|
+
provider_id: discovered[provider_id]
|
|
77
|
+
for provider_id in bundled_order
|
|
78
|
+
if provider_id in discovered
|
|
79
|
+
}
|
|
76
80
|
ordered.update(
|
|
77
|
-
(provider_id,
|
|
78
|
-
for provider_id
|
|
81
|
+
(provider_id, discovered[provider_id])
|
|
82
|
+
for provider_id in sorted(discovered)
|
|
79
83
|
if provider_id not in ordered
|
|
80
84
|
)
|
|
81
85
|
return ProviderRegistry(ordered)
|