okstra 0.146.1 → 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 +23 -9
- package/docs/architecture/storage-model.md +39 -65
- package/docs/architecture.md +68 -60
- package/docs/cli.md +40 -23
- 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 +26 -22
- 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 +12 -12
- 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-render-final-report.py +4 -4
- package/runtime/bin/okstra-render-report-views.py +100 -12
- 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 +5 -5
- package/runtime/prompts/lead/report-writer.md +16 -12
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +16 -10
- 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 +6 -2
- 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/final_report_schema.py +37 -12
- 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/render_final_report.py +136 -28
- package/runtime/python/okstra_ctl/report_contract.py +124 -0
- package/runtime/python/okstra_ctl/report_finalize.py +1 -1
- package/runtime/python/okstra_ctl/report_html/__init__.py +10 -0
- package/runtime/python/okstra_ctl/report_html/common.py +86 -0
- package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
- package/runtime/python/okstra_ctl/report_html/models.py +59 -0
- package/runtime/python/okstra_ctl/report_html/render.py +76 -0
- package/runtime/python/okstra_ctl/report_html/router.py +40 -0
- package/runtime/python/okstra_ctl/report_html/view_models/__init__.py +1 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +39 -0
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +49 -0
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +39 -0
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +47 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +47 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +103 -0
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +43 -0
- package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +54 -0
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +54 -0
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +55 -0
- package/runtime/python/okstra_ctl/report_html/visualizations.py +139 -0
- package/runtime/python/okstra_ctl/report_view_artifacts.py +4 -1
- package/runtime/python/okstra_ctl/report_views.py +15 -43
- package/runtime/python/okstra_ctl/run.py +276 -51
- package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
- package/runtime/python/okstra_ctl/schema_excerpt.py +7 -17
- 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 +3923 -0
- package/runtime/skills/okstra-run/SKILL.md +31 -42
- package/runtime/templates/prd/pr-body.template.md +1 -1
- package/runtime/templates/reports/final-report-v2.template.md +66 -0
- package/runtime/templates/reports/html/assets/base.css +41 -0
- package/runtime/templates/reports/html/assets/base.js +5 -0
- package/runtime/templates/reports/html/base.template.html +79 -0
- package/runtime/templates/reports/html/macros/forms.html +47 -0
- package/runtime/templates/reports/html/macros/layout.html +19 -0
- package/runtime/templates/reports/html/macros/visualizations.html +27 -0
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +40 -0
- package/runtime/templates/reports/html/tasks/final-verification.template.html +39 -0
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +47 -0
- package/runtime/templates/reports/html/tasks/implementation.template.html +40 -0
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +29 -0
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +57 -0
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +36 -0
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +37 -0
- 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-report-views.py +86 -4
- package/runtime/validators/validate-run.py +73 -15
- package/runtime/validators/validate_improvement_report.py +55 -0
- 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 +49 -21
- package/src/commands/report/finalize.mjs +2 -3
- package/src/commands/report/render-final-report.mjs +4 -2
- package/src/commands/report/render-views.mjs +8 -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
|
@@ -20,7 +20,9 @@ from .antigravity import (
|
|
|
20
20
|
find_antigravity_sessions,
|
|
21
21
|
)
|
|
22
22
|
from .paths import claude_project_dir, utc_now
|
|
23
|
-
from .pricing import
|
|
23
|
+
from .pricing import antigravity_cost_usd, provider_cost_usd
|
|
24
|
+
from okstra_ctl.models import provider_wrappers
|
|
25
|
+
from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES
|
|
24
26
|
from okstra_ctl.wrapper_status import read_wrapper_status, status_path_for_prompt
|
|
25
27
|
|
|
26
28
|
|
|
@@ -446,14 +448,16 @@ def _codex_worker_windows(project_root: Path, state: dict) -> dict[str, list[tup
|
|
|
446
448
|
return windows
|
|
447
449
|
|
|
448
450
|
|
|
449
|
-
def
|
|
451
|
+
def _cli_assignment_provider(worker: dict) -> str:
|
|
450
452
|
worker_id = str(worker.get("workerId") or "").strip()
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if
|
|
455
|
-
return
|
|
456
|
-
|
|
453
|
+
explicit_provider = str(worker.get("provider") or "").strip()
|
|
454
|
+
provider = explicit_provider or str(worker.get("agent") or "").strip()
|
|
455
|
+
runner = str(worker.get("runner") or "").strip()
|
|
456
|
+
if runner and runner != "cli-wrapper":
|
|
457
|
+
return ""
|
|
458
|
+
if not explicit_provider and worker_id == "report-writer":
|
|
459
|
+
provider = "codex"
|
|
460
|
+
return provider if provider in provider_wrappers() else ""
|
|
457
461
|
|
|
458
462
|
|
|
459
463
|
def _cli_sessions_for_windows(
|
|
@@ -482,8 +486,10 @@ def _cli_session_totals(provider: str, session_paths: list[Path]) -> list[dict]:
|
|
|
482
486
|
for session_path in session_paths:
|
|
483
487
|
if provider == "codex":
|
|
484
488
|
total = codex_session_total(session_path)
|
|
485
|
-
|
|
489
|
+
elif provider == "antigravity":
|
|
486
490
|
total = antigravity_session_total(session_path)
|
|
491
|
+
else:
|
|
492
|
+
continue
|
|
487
493
|
if total.get("available"):
|
|
488
494
|
totals.append(total)
|
|
489
495
|
return totals
|
|
@@ -496,8 +502,9 @@ def _cli_usage_block(provider: str, totals: dict, session_paths: list[Path]) ->
|
|
|
496
502
|
if totals.get("model"):
|
|
497
503
|
block["model"] = totals["model"]
|
|
498
504
|
block["cliModel"] = totals["model"]
|
|
499
|
-
if provider
|
|
500
|
-
cost =
|
|
505
|
+
if provider in {"codex", "grok", "kimi"}:
|
|
506
|
+
cost = provider_cost_usd(
|
|
507
|
+
provider,
|
|
501
508
|
totals.get("model"),
|
|
502
509
|
totals.get("inputTokens", 0) or 0,
|
|
503
510
|
totals.get("cachedInputTokens", 0) or 0,
|
|
@@ -627,7 +634,7 @@ def _attach_cli_usage(
|
|
|
627
634
|
) -> None:
|
|
628
635
|
"""Layer aggregated CLI token/cost onto a Claude-side worker ``block``.
|
|
629
636
|
|
|
630
|
-
A
|
|
637
|
+
A CLI-wrapper worker re-dispatched within one aggregated wrapper
|
|
631
638
|
window produces several rollout jsonls; pricing only the latest (the old
|
|
632
639
|
``find_codex_session`` -> ``sessions[-1]`` behavior) dropped the earlier
|
|
633
640
|
attempts' tokens. We sum every in-window session here so the redispatched
|
|
@@ -653,18 +660,19 @@ def _attach_cli_usage(
|
|
|
653
660
|
block[key] = cli[key]
|
|
654
661
|
|
|
655
662
|
|
|
656
|
-
def
|
|
663
|
+
def _collect_cli_runtime_usage(state: dict, project_root: Path) -> dict:
|
|
657
664
|
windows_by_worker = _codex_worker_windows(project_root, state)
|
|
658
665
|
state["leadUsage"] = na_block(
|
|
659
|
-
"
|
|
666
|
+
"Host lead token accounting is unavailable; CLI worker usage is collected only from attributable provider logs."
|
|
660
667
|
)
|
|
661
668
|
for worker in state.get("workers", []):
|
|
662
669
|
if not isinstance(worker, dict):
|
|
663
670
|
continue
|
|
664
|
-
provider =
|
|
671
|
+
provider = _cli_assignment_provider(worker)
|
|
665
672
|
if not provider:
|
|
666
673
|
worker["usage"] = na_block(
|
|
667
|
-
|
|
674
|
+
"worker is host-native or has no registered CLI provider: "
|
|
675
|
+
f"{worker.get('provider') or worker.get('agent') or worker.get('workerId')}"
|
|
668
676
|
)
|
|
669
677
|
continue
|
|
670
678
|
worker_id = str(worker.get("workerId") or "").strip()
|
|
@@ -745,7 +753,7 @@ def _populate_usage_summary(
|
|
|
745
753
|
"definitions": {
|
|
746
754
|
"totalTokens": "Sum of input + output + cache_creation + cache_read tokens (raw processed volume; matches Anthropic API breakdown). Cache reads are 95%+ in long sessions.",
|
|
747
755
|
"billableEquivalentTokens": "Tokens normalized to base-input-price units (cache_creation_5m x1.25, cache_creation_1h x2.0, cache_read x0.1, output x5). 5m vs 1h is split from usage.cache_creation when the API breakdown is present; otherwise all cache_creation falls into 5m.",
|
|
748
|
-
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers
|
|
756
|
+
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers attributable registered-provider CLI calls.",
|
|
749
757
|
},
|
|
750
758
|
}
|
|
751
759
|
|
|
@@ -761,8 +769,8 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
761
769
|
task_key = state.get("taskKey", "")
|
|
762
770
|
team_name = resolve_team_name(state)
|
|
763
771
|
lead_sid = (state.get("lead") or {}).get("sessionId")
|
|
764
|
-
if state.get("leadRuntime")
|
|
765
|
-
return
|
|
772
|
+
if state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES:
|
|
773
|
+
return _collect_cli_runtime_usage(state, cwd)
|
|
766
774
|
|
|
767
775
|
# 1) Claude sessions (lead + claude-side workers). Cache totals at scan
|
|
768
776
|
# time so we don't re-read the jsonl when a worker matches multiple
|
|
@@ -878,9 +886,10 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
878
886
|
block["additionalSessionIds"] = [sid for sid, _, _ in matched[1:]]
|
|
879
887
|
block["matchedAgentNames"] = sorted({t.get("agentName") for _, _, t in matched if t.get("agentName")})
|
|
880
888
|
|
|
881
|
-
# For
|
|
882
|
-
#
|
|
883
|
-
|
|
889
|
+
# For CLI-wrapper workers, retain execution evidence and attach any
|
|
890
|
+
# attributable provider transcript usage without treating absence as zero.
|
|
891
|
+
provider = _cli_assignment_provider(worker)
|
|
892
|
+
if provider:
|
|
884
893
|
windows, status_path, used_fallback = _worker_cli_windows(
|
|
885
894
|
cwd,
|
|
886
895
|
worker,
|
|
@@ -888,7 +897,7 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
888
897
|
)
|
|
889
898
|
_attach_cli_usage(
|
|
890
899
|
block,
|
|
891
|
-
|
|
900
|
+
provider,
|
|
892
901
|
cwd,
|
|
893
902
|
windows,
|
|
894
903
|
status_path,
|
|
@@ -4,18 +4,18 @@ Pricing is matched by substring against the model id recorded in the session
|
|
|
4
4
|
transcript, so keys must reflect the *actual* model id form emitted by each
|
|
5
5
|
provider:
|
|
6
6
|
|
|
7
|
-
* Anthropic — `claude-fable-5*`, `claude-opus-5*`, `claude-opus-4-*`, `claude-sonnet-4-*`,
|
|
7
|
+
* Anthropic — `claude-fable-5*`, `claude-opus-5*`, `claude-opus-4-*`, `claude-sonnet-5*`, `claude-sonnet-4-*`,
|
|
8
8
|
`claude-haiku-4-5-*`, `claude-3-5-sonnet-*`, `claude-3-5-haiku-*`,
|
|
9
9
|
`claude-3-opus-*`, `claude-3-haiku-*`.
|
|
10
10
|
* OpenAI / Codex — `gpt-5*`, `gpt-4o*`, `gpt-4*`.
|
|
11
|
-
* Google / Gemini — `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
|
|
11
|
+
* Google / Gemini — `gemini-3.6-flash*`, `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
|
|
12
12
|
`gemini-2.5-pro*`, `gemini-2.5-flash*`, `gemini-2.0-flash*`.
|
|
13
13
|
|
|
14
14
|
Matching prefers the longest key that is a substring of the model id (most
|
|
15
15
|
specific wins), so table ordering does not affect the result. Update when
|
|
16
16
|
providers change list pricing.
|
|
17
17
|
|
|
18
|
-
Sources (last verified 2026-
|
|
18
|
+
Sources (last verified 2026-08-03, public list prices, USD per 1M tokens):
|
|
19
19
|
* Anthropic: https://www.anthropic.com/pricing
|
|
20
20
|
* OpenAI: https://openai.com/api/pricing
|
|
21
21
|
* Google: https://ai.google.dev/gemini-api/docs/pricing
|
|
@@ -55,6 +55,9 @@ CLAUDE_PRICING = {
|
|
|
55
55
|
# Claude Opus 5.
|
|
56
56
|
"opus-5": (5.0, 6.25, 0.50, 25.0), # Opus 5 (cache prices derived from ratios)
|
|
57
57
|
|
|
58
|
+
# Claude Sonnet 5.
|
|
59
|
+
"sonnet-5": (3.0, 3.75, 0.30, 15.0), # Sonnet 5 (cache prices derived from ratios)
|
|
60
|
+
|
|
58
61
|
# Claude 4 point releases (explicit so future divergence is easy to see).
|
|
59
62
|
"opus-4-8": (5.0, 6.25, 0.50, 25.0), # Opus 4.8 (cache prices derived from ratios)
|
|
60
63
|
"opus-4-7": (5.0, 6.25, 0.50, 25.0), # Opus 4.7 (cache prices derived from ratios)
|
|
@@ -107,6 +110,8 @@ _LEGACY_CODEX_PRICING = {
|
|
|
107
110
|
# catalog + legacy: catalog owns selectable prices, legacy covers the rest. No
|
|
108
111
|
# key overlaps today; if one ever collided, the legacy (right) side would win.
|
|
109
112
|
CODEX_PRICING = {**catalog_pricing("codex"), **_LEGACY_CODEX_PRICING}
|
|
113
|
+
GROK_PRICING = catalog_pricing("grok")
|
|
114
|
+
KIMI_PRICING = catalog_pricing("kimi")
|
|
110
115
|
|
|
111
116
|
GEMINI_PRICING = {
|
|
112
117
|
# model substring -> (input USD/1M, output USD/1M).
|
|
@@ -121,6 +126,8 @@ GEMINI_PRICING = {
|
|
|
121
126
|
# forms appear in the wild, so include both for the new 3.x families.
|
|
122
127
|
|
|
123
128
|
# Gemini 3 series.
|
|
129
|
+
"3.6-flash": (1.50, 7.50),
|
|
130
|
+
"3-6-flash": (1.50, 7.50),
|
|
124
131
|
"3.5-flash": (1.50, 9.00),
|
|
125
132
|
"3-5-flash": (1.50, 9.00),
|
|
126
133
|
"3.1-pro": (2.00, 12.0),
|
|
@@ -213,6 +220,31 @@ def codex_cost_usd(model: str | None, input_t: int, cached_input_t: int, output_
|
|
|
213
220
|
return round((fresh * pi + cached_input_t * pci + output_t * po) / 1_000_000, 4)
|
|
214
221
|
|
|
215
222
|
|
|
223
|
+
def provider_cost_usd(
|
|
224
|
+
provider: str,
|
|
225
|
+
model: str | None,
|
|
226
|
+
input_t: int,
|
|
227
|
+
cached_input_t: int,
|
|
228
|
+
output_t: int,
|
|
229
|
+
) -> float | None:
|
|
230
|
+
"""Price a three-rate provider without fabricating an unknown model rate."""
|
|
231
|
+
tables = {
|
|
232
|
+
"codex": CODEX_PRICING,
|
|
233
|
+
"grok": GROK_PRICING,
|
|
234
|
+
"kimi": KIMI_PRICING,
|
|
235
|
+
}
|
|
236
|
+
pricing = _match_pricing(model, tables.get(provider, {}))
|
|
237
|
+
if pricing is None:
|
|
238
|
+
return None
|
|
239
|
+
input_price, cached_price, output_price = pricing
|
|
240
|
+
fresh_input = max(0, input_t - cached_input_t)
|
|
241
|
+
return round((
|
|
242
|
+
fresh_input * input_price
|
|
243
|
+
+ cached_input_t * cached_price
|
|
244
|
+
+ output_t * output_price
|
|
245
|
+
) / 1_000_000, 4)
|
|
246
|
+
|
|
247
|
+
|
|
216
248
|
def antigravity_cost_usd(model: str | None, input_t: int, output_t: int) -> float | None:
|
|
217
249
|
p = _match_pricing(model, GEMINI_PRICING)
|
|
218
250
|
if p is None:
|