okstra 0.179.2 → 0.183.2

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 (258) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-registry.mjs +30 -2
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/execute/incremental-carry.mjs +9 -8
  5. package/dist/commands/execute/incremental-carry.mjs.map +1 -1
  6. package/dist/commands/execute/plan-verify.mjs +3 -1
  7. package/dist/commands/execute/plan-verify.mjs.map +1 -1
  8. package/dist/commands/execute/render-bundle.d.mts +4 -2
  9. package/dist/commands/execute/render-bundle.mjs +46 -5
  10. package/dist/commands/execute/render-bundle.mjs.map +1 -1
  11. package/dist/commands/execute/run.mjs +11 -3
  12. package/dist/commands/execute/run.mjs.map +1 -1
  13. package/dist/commands/inspect/model-io.d.mts +1 -0
  14. package/dist/commands/inspect/model-io.mjs +25 -0
  15. package/dist/commands/inspect/model-io.mjs.map +1 -0
  16. package/dist/commands/inspect/stage-map.mjs +29 -8
  17. package/dist/commands/inspect/stage-map.mjs.map +1 -1
  18. package/dist/commands/inspect/task-list.mjs +52 -6
  19. package/dist/commands/inspect/task-list.mjs.map +1 -1
  20. package/dist/commands/inspect/user-response.mjs +14 -4
  21. package/dist/commands/inspect/user-response.mjs.map +1 -1
  22. package/dist/commands/lifecycle/check-project.d.mts +1 -0
  23. package/dist/commands/lifecycle/check-project.mjs +69 -50
  24. package/dist/commands/lifecycle/check-project.mjs.map +1 -1
  25. package/dist/commands/lifecycle/contract-check.d.mts +1 -0
  26. package/dist/commands/lifecycle/contract-check.mjs +18 -0
  27. package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
  28. package/dist/commands/lifecycle/preflight.mjs +154 -51
  29. package/dist/commands/lifecycle/preflight.mjs.map +1 -1
  30. package/dist/commands/pr/pr.d.mts +1 -0
  31. package/dist/commands/pr/pr.mjs +19 -1
  32. package/dist/commands/pr/pr.mjs.map +1 -1
  33. package/dist/commands/report/agent-activity.mjs +2 -2
  34. package/dist/commands/report/approval-decision.d.mts +1 -0
  35. package/dist/commands/report/approval-decision.mjs +21 -0
  36. package/dist/commands/report/approval-decision.mjs.map +1 -0
  37. package/dist/commands/report/design-snapshot.d.mts +1 -0
  38. package/dist/commands/report/design-snapshot.mjs +19 -0
  39. package/dist/commands/report/design-snapshot.mjs.map +1 -0
  40. package/dist/commands/report/translate.mjs +3 -0
  41. package/dist/commands/report/translate.mjs.map +1 -1
  42. package/dist/lib/host-registry-client.mjs +13 -9
  43. package/dist/lib/host-registry-client.mjs.map +1 -1
  44. package/docs/architecture/storage-model.md +1 -1
  45. package/docs/architecture.md +21 -10
  46. package/docs/cli.md +36 -20
  47. package/docs/container.md +6 -4
  48. package/docs/contributor-change-matrix.md +1 -1
  49. package/docs/for-ai/README.md +2 -2
  50. package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
  51. package/docs/for-ai/skills/okstra-code-review.md +4 -4
  52. package/docs/for-ai/skills/okstra-container-build.md +20 -17
  53. package/docs/for-ai/skills/okstra-inspect.md +20 -23
  54. package/docs/for-ai/skills/okstra-manager.md +19 -18
  55. package/docs/for-ai/skills/okstra-memory.md +2 -2
  56. package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
  57. package/docs/for-ai/skills/okstra-rollup.md +14 -13
  58. package/docs/for-ai/skills/okstra-run.md +7 -3
  59. package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
  60. package/docs/for-ai/skills/okstra-setup.md +7 -7
  61. package/docs/for-ai/skills/okstra-usage.md +5 -4
  62. package/docs/for-ai/skills/okstra-user-response.md +50 -32
  63. package/docs/project-structure-overview.md +43 -31
  64. package/docs/task-process/implementation-planning.md +2 -2
  65. package/docs/task-process/implementation.md +7 -4
  66. package/package.json +1 -1
  67. package/runtime/BUILD.json +2 -2
  68. package/runtime/agents/workers/claude-worker.md +4 -4
  69. package/runtime/agents/workers/report-writer-worker.md +15 -164
  70. package/runtime/agents/workers/translator-worker.md +5 -13
  71. package/runtime/bin/okstra-error-log.py +51 -11
  72. package/runtime/bin/okstra-report-translate.py +210 -23
  73. package/runtime/prompts/host-orchestration/implementation.md +1 -1
  74. package/runtime/prompts/launch.template.md +9 -12
  75. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  76. package/runtime/prompts/lead/context-loader.md +41 -141
  77. package/runtime/prompts/lead/convergence.md +10 -8
  78. package/runtime/prompts/lead/okstra-lead-contract.md +40 -49
  79. package/runtime/prompts/lead/plan-body-verification.md +217 -23
  80. package/runtime/prompts/lead/report-writer.md +81 -421
  81. package/runtime/prompts/lead/team-contract.md +9 -54
  82. package/runtime/prompts/profiles/_clarification-recommendation.md +5 -4
  83. package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
  84. package/runtime/prompts/profiles/_common-contract.md +4 -4
  85. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  86. package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
  87. package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
  88. package/runtime/prompts/profiles/change-impact-analysis.md +1 -1
  89. package/runtime/prompts/profiles/error-analysis.md +1 -1
  90. package/runtime/prompts/profiles/feature-analysis.md +1 -1
  91. package/runtime/prompts/profiles/final-verification.md +1 -1
  92. package/runtime/prompts/profiles/implementation-planning.md +20 -12
  93. package/runtime/prompts/profiles/improvement-discovery.md +1 -1
  94. package/runtime/prompts/profiles/project-analysis.md +1 -1
  95. package/runtime/prompts/profiles/release-handoff.md +0 -1
  96. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  97. package/runtime/prompts/wizard/prompts.ko.json +0 -1
  98. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
  99. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
  100. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +8 -2
  101. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
  102. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
  103. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
  104. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
  105. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
  106. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
  107. package/runtime/python/okstra_ctl/agent_activity.py +141 -38
  108. package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
  109. package/runtime/python/okstra_ctl/agent_prompt_cli.py +70 -23
  110. package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
  111. package/runtime/python/okstra_ctl/analysis_packet.py +124 -3
  112. package/runtime/python/okstra_ctl/approval_decisions.py +328 -0
  113. package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
  114. package/runtime/python/okstra_ctl/backfill.py +13 -10
  115. package/runtime/python/okstra_ctl/batch.py +2 -4
  116. package/runtime/python/okstra_ctl/build_tools.py +6 -3
  117. package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
  118. package/runtime/python/okstra_ctl/clarification_items.py +27 -13
  119. package/runtime/python/okstra_ctl/cmux.py +130 -52
  120. package/runtime/python/okstra_ctl/code_review_target.py +34 -8
  121. package/runtime/python/okstra_ctl/consumers.py +5 -4
  122. package/runtime/python/okstra_ctl/container.py +103 -8
  123. package/runtime/python/okstra_ctl/context_cost.py +2 -1
  124. package/runtime/python/okstra_ctl/contract_graph.py +497 -0
  125. package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
  126. package/runtime/python/okstra_ctl/convergence.py +338 -17
  127. package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
  128. package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
  129. package/runtime/python/okstra_ctl/convergence_store.py +55 -34
  130. package/runtime/python/okstra_ctl/design_prep.py +7 -4
  131. package/runtime/python/okstra_ctl/design_snapshot.py +134 -0
  132. package/runtime/python/okstra_ctl/dispatch_core.py +97 -69
  133. package/runtime/python/okstra_ctl/dispatch_state.py +163 -63
  134. package/runtime/python/okstra_ctl/doctor.py +6 -3
  135. package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
  136. package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
  137. package/runtime/python/okstra_ctl/error_log_write.py +35 -30
  138. package/runtime/python/okstra_ctl/error_report.py +26 -1
  139. package/runtime/python/okstra_ctl/error_zip.py +27 -5
  140. package/runtime/python/okstra_ctl/execution_identity.py +3 -2
  141. package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
  142. package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -2
  143. package/runtime/python/okstra_ctl/final_report_schema.py +26 -17
  144. package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
  145. package/runtime/python/okstra_ctl/fixed_text.py +39 -0
  146. package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
  147. package/runtime/python/okstra_ctl/handoff.py +5 -4
  148. package/runtime/python/okstra_ctl/i18n.py +4 -2
  149. package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
  150. package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
  151. package/runtime/python/okstra_ctl/incremental_carry.py +130 -17
  152. package/runtime/python/okstra_ctl/incremental_scope.py +12 -4
  153. package/runtime/python/okstra_ctl/index.py +8 -11
  154. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
  155. package/runtime/python/okstra_ctl/invocation.py +3 -6
  156. package/runtime/python/okstra_ctl/json_boundary.py +366 -0
  157. package/runtime/python/okstra_ctl/json_registry.py +10 -12
  158. package/runtime/python/okstra_ctl/jsonl.py +19 -2
  159. package/runtime/python/okstra_ctl/lead_events.py +33 -1
  160. package/runtime/python/okstra_ctl/listing.py +3 -3
  161. package/runtime/python/okstra_ctl/log_report.py +24 -2
  162. package/runtime/python/okstra_ctl/manager_cli.py +92 -7
  163. package/runtime/python/okstra_ctl/manager_store.py +12 -10
  164. package/runtime/python/okstra_ctl/material.py +5 -1
  165. package/runtime/python/okstra_ctl/migrate.py +29 -25
  166. package/runtime/python/okstra_ctl/model_cli.py +3 -15
  167. package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
  168. package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
  169. package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
  170. package/runtime/python/okstra_ctl/path_hints.py +12 -0
  171. package/runtime/python/okstra_ctl/paths.py +21 -0
  172. package/runtime/python/okstra_ctl/plan_items.py +115 -4
  173. package/runtime/python/okstra_ctl/plan_items_cli.py +626 -34
  174. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +2 -1
  175. package/runtime/python/okstra_ctl/pr_template.py +3 -2
  176. package/runtime/python/okstra_ctl/project_meta.py +5 -7
  177. package/runtime/python/okstra_ctl/recap.py +5 -4
  178. package/runtime/python/okstra_ctl/reconcile.py +21 -27
  179. package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
  180. package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
  181. package/runtime/python/okstra_ctl/render.py +74 -16
  182. package/runtime/python/okstra_ctl/render_final_report.py +10 -8
  183. package/runtime/python/okstra_ctl/report_assembly.py +509 -0
  184. package/runtime/python/okstra_ctl/report_contract.py +2 -1
  185. package/runtime/python/okstra_ctl/report_finalize.py +67 -32
  186. package/runtime/python/okstra_ctl/report_html/render.py +3 -2
  187. package/runtime/python/okstra_ctl/report_inputs.py +72 -0
  188. package/runtime/python/okstra_ctl/report_language.py +3 -2
  189. package/runtime/python/okstra_ctl/report_markdown.py +81 -8
  190. package/runtime/python/okstra_ctl/report_narrative.py +351 -0
  191. package/runtime/python/okstra_ctl/report_projections.py +265 -0
  192. package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
  193. package/runtime/python/okstra_ctl/report_views.py +3 -2
  194. package/runtime/python/okstra_ctl/rollup.py +65 -4
  195. package/runtime/python/okstra_ctl/run.py +184 -65
  196. package/runtime/python/okstra_ctl/run_audit.py +3 -2
  197. package/runtime/python/okstra_ctl/run_context.py +6 -9
  198. package/runtime/python/okstra_ctl/run_index_row.py +2 -8
  199. package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
  200. package/runtime/python/okstra_ctl/schema_excerpt.py +15 -8
  201. package/runtime/python/okstra_ctl/session_transcript.py +27 -1
  202. package/runtime/python/okstra_ctl/set_work_status.py +64 -38
  203. package/runtime/python/okstra_ctl/stage_fix_carry.py +8 -6
  204. package/runtime/python/okstra_ctl/stage_ledger.py +132 -18
  205. package/runtime/python/okstra_ctl/stage_map.py +96 -28
  206. package/runtime/python/okstra_ctl/stage_targets.py +3 -4
  207. package/runtime/python/okstra_ctl/team.py +3 -2
  208. package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
  209. package/runtime/python/okstra_ctl/time_report.py +51 -4
  210. package/runtime/python/okstra_ctl/usage_identity.py +2 -1
  211. package/runtime/python/okstra_ctl/usage_report.py +58 -4
  212. package/runtime/python/okstra_ctl/user_response.py +1431 -66
  213. package/runtime/python/okstra_ctl/wizard.py +11 -44
  214. package/runtime/python/okstra_ctl/work_categories.py +3 -2
  215. package/runtime/python/okstra_ctl/worker_dispatch.py +5 -2
  216. package/runtime/python/okstra_ctl/worker_prompt_body.py +49 -3
  217. package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
  218. package/runtime/python/okstra_ctl/worker_prompt_policy.py +31 -3
  219. package/runtime/python/okstra_ctl/worker_runner.py +14 -12
  220. package/runtime/python/okstra_ctl/workflow.py +2 -1
  221. package/runtime/python/okstra_ctl/worktree.py +3 -2
  222. package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
  223. package/runtime/python/okstra_ctl/write_policy.py +4 -2
  224. package/runtime/python/okstra_token_usage/antigravity.py +39 -12
  225. package/runtime/python/okstra_token_usage/collect.py +90 -38
  226. package/runtime/python/okstra_token_usage/grok.py +127 -0
  227. package/runtime/schemas/final-report-v2.0.schema.json +9 -0
  228. package/runtime/schemas/final-report-v3.0.schema.json +10219 -0
  229. package/runtime/schemas/report-narrative-v3.0.schema.json +30 -0
  230. package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
  231. package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
  232. package/runtime/skills/okstra-code-review/SKILL.md +21 -11
  233. package/runtime/skills/okstra-container-build/SKILL.md +18 -18
  234. package/runtime/skills/okstra-inspect/SKILL.md +12 -11
  235. package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
  236. package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
  237. package/runtime/skills/okstra-inspect/facets/history.md +9 -14
  238. package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
  239. package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
  240. package/runtime/skills/okstra-inspect/facets/report.md +6 -10
  241. package/runtime/skills/okstra-inspect/facets/status.md +9 -8
  242. package/runtime/skills/okstra-inspect/facets/time.md +3 -3
  243. package/runtime/skills/okstra-manager/SKILL.md +16 -14
  244. package/runtime/skills/okstra-memory/SKILL.md +3 -3
  245. package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
  246. package/runtime/skills/okstra-rollup/SKILL.md +6 -16
  247. package/runtime/skills/okstra-run/SKILL.md +7 -7
  248. package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
  249. package/runtime/skills/okstra-setup/SKILL.md +21 -13
  250. package/runtime/skills/okstra-setup/references/project-config.md +2 -2
  251. package/runtime/skills/okstra-usage/SKILL.md +10 -10
  252. package/runtime/skills/okstra-user-response/SKILL.md +78 -107
  253. package/runtime/templates/report-writer-prompt-preamble.md +29 -19
  254. package/runtime/templates/reports/html/macros/forms.html +6 -4
  255. package/runtime/templates/reports/schedule.template.md +4 -4
  256. package/runtime/templates/worker-error-contract.md +17 -29
  257. package/runtime/validators/validate-run.py +636 -72
  258. package/runtime/validators/validate_session_conformance.py +43 -7
