okstra 0.147.0 → 0.148.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 +21 -7
- package/docs/architecture/storage-model.md +34 -61
- package/docs/architecture.md +51 -49
- package/docs/cli.md +38 -21
- package/docs/for-ai/skills/okstra-run.md +13 -34
- package/docs/performance-improvement-plan-v2.md +2 -2
- package/docs/pr-template-usage.md +1 -1
- package/docs/project-structure-overview.md +10 -8
- package/docs/task-process/README.md +4 -4
- package/docs/task-process/common-flow.md +12 -12
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +2 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +2 -2
- package/runtime/agents/workers/grok-worker.md +256 -0
- package/runtime/agents/workers/kimi-worker.md +256 -0
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +13 -1
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/usage.sh +17 -12
- package/runtime/bin/okstra-grok-exec.sh +5 -0
- package/runtime/bin/okstra-kimi-exec.sh +5 -0
- package/runtime/bin/okstra-provider-exec.py +235 -0
- package/runtime/bin/okstra.sh +3 -0
- package/runtime/prompts/lead/adapters/antigravity.md +48 -0
- package/runtime/prompts/lead/adapters/claude-code.md +13 -11
- package/runtime/prompts/lead/adapters/codex.md +7 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +12 -12
- package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
- package/runtime/prompts/profiles/error-analysis.md +2 -0
- package/runtime/prompts/profiles/feature-analysis.md +2 -0
- package/runtime/prompts/profiles/final-verification.md +3 -1
- package/runtime/prompts/profiles/forbidden-actions.json +4 -4
- package/runtime/prompts/profiles/implementation-planning.md +3 -1
- package/runtime/prompts/profiles/implementation.md +2 -2
- package/runtime/prompts/profiles/improvement-discovery.md +3 -1
- package/runtime/prompts/profiles/project-analysis.md +2 -0
- package/runtime/prompts/profiles/release-handoff.md +7 -7
- package/runtime/prompts/profiles/requirements-discovery.md +2 -0
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
- package/runtime/python/okstra_ctl/lead_events.py +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
- package/runtime/python/okstra_ctl/models.py +156 -8
- package/runtime/python/okstra_ctl/path_hints.py +9 -25
- package/runtime/python/okstra_ctl/paths.py +1 -1
- package/runtime/python/okstra_ctl/render.py +172 -74
- package/runtime/python/okstra_ctl/report_html/common.py +38 -2
- package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
- package/runtime/python/okstra_ctl/report_html/render.py +7 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
- package/runtime/python/okstra_ctl/run.py +264 -45
- package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
- package/runtime/python/okstra_ctl/team.py +2 -7
- package/runtime/python/okstra_ctl/wizard.py +194 -21
- package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
- package/runtime/python/okstra_ctl/workers.py +3 -1
- package/runtime/python/okstra_ctl/workflow.py +4 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -0
- package/runtime/python/okstra_token_usage/collect.py +32 -23
- package/runtime/python/okstra_token_usage/pricing.py +35 -3
- package/runtime/schemas/final-report-v2.0.schema.json +2 -2
- package/runtime/skills/okstra-run/SKILL.md +31 -42
- package/runtime/templates/prd/pr-body.template.md +1 -1
- package/runtime/templates/reports/html/assets/base.css +6 -3
- package/runtime/templates/reports/html/base.template.html +22 -8
- package/runtime/templates/reports/html/macros/forms.html +2 -2
- package/runtime/templates/reports/html/macros/layout.html +5 -5
- package/runtime/templates/reports/html/macros/visualizations.html +11 -1
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
- package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
- package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
- package/runtime/templates/reports/report.js +21 -4
- package/runtime/templates/reports/settings.template.json +4 -0
- package/runtime/templates/reports/task-brief.template.md +7 -7
- package/runtime/validators/validate-run.py +11 -6
- package/runtime/validators/validate_session_conformance.py +2 -1
- package/src/cli-registry.mjs +4 -4
- package/src/commands/execute/codex-dispatch.mjs +7 -10
- package/src/commands/execute/render-bundle.mjs +3 -3
- package/src/commands/execute/run.mjs +17 -52
- package/src/commands/execute/wizard.mjs +4 -1
- package/src/commands/lifecycle/doctor.mjs +6 -3
- package/src/commands/lifecycle/install.mjs +31 -8
- package/src/lib/runtime-manifest.mjs +1 -1
- package/src/lib/runtime-resolver.mjs +2 -2
- package/src/lib/worker-agent-render.mjs +50 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""CLI-wrapper dispatcher for a Codex-hosted Okstra lead.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Codex opt-in because the prepared manifest still records a Claude-side model by
|
|
7
|
-
default.
|
|
3
|
+
The prepared assignment's ``runner`` field is authoritative. Native Codex
|
|
4
|
+
assignments stay in the host session; this module runs only ``cli-wrapper``
|
|
5
|
+
assignments through their registered provider wrappers.
|
|
8
6
|
"""
|
|
9
7
|
from __future__ import annotations
|
|
10
8
|
|
|
@@ -64,12 +62,10 @@ from .report_finalize import (
|
|
|
64
62
|
)
|
|
65
63
|
from .wrapper_status import status_path_for_prompt
|
|
66
64
|
from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
65
|
+
from .models import provider_wrappers
|
|
67
66
|
|
|
68
67
|
|
|
69
|
-
SUPPORTED_CLI_WORKERS =
|
|
70
|
-
"codex": "okstra-codex-exec.sh",
|
|
71
|
-
"antigravity": "okstra-antigravity-exec.sh",
|
|
72
|
-
}
|
|
68
|
+
SUPPORTED_CLI_WORKERS = provider_wrappers("analyser")
|
|
73
69
|
MAX_WORKER_ATTEMPTS = 2
|
|
74
70
|
|
|
75
71
|
|
|
@@ -120,8 +116,6 @@ def build_dispatch_plan(
|
|
|
120
116
|
okstra_bin: Path | None = None,
|
|
121
117
|
requested_workers: Sequence[str] = (),
|
|
122
118
|
idle_timeout_seconds: int = 600,
|
|
123
|
-
enable_codex_report_writer: bool = False,
|
|
124
|
-
report_writer_codex_model: str = "",
|
|
125
119
|
dispatch_kind: str = "initial",
|
|
126
120
|
jobs_file: Path | None = None,
|
|
127
121
|
) -> DispatchPlan:
|
|
@@ -129,7 +123,6 @@ def build_dispatch_plan(
|
|
|
129
123
|
project_root,
|
|
130
124
|
run_manifest_path,
|
|
131
125
|
requested_workers,
|
|
132
|
-
enable_codex_report_writer=enable_codex_report_writer,
|
|
133
126
|
select_roster=jobs_file is None,
|
|
134
127
|
)
|
|
135
128
|
if jobs_file is not None:
|
|
@@ -148,7 +141,6 @@ def build_dispatch_plan(
|
|
|
148
141
|
workspace_root=workspace_root,
|
|
149
142
|
team_state=inputs.team_state,
|
|
150
143
|
selected_workers=inputs.selected_workers,
|
|
151
|
-
report_writer_codex_model=report_writer_codex_model,
|
|
152
144
|
)
|
|
153
145
|
workers = _build_selected_worker_dispatches(
|
|
154
146
|
project_root=inputs.project_root,
|
|
@@ -204,35 +196,34 @@ def _load_dispatch_inputs(
|
|
|
204
196
|
run_manifest_path: Path,
|
|
205
197
|
requested_workers: Sequence[str],
|
|
206
198
|
*,
|
|
207
|
-
enable_codex_report_writer: bool,
|
|
208
199
|
select_roster: bool = True,
|
|
209
200
|
) -> _DispatchInputs:
|
|
210
201
|
project_root = project_root.resolve()
|
|
211
202
|
manifest_path = _resolve_project_path(project_root, str(run_manifest_path))
|
|
212
203
|
manifest = _load_json_object(manifest_path, "run manifest")
|
|
213
204
|
_validate_codex_manifest(manifest, manifest_path)
|
|
205
|
+
team_state_path = _resolve_required_path(project_root, manifest, "teamStatePath")
|
|
206
|
+
team_state = _load_json_object(team_state_path, "team-state")
|
|
214
207
|
selected_workers = (
|
|
215
208
|
tuple(
|
|
216
209
|
_select_workers(
|
|
217
210
|
manifest,
|
|
211
|
+
team_state,
|
|
218
212
|
requested_workers,
|
|
219
|
-
enable_codex_report_writer=enable_codex_report_writer,
|
|
220
213
|
)
|
|
221
214
|
)
|
|
222
215
|
if select_roster
|
|
223
216
|
else ()
|
|
224
217
|
)
|
|
225
|
-
team_state_path = _resolve_required_path(project_root, manifest, "teamStatePath")
|
|
226
|
-
team_state = _load_json_object(team_state_path, "team-state")
|
|
227
218
|
if select_roster:
|
|
228
219
|
_mark_skipped_workers(
|
|
229
220
|
team_state_path,
|
|
230
221
|
team_state,
|
|
231
222
|
_skipped_worker_reasons(
|
|
232
223
|
manifest,
|
|
224
|
+
team_state,
|
|
233
225
|
selected_workers,
|
|
234
226
|
requested_workers,
|
|
235
|
-
enable_codex_report_writer=enable_codex_report_writer,
|
|
236
227
|
),
|
|
237
228
|
)
|
|
238
229
|
active_context = _load_optional_json_object(
|
|
@@ -257,16 +248,11 @@ def _materialize_selected_worker_prompts(
|
|
|
257
248
|
workspace_root: Path,
|
|
258
249
|
team_state: Mapping[str, Any],
|
|
259
250
|
selected_workers: Sequence[str],
|
|
260
|
-
report_writer_codex_model: str,
|
|
261
251
|
) -> tuple[tuple[InitialPromptWorkerRequest, ...], tuple[Path, ...]]:
|
|
262
252
|
worker_requests = tuple(
|
|
263
253
|
InitialPromptWorkerRequest(
|
|
264
254
|
worker_id=worker_id,
|
|
265
|
-
model=_requested_worker_model(
|
|
266
|
-
worker_id,
|
|
267
|
-
team_state,
|
|
268
|
-
report_writer_codex_model,
|
|
269
|
-
),
|
|
255
|
+
model=_requested_worker_model(worker_id, team_state),
|
|
270
256
|
)
|
|
271
257
|
for worker_id in selected_workers
|
|
272
258
|
)
|
|
@@ -472,8 +458,6 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
472
458
|
okstra_bin=Path(args.okstra_bin) if args.okstra_bin else None,
|
|
473
459
|
requested_workers=_parse_workers(args.workers),
|
|
474
460
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
475
|
-
enable_codex_report_writer=args.enable_codex_report_writer,
|
|
476
|
-
report_writer_codex_model=args.report_writer_codex_model,
|
|
477
461
|
dispatch_kind=args.dispatch_kind,
|
|
478
462
|
jobs_file=Path(args.jobs_file) if args.jobs_file else None,
|
|
479
463
|
)
|
|
@@ -500,8 +484,6 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
500
484
|
parser.add_argument("--workers", default="")
|
|
501
485
|
parser.add_argument("--dry-run", action="store_true")
|
|
502
486
|
parser.add_argument("--idle-timeout-seconds", default="600")
|
|
503
|
-
parser.add_argument("--enable-codex-report-writer", action="store_true")
|
|
504
|
-
parser.add_argument("--report-writer-codex-model", default="")
|
|
505
487
|
parser.add_argument("--dispatch-kind", default="initial")
|
|
506
488
|
parser.add_argument("--jobs-file", default="")
|
|
507
489
|
return parser
|
|
@@ -520,6 +502,8 @@ def _build_worker_dispatch(
|
|
|
520
502
|
model_execution_value: str,
|
|
521
503
|
prompt_path: Path,
|
|
522
504
|
) -> WorkerJob:
|
|
505
|
+
worker_state = _worker_state(team_state, worker_id)
|
|
506
|
+
provider = _assigned_provider(worker_state, worker_id)
|
|
523
507
|
if worker_id == REPORT_WRITER_WORKER_ID:
|
|
524
508
|
return _build_report_writer_dispatch(
|
|
525
509
|
project_root=project_root,
|
|
@@ -530,17 +514,17 @@ def _build_worker_dispatch(
|
|
|
530
514
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
531
515
|
model_execution_value=model_execution_value,
|
|
532
516
|
prompt_path=prompt_path,
|
|
517
|
+
provider=provider,
|
|
533
518
|
)
|
|
534
|
-
worker_state = _worker_state(team_state, worker_id)
|
|
535
519
|
result_path = _resolve_project_path(
|
|
536
520
|
project_root, _require_string(worker_state, "resultPath")
|
|
537
521
|
)
|
|
538
|
-
wrapper = _resolve_wrapper(
|
|
522
|
+
wrapper = _resolve_wrapper(provider, workspace_root, okstra_bin)
|
|
539
523
|
worktree_path = _worktree_path(manifest, active_context)
|
|
540
524
|
role = _pane_role(prompt_path)
|
|
541
525
|
return WorkerJob(
|
|
542
526
|
worker_id=worker_id,
|
|
543
|
-
provider=
|
|
527
|
+
provider=provider,
|
|
544
528
|
backend=BACKEND_CLI_WRAPPER,
|
|
545
529
|
project_root=project_root,
|
|
546
530
|
model_execution_value=model_execution_value,
|
|
@@ -566,6 +550,7 @@ def _build_report_writer_dispatch(
|
|
|
566
550
|
idle_timeout_seconds: int,
|
|
567
551
|
model_execution_value: str,
|
|
568
552
|
prompt_path: Path,
|
|
553
|
+
provider: str,
|
|
569
554
|
) -> WorkerJob:
|
|
570
555
|
worker_state = _worker_state(team_state, REPORT_WRITER_WORKER_ID)
|
|
571
556
|
data_json_path = _final_report_data_path(
|
|
@@ -577,12 +562,11 @@ def _build_report_writer_dispatch(
|
|
|
577
562
|
)
|
|
578
563
|
return WorkerJob(
|
|
579
564
|
worker_id=REPORT_WRITER_WORKER_ID,
|
|
580
|
-
|
|
581
|
-
provider="codex",
|
|
565
|
+
provider=provider,
|
|
582
566
|
backend=BACKEND_CLI_WRAPPER,
|
|
583
567
|
project_root=project_root,
|
|
584
568
|
model_execution_value=model_execution_value,
|
|
585
|
-
wrapper_path=_resolve_wrapper(
|
|
569
|
+
wrapper_path=_resolve_wrapper(provider, workspace_root, okstra_bin),
|
|
586
570
|
prompt_path=prompt_path,
|
|
587
571
|
result_path=data_json_path,
|
|
588
572
|
worker_result_path=worker_result_path,
|
|
@@ -608,46 +592,26 @@ def _validate_codex_manifest(manifest: Mapping[str, Any], path: Path) -> None:
|
|
|
608
592
|
def _requested_worker_model(
|
|
609
593
|
worker_id: str,
|
|
610
594
|
team_state: Mapping[str, Any],
|
|
611
|
-
report_writer_codex_model: str,
|
|
612
595
|
) -> str:
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
_worker_state(team_state, worker_id),
|
|
616
|
-
"modelExecutionValue",
|
|
617
|
-
)
|
|
618
|
-
model = report_writer_codex_model.strip()
|
|
619
|
-
if model:
|
|
620
|
-
return model
|
|
621
|
-
raise DispatchError(
|
|
622
|
-
"Codex report-writer dispatch requires --report-writer-codex-model"
|
|
623
|
-
)
|
|
596
|
+
state = _worker_state(team_state, worker_id)
|
|
597
|
+
return _require_string(state, "modelExecutionValue")
|
|
624
598
|
|
|
625
599
|
|
|
626
600
|
def _select_workers(
|
|
627
601
|
manifest: Mapping[str, Any],
|
|
602
|
+
team_state: Mapping[str, Any],
|
|
628
603
|
requested_workers: Sequence[str],
|
|
629
|
-
*,
|
|
630
|
-
enable_codex_report_writer: bool,
|
|
631
604
|
) -> list[str]:
|
|
632
605
|
recommended = _string_list(manifest.get("recommendedWorkers"))
|
|
633
606
|
if not recommended:
|
|
634
607
|
raise DispatchError("run manifest has no recommendedWorkers entries")
|
|
635
|
-
|
|
636
|
-
enable_codex_report_writer=enable_codex_report_writer
|
|
637
|
-
)
|
|
608
|
+
dispatchable = _dispatchable_workers(recommended, team_state)
|
|
638
609
|
if not requested_workers:
|
|
639
|
-
selected = [worker for worker in recommended if worker in
|
|
610
|
+
selected = [worker for worker in recommended if worker in dispatchable]
|
|
640
611
|
if selected:
|
|
641
612
|
return selected
|
|
642
|
-
if REPORT_WRITER_WORKER_ID in recommended and not enable_codex_report_writer:
|
|
643
|
-
raise DispatchError(
|
|
644
|
-
"report-writer requires --enable-codex-report-writer because it "
|
|
645
|
-
"runs through the Codex wrapper instead of Claude Agent"
|
|
646
|
-
)
|
|
647
|
-
supported = ", ".join(sorted(supported_workers))
|
|
648
613
|
raise DispatchError(
|
|
649
|
-
"run roster has no
|
|
650
|
-
f"(CLI dispatcher supports: {supported})"
|
|
614
|
+
"run roster has no cli-wrapper assignments for codex-dispatch"
|
|
651
615
|
)
|
|
652
616
|
|
|
653
617
|
selected = list(requested_workers)
|
|
@@ -656,17 +620,18 @@ def _select_workers(
|
|
|
656
620
|
raise DispatchError(
|
|
657
621
|
"requested worker(s) are not in this run roster: " + ", ".join(unknown)
|
|
658
622
|
)
|
|
659
|
-
|
|
660
|
-
raise DispatchError(
|
|
661
|
-
"report-writer requires --enable-codex-report-writer because it "
|
|
662
|
-
"runs through the Codex wrapper instead of Claude Agent"
|
|
663
|
-
)
|
|
664
|
-
unsupported = [
|
|
623
|
+
native = [
|
|
665
624
|
worker for worker in selected
|
|
666
|
-
if worker
|
|
625
|
+
if _assigned_runner(_worker_state(team_state, worker)) == "native-session"
|
|
667
626
|
]
|
|
627
|
+
if native:
|
|
628
|
+
raise DispatchError(
|
|
629
|
+
"requested worker(s) use runner=native-session and must be dispatched "
|
|
630
|
+
"by the current Codex host: " + ", ".join(native)
|
|
631
|
+
)
|
|
632
|
+
unsupported = [worker for worker in selected if worker not in dispatchable]
|
|
668
633
|
if unsupported:
|
|
669
|
-
supported = ", ".join(sorted(
|
|
634
|
+
supported = ", ".join(sorted(dispatchable))
|
|
670
635
|
raise DispatchError(
|
|
671
636
|
"unsupported Codex lead worker(s): "
|
|
672
637
|
+ ", ".join(unsupported)
|
|
@@ -675,19 +640,37 @@ def _select_workers(
|
|
|
675
640
|
return selected
|
|
676
641
|
|
|
677
642
|
|
|
678
|
-
def
|
|
679
|
-
|
|
680
|
-
if
|
|
681
|
-
|
|
682
|
-
return
|
|
643
|
+
def _assigned_provider(worker_state: Mapping[str, Any], worker_id: str) -> str:
|
|
644
|
+
provider = _string_value(worker_state.get("provider"))
|
|
645
|
+
if provider:
|
|
646
|
+
return provider
|
|
647
|
+
return "codex" if worker_id == REPORT_WRITER_WORKER_ID else worker_id
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def _assigned_runner(worker_state: Mapping[str, Any]) -> str:
|
|
651
|
+
return _string_value(worker_state.get("runner")) or BACKEND_CLI_WRAPPER
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _dispatchable_workers(
|
|
655
|
+
recommended: Sequence[str], team_state: Mapping[str, Any],
|
|
656
|
+
) -> set[str]:
|
|
657
|
+
dispatchable = set()
|
|
658
|
+
for worker_id in recommended:
|
|
659
|
+
state = _worker_state(team_state, worker_id)
|
|
660
|
+
provider = _assigned_provider(state, worker_id)
|
|
661
|
+
if (
|
|
662
|
+
_assigned_runner(state) == BACKEND_CLI_WRAPPER
|
|
663
|
+
and provider in SUPPORTED_CLI_WORKERS
|
|
664
|
+
):
|
|
665
|
+
dispatchable.add(worker_id)
|
|
666
|
+
return dispatchable
|
|
683
667
|
|
|
684
668
|
|
|
685
669
|
def _skipped_worker_reasons(
|
|
686
670
|
manifest: Mapping[str, Any],
|
|
671
|
+
team_state: Mapping[str, Any],
|
|
687
672
|
selected_workers: Sequence[str],
|
|
688
673
|
requested_workers: Sequence[str],
|
|
689
|
-
*,
|
|
690
|
-
enable_codex_report_writer: bool,
|
|
691
674
|
) -> dict[str, str]:
|
|
692
675
|
recommended = _string_list(manifest.get("recommendedWorkers"))
|
|
693
676
|
selected = set(selected_workers)
|
|
@@ -696,24 +679,22 @@ def _skipped_worker_reasons(
|
|
|
696
679
|
worker_id: "skipped by Codex dispatch: worker was not requested in this invocation"
|
|
697
680
|
for worker_id in recommended
|
|
698
681
|
if worker_id not in selected
|
|
682
|
+
and _assigned_runner(_worker_state(team_state, worker_id))
|
|
683
|
+
== BACKEND_CLI_WRAPPER
|
|
699
684
|
}
|
|
700
685
|
|
|
701
|
-
supported =
|
|
702
|
-
enable_codex_report_writer=enable_codex_report_writer
|
|
703
|
-
)
|
|
686
|
+
supported = _dispatchable_workers(recommended, team_state)
|
|
704
687
|
reasons = {}
|
|
705
688
|
for worker_id in recommended:
|
|
706
689
|
if worker_id in selected:
|
|
707
690
|
continue
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
)
|
|
713
|
-
elif worker_id not in supported:
|
|
691
|
+
state = _worker_state(team_state, worker_id)
|
|
692
|
+
if _assigned_runner(state) == "native-session":
|
|
693
|
+
continue
|
|
694
|
+
if worker_id not in supported:
|
|
714
695
|
reasons[worker_id] = (
|
|
715
|
-
"skipped by Codex dispatch default:
|
|
716
|
-
"
|
|
696
|
+
"skipped by Codex dispatch default: assigned provider has no "
|
|
697
|
+
"registered CLI wrapper"
|
|
717
698
|
)
|
|
718
699
|
else:
|
|
719
700
|
reasons[worker_id] = "skipped by Codex dispatch default"
|
|
@@ -135,8 +135,6 @@ def build_dispatch_plan(
|
|
|
135
135
|
okstra_bin: Path | None = None,
|
|
136
136
|
requested_workers: Sequence[str] = (),
|
|
137
137
|
idle_timeout_seconds: int = 600,
|
|
138
|
-
enable_report_writer: bool = False,
|
|
139
|
-
report_writer_model: str = "",
|
|
140
138
|
required_lead_runtime: str | None = None,
|
|
141
139
|
default_backend: str = BACKEND_CLI_WRAPPER,
|
|
142
140
|
supported_worker_wrappers: Mapping[str, str],
|
|
@@ -171,8 +169,6 @@ def build_dispatch_plan(
|
|
|
171
169
|
team_state,
|
|
172
170
|
active_context,
|
|
173
171
|
requested_workers,
|
|
174
|
-
enable_report_writer,
|
|
175
|
-
report_writer_model,
|
|
176
172
|
options,
|
|
177
173
|
)
|
|
178
174
|
return DispatchPlan(
|
|
@@ -240,11 +236,9 @@ def _jobs_from_roster(
|
|
|
240
236
|
team_state: Mapping[str, Any],
|
|
241
237
|
active_context: Mapping[str, Any],
|
|
242
238
|
requested_workers: Sequence[str],
|
|
243
|
-
enable_report_writer: bool,
|
|
244
|
-
report_writer_model: str,
|
|
245
239
|
options: _BuildOptions,
|
|
246
240
|
) -> list[WorkerJob]:
|
|
247
|
-
selected = _select_workers(manifest, requested_workers,
|
|
241
|
+
selected = _select_workers(manifest, requested_workers, options)
|
|
248
242
|
_mark_roster_skips(project_root, manifest, team_state, selected, requested_workers, options)
|
|
249
243
|
worker_facts, prompt_paths = _materialize_roster_prompts(
|
|
250
244
|
project_root,
|
|
@@ -253,7 +247,6 @@ def _jobs_from_roster(
|
|
|
253
247
|
manifest,
|
|
254
248
|
team_state,
|
|
255
249
|
selected,
|
|
256
|
-
report_writer_model,
|
|
257
250
|
options,
|
|
258
251
|
)
|
|
259
252
|
return [
|
|
@@ -273,15 +266,12 @@ def _jobs_from_roster(
|
|
|
273
266
|
def _select_workers(
|
|
274
267
|
manifest: Mapping[str, Any],
|
|
275
268
|
requested_workers: Sequence[str],
|
|
276
|
-
enable_report_writer: bool,
|
|
277
269
|
options: _BuildOptions,
|
|
278
270
|
) -> list[str]:
|
|
279
271
|
recommended = _string_list(manifest.get("recommendedWorkers"))
|
|
280
272
|
if not recommended:
|
|
281
273
|
raise DispatchError("run manifest has no recommendedWorkers entries")
|
|
282
274
|
supported = set(options.supported_worker_wrappers)
|
|
283
|
-
if not enable_report_writer and not requested_workers:
|
|
284
|
-
supported.discard(REPORT_WRITER_WORKER_ID)
|
|
285
275
|
selected = list(requested_workers) if requested_workers else [w for w in recommended if w in supported]
|
|
286
276
|
unknown = [worker for worker in selected if worker not in recommended]
|
|
287
277
|
if unknown:
|
|
@@ -336,11 +326,10 @@ def _materialize_roster_prompts(
|
|
|
336
326
|
manifest: Mapping[str, Any],
|
|
337
327
|
team_state: Mapping[str, Any],
|
|
338
328
|
selected: Sequence[str],
|
|
339
|
-
report_writer_model: str,
|
|
340
329
|
options: _BuildOptions,
|
|
341
330
|
) -> tuple[tuple[_RosterWorkerFacts, ...], tuple[Path, ...]]:
|
|
342
331
|
worker_facts = tuple(
|
|
343
|
-
_roster_worker_facts(team_state, worker_id
|
|
332
|
+
_roster_worker_facts(team_state, worker_id)
|
|
344
333
|
for worker_id in selected
|
|
345
334
|
)
|
|
346
335
|
try:
|
|
@@ -364,13 +353,12 @@ def _materialize_roster_prompts(
|
|
|
364
353
|
def _roster_worker_facts(
|
|
365
354
|
team_state: Mapping[str, Any],
|
|
366
355
|
worker_id: str,
|
|
367
|
-
report_writer_model: str,
|
|
368
356
|
) -> _RosterWorkerFacts:
|
|
369
357
|
state = _worker_state(team_state, worker_id)
|
|
370
358
|
return _RosterWorkerFacts(
|
|
371
359
|
worker_id=worker_id,
|
|
372
|
-
model=
|
|
373
|
-
provider=_provider_for_worker(worker_id),
|
|
360
|
+
model=_require_string(state, "modelExecutionValue"),
|
|
361
|
+
provider=_string_value(state.get("provider")) or _provider_for_worker(worker_id),
|
|
374
362
|
result_path=_require_string(state, "resultPath"),
|
|
375
363
|
role=_string_value(state.get("role")) or "worker",
|
|
376
364
|
)
|
|
@@ -771,12 +759,6 @@ def _provider_for_worker(worker_id: str) -> str:
|
|
|
771
759
|
return worker_id
|
|
772
760
|
|
|
773
761
|
|
|
774
|
-
def _model_for_worker(worker_id: str, state: Mapping[str, Any], report_writer_model: str) -> str:
|
|
775
|
-
if worker_id == REPORT_WRITER_WORKER_ID and report_writer_model.strip():
|
|
776
|
-
return report_writer_model.strip()
|
|
777
|
-
return _require_string(state, "modelExecutionValue")
|
|
778
|
-
|
|
779
|
-
|
|
780
762
|
def _result_path_for_worker(worker_id: str, result_path: Path, manifest: Mapping[str, Any], project_root: Path) -> Path:
|
|
781
763
|
if worker_id != REPORT_WRITER_WORKER_ID:
|
|
782
764
|
return result_path
|
|
@@ -24,8 +24,8 @@ class LeadRuntimeInfo:
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
ALLOWED_LEAD_RUNTIMES = ("claude-code", "codex", "external")
|
|
28
|
-
ARTIFACT_ONLY_LEAD_RUNTIMES = frozenset({"codex", "external"})
|
|
27
|
+
ALLOWED_LEAD_RUNTIMES = ("claude-code", "codex", "antigravity", "external")
|
|
28
|
+
ARTIFACT_ONLY_LEAD_RUNTIMES = frozenset({"codex", "antigravity", "external"})
|
|
29
29
|
|
|
30
30
|
_LEAD_RUNTIMES = {
|
|
31
31
|
"claude-code": LeadRuntimeInfo(
|
|
@@ -50,6 +50,17 @@ _LEAD_RUNTIMES = {
|
|
|
50
50
|
has_claude_session=False,
|
|
51
51
|
adapter_contract_relative_path="lead/adapters/codex.md",
|
|
52
52
|
),
|
|
53
|
+
"antigravity": LeadRuntimeInfo(
|
|
54
|
+
runtime="antigravity",
|
|
55
|
+
agent="antigravity",
|
|
56
|
+
agent_label="Antigravity CLI",
|
|
57
|
+
role="Antigravity lead",
|
|
58
|
+
adapter_name="antigravity",
|
|
59
|
+
adapter_dispatch_mode="render-only",
|
|
60
|
+
session_accounting="artifact-only",
|
|
61
|
+
has_claude_session=False,
|
|
62
|
+
adapter_contract_relative_path="lead/adapters/antigravity.md",
|
|
63
|
+
),
|
|
53
64
|
"external": LeadRuntimeInfo(
|
|
54
65
|
runtime="external",
|
|
55
66
|
agent="external",
|
|
@@ -6,14 +6,14 @@ role default = one ROLE_DEFAULTS line here.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
|
-
from typing import Optional
|
|
9
|
+
from typing import Mapping, Optional
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass(frozen=True)
|
|
13
13
|
class ModelSpec:
|
|
14
14
|
display: str
|
|
15
15
|
execution: str
|
|
16
|
-
pricing: Optional[tuple] = None
|
|
16
|
+
pricing: Optional[tuple[float, float, float]] = None
|
|
17
17
|
in_picker: bool = False # exposed in the wizard 3-option picker
|
|
18
18
|
|
|
19
19
|
|
|
@@ -31,6 +31,8 @@ CLAUDE = {
|
|
|
31
31
|
"opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
|
|
32
32
|
"claude-opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
|
|
33
33
|
"sonnet": ModelSpec("sonnet", "sonnet", in_picker=True),
|
|
34
|
+
"sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5", in_picker=True),
|
|
35
|
+
"claude-sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5"),
|
|
34
36
|
"sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
|
|
35
37
|
"claude-sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
|
|
36
38
|
"haiku": ModelSpec("haiku", "haiku", in_picker=True),
|
|
@@ -47,6 +49,8 @@ CLAUDE = {
|
|
|
47
49
|
ANTIGRAVITY = {
|
|
48
50
|
"gemini-3.1-pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro", in_picker=True),
|
|
49
51
|
"gemini 3.1 pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro"),
|
|
52
|
+
"gemini-3.6-flash": ModelSpec("Gemini 3.6 Flash", "gemini-3.6-flash", in_picker=True),
|
|
53
|
+
"gemini 3.6 flash": ModelSpec("Gemini 3.6 Flash", "gemini-3.6-flash"),
|
|
50
54
|
"gemini-3.5-flash": ModelSpec("Gemini 3.5 Flash", "gemini-3.5-flash", in_picker=True),
|
|
51
55
|
"gemini 3.5 flash": ModelSpec("Gemini 3.5 Flash", "gemini-3.5-flash"),
|
|
52
56
|
}
|
|
@@ -61,12 +65,111 @@ CODEX = {
|
|
|
61
65
|
"gpt-5.2": ModelSpec("gpt-5.2", "gpt-5.2", (1.75, 0.175, 14.0)),
|
|
62
66
|
"codex-auto-review": ModelSpec("codex-auto-review", "codex-auto-review"),
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
GROK = {
|
|
69
|
+
"grok-build-0.1": ModelSpec(
|
|
70
|
+
"grok-build-0.1", "grok-build-0.1", (1.00, 0.20, 2.00), in_picker=True,
|
|
71
|
+
),
|
|
72
|
+
"grok-4.5": ModelSpec(
|
|
73
|
+
"grok-4.5", "grok-4.5", (2.00, 0.30, 6.00), in_picker=True,
|
|
74
|
+
),
|
|
75
|
+
}
|
|
76
|
+
KIMI = {
|
|
77
|
+
"kimi-k2.7-code": ModelSpec(
|
|
78
|
+
"Kimi K2.7 Code", "kimi-k2.7-code", (0.95, 0.19, 4.00), in_picker=True,
|
|
79
|
+
),
|
|
80
|
+
"kimi k2.7 code": ModelSpec("Kimi K2.7 Code", "kimi-k2.7-code"),
|
|
81
|
+
"kimi-for-coding": ModelSpec(
|
|
82
|
+
"Kimi K2.7 Code", "kimi-for-coding", in_picker=True,
|
|
83
|
+
),
|
|
84
|
+
"kimi-k3": ModelSpec(
|
|
85
|
+
"Kimi K3", "kimi-k3", (3.00, 0.30, 15.00), in_picker=True,
|
|
86
|
+
),
|
|
87
|
+
"kimi k3": ModelSpec("Kimi K3", "kimi-k3"),
|
|
88
|
+
"k3": ModelSpec("Kimi K3", "k3", in_picker=True),
|
|
89
|
+
"k3-256k": ModelSpec("Kimi K3 256K", "k3-256k", in_picker=True),
|
|
90
|
+
"kimi k3 256k": ModelSpec("Kimi K3 256K", "k3-256k"),
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ANALYSIS_ROLES = frozenset({"analyser", "critic"})
|
|
94
|
+
IMPLEMENTATION_ROLES = frozenset({"executor", "verifier"})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class ProviderSpec:
|
|
99
|
+
provider: str
|
|
100
|
+
display_label: str
|
|
101
|
+
models: Mapping[str, ModelSpec]
|
|
102
|
+
default_models: Mapping[str, str]
|
|
103
|
+
wrapper: str
|
|
104
|
+
supported_roles: frozenset[str]
|
|
65
105
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
|
|
107
|
+
PROVIDERS = {
|
|
108
|
+
"claude": ProviderSpec(
|
|
109
|
+
provider="claude",
|
|
110
|
+
display_label="Claude",
|
|
111
|
+
models=CLAUDE,
|
|
112
|
+
default_models={
|
|
113
|
+
"lead": "opus",
|
|
114
|
+
"analyser": "opus",
|
|
115
|
+
"critic": "opus",
|
|
116
|
+
"executor": "opus",
|
|
117
|
+
"verifier": "opus",
|
|
118
|
+
"report-writer": "sonnet",
|
|
119
|
+
},
|
|
120
|
+
wrapper="okstra-claude-exec.sh",
|
|
121
|
+
supported_roles=frozenset(
|
|
122
|
+
{"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
"antigravity": ProviderSpec(
|
|
126
|
+
provider="antigravity",
|
|
127
|
+
display_label="Antigravity",
|
|
128
|
+
models=ANTIGRAVITY,
|
|
129
|
+
default_models={role: "gemini-3.1-pro" for role in (
|
|
130
|
+
"lead", "analyser", "critic", "executor", "verifier",
|
|
131
|
+
)},
|
|
132
|
+
wrapper="okstra-antigravity-exec.sh",
|
|
133
|
+
supported_roles=frozenset(
|
|
134
|
+
{"lead", "analyser", "critic", "executor", "verifier"}
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
"codex": ProviderSpec(
|
|
138
|
+
provider="codex",
|
|
139
|
+
display_label="Codex",
|
|
140
|
+
models=CODEX,
|
|
141
|
+
default_models={role: "gpt-5.6-sol" for role in (
|
|
142
|
+
"lead", "analyser", "critic", "executor", "verifier", "report-writer",
|
|
143
|
+
)},
|
|
144
|
+
wrapper="okstra-codex-exec.sh",
|
|
145
|
+
supported_roles=frozenset(
|
|
146
|
+
{"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
"grok": ProviderSpec(
|
|
150
|
+
provider="grok",
|
|
151
|
+
display_label="Grok",
|
|
152
|
+
models=GROK,
|
|
153
|
+
default_models={"analyser": "grok-build-0.1", "critic": "grok-4.5"},
|
|
154
|
+
wrapper="okstra-grok-exec.sh",
|
|
155
|
+
supported_roles=ANALYSIS_ROLES,
|
|
156
|
+
),
|
|
157
|
+
"kimi": ProviderSpec(
|
|
158
|
+
provider="kimi",
|
|
159
|
+
display_label="Kimi",
|
|
160
|
+
models=KIMI,
|
|
161
|
+
default_models={"analyser": "kimi-k2.7-code", "critic": "kimi-k3"},
|
|
162
|
+
wrapper="okstra-kimi-exec.sh",
|
|
163
|
+
supported_roles=ANALYSIS_ROLES,
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
PROVIDER_MAPPINGS = {
|
|
168
|
+
provider: spec.models for provider, spec in PROVIDERS.items()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Legacy role-keyed compatibility defaults. Canonical assignments resolve a
|
|
172
|
+
# provider first and then use ProviderSpec.default_models for that role.
|
|
70
173
|
ROLE_DEFAULTS = {
|
|
71
174
|
"lead": "opus", "claude": "opus", "codex": "gpt-5.6-sol",
|
|
72
175
|
"antigravity": "gemini-3.1-pro", "report-writer": "sonnet",
|
|
@@ -83,6 +186,51 @@ class UnknownModelError(ValueError):
|
|
|
83
186
|
"""raw_value 가 provider mapping 에 등록되어 있지 않을 때 발생."""
|
|
84
187
|
|
|
85
188
|
|
|
189
|
+
class UnknownProviderError(ValueError):
|
|
190
|
+
"""Raised when a requested provider is absent from the registry."""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def provider_spec(provider: str) -> ProviderSpec:
|
|
194
|
+
"""Return one registered provider or fail before assignment/dispatch."""
|
|
195
|
+
normalized = (provider or "").strip().lower()
|
|
196
|
+
try:
|
|
197
|
+
return PROVIDERS[normalized]
|
|
198
|
+
except KeyError as exc:
|
|
199
|
+
allowed = ", ".join(PROVIDERS)
|
|
200
|
+
raise UnknownProviderError(
|
|
201
|
+
f"unknown provider {provider!r}. Allowed values: {allowed}"
|
|
202
|
+
) from exc
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def provider_ids(role: str | None = None) -> list[str]:
|
|
206
|
+
"""Registered provider ids, optionally filtered by functional role."""
|
|
207
|
+
if not role:
|
|
208
|
+
return list(PROVIDERS)
|
|
209
|
+
return [
|
|
210
|
+
provider for provider, spec in PROVIDERS.items()
|
|
211
|
+
if role in spec.supported_roles
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def provider_supports_role(provider: str, role: str) -> bool:
|
|
216
|
+
"""Whether the provider has passed the registry gate for a role."""
|
|
217
|
+
return role in provider_spec(provider).supported_roles
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def provider_wrappers(role: str | None = None) -> dict[str, str]:
|
|
221
|
+
"""Provider-to-wrapper mapping derived from capability entries."""
|
|
222
|
+
return {
|
|
223
|
+
provider: PROVIDERS[provider].wrapper
|
|
224
|
+
for provider in provider_ids(role)
|
|
225
|
+
if PROVIDERS[provider].wrapper
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def provider_default_model(provider: str, role: str) -> Optional[str]:
|
|
230
|
+
"""Resolve a provider's explicit default for one functional role."""
|
|
231
|
+
return provider_spec(provider).default_models.get(role)
|
|
232
|
+
|
|
233
|
+
|
|
86
234
|
def resolve_model_metadata(
|
|
87
235
|
*, provider: str, raw_value: str, default_display: str, default_execution: str,
|
|
88
236
|
) -> ModelAssignment:
|
|
@@ -120,6 +268,6 @@ def default_model(role: str) -> Optional[str]:
|
|
|
120
268
|
|
|
121
269
|
|
|
122
270
|
def catalog_pricing(provider: str) -> dict:
|
|
123
|
-
"""Selectable-model pricing
|
|
271
|
+
"""Selectable-model pricing as (input, cached input, output) USD/1M."""
|
|
124
272
|
mapping = PROVIDER_MAPPINGS.get(provider, {})
|
|
125
273
|
return {alias: spec.pricing for alias, spec in mapping.items() if spec.pricing is not None}
|