okstra 0.86.0 → 0.87.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/kr/architecture.md +27 -27
- package/docs/kr/cli.md +21 -21
- package/docs/kr/performance-improvement-plan-v2.md +4 -4
- package/docs/kr/performance-improvement-plan.md +3 -3
- package/docs/project-structure-overview.md +6 -6
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +724 -0
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +123 -0
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +4 -4
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +3 -3
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/{gemini-worker.md → antigravity-worker.md} +56 -56
- package/runtime/agents/workers/claude-worker.md +2 -2
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +3 -3
- package/runtime/bin/lib/okstra/globals.sh +8 -8
- package/runtime/bin/lib/okstra/interactive.sh +1 -1
- package/runtime/bin/lib/okstra/usage.sh +7 -7
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +5 -3
- package/runtime/bin/lib/okstra-ctl/prepare.sh +4 -2
- package/runtime/bin/{okstra-gemini-exec.sh → okstra-antigravity-exec.sh} +67 -60
- package/runtime/bin/okstra-error-log.py +2 -1
- package/runtime/bin/okstra-token-usage.py +3 -4
- package/runtime/bin/okstra-trace-cleanup.sh +4 -4
- package/runtime/bin/okstra-wrapper-status.py +3 -3
- package/runtime/bin/okstra.sh +1 -1
- package/runtime/prompts/launch.template.md +3 -3
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +8 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +15 -15
- package/runtime/prompts/lead/report-writer.md +6 -6
- package/runtime/prompts/lead/team-contract.md +31 -31
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +2 -2
- package/runtime/prompts/profiles/_common-contract.md +7 -7
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +6 -6
- package/runtime/prompts/profiles/_implementation-verifier.md +4 -4
- package/runtime/prompts/profiles/_stage-discipline.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/implementation.md +3 -3
- package/runtime/prompts/profiles/improvement-discovery.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +9 -5
- package/runtime/python/okstra_ctl/__init__.py +2 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +1 -1
- package/runtime/python/okstra_ctl/clarification_items.py +17 -7
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -2
- package/runtime/python/okstra_ctl/context_cost.py +10 -44
- package/runtime/python/okstra_ctl/doctor.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +185 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +1 -1
- package/runtime/python/okstra_ctl/models.py +10 -10
- package/runtime/python/okstra_ctl/paths.py +9 -9
- package/runtime/python/okstra_ctl/reconcile.py +74 -11
- package/runtime/python/okstra_ctl/render.py +21 -21
- package/runtime/python/okstra_ctl/report_views.py +7 -13
- package/runtime/python/okstra_ctl/run.py +16 -16
- package/runtime/python/okstra_ctl/run_context.py +1 -1
- package/runtime/python/okstra_ctl/seeding.py +1 -1
- package/runtime/python/okstra_ctl/task_target.py +44 -0
- package/runtime/python/okstra_ctl/team.py +1 -1
- package/runtime/python/okstra_ctl/wizard.py +55 -30
- package/runtime/python/okstra_ctl/workers.py +3 -3
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree.py +1 -1
- package/runtime/python/okstra_token_usage/__init__.py +2 -3
- package/runtime/python/okstra_token_usage/antigravity.py +26 -0
- package/runtime/python/okstra_token_usage/blocks.py +4 -0
- package/runtime/python/okstra_token_usage/collect.py +20 -15
- package/runtime/python/okstra_token_usage/paths.py +0 -1
- package/runtime/python/okstra_token_usage/pricing.py +6 -3
- package/runtime/schemas/final-report-v1.0.schema.json +1 -1
- package/runtime/skills/okstra-brief/SKILL.md +1 -1
- package/runtime/skills/okstra-inspect/SKILL.md +85 -21
- package/runtime/skills/okstra-run/SKILL.md +3 -3
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/runtime/templates/prd/brief.template.md +2 -2
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/i18n/ko.json +1 -1
- package/runtime/templates/reports/report.js +5 -11
- package/runtime/templates/reports/settings.template.json +2 -2
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +7 -7
- package/runtime/validators/forbidden_actions.py +1 -1
- package/runtime/validators/lib/fixtures.sh +3 -3
- package/runtime/validators/lib/validate-assets.sh +1 -1
- package/runtime/validators/validate-run.py +8 -8
- package/src/cli-registry.mjs +7 -0
- package/src/codex-dispatch.mjs +3 -3
- package/src/codex-run.mjs +2 -2
- package/src/doctor.mjs +1 -1
- package/src/error-report.mjs +27 -0
- package/src/install.mjs +1 -1
- package/src/render-bundle.mjs +1 -1
- package/src/uninstall.mjs +2 -2
- package/runtime/python/okstra_token_usage/gemini.py +0 -82
|
@@ -294,7 +294,7 @@ class PrepareInputs:
|
|
|
294
294
|
lead_model: str = ""
|
|
295
295
|
claude_model: str = ""
|
|
296
296
|
codex_model: str = ""
|
|
297
|
-
|
|
297
|
+
antigravity_model: str = ""
|
|
298
298
|
report_writer_model: str = ""
|
|
299
299
|
lead_runtime: str = "claude-code"
|
|
300
300
|
lead_runtime_request: str = ""
|
|
@@ -989,7 +989,7 @@ def _canonical_argv(inp: PrepareInputs, ctx: dict) -> list[str]:
|
|
|
989
989
|
("--lead-model", inp.lead_model or ctx.get("LEAD_MODEL", "")),
|
|
990
990
|
("--claude-model", inp.claude_model or ctx.get("CLAUDE_WORKER_MODEL", "")),
|
|
991
991
|
("--codex-model", inp.codex_model or ctx.get("CODEX_WORKER_MODEL", "")),
|
|
992
|
-
("--
|
|
992
|
+
("--antigravity-model", inp.antigravity_model or ctx.get("ANTIGRAVITY_WORKER_MODEL", "")),
|
|
993
993
|
("--report-writer-model", inp.report_writer_model or ctx.get("REPORT_WRITER_MODEL", "")),
|
|
994
994
|
("--lead-runtime", inp.lead_runtime if inp.lead_runtime != "claude-code" else ""),
|
|
995
995
|
("--executor", inp.executor or ctx.get("EXECUTOR_PROVIDER", "")),
|
|
@@ -1441,11 +1441,11 @@ class _ModelBindings:
|
|
|
1441
1441
|
|
|
1442
1442
|
|
|
1443
1443
|
def _resolve_worker_models(inp: PrepareInputs) -> dict:
|
|
1444
|
-
"""lead/claude/codex/
|
|
1444
|
+
"""lead/claude/codex/antigravity/report-writer 모델을 alias → (display, execution) 로 해소."""
|
|
1445
1445
|
lead_default = _default("OKSTRA_DEFAULT_LEAD_MODEL", "opus")
|
|
1446
1446
|
claude_default = _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "opus")
|
|
1447
1447
|
codex_default = _default("OKSTRA_DEFAULT_CODEX_MODEL", "gpt-5.5")
|
|
1448
|
-
|
|
1448
|
+
antigravity_default = _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", "auto")
|
|
1449
1449
|
report_writer_default = _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default)
|
|
1450
1450
|
return {
|
|
1451
1451
|
"lead": resolve_model_metadata(
|
|
@@ -1461,8 +1461,8 @@ def _resolve_worker_models(inp: PrepareInputs) -> dict:
|
|
|
1461
1461
|
default_display=codex_default, default_execution=codex_default,
|
|
1462
1462
|
),
|
|
1463
1463
|
"ge": resolve_model_metadata(
|
|
1464
|
-
provider="
|
|
1465
|
-
default_display=
|
|
1464
|
+
provider="antigravity", raw_value=inp.antigravity_model,
|
|
1465
|
+
default_display=antigravity_default, default_execution=antigravity_default,
|
|
1466
1466
|
),
|
|
1467
1467
|
"rw": resolve_model_metadata(
|
|
1468
1468
|
provider="claude", raw_value=inp.report_writer_model,
|
|
@@ -1475,15 +1475,15 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
1475
1475
|
"""worker 모델 + critic 선택 + executor 바인딩을 한 묶음으로 해소·검증한다."""
|
|
1476
1476
|
m = _resolve_worker_models(inp)
|
|
1477
1477
|
critic_choice = (inp.critic or "").strip().lower()
|
|
1478
|
-
if critic_choice not in ("", "off", "claude", "codex", "
|
|
1478
|
+
if critic_choice not in ("", "off", "claude", "codex", "antigravity"):
|
|
1479
1479
|
raise PrepareError(
|
|
1480
|
-
f"--critic must be one of: off, claude, codex,
|
|
1480
|
+
f"--critic must be one of: off, claude, codex, antigravity (got: {critic_choice!r})"
|
|
1481
1481
|
)
|
|
1482
1482
|
executor_default = _default("OKSTRA_DEFAULT_EXECUTOR", "claude")
|
|
1483
1483
|
executor_provider = (inp.executor or executor_default).strip().lower()
|
|
1484
|
-
if executor_provider not in ("claude", "codex", "
|
|
1484
|
+
if executor_provider not in ("claude", "codex", "antigravity"):
|
|
1485
1485
|
raise PrepareError(
|
|
1486
|
-
f"--executor must be one of: claude, codex,
|
|
1486
|
+
f"--executor must be one of: claude, codex, antigravity (got: {executor_provider!r})"
|
|
1487
1487
|
)
|
|
1488
1488
|
if inp.task_type == "implementation" and executor_provider not in workers:
|
|
1489
1489
|
raise PrepareError(
|
|
@@ -1494,7 +1494,7 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
|
|
|
1494
1494
|
provider_to_meta = {
|
|
1495
1495
|
"claude": ("Claude executor", "claude-worker", m["cw"]),
|
|
1496
1496
|
"codex": ("Codex executor", "codex-worker", m["co"]),
|
|
1497
|
-
"
|
|
1497
|
+
"antigravity": ("Antigravity executor", "antigravity-worker", m["ge"]),
|
|
1498
1498
|
}
|
|
1499
1499
|
display_name, worker_agent, model_meta = provider_to_meta[executor_provider]
|
|
1500
1500
|
return _ModelBindings(
|
|
@@ -1916,7 +1916,7 @@ def _persist_run_inputs(
|
|
|
1916
1916
|
"leadModel": models.lead.display,
|
|
1917
1917
|
"claudeModel": models.cw.display,
|
|
1918
1918
|
"codexModel": models.co.display,
|
|
1919
|
-
"
|
|
1919
|
+
"antigravityModel": models.ge.display,
|
|
1920
1920
|
"reportWriterModel": models.rw.display,
|
|
1921
1921
|
"executor": models.executor_provider,
|
|
1922
1922
|
"relatedTasks": inp.related_tasks_raw,
|
|
@@ -2273,8 +2273,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2273
2273
|
"CLAUDE_WORKER_MODEL_EXECUTION_VALUE": cw.execution,
|
|
2274
2274
|
"CODEX_WORKER_MODEL": co.display,
|
|
2275
2275
|
"CODEX_WORKER_MODEL_EXECUTION_VALUE": co.execution,
|
|
2276
|
-
"
|
|
2277
|
-
"
|
|
2276
|
+
"ANTIGRAVITY_WORKER_MODEL": ge.display,
|
|
2277
|
+
"ANTIGRAVITY_WORKER_MODEL_EXECUTION_VALUE": ge.execution,
|
|
2278
2278
|
"REPORT_WRITER_MODEL": rw.display,
|
|
2279
2279
|
"REPORT_WRITER_MODEL_EXECUTION_VALUE": rw.execution,
|
|
2280
2280
|
"EXECUTOR_PROVIDER": executor_provider,
|
|
@@ -2423,7 +2423,7 @@ def main(argv: list[str]) -> int:
|
|
|
2423
2423
|
p.add_argument("--lead-model", default="")
|
|
2424
2424
|
p.add_argument("--claude-model", default="")
|
|
2425
2425
|
p.add_argument("--codex-model", default="")
|
|
2426
|
-
p.add_argument("--
|
|
2426
|
+
p.add_argument("--antigravity-model", default="")
|
|
2427
2427
|
p.add_argument("--report-writer-model", default="")
|
|
2428
2428
|
p.add_argument(
|
|
2429
2429
|
"--lead-runtime",
|
|
@@ -2582,7 +2582,7 @@ def main(argv: list[str]) -> int:
|
|
|
2582
2582
|
lead_model=args.lead_model,
|
|
2583
2583
|
claude_model=args.claude_model,
|
|
2584
2584
|
codex_model=args.codex_model,
|
|
2585
|
-
|
|
2585
|
+
antigravity_model=args.antigravity_model,
|
|
2586
2586
|
report_writer_model=args.report_writer_model,
|
|
2587
2587
|
lead_runtime=args.lead_runtime,
|
|
2588
2588
|
lead_runtime_request=args.lead_runtime_request,
|
|
@@ -205,7 +205,7 @@ def write_run_inputs(
|
|
|
205
205
|
|
|
206
206
|
inputs schema (모든 키 optional):
|
|
207
207
|
taskBriefPath, directive, workers, leadModel, claudeModel, codexModel,
|
|
208
|
-
|
|
208
|
+
antigravityModel, reportWriterModel, relatedTasks, approvedPlanPath,
|
|
209
209
|
clarificationResponsePath, renderOnly
|
|
210
210
|
"""
|
|
211
211
|
run_manifests_dir = Path(run_manifests_dir)
|
|
@@ -135,7 +135,7 @@ def ensure_project_settings_symlink(*, project_root: Path) -> Optional[Path]:
|
|
|
135
135
|
|
|
136
136
|
Claude Code 가 그 프로젝트에서 host 세션으로 실행될 때 이 파일을
|
|
137
137
|
자동으로 로드하므로, okstra worker wrapper 호출(`okstra-codex-exec.sh`,
|
|
138
|
-
`okstra-
|
|
138
|
+
`okstra-antigravity-exec.sh`) 이 별도 `--settings` 인자 없이도 허용된다.
|
|
139
139
|
|
|
140
140
|
반환값:
|
|
141
141
|
- target Path: symlink 가 새로 생성되었거나 이미 올바른 위치를
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""task-key → (task_root, project_root) 해석 공유 헬퍼.
|
|
2
|
+
|
|
3
|
+
read-side CLI(context-cost, error-report)가 동일한 입력 계약을 공유하기 위한
|
|
4
|
+
SSOT. target 은 task-root 경로 / task-key 둘 다 받는다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from okstra_project import ResolverError, find_task_root, resolve_project_root
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def resolve_task_root(target: str, project_root: str, cwd: str) -> tuple[Path, Path]:
|
|
14
|
+
target_path = Path(target).expanduser()
|
|
15
|
+
if target_path.exists():
|
|
16
|
+
task_root = target_path.resolve()
|
|
17
|
+
return task_root, infer_project_root(task_root)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
resolved_project = Path(
|
|
21
|
+
resolve_project_root(explicit_root=project_root, cwd=cwd)
|
|
22
|
+
).resolve()
|
|
23
|
+
except ResolverError as exc:
|
|
24
|
+
raise SystemExit(f"project root resolution failed: {exc}") from exc
|
|
25
|
+
|
|
26
|
+
task_root = find_task_root(resolved_project, target)
|
|
27
|
+
if task_root is None:
|
|
28
|
+
raise SystemExit(f"no task root for task key: {target}")
|
|
29
|
+
return task_root.resolve(), resolved_project
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def infer_project_root(task_root: Path) -> Path:
|
|
33
|
+
parts = task_root.parts
|
|
34
|
+
if ".okstra" not in parts:
|
|
35
|
+
return task_root
|
|
36
|
+
idx = parts.index(".okstra")
|
|
37
|
+
return Path(*parts[:idx]).resolve()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def project_rel(path: Path, project_root: Path) -> str:
|
|
41
|
+
try:
|
|
42
|
+
return str(path.resolve().relative_to(project_root.resolve()))
|
|
43
|
+
except ValueError:
|
|
44
|
+
return str(path)
|
|
@@ -20,7 +20,7 @@ from .dispatch_core import (
|
|
|
20
20
|
_SUPPORTED_WRAPPERS = {
|
|
21
21
|
"claude": "okstra-claude-exec.sh",
|
|
22
22
|
"codex": "okstra-codex-exec.sh",
|
|
23
|
-
"
|
|
23
|
+
"antigravity": "okstra-antigravity-exec.sh",
|
|
24
24
|
"report-writer": "okstra-claude-exec.sh",
|
|
25
25
|
}
|
|
26
26
|
_TERMINAL_STATUSES = {"completed", "timeout", "error", "not-run"}
|
|
@@ -31,7 +31,7 @@ from okstra_ctl.models import (
|
|
|
31
31
|
UnknownModelError,
|
|
32
32
|
resolve_model_metadata,
|
|
33
33
|
)
|
|
34
|
-
from okstra_ctl.clarification_items import scan_approval_gate
|
|
34
|
+
from okstra_ctl.clarification_items import scan_approval_gate, user_response_sidecars
|
|
35
35
|
from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
|
|
36
36
|
from okstra_ctl.run import (
|
|
37
37
|
APPROVED_FRONTMATTER_PATTERN,
|
|
@@ -89,7 +89,7 @@ TASK_TYPES: list[tuple[str, str]] = [
|
|
|
89
89
|
]
|
|
90
90
|
TASK_TYPE_VALUES = [tt for tt, _ in TASK_TYPES]
|
|
91
91
|
|
|
92
|
-
EXECUTORS = ["claude", "codex", "
|
|
92
|
+
EXECUTORS = ["claude", "codex", "antigravity"]
|
|
93
93
|
|
|
94
94
|
# Task types that consume an approved plan and need a stage-scope pick:
|
|
95
95
|
# implementation executes a stage, final-verification verifies a stage
|
|
@@ -108,7 +108,7 @@ BASE_REF_FREE_INPUT_TOKEN = "__free_input__"
|
|
|
108
108
|
|
|
109
109
|
CLAUDE_MODEL_OPTIONS = ["default", "fable", "opus", "sonnet", "haiku"]
|
|
110
110
|
CODEX_MODEL_OPTIONS = ["default", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]
|
|
111
|
-
|
|
111
|
+
ANTIGRAVITY_MODEL_OPTIONS = ["default", "gemini-3.1-pro", "gemini-3.5-flash"]
|
|
112
112
|
|
|
113
113
|
# special pick value: start a brand-new task
|
|
114
114
|
TASK_PICK_NEW_TOKEN = "__new__"
|
|
@@ -263,7 +263,7 @@ S_LEAD_MODEL = "lead_model"
|
|
|
263
263
|
S_EXECUTOR_MODEL = "executor_model"
|
|
264
264
|
S_CLAUDE_MODEL = "claude_model"
|
|
265
265
|
S_CODEX_MODEL = "codex_model"
|
|
266
|
-
|
|
266
|
+
S_ANTIGRAVITY_MODEL = "antigravity_model"
|
|
267
267
|
S_REPORT_WRITER_MODEL = "report_writer_model"
|
|
268
268
|
S_DIRECTIVE_PICK = "directive_pick"
|
|
269
269
|
S_DIRECTIVE = "directive"
|
|
@@ -291,7 +291,7 @@ GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
|
|
|
291
291
|
# *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
|
|
292
292
|
PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
|
|
293
293
|
GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
|
|
294
|
-
S_CODEX_MODEL,
|
|
294
|
+
S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_REPORT_WRITER_MODEL),
|
|
295
295
|
GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
|
|
296
296
|
S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
|
|
297
297
|
}
|
|
@@ -371,7 +371,7 @@ class WizardState:
|
|
|
371
371
|
lead_model: str = ""
|
|
372
372
|
claude_model: str = ""
|
|
373
373
|
codex_model: str = ""
|
|
374
|
-
|
|
374
|
+
antigravity_model: str = ""
|
|
375
375
|
report_writer_model: str = ""
|
|
376
376
|
directive: str = ""
|
|
377
377
|
directive_pending_text: bool = False
|
|
@@ -689,13 +689,13 @@ def _validate_model(provider: str, raw: str) -> str:
|
|
|
689
689
|
def _executor_model_field(executor: str) -> str:
|
|
690
690
|
return {"claude": "claude_model",
|
|
691
691
|
"codex": "codex_model",
|
|
692
|
-
"
|
|
692
|
+
"antigravity": "antigravity_model"}[executor]
|
|
693
693
|
|
|
694
694
|
|
|
695
695
|
def _executor_model_options(executor: str) -> list[str]:
|
|
696
696
|
return {"claude": CLAUDE_MODEL_OPTIONS,
|
|
697
697
|
"codex": CODEX_MODEL_OPTIONS,
|
|
698
|
-
"
|
|
698
|
+
"antigravity": ANTIGRAVITY_MODEL_OPTIONS}[executor]
|
|
699
699
|
|
|
700
700
|
|
|
701
701
|
# ---- Roster / profile helpers -------------------------------------------
|
|
@@ -797,6 +797,7 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
|
797
797
|
"echo_variants": raw.get("echo_variants", {}),
|
|
798
798
|
"errors": raw.get("errors", {}),
|
|
799
799
|
"labels": raw.get("labels", {}),
|
|
800
|
+
"sidecar_note": raw.get("sidecar_note", {}),
|
|
800
801
|
"echo_suffixes": raw.get("echo_suffixes", {}),
|
|
801
802
|
"recent_label_prefix": raw.get("recent_label_prefix", ""),
|
|
802
803
|
}
|
|
@@ -1951,6 +1952,20 @@ def _suggest_latest_final_report(state: WizardState) -> str:
|
|
|
1951
1952
|
return str(best)
|
|
1952
1953
|
|
|
1953
1954
|
|
|
1955
|
+
def _clarification_sidecar_note(state: WizardState, suggestion: str) -> str:
|
|
1956
|
+
"""추천 final-report 와 함께 carry-in 될 ``user-responses/`` 사이드카 현황 문구.
|
|
1957
|
+
|
|
1958
|
+
"final-report 만 물어보니 user-responses 파일이 쓰이는지 불확실하다" 는 혼선을
|
|
1959
|
+
없애기 위해, picker 단계에서 실제 첨부될 답변 파일 개수를 그 자리에서 보여준다.
|
|
1960
|
+
"""
|
|
1961
|
+
p = Path(suggestion)
|
|
1962
|
+
if not p.is_absolute() and state.project_root:
|
|
1963
|
+
p = Path(state.project_root) / p
|
|
1964
|
+
count = len(user_response_sidecars(p))
|
|
1965
|
+
note = _p(state.workspace_root, "clarification_pick")["sidecar_note"]
|
|
1966
|
+
return note["attached"].format(count=count) if count else note["empty"]
|
|
1967
|
+
|
|
1968
|
+
|
|
1954
1969
|
def _latest_run_inputs(
|
|
1955
1970
|
state: WizardState, *, phase_segment: str = ""
|
|
1956
1971
|
) -> dict:
|
|
@@ -1991,6 +2006,9 @@ class _OptionalCachedPickSpec:
|
|
|
1991
2006
|
target_attr: str # 확정값을 쓸 state 속성
|
|
1992
2007
|
pending_attr: str # 직접 입력 대기 플래그 state 속성
|
|
1993
2008
|
extra_clear_attrs: tuple[str, ...] = () # skip 시 추가로 비울 속성
|
|
2009
|
+
# 추천 옵션 라벨·echo 뒤에 덧붙일 동적 안내(예: 함께 첨부될 사이드카 개수).
|
|
2010
|
+
# (state, 추천 relpath) -> note. 미설정 picker 는 영향 없음.
|
|
2011
|
+
recommend_note: Optional[Callable[["WizardState", str], str]] = None
|
|
1994
2012
|
|
|
1995
2013
|
|
|
1996
2014
|
def _pick_snippet(value: str, style: str) -> str:
|
|
@@ -2007,7 +2025,10 @@ def _build_optional_cached_pick(state: WizardState, spec: _OptionalCachedPickSpe
|
|
|
2007
2025
|
options: list[Option] = []
|
|
2008
2026
|
if suggestion:
|
|
2009
2027
|
snippet = _pick_snippet(suggestion, spec.snippet_style)
|
|
2010
|
-
|
|
2028
|
+
label = t["labels"][spec.label_key].format(snippet=snippet)
|
|
2029
|
+
if spec.recommend_note:
|
|
2030
|
+
label += spec.recommend_note(state, suggestion)
|
|
2031
|
+
options.append(_opt(spec.recommend_token, label))
|
|
2011
2032
|
setattr(state, spec.cache_attr, suggestion)
|
|
2012
2033
|
options.append(_opt(PICK_SKIP, t["options"][PICK_SKIP]))
|
|
2013
2034
|
options.append(_opt(PICK_TYPE_CUSTOM, t["options"][PICK_TYPE_CUSTOM]))
|
|
@@ -2032,7 +2053,10 @@ def _submit_optional_cached_pick(
|
|
|
2032
2053
|
cached = getattr(state, spec.cache_attr)
|
|
2033
2054
|
setattr(state, spec.target_attr, cached)
|
|
2034
2055
|
setattr(state, spec.pending_attr, False)
|
|
2035
|
-
|
|
2056
|
+
echo = t["echo_suffixes"][spec.echo_suffix_key].format(value=cached)
|
|
2057
|
+
if spec.recommend_note:
|
|
2058
|
+
echo += spec.recommend_note(state, cached)
|
|
2059
|
+
return echo
|
|
2036
2060
|
if value == PICK_TYPE_CUSTOM:
|
|
2037
2061
|
setattr(state, spec.pending_attr, True)
|
|
2038
2062
|
return None
|
|
@@ -2104,6 +2128,7 @@ _CLARIFICATION_PICK_SPEC = _OptionalCachedPickSpec(
|
|
|
2104
2128
|
suggest=_suggest_latest_final_report, snippet_style="suffix",
|
|
2105
2129
|
cache_attr="last_final_report_cached", target_attr="clarification_response_path",
|
|
2106
2130
|
pending_attr="clarification_pending_text",
|
|
2131
|
+
recommend_note=_clarification_sidecar_note,
|
|
2107
2132
|
)
|
|
2108
2133
|
|
|
2109
2134
|
|
|
@@ -2251,7 +2276,7 @@ _RESUME_REUSE_PHASES = (
|
|
|
2251
2276
|
# YES 선택 시 prefill 로 충족되어 다시 묻지 않는 설정 단계들.
|
|
2252
2277
|
_REUSE_FILLED_STEPS = (
|
|
2253
2278
|
S_DEFAULTS_OR_CUSTOM, S_WORKERS_OVERRIDE, S_LEAD_MODEL, S_EXECUTOR_MODEL,
|
|
2254
|
-
S_CLAUDE_MODEL, S_CODEX_MODEL,
|
|
2279
|
+
S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_REPORT_WRITER_MODEL,
|
|
2255
2280
|
S_DIRECTIVE_PICK, S_DIRECTIVE, S_RELATED_TASKS_PICK, S_RELATED_TASKS,
|
|
2256
2281
|
S_CLARIFICATION_PICK, S_CLARIFICATION)
|
|
2257
2282
|
|
|
@@ -2299,7 +2324,7 @@ def _submit_reuse_previous(state: WizardState, value: str) -> Optional[str]:
|
|
|
2299
2324
|
state.lead_model = _safe_model("claude", inputs.get("leadModel"))
|
|
2300
2325
|
state.claude_model = _safe_model("claude", inputs.get("claudeModel"))
|
|
2301
2326
|
state.codex_model = _safe_model("codex", inputs.get("codexModel"))
|
|
2302
|
-
state.
|
|
2327
|
+
state.antigravity_model = _safe_model("antigravity", inputs.get("antigravityModel"))
|
|
2303
2328
|
state.report_writer_model = _safe_model(
|
|
2304
2329
|
"claude", inputs.get("reportWriterModel"))
|
|
2305
2330
|
directive = inputs.get("directive")
|
|
@@ -2445,15 +2470,15 @@ def _submit_codex_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
2445
2470
|
return f"codex-model: {state.codex_model or 'default'}"
|
|
2446
2471
|
|
|
2447
2472
|
|
|
2448
|
-
def
|
|
2449
|
-
t = _p(state.workspace_root, "
|
|
2450
|
-
return _model_pick(
|
|
2451
|
-
|
|
2473
|
+
def _build_antigravity_model(state: WizardState) -> Prompt:
|
|
2474
|
+
t = _p(state.workspace_root, "antigravity_model")
|
|
2475
|
+
return _model_pick(S_ANTIGRAVITY_MODEL, t["label"],
|
|
2476
|
+
ANTIGRAVITY_MODEL_OPTIONS, t["echo_template"])
|
|
2452
2477
|
|
|
2453
2478
|
|
|
2454
|
-
def
|
|
2455
|
-
state.
|
|
2456
|
-
return f"
|
|
2479
|
+
def _submit_antigravity_model(state: WizardState, value: str) -> Optional[str]:
|
|
2480
|
+
state.antigravity_model = _validate_model("antigravity", value)
|
|
2481
|
+
return f"antigravity-model: {state.antigravity_model or 'default'}"
|
|
2457
2482
|
|
|
2458
2483
|
|
|
2459
2484
|
def _build_report_writer_model(state: WizardState) -> Prompt:
|
|
@@ -2930,7 +2955,7 @@ STEPS: list[Step] = [
|
|
|
2930
2955
|
and s.task_type == "implementation"
|
|
2931
2956
|
and S_EXECUTOR_MODEL not in s.answered),
|
|
2932
2957
|
build=_build_executor_model, submit=_submit_executor_model,
|
|
2933
|
-
owns=("claude_model", "codex_model", "
|
|
2958
|
+
owns=("claude_model", "codex_model", "antigravity_model")),
|
|
2934
2959
|
Step(S_CLAUDE_MODEL,
|
|
2935
2960
|
applies=lambda s: (s.use_defaults is False
|
|
2936
2961
|
and s.task_type != "implementation"
|
|
@@ -2945,13 +2970,13 @@ STEPS: list[Step] = [
|
|
|
2945
2970
|
and S_CODEX_MODEL not in s.answered),
|
|
2946
2971
|
build=_build_codex_model, submit=_submit_codex_model,
|
|
2947
2972
|
owns=("codex_model",)),
|
|
2948
|
-
Step(
|
|
2973
|
+
Step(S_ANTIGRAVITY_MODEL,
|
|
2949
2974
|
applies=lambda s: (s.use_defaults is False
|
|
2950
2975
|
and s.task_type != "implementation"
|
|
2951
|
-
and "
|
|
2952
|
-
and
|
|
2953
|
-
build=
|
|
2954
|
-
owns=("
|
|
2976
|
+
and "antigravity" in _resolved_roster(s)
|
|
2977
|
+
and S_ANTIGRAVITY_MODEL not in s.answered),
|
|
2978
|
+
build=_build_antigravity_model, submit=_submit_antigravity_model,
|
|
2979
|
+
owns=("antigravity_model",)),
|
|
2955
2980
|
Step(S_REPORT_WRITER_MODEL,
|
|
2956
2981
|
applies=lambda s: (s.use_defaults is False
|
|
2957
2982
|
and (s.task_type == "implementation"
|
|
@@ -3088,7 +3113,7 @@ def _ready_for_confirm(s: WizardState) -> bool:
|
|
|
3088
3113
|
return True
|
|
3089
3114
|
# customize: every customize-branch step must be answered or not-applicable.
|
|
3090
3115
|
custom_ids = [S_LEAD_MODEL, S_EXECUTOR_MODEL,
|
|
3091
|
-
S_CLAUDE_MODEL, S_CODEX_MODEL,
|
|
3116
|
+
S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL,
|
|
3092
3117
|
S_REPORT_WRITER_MODEL, S_DIRECTIVE, S_RELATED_TASKS,
|
|
3093
3118
|
S_CLARIFICATION, S_PR_TEMPLATE, S_PR_TEMPLATE_SCOPE]
|
|
3094
3119
|
for sid in custom_ids:
|
|
@@ -3130,7 +3155,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
|
|
|
3130
3155
|
"reuse_previous": None,
|
|
3131
3156
|
"use_defaults": None, "workers_override": "",
|
|
3132
3157
|
"lead_model": "", "claude_model": "", "codex_model": "",
|
|
3133
|
-
"
|
|
3158
|
+
"antigravity_model": "", "report_writer_model": "", "directive": "",
|
|
3134
3159
|
"directive_pending_text": False,
|
|
3135
3160
|
"related_tasks_raw": "", "related_tasks_pending_text": False,
|
|
3136
3161
|
"clarification_response_path": "", "clarification_pending_text": False,
|
|
@@ -3299,7 +3324,7 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
3299
3324
|
"lead-model": state.lead_model,
|
|
3300
3325
|
"claude-model": state.claude_model,
|
|
3301
3326
|
"codex-model": state.codex_model,
|
|
3302
|
-
"
|
|
3327
|
+
"antigravity-model": state.antigravity_model,
|
|
3303
3328
|
"report-writer-model": state.report_writer_model,
|
|
3304
3329
|
"related-tasks": state.related_tasks_raw,
|
|
3305
3330
|
"clarification-response": state.clarification_response_path,
|
|
@@ -3339,8 +3364,8 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
3339
3364
|
lines.append(f" claude-model : {state.claude_model}")
|
|
3340
3365
|
if state.codex_model:
|
|
3341
3366
|
lines.append(f" codex-model : {state.codex_model}")
|
|
3342
|
-
if state.
|
|
3343
|
-
lines.append(f"
|
|
3367
|
+
if state.antigravity_model:
|
|
3368
|
+
lines.append(f" antigravity-model : {state.antigravity_model}")
|
|
3344
3369
|
if state.report_writer_model:
|
|
3345
3370
|
lines.append(f" report-writer : {state.report_writer_model}")
|
|
3346
3371
|
lines.append(f" directive : {state.directive or '(none)'}")
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
ALLOWED_WORKERS = ["claude", "codex", "
|
|
11
|
+
ALLOWED_WORKERS = ["claude", "codex", "antigravity", "report-writer"]
|
|
12
12
|
DEFAULT_WORKERS = ["claude", "codex", "report-writer"]
|
|
13
13
|
PROFILE_BULLET_HEADERS = {
|
|
14
14
|
"- Workers:",
|
|
@@ -64,7 +64,7 @@ def resolve_profile_workers(profile_path: Path) -> list[str]:
|
|
|
64
64
|
|
|
65
65
|
def resolve_optional_workers(profile_path: Path) -> list[str]:
|
|
66
66
|
r"""`- Optional workers (opt-in via \`--workers\`):` 섹션 아래 sub-bullet
|
|
67
|
-
들에서 worker id 만 추출한다. (` -
|
|
67
|
+
들에서 worker id 만 추출한다. (` - antigravity — when added …` → `antigravity`)
|
|
68
68
|
Required 와 중복되는 항목은 제거. ALLOWED_WORKERS 밖 토큰도 제거.
|
|
69
69
|
"""
|
|
70
70
|
raw = _resolve_workers_under(profile_path, PROFILE_BULLET_HEADERS_OPTIONAL)
|
|
@@ -83,7 +83,7 @@ def normalize_workers(value: str) -> list[str]:
|
|
|
83
83
|
"""CSV 입력을 정규화한다.
|
|
84
84
|
|
|
85
85
|
- 공백 strip, 소문자화, 중복 제거(첫 출현 우선).
|
|
86
|
-
- 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (
|
|
86
|
+
- 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (antigravity 제외).
|
|
87
87
|
- 허용 외 worker 가 포함되면 `WorkersError`.
|
|
88
88
|
"""
|
|
89
89
|
items = [v.strip().lower() for v in (value or "").split(",") if v.strip()]
|
|
@@ -83,7 +83,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
83
83
|
" - `Out-of-plan edits` block listing every file touched outside the approved plan with rationale (empty block preferred)\n"
|
|
84
84
|
' - validation evidence: actual stdout/stderr and exit code for every pre / mid / post command from the plan (no paraphrased "tests pass")\n'
|
|
85
85
|
" - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n"
|
|
86
|
-
" - per-verifier sections (`
|
|
86
|
+
" - per-verifier sections (`Antigravity verifier`, `Codex verifier`, `Claude verifier`) with independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by `Claude lead`\n"
|
|
87
87
|
" - rollback verification (revert SHA reachable, feature flag toggle works, migration down step valid; dry-run preferred)\n"
|
|
88
88
|
" - routing recommendation for `final-verification` (ready / needs new error-analysis or planning loop)"
|
|
89
89
|
),
|
|
@@ -479,7 +479,7 @@ def _link_sync_files(source_root: Path, worktree_path: Path) -> list[str]:
|
|
|
479
479
|
|
|
480
480
|
def _seed_worktree_settings_symlink(worktree_path: Path) -> None:
|
|
481
481
|
"""Seed `.claude/settings.local.json` in the worker worktree so dispatched
|
|
482
|
-
Claude / codex /
|
|
482
|
+
Claude / codex / antigravity sessions inherit the okstra read-only / write
|
|
483
483
|
allowlist. Mirrors the main-project seeding done in `run.py` — needed
|
|
484
484
|
because `_link_sync_dirs` skips `.claude/` whenever `git worktree add`
|
|
485
485
|
already materialised the directory (e.g. tracked `.claude/handoff-*.md`).
|
|
@@ -5,12 +5,11 @@ from .blocks import na_block, usage_block
|
|
|
5
5
|
from .claude import claude_session_totals, find_claude_team_sessions
|
|
6
6
|
from .codex import codex_session_total, find_codex_session
|
|
7
7
|
from .collect import collect
|
|
8
|
-
from .
|
|
8
|
+
from .antigravity import antigravity_session_total, find_antigravity_session
|
|
9
9
|
from .jsonl_io import iter_jsonl
|
|
10
10
|
from .paths import (
|
|
11
11
|
CLAUDE_PROJECTS,
|
|
12
12
|
CODEX_SESSIONS,
|
|
13
|
-
GEMINI_TMP,
|
|
14
13
|
claude_project_dir,
|
|
15
14
|
utc_now,
|
|
16
15
|
)
|
|
@@ -21,7 +20,7 @@ from .pricing import (
|
|
|
21
20
|
claude_billable_equivalent,
|
|
22
21
|
claude_cost_usd,
|
|
23
22
|
codex_cost_usd,
|
|
24
|
-
|
|
23
|
+
antigravity_cost_usd,
|
|
25
24
|
)
|
|
26
25
|
from .report import (
|
|
27
26
|
SubstituteRefusedError,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Antigravity (`agy`) CLI usage collector.
|
|
2
|
+
|
|
3
|
+
okstra dispatches the antigravity-worker via `agy --print` (see
|
|
4
|
+
`okstra-antigravity-exec.sh`). That non-interactive print mode does not persist
|
|
5
|
+
a parseable transcript with per-call token counts to any known location, so
|
|
6
|
+
per-run token/cost usage cannot be attributed to the antigravity worker. These
|
|
7
|
+
collectors therefore report "unavailable" rather than fabricating numbers — the
|
|
8
|
+
worker's usage block degrades to `na` and `/okstra-inspect context-cost` shows
|
|
9
|
+
N/A for antigravity. If a future `agy` build exposes a transcript, wire its
|
|
10
|
+
parser here (model ids stay `gemini-*`, so `antigravity_cost_usd` already prices it).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def antigravity_session_total(json_path: Path) -> dict:
|
|
18
|
+
return {"totalTokens": 0, "available": False}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def find_antigravity_session(project_root: Path, started_at: str, ended_at: str) -> Path | None:
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_antigravity_sessions(project_root: Path, started_at: str, ended_at: str) -> list[Path]:
|
|
26
|
+
return []
|
|
@@ -15,6 +15,8 @@ def usage_block(totals: dict, source: str, note: str | None = None) -> dict:
|
|
|
15
15
|
"outputTokens": totals.get("outputTokens", 0) or 0,
|
|
16
16
|
"toolUses": totals.get("toolUses", 0) or 0,
|
|
17
17
|
"durationMs": totals.get("durationMs", 0) or 0,
|
|
18
|
+
"startedAt": totals.get("startedAt"),
|
|
19
|
+
"endedAt": totals.get("endedAt"),
|
|
18
20
|
"source": source,
|
|
19
21
|
"collectedAt": utc_now(),
|
|
20
22
|
}
|
|
@@ -57,6 +59,8 @@ def na_block(reason: str) -> dict:
|
|
|
57
59
|
"totalTokens": 0,
|
|
58
60
|
"toolUses": 0,
|
|
59
61
|
"durationMs": 0,
|
|
62
|
+
"startedAt": None,
|
|
63
|
+
"endedAt": None,
|
|
60
64
|
"source": "unavailable",
|
|
61
65
|
"collectedAt": utc_now(),
|
|
62
66
|
"note": reason,
|
|
@@ -14,9 +14,13 @@ from .claude import (
|
|
|
14
14
|
find_claude_team_sessions,
|
|
15
15
|
)
|
|
16
16
|
from .codex import codex_session_total, find_codex_session, find_codex_sessions
|
|
17
|
-
from .
|
|
17
|
+
from .antigravity import (
|
|
18
|
+
antigravity_session_total,
|
|
19
|
+
find_antigravity_session,
|
|
20
|
+
find_antigravity_sessions,
|
|
21
|
+
)
|
|
18
22
|
from .paths import claude_project_dir, utc_now
|
|
19
|
-
from .pricing import codex_cost_usd,
|
|
23
|
+
from .pricing import codex_cost_usd, antigravity_cost_usd
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
def match_prefixes(worker_id: str) -> list[str]:
|
|
@@ -275,7 +279,7 @@ def _codex_runtime_provider(worker: dict) -> str:
|
|
|
275
279
|
agent = str(worker.get("agent") or "").strip()
|
|
276
280
|
if worker_id == "report-writer":
|
|
277
281
|
return "codex"
|
|
278
|
-
if agent in {"codex", "
|
|
282
|
+
if agent in {"codex", "antigravity"}:
|
|
279
283
|
return agent
|
|
280
284
|
return ""
|
|
281
285
|
|
|
@@ -290,8 +294,8 @@ def _cli_sessions_for_windows(
|
|
|
290
294
|
for started_at, ended_at in windows:
|
|
291
295
|
if provider == "codex":
|
|
292
296
|
matches = find_codex_sessions(project_root, started_at, ended_at)
|
|
293
|
-
elif provider == "
|
|
294
|
-
matches =
|
|
297
|
+
elif provider == "antigravity":
|
|
298
|
+
matches = find_antigravity_sessions(project_root, started_at, ended_at)
|
|
295
299
|
else:
|
|
296
300
|
matches = []
|
|
297
301
|
for path in matches:
|
|
@@ -307,7 +311,7 @@ def _cli_session_totals(provider: str, session_paths: list[Path]) -> list[dict]:
|
|
|
307
311
|
if provider == "codex":
|
|
308
312
|
total = codex_session_total(session_path)
|
|
309
313
|
else:
|
|
310
|
-
total =
|
|
314
|
+
total = antigravity_session_total(session_path)
|
|
311
315
|
if total.get("available"):
|
|
312
316
|
totals.append(total)
|
|
313
317
|
return totals
|
|
@@ -328,7 +332,7 @@ def _cli_usage_block(provider: str, totals: dict, session_paths: list[Path]) ->
|
|
|
328
332
|
totals.get("outputTokens", 0) or 0,
|
|
329
333
|
)
|
|
330
334
|
else:
|
|
331
|
-
cost =
|
|
335
|
+
cost = antigravity_cost_usd(
|
|
332
336
|
totals.get("model"),
|
|
333
337
|
totals.get("inputTokens", 0) or 0,
|
|
334
338
|
totals.get("outputTokens", 0) or 0,
|
|
@@ -403,7 +407,7 @@ def _populate_usage_summary(
|
|
|
403
407
|
for w in workers:
|
|
404
408
|
u = w.get("usage") or {}
|
|
405
409
|
if (
|
|
406
|
-
u.get("source") not in {"codex-cli", "
|
|
410
|
+
u.get("source") not in {"codex-cli", "agy-cli"}
|
|
407
411
|
and u.get("model")
|
|
408
412
|
and u.get("estimatedCostUsd") is None
|
|
409
413
|
and (u.get("totalTokens") or 0) > 0
|
|
@@ -433,7 +437,7 @@ def _populate_usage_summary(
|
|
|
433
437
|
"definitions": {
|
|
434
438
|
"totalTokens": "Sum of input + output + cache_creation + cache_read tokens (raw processed volume; matches Anthropic API breakdown). Cache reads are 95%+ in long sessions.",
|
|
435
439
|
"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.",
|
|
436
|
-
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers Codex/
|
|
440
|
+
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers Codex/Antigravity CLI calls billed under those providers.",
|
|
437
441
|
},
|
|
438
442
|
}
|
|
439
443
|
|
|
@@ -547,17 +551,18 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
547
551
|
block["additionalSessionIds"] = [sid for sid, _, _ in matched[1:]]
|
|
548
552
|
block["matchedAgentNames"] = sorted({t.get("agentName") for _, _, t in matched if t.get("agentName")})
|
|
549
553
|
|
|
550
|
-
# For codex/
|
|
551
|
-
# aggregated wrapper window.
|
|
554
|
+
# For codex/antigravity workers, find every CLI session that fell inside
|
|
555
|
+
# the aggregated wrapper window. (agy print mode leaves no transcript, so
|
|
556
|
+
# the antigravity path always resolves to "session not found" → na cost.)
|
|
552
557
|
wrapper_start = aggregate.get("startedAt") or ""
|
|
553
558
|
wrapper_end = aggregate.get("endedAt") or ""
|
|
554
|
-
if agent in ("codex", "
|
|
559
|
+
if agent in ("codex", "antigravity"):
|
|
555
560
|
if agent == "codex":
|
|
556
561
|
cli = find_codex_session(cwd, wrapper_start, wrapper_end)
|
|
557
562
|
cli_totals = codex_session_total(cli) if cli else None
|
|
558
563
|
else:
|
|
559
|
-
cli =
|
|
560
|
-
cli_totals =
|
|
564
|
+
cli = find_antigravity_session(cwd, wrapper_start, wrapper_end)
|
|
565
|
+
cli_totals = antigravity_session_total(cli) if cli else None
|
|
561
566
|
if cli is None:
|
|
562
567
|
block["cliNote"] = f"{agent} CLI session not found in wrapper window (fallback or never invoked)"
|
|
563
568
|
else:
|
|
@@ -574,7 +579,7 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
574
579
|
cli_totals.get("outputTokens", 0) or 0,
|
|
575
580
|
)
|
|
576
581
|
else:
|
|
577
|
-
cost =
|
|
582
|
+
cost = antigravity_cost_usd(
|
|
578
583
|
cli_totals.get("model"),
|
|
579
584
|
cli_totals.get("inputTokens", 0) or 0,
|
|
580
585
|
cli_totals.get("outputTokens", 0) or 0,
|