@@ -14,16 +14,17 @@ from .claude import (
14
14
  find_claude_team_sessions,
15
15
  )
16
16
  from .codex import codex_session_total, find_codex_sessions
17
- from .antigravity import (
18
- PRINT_MODE_USAGE_NOTE,
19
- antigravity_session_total,
20
- find_antigravity_sessions,
21
- )
17
+ from .antigravity import antigravity_session_total, find_antigravity_sessions
18
+ from .grok import find_grok_sessions, grok_session_total
22
19
  from .paths import claude_project_dir, utc_now
23
20
  from .pricing import antigravity_cost_usd, provider_cost_usd
24
21
  from okstra_ctl.dispatch_state import worker_session_ids
25
22
  from okstra_ctl.models import provider_wrappers
26
- from okstra_ctl.wrapper_status import read_wrapper_status, status_path_for_prompt
23
+ from okstra_ctl.wrapper_status import (
24
+ log_path_for_prompt,
25
+ read_wrapper_status,
26
+ status_path_for_prompt,
27
+ )
27
28
 
28
29
 
29
30
  def match_prefixes(worker_id: str) -> list[str]:
@@ -472,6 +473,8 @@ def _cli_sessions_for_windows(
472
473
  matches = find_codex_sessions(project_root, started_at, ended_at)
473
474
  elif provider == "antigravity":
474
475
  matches = find_antigravity_sessions(project_root, started_at, ended_at)
476
+ elif provider == "grok":
477
+ matches = find_grok_sessions(project_root, started_at, ended_at)
475
478
  else:
476
479
  matches = []
477
480
  for path in matches:
@@ -488,6 +491,8 @@ def _cli_session_totals(provider: str, session_paths: list[Path]) -> list[dict]:
488
491
  total = codex_session_total(session_path)
489
492
  elif provider == "antigravity":
490
493
  total = antigravity_session_total(session_path)
494
+ elif provider == "grok":
495
+ total = grok_session_total(session_path)
491
496
  else:
492
497
  continue
493
498
  if total.get("available"):
@@ -582,24 +587,20 @@ def collect_cli_usage(
582
587
  execution = wrapper_execution(status_path)
583
588
  execution_note = _execution_note(execution)
584
589
 
585
- if provider == "antigravity":
586
- block = na_block(f"{PRINT_MODE_USAGE_NOTE}; {execution_note}")
590
+ totals = _cli_session_totals(provider, sessions)
591
+ if not sessions:
592
+ block = na_block(
593
+ f"{execution_note}; CLI usage attribution unavailable because no transcript was found"
594
+ )
587
595
  block["cliNote"] = block["note"]
596
+ elif not totals:
597
+ block = na_block(
598
+ f"{execution_note}; transcript found but no final token snapshot was recorded"
599
+ )
600
+ block["cliNote"] = block["note"]
601
+ block["cliSessionPaths"] = [str(path) for path in sessions]
588
602
  else:
589
- totals = _cli_session_totals(provider, sessions)
590
- if not sessions:
591
- block = na_block(
592
- f"{execution_note}; CLI usage attribution unavailable because no transcript was found"
593
- )
594
- block["cliNote"] = block["note"]
595
- elif not totals:
596
- block = na_block(
597
- f"{execution_note}; transcript found but no final token snapshot was recorded"
598
- )
599
- block["cliNote"] = block["note"]
600
- block["cliSessionPaths"] = [str(path) for path in sessions]
601
- else:
602
- block = _cli_usage_block(provider, _aggregate_totals(totals), sessions)
603
+ block = _cli_usage_block(provider, _aggregate_totals(totals), sessions)
603
604
 
604
605
  block["cliExecutionStatus"] = execution["status"]
605
606
  if fallback_window_used:
@@ -624,6 +625,51 @@ def _worker_cli_windows(
624
625
  return fallback_windows, status_path, True
625
626
 
626
627
 
628
+ def _antigravity_log_paths(
629
+ project_root: Path,
630
+ worker: dict,
631
+ status_path: Path | None,
632
+ ) -> list[Path]:
633
+ """agy stream-json 은 워커 로그에 남는다. 홈 세션 디렉터리가 아니다."""
634
+ paths: list[Path] = []
635
+ prompt_path = _resolve_project_path(project_root, str(worker.get("promptPath") or ""))
636
+ if prompt_path is not None:
637
+ log_path = log_path_for_prompt(prompt_path)
638
+ if log_path.is_file():
639
+ paths.append(log_path)
640
+ status = read_wrapper_status(status_path) if status_path else None
641
+ if status is not None and status.log_path is not None and status.log_path.is_file():
642
+ if status.log_path not in paths:
643
+ paths.append(status.log_path)
644
+ return paths
645
+
646
+
647
+ def _worker_cli_usage_block(
648
+ *,
649
+ provider: str,
650
+ project_root: Path,
651
+ worker: dict,
652
+ fallback_windows: list[tuple[str, str]],
653
+ ) -> dict:
654
+ """공급자 프로세스 트랜스크립트와 실행 증거로 워커 사용량 블록을 만든다."""
655
+ windows, status_path, used_fallback = _worker_cli_windows(
656
+ project_root,
657
+ worker,
658
+ fallback_windows,
659
+ )
660
+ session_paths = _cli_sessions_for_windows(provider, project_root, windows)
661
+ if provider == "antigravity":
662
+ for log_path in _antigravity_log_paths(project_root, worker, status_path):
663
+ if log_path not in session_paths:
664
+ session_paths.append(log_path)
665
+ return collect_cli_usage(
666
+ provider=provider,
667
+ status_path=status_path,
668
+ sessions=session_paths,
669
+ fallback_window_used=used_fallback,
670
+ )
671
+
672
+
627
673
  def _attach_cli_usage(
628
674
  block: dict,
629
675
  provider: str,
@@ -638,8 +684,8 @@ def _attach_cli_usage(
638
684
  window produces several rollout jsonls; pricing only the latest (the old
639
685
  ``find_codex_session`` -> ``sessions[-1]`` behavior) dropped the earlier
640
686
  attempts' tokens. We sum every in-window session here so the redispatched
641
- CLI spend is fully reported. (agy print mode leaves no transcript, so the
642
- antigravity path still resolves to "session not found" → na cost.)
687
+ CLI spend is fully reported. antigravity 워커 로그의 stream-json
688
+ ``result.usage`` 같은 합산 경로에 붙인다.
643
689
  """
644
690
  session_paths = _cli_sessions_for_windows(provider, project_root, windows)
645
691
  cli = collect_cli_usage(
@@ -676,17 +722,11 @@ def _collect_cli_runtime_usage(state: dict, project_root: Path) -> dict:
676
722
  )
677
723
  continue
678
724
  worker_id = str(worker.get("workerId") or "").strip()
679
- windows, status_path, used_fallback = _worker_cli_windows(
680
- project_root,
681
- worker,
682
- windows_by_worker.get(worker_id, []),
683
- )
684
- session_paths = _cli_sessions_for_windows(provider, project_root, windows)
685
- worker["usage"] = collect_cli_usage(
725
+ worker["usage"] = _worker_cli_usage_block(
686
726
  provider=provider,
687
- status_path=status_path,
688
- sessions=session_paths,
689
- fallback_window_used=used_fallback,
727
+ project_root=project_root,
728
+ worker=worker,
729
+ fallback_windows=windows_by_worker.get(worker_id, []),
690
730
  )
691
731
  _populate_usage_summary(state, team_name=resolve_team_name(state),
692
732
  sessions_found=0, needle_source="none")
@@ -871,6 +911,7 @@ def collect_claude_runtime_usage(
871
911
  sessions_dir = claude_project_dir(cwd)
872
912
  # sid 로 귀속한 세션 id 전체 — 아래 unattributed 폴드에서 빼는 데 쓴다.
873
913
  attributed_sids: set[str] = set()
914
+ cli_windows_by_worker = _codex_worker_windows(cwd, state)
874
915
  for worker in state.get("workers", []):
875
916
  worker_id = worker.get("workerId")
876
917
  agent = worker.get("agent")
@@ -922,10 +963,21 @@ def collect_claude_runtime_usage(
922
963
  matched_sids.add(entry[0])
923
964
 
924
965
  if not matched:
925
- worker["usage"] = na_block(
926
- "no Claude session jsonl in the run window for dispatched "
927
- f"sessionIds {dispatched_sids} or agentName prefixes {prefixes}"
928
- )
966
+ provider = _cli_assignment_provider(worker)
967
+ if provider and str(worker.get("runner") or "") == "cli-wrapper":
968
+ worker["usage"] = _worker_cli_usage_block(
969
+ provider=provider,
970
+ project_root=cwd,
971
+ worker=worker,
972
+ fallback_windows=cli_windows_by_worker.get(
973
+ str(worker_id or "").strip(), []
974
+ ),
975
+ )
976
+ else:
977
+ worker["usage"] = na_block(
978
+ "no Claude session jsonl in the run window for dispatched "
979
+ f"sessionIds {dispatched_sids} or agentName prefixes {prefixes}"
980
+ )
929
981
  continue
930
982
 
931
983
  # Stable order by startedAt so the "primary" session is the first one.
@@ -0,0 +1,127 @@
1
+ """Grok Build session collectors.
2
+
3
+ 공식 세션 문서는 대화를 ``~/.grok/sessions/`` 에 둔다. cwd 는 퍼센트 인코딩된
4
+ 디렉터리 이름이다. 누적 사용량은 ``updates.jsonl`` 의
5
+ ``params.update.usage.modelUsage`` 마지막 스냅샷이다.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import Mapping
13
+ from urllib.parse import quote, unquote
14
+
15
+ from .jsonl_io import iter_jsonl
16
+ from .paths import ts_in_window
17
+
18
+
19
+ def grok_sessions_root(
20
+ home: Path | None = None,
21
+ env: Mapping[str, str] | None = None,
22
+ ) -> Path:
23
+ environ = env if env is not None else os.environ
24
+ configured = str(environ.get("GROK_HOME") or "").strip()
25
+ base = Path(configured).expanduser() if configured else (home or Path.home()) / ".grok"
26
+ return base / "sessions"
27
+
28
+
29
+ def _iso_from_unix(value: object) -> str | None:
30
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
31
+ return None
32
+ return datetime.fromtimestamp(value, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
33
+
34
+
35
+ def _usage_payload(record: dict) -> dict | None:
36
+ params = record.get("params")
37
+ if not isinstance(params, dict):
38
+ return None
39
+ update = params.get("update")
40
+ if not isinstance(update, dict):
41
+ return None
42
+ usage = update.get("usage")
43
+ return usage if isinstance(usage, dict) else None
44
+
45
+
46
+ def _model_snapshot(usage: dict) -> tuple[dict, str | None]:
47
+ models = usage.get("modelUsage")
48
+ if isinstance(models, dict) and len(models) == 1:
49
+ name, payload = next(iter(models.items()))
50
+ if isinstance(payload, dict):
51
+ return payload, name if isinstance(name, str) else None
52
+ return usage, None
53
+
54
+
55
+ def grok_session_total(updates_path: Path) -> dict:
56
+ """마지막 modelUsage 스냅샷. 세션 누적이라 더하지 않는다."""
57
+ last: dict | None = None
58
+ model: str | None = None
59
+ started: str | None = None
60
+ ended: str | None = None
61
+ for record in iter_jsonl(updates_path):
62
+ usage = _usage_payload(record)
63
+ if usage is None:
64
+ continue
65
+ iso = _iso_from_unix(record.get("timestamp"))
66
+ if iso and started is None:
67
+ started = iso
68
+ if iso:
69
+ ended = iso
70
+ snapshot, snapshot_model = _model_snapshot(usage)
71
+ last = snapshot
72
+ if snapshot_model:
73
+ model = snapshot_model
74
+ if last is None:
75
+ return {"totalTokens": 0, "available": False}
76
+ return {
77
+ "totalTokens": last.get("totalTokens", 0) or 0,
78
+ "inputTokens": last.get("inputTokens", 0) or 0,
79
+ "outputTokens": last.get("outputTokens", 0) or 0,
80
+ "cachedInputTokens": last.get("cachedReadTokens", 0) or 0,
81
+ "reasoningOutputTokens": last.get("reasoningTokens", 0) or 0,
82
+ "durationMs": last.get("apiDurationMs", 0) or 0,
83
+ "model": model,
84
+ "startedAt": started,
85
+ "endedAt": ended,
86
+ "available": True,
87
+ }
88
+
89
+
90
+ def _session_in_window(updates_path: Path, started_at: str, ended_at: str) -> bool:
91
+ for record in iter_jsonl(updates_path):
92
+ iso = _iso_from_unix(record.get("timestamp"))
93
+ if iso and ts_in_window(iso, started_at, ended_at):
94
+ return True
95
+ return False
96
+
97
+
98
+ def find_grok_sessions(
99
+ cwd: Path,
100
+ started_at: str,
101
+ ended_at: str,
102
+ *,
103
+ session_root: Path | None = None,
104
+ ) -> list[Path]:
105
+ """cwd 로 인코딩된 세션 중 창 안에 갱신이 있는 updates.jsonl."""
106
+ if not started_at or not ended_at:
107
+ return []
108
+ root = session_root or grok_sessions_root()
109
+ if not root.is_dir():
110
+ return []
111
+ target = str(cwd)
112
+ matches: list[Path] = []
113
+ for child in root.iterdir():
114
+ if not child.is_dir():
115
+ continue
116
+ decoded = unquote(child.name)
117
+ if decoded != target:
118
+ continue
119
+ for session in child.iterdir():
120
+ updates = session / "updates.jsonl"
121
+ if updates.is_file() and _session_in_window(updates, started_at, ended_at):
122
+ matches.append(updates)
123
+ return sorted(matches)
124
+
125
+
126
+ def grok_session_dir_name(cwd: Path) -> str:
127
+ return quote(str(cwd), safe="")
@@ -8943,6 +8943,7 @@
8943
8943
  ],
8944
8944
  "additionalProperties": false,
8945
8945
  "properties": {
8946
+ "stageRefs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}},
8946
8947
  "id": {
8947
8948
  "type": "string",
8948
8949
  "pattern": "^VC-\\d{3,}$"
@@ -9142,6 +9143,8 @@
9142
9143
  ],
9143
9144
  "additionalProperties": false,
9144
9145
  "properties": {
9146
+ "setAside": {"type": "array", "items": {"type": "object", "required": ["id", "reason"], "additionalProperties": false, "properties": {"id": {"type": "string", "minLength": 1}, "reason": {"type": "string", "enum": ["record", "observed", "deferred"]}}}},
9147
+ "stageLedger": {"type": "object", "additionalProperties": {"type": "string", "enum": ["done", "active", "ready", "blocked"]}},
9145
9148
  "uniformVerifiers": {
9146
9149
  "type": "array",
9147
9150
  "description": "Verifiers whose every vote this round was one verdict. Advisory: a unanimous round is legitimate, but the gate reads as a three-way cross-check unless this sits beside it.",
@@ -9242,6 +9245,8 @@
9242
9245
  ],
9243
9246
  "additionalProperties": false,
9244
9247
  "properties": {
9248
+ "block": {"type": "string", "enum": ["execution", "record"]},
9249
+ "stageScope": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}},
9245
9250
  "id": {
9246
9251
  "type": "string",
9247
9252
  "minLength": 1
@@ -9280,6 +9285,9 @@
9280
9285
  ],
9281
9286
  "additionalProperties": false,
9282
9287
  "properties": {
9288
+ "claimKind": {"description": "Set only on DISAGREE. `fact` claims are settled by reproduction, `judgement` by quorum.", "enum": ["fact", "judgement", null]},
9289
+ "reproduction": {"description": "Set only on a `fact` claim. The probe okstra runs; see okstra_ctl/claim_reproduction.py.", "type": ["object", "null"]},
9290
+ "reproductionResult": {"description": "Written by okstra, not by the worker.", "enum": ["reproduced", "not-reproduced", "not-runnable", null]},
9283
9291
  "worker": {
9284
9292
  "type": "string",
9285
9293
  "minLength": 1
@@ -9752,6 +9760,7 @@
9752
9760
  ],
9753
9761
  "additionalProperties": false,
9754
9762
  "properties": {
9763
+ "stageRefs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}},
9755
9764
  "id": {
9756
9765
  "type": "string",
9757
9766
  "pattern": "^R-\\d{3,}$"