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.
Files changed (137) hide show
  1. package/README.md +23 -9
  2. package/docs/architecture/storage-model.md +39 -65
  3. package/docs/architecture.md +68 -60
  4. package/docs/cli.md +40 -23
  5. package/docs/for-ai/skills/okstra-run.md +13 -34
  6. package/docs/performance-improvement-plan-v2.md +2 -2
  7. package/docs/pr-template-usage.md +1 -1
  8. package/docs/project-structure-overview.md +26 -22
  9. package/docs/task-process/README.md +4 -4
  10. package/docs/task-process/common-flow.md +12 -12
  11. package/docs/task-process/final-verification.md +2 -2
  12. package/docs/task-process/implementation.md +1 -1
  13. package/docs/task-process/release-handoff.md +1 -1
  14. package/package.json +2 -2
  15. package/runtime/BUILD.json +2 -2
  16. package/runtime/agents/workers/antigravity-worker.md +2 -2
  17. package/runtime/agents/workers/claude-worker.md +1 -1
  18. package/runtime/agents/workers/codex-worker.md +2 -2
  19. package/runtime/agents/workers/grok-worker.md +256 -0
  20. package/runtime/agents/workers/kimi-worker.md +256 -0
  21. package/runtime/agents/workers/report-writer-worker.md +12 -12
  22. package/runtime/bin/lib/okstra/cli.sh +13 -1
  23. package/runtime/bin/lib/okstra/globals.sh +3 -0
  24. package/runtime/bin/lib/okstra/usage.sh +17 -12
  25. package/runtime/bin/okstra-grok-exec.sh +5 -0
  26. package/runtime/bin/okstra-kimi-exec.sh +5 -0
  27. package/runtime/bin/okstra-provider-exec.py +235 -0
  28. package/runtime/bin/okstra-render-final-report.py +4 -4
  29. package/runtime/bin/okstra-render-report-views.py +100 -12
  30. package/runtime/bin/okstra.sh +3 -0
  31. package/runtime/prompts/lead/adapters/antigravity.md +48 -0
  32. package/runtime/prompts/lead/adapters/claude-code.md +13 -11
  33. package/runtime/prompts/lead/adapters/codex.md +7 -7
  34. package/runtime/prompts/lead/okstra-lead-contract.md +5 -5
  35. package/runtime/prompts/lead/report-writer.md +16 -12
  36. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  37. package/runtime/prompts/profiles/_common-contract.md +16 -10
  38. package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
  39. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  40. package/runtime/prompts/profiles/_implementation-executor.md +12 -12
  41. package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
  42. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  43. package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
  44. package/runtime/prompts/profiles/error-analysis.md +2 -0
  45. package/runtime/prompts/profiles/feature-analysis.md +2 -0
  46. package/runtime/prompts/profiles/final-verification.md +3 -1
  47. package/runtime/prompts/profiles/forbidden-actions.json +4 -4
  48. package/runtime/prompts/profiles/implementation-planning.md +3 -1
  49. package/runtime/prompts/profiles/implementation.md +2 -2
  50. package/runtime/prompts/profiles/improvement-discovery.md +6 -2
  51. package/runtime/prompts/profiles/project-analysis.md +2 -0
  52. package/runtime/prompts/profiles/release-handoff.md +7 -7
  53. package/runtime/prompts/profiles/requirements-discovery.md +2 -0
  54. package/runtime/prompts/wizard/prompts.ko.json +9 -1
  55. package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
  56. package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
  57. package/runtime/python/okstra_ctl/final_report_schema.py +37 -12
  58. package/runtime/python/okstra_ctl/lead_events.py +1 -1
  59. package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
  60. package/runtime/python/okstra_ctl/models.py +156 -8
  61. package/runtime/python/okstra_ctl/path_hints.py +9 -25
  62. package/runtime/python/okstra_ctl/paths.py +1 -1
  63. package/runtime/python/okstra_ctl/render.py +172 -74
  64. package/runtime/python/okstra_ctl/render_final_report.py +136 -28
  65. package/runtime/python/okstra_ctl/report_contract.py +124 -0
  66. package/runtime/python/okstra_ctl/report_finalize.py +1 -1
  67. package/runtime/python/okstra_ctl/report_html/__init__.py +10 -0
  68. package/runtime/python/okstra_ctl/report_html/common.py +86 -0
  69. package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
  70. package/runtime/python/okstra_ctl/report_html/models.py +59 -0
  71. package/runtime/python/okstra_ctl/report_html/render.py +76 -0
  72. package/runtime/python/okstra_ctl/report_html/router.py +40 -0
  73. package/runtime/python/okstra_ctl/report_html/view_models/__init__.py +1 -0
  74. package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +39 -0
  75. package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +49 -0
  76. package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +39 -0
  77. package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +47 -0
  78. package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +47 -0
  79. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +103 -0
  80. package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +43 -0
  81. package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +54 -0
  82. package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +54 -0
  83. package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +55 -0
  84. package/runtime/python/okstra_ctl/report_html/visualizations.py +139 -0
  85. package/runtime/python/okstra_ctl/report_view_artifacts.py +4 -1
  86. package/runtime/python/okstra_ctl/report_views.py +15 -43
  87. package/runtime/python/okstra_ctl/run.py +276 -51
  88. package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
  89. package/runtime/python/okstra_ctl/schema_excerpt.py +7 -17
  90. package/runtime/python/okstra_ctl/team.py +2 -7
  91. package/runtime/python/okstra_ctl/wizard.py +194 -21
  92. package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
  93. package/runtime/python/okstra_ctl/workers.py +3 -1
  94. package/runtime/python/okstra_ctl/workflow.py +4 -2
  95. package/runtime/python/okstra_token_usage/__init__.py +1 -0
  96. package/runtime/python/okstra_token_usage/collect.py +32 -23
  97. package/runtime/python/okstra_token_usage/pricing.py +35 -3
  98. package/runtime/schemas/final-report-v2.0.schema.json +3923 -0
  99. package/runtime/skills/okstra-run/SKILL.md +31 -42
  100. package/runtime/templates/prd/pr-body.template.md +1 -1
  101. package/runtime/templates/reports/final-report-v2.template.md +66 -0
  102. package/runtime/templates/reports/html/assets/base.css +41 -0
  103. package/runtime/templates/reports/html/assets/base.js +5 -0
  104. package/runtime/templates/reports/html/base.template.html +79 -0
  105. package/runtime/templates/reports/html/macros/forms.html +47 -0
  106. package/runtime/templates/reports/html/macros/layout.html +19 -0
  107. package/runtime/templates/reports/html/macros/visualizations.html +27 -0
  108. package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +40 -0
  109. package/runtime/templates/reports/html/tasks/error-analysis.template.html +40 -0
  110. package/runtime/templates/reports/html/tasks/feature-analysis.template.html +40 -0
  111. package/runtime/templates/reports/html/tasks/final-verification.template.html +39 -0
  112. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +47 -0
  113. package/runtime/templates/reports/html/tasks/implementation.template.html +40 -0
  114. package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +29 -0
  115. package/runtime/templates/reports/html/tasks/project-analysis.template.html +57 -0
  116. package/runtime/templates/reports/html/tasks/release-handoff.template.html +36 -0
  117. package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +37 -0
  118. package/runtime/templates/reports/report.js +21 -4
  119. package/runtime/templates/reports/settings.template.json +4 -0
  120. package/runtime/templates/reports/task-brief.template.md +7 -7
  121. package/runtime/validators/validate-report-views.py +86 -4
  122. package/runtime/validators/validate-run.py +73 -15
  123. package/runtime/validators/validate_improvement_report.py +55 -0
  124. package/runtime/validators/validate_session_conformance.py +2 -1
  125. package/src/cli-registry.mjs +4 -4
  126. package/src/commands/execute/codex-dispatch.mjs +7 -10
  127. package/src/commands/execute/render-bundle.mjs +3 -3
  128. package/src/commands/execute/run.mjs +17 -52
  129. package/src/commands/execute/wizard.mjs +4 -1
  130. package/src/commands/lifecycle/doctor.mjs +6 -3
  131. package/src/commands/lifecycle/install.mjs +49 -21
  132. package/src/commands/report/finalize.mjs +2 -3
  133. package/src/commands/report/render-final-report.mjs +4 -2
  134. package/src/commands/report/render-views.mjs +8 -8
  135. package/src/lib/runtime-manifest.mjs +1 -1
  136. package/src/lib/runtime-resolver.mjs +2 -2
  137. 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 codex_cost_usd, antigravity_cost_usd
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 _codex_runtime_provider(worker: dict) -> str:
451
+ def _cli_assignment_provider(worker: dict) -> str:
450
452
  worker_id = str(worker.get("workerId") or "").strip()
451
- agent = str(worker.get("agent") or "").strip()
452
- if worker_id == "report-writer":
453
- return "codex"
454
- if agent in {"codex", "antigravity"}:
455
- return agent
456
- return ""
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
- else:
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 == "codex":
500
- cost = codex_cost_usd(
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 codex/antigravity worker re-dispatched within one aggregated wrapper
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 _collect_codex_runtime_usage(state: dict, project_root: Path) -> dict:
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
- "Codex lead token accounting is not available yet; worker CLI usage is collected from provider logs."
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 = _codex_runtime_provider(worker)
671
+ provider = _cli_assignment_provider(worker)
665
672
  if not provider:
666
673
  worker["usage"] = na_block(
667
- f"worker provider is not CLI-backed in Codex runtime: {worker.get('agent') or worker.get('workerId')}"
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 Codex/Antigravity CLI calls billed under those providers.",
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") == "codex":
765
- return _collect_codex_runtime_usage(state, cwd)
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 codex/antigravity workers, sum every CLI session that fell inside
882
- # the aggregated wrapper window (re-dispatches leave one rollout each).
883
- if agent in ("codex", "antigravity"):
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
- agent,
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-05-17, public list prices, USD per 1M tokens):
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: