okstra 0.200.0 → 0.201.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 +4 -2
- package/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/docs/cli.md +14 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +7 -3
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/duties/technical-verification-worker.md +44 -0
- package/runtime/prompts/launch.template.md +7 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +7 -2
- package/runtime/prompts/lead/plan-body-verification.md +3 -1
- package/runtime/prompts/lead/report-writer.md +11 -5
- package/runtime/prompts/lead/team-contract.md +6 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +7 -1
- package/runtime/prompts/profiles/final-verification.md +5 -0
- package/runtime/prompts/profiles/forbidden-actions.json +6 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +7 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -0
- package/runtime/prompts/profiles/technical-verification.md +53 -0
- package/runtime/prompts/wizard/prompts.ko.json +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -4
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +2 -0
- package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +36 -5
- package/runtime/python/okstra_ctl/agent/invocation.py +14 -6
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +4 -3
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +83 -22
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +44 -2
- package/runtime/python/okstra_ctl/conformance.py +2 -20
- package/runtime/python/okstra_ctl/dispatch_core.py +25 -5
- package/runtime/python/okstra_ctl/dispatch_state.py +2 -0
- package/runtime/python/okstra_ctl/domain/provider.py +0 -1
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -1
- package/runtime/python/okstra_ctl/implementation_direction.py +64 -7
- package/runtime/python/okstra_ctl/implementation_options.py +58 -45
- package/runtime/python/okstra_ctl/model_pool.py +2 -5
- package/runtime/python/okstra_ctl/next_phase.py +3 -0
- package/runtime/python/okstra_ctl/plan_items.py +15 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +9 -3
- package/runtime/python/okstra_ctl/qa_commands.py +30 -0
- package/runtime/python/okstra_ctl/registry/provider_registry.py +11 -8
- package/runtime/python/okstra_ctl/render.py +3 -0
- package/runtime/python/okstra_ctl/render_final_report.py +1 -0
- package/runtime/python/okstra_ctl/report_assembly.py +8 -2
- package/runtime/python/okstra_ctl/report_contract.py +3 -0
- package/runtime/python/okstra_ctl/report_corrections.py +209 -93
- package/runtime/python/okstra_ctl/report_finalize.py +25 -8
- package/runtime/python/okstra_ctl/report_html/router.py +2 -0
- package/runtime/python/okstra_ctl/report_html/view_models/technical_verification.py +21 -0
- package/runtime/python/okstra_ctl/report_projections.py +4 -3
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +181 -47
- package/runtime/python/okstra_ctl/run.py +82 -0
- package/runtime/python/okstra_ctl/team.py +4 -1
- package/runtime/python/okstra_ctl/technical_verification.py +195 -0
- package/runtime/python/okstra_ctl/usage_identity.py +54 -0
- package/runtime/python/okstra_ctl/usage_report.py +22 -8
- package/runtime/python/okstra_ctl/verification_target.py +74 -0
- package/runtime/python/okstra_ctl/wizard/__init__.py +1 -1
- package/runtime/python/okstra_ctl/wizard/cli.py +2 -1
- package/runtime/python/okstra_ctl/wizard/confirmation.py +38 -2
- package/runtime/python/okstra_ctl/wizard/engine.py +3 -0
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -0
- package/runtime/python/okstra_ctl/wizard/outcome.py +63 -0
- package/runtime/python/okstra_ctl/wizard/picker_navigation.py +2 -2
- package/runtime/python/okstra_ctl/wizard/registry.py +1 -1
- package/runtime/python/okstra_ctl/wizard/render.py +8 -55
- package/runtime/python/okstra_ctl/wizard/roles.py +11 -7
- package/runtime/python/okstra_ctl/wizard/sources.py +28 -2
- package/runtime/python/okstra_ctl/wizard/state.py +13 -6
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +8 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +52 -39
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
- package/runtime/python/okstra_ctl/workflow.py +8 -0
- package/runtime/python/okstra_ctl/write_policy.py +23 -0
- package/runtime/python/okstra_token_usage/blocks.py +50 -1
- package/runtime/python/okstra_token_usage/claude.py +42 -21
- package/runtime/python/okstra_token_usage/codex.py +17 -0
- package/runtime/python/okstra_token_usage/collect.py +299 -162
- package/runtime/python/okstra_token_usage/cursor.py +2 -3
- package/runtime/python/okstra_token_usage/report.py +35 -30
- package/runtime/python/okstra_token_usage/task_totals.py +3 -12
- package/runtime/schemas/final-report-v2.0.schema.json +298 -7
- package/runtime/schemas/final-report-v3.0.schema.json +298 -7
- package/runtime/schemas/report-narrative-v3.0.schema.json +1 -0
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +1 -1
- package/runtime/schemas/report-writer-corrections-v1.0.schema.json +30 -3
- package/runtime/skills/okstra-run/SKILL.md +10 -2
- package/runtime/skills/okstra-setup/SKILL.md +42 -7
- package/runtime/templates/report-writer-prompt-preamble.md +7 -3
- package/runtime/templates/reports/html/i18n/en.json +11 -0
- package/runtime/templates/reports/html/i18n/ko.json +11 -0
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +7 -3
- package/runtime/templates/reports/html/tasks/technical-verification.template.html +35 -0
- package/runtime/templates/reports/md/tasks/technical-verification.template.md +5 -0
- package/runtime/validators/validate-run.py +9 -4
|
@@ -4,12 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
6
6
|
import re
|
|
7
|
-
from datetime import datetime
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
|
|
10
9
|
from .cursor import MAX_NEEDLES, fresh_cache, load_cache, save_cache
|
|
11
10
|
from .paths import claude_project_dir, ts_in_window
|
|
12
11
|
from okstra_ctl.wrapper_status import read_wrapper_status
|
|
12
|
+
from okstra_ctl.usage_cells import duration_ms_from_bounds
|
|
13
13
|
|
|
14
14
|
# lead 의 단계 체크포인트 라인(prompts/lead/okstra-lead-contract.md "Progress reporting") — phase id 가
|
|
15
15
|
# `phase-<digit>` 로 시작하는 라인만 마커로 인정해 일반 대화의 오탐을 줄인다.
|
|
@@ -45,19 +45,13 @@ def claude_code_status_total(status_path: Path) -> dict:
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
키: t=timestamp, i/o=input/output, c=cache_creation 합, c5/c1=ephemeral
|
|
52
|
-
5m/1h, r=cache_read, u=tool_use 수. 0/부재 필드는 생략(캐시 크기 절약).
|
|
53
|
-
ts-only 레코드도 보존한다 — 임의 윈도우의 first/last ts 산출에 필요.
|
|
54
|
-
"""
|
|
55
|
-
msg = rec.get("message")
|
|
56
|
-
if not isinstance(msg, dict):
|
|
57
|
-
msg = {}
|
|
48
|
+
def _usage_from_message(msg: dict) -> dict:
|
|
49
|
+
"""응답 식별자를 남겨 여러 출력 조각의 사용량을 한 번만 집계한다."""
|
|
58
50
|
ev: dict = {}
|
|
59
51
|
usage = msg.get("usage")
|
|
60
|
-
if usage:
|
|
52
|
+
if isinstance(usage, dict):
|
|
53
|
+
if isinstance(msg.get("id"), str) and msg["id"]:
|
|
54
|
+
ev["m"] = msg["id"]
|
|
61
55
|
for src, key in (("input_tokens", "i"), ("output_tokens", "o"),
|
|
62
56
|
("cache_read_input_tokens", "r")):
|
|
63
57
|
v = usage.get(src, 0) or 0
|
|
@@ -79,6 +73,15 @@ def _event_from_record(rec: dict) -> dict | None:
|
|
|
79
73
|
elif cc_total:
|
|
80
74
|
# API 분해가 없으면 전부 5m 티어로(1.25x — 더 싼 가정, 기존 동작).
|
|
81
75
|
ev["c5"] = cc_total
|
|
76
|
+
return ev
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _event_from_record(rec: dict) -> dict | None:
|
|
80
|
+
"""사용량과 별개로 각 조각의 도구·진행·시각 이벤트를 보존한다."""
|
|
81
|
+
msg = rec.get("message")
|
|
82
|
+
if not isinstance(msg, dict):
|
|
83
|
+
msg = {}
|
|
84
|
+
ev = _usage_from_message(msg)
|
|
82
85
|
if rec.get("type") == "assistant":
|
|
83
86
|
tools = sum(1 for b in (msg.get("content") or [])
|
|
84
87
|
if isinstance(b, dict) and b.get("type") == "tool_use")
|
|
@@ -102,6 +105,31 @@ def _event_from_record(rec: dict) -> dict | None:
|
|
|
102
105
|
return ev or None
|
|
103
106
|
|
|
104
107
|
|
|
108
|
+
def _response_usage_events(events: list[dict], since: str | None, until: str | None):
|
|
109
|
+
"""응답별 누적 스냅샷의 창 안 증가량만 세며 도구 이벤트는 그대로 둔다."""
|
|
110
|
+
latest: dict[str, int] = {}
|
|
111
|
+
baseline: dict[str, dict] = {}
|
|
112
|
+
for index, event in enumerate(events):
|
|
113
|
+
message_id = event.get("m")
|
|
114
|
+
timestamp = event.get("t")
|
|
115
|
+
if message_id and (not timestamp or ts_in_window(timestamp, None, until)):
|
|
116
|
+
latest[message_id] = index
|
|
117
|
+
if timestamp and since and not ts_in_window(timestamp, since, None):
|
|
118
|
+
baseline[message_id] = event
|
|
119
|
+
usage_keys = {"i", "o", "c", "c5", "c1", "r"}
|
|
120
|
+
for index, event in enumerate(events):
|
|
121
|
+
message_id = event.get("m")
|
|
122
|
+
if not message_id:
|
|
123
|
+
yield event
|
|
124
|
+
continue
|
|
125
|
+
projected = {key: value for key, value in event.items() if key not in usage_keys}
|
|
126
|
+
if latest.get(message_id) == index:
|
|
127
|
+
previous = baseline.get(message_id) or {}
|
|
128
|
+
projected.update({key: max(0, event.get(key, 0) - previous.get(key, 0))
|
|
129
|
+
for key in usage_keys})
|
|
130
|
+
yield projected
|
|
131
|
+
|
|
132
|
+
|
|
105
133
|
def _session_meta_from_record(rec: dict) -> tuple[str | None, str | None]:
|
|
106
134
|
"""레코드에서 (agentName, model) 후보 추출 — 둘 다 first-non-null 정책."""
|
|
107
135
|
agent = rec.get("agentName") or None
|
|
@@ -213,7 +241,7 @@ def _totals_from_events(events: list[dict], agent_name: str | None,
|
|
|
213
241
|
progress_markers: list[dict] = []
|
|
214
242
|
first_ts: str | None = None
|
|
215
243
|
last_ts: str | None = None
|
|
216
|
-
for ev in events:
|
|
244
|
+
for ev in _response_usage_events(events, since, until):
|
|
217
245
|
ts = ev.get("t")
|
|
218
246
|
if ts and not ts_in_window(ts, since, until):
|
|
219
247
|
continue
|
|
@@ -231,14 +259,7 @@ def _totals_from_events(events: list[dict], agent_name: str | None,
|
|
|
231
259
|
first_ts = ts
|
|
232
260
|
if last_ts is None or ts > last_ts:
|
|
233
261
|
last_ts = ts
|
|
234
|
-
duration_ms = 0
|
|
235
|
-
if first_ts and last_ts:
|
|
236
|
-
try:
|
|
237
|
-
a = datetime.fromisoformat(first_ts.replace("Z", "+00:00"))
|
|
238
|
-
b = datetime.fromisoformat(last_ts.replace("Z", "+00:00"))
|
|
239
|
-
duration_ms = max(0, int((b - a).total_seconds() * 1000))
|
|
240
|
-
except ValueError:
|
|
241
|
-
duration_ms = 0
|
|
262
|
+
duration_ms = duration_ms_from_bounds(first_ts, last_ts) or 0
|
|
242
263
|
# '처리 토큰' total 에서 cache_read 는 제외한다. claude 는 매 턴 직전까지의
|
|
243
264
|
# 컨텍스트 전체를 캐시에서 재읽기(cache_read)하므로, 단순 합산하면 같은 토큰을
|
|
244
265
|
# 턴 수만큼 중복 카운트해 처리량이 비현실적으로 부풀려진다(예: in-session
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
from datetime import datetime, timezone
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from .jsonl_io import iter_jsonl
|
|
@@ -12,6 +13,22 @@ from .paths import CODEX_SESSIONS, codex_session_roots, ts_in_window
|
|
|
12
13
|
_DEFAULT_CODEX_SESSIONS = CODEX_SESSIONS
|
|
13
14
|
|
|
14
15
|
|
|
16
|
+
def codex_wrapper_session_ids(log_path: Path) -> set[str]:
|
|
17
|
+
"""사용자 프롬프트 앞 실행기 헤더에서만 세션 ID를 읽는다."""
|
|
18
|
+
try:
|
|
19
|
+
with log_path.open(encoding="utf-8", errors="replace") as stream:
|
|
20
|
+
for line in stream:
|
|
21
|
+
clean = re.sub(r"\x1b\[[0-9;]*m", "", line).strip()
|
|
22
|
+
if clean == "user" or clean.endswith("[worker]user"):
|
|
23
|
+
break
|
|
24
|
+
match = re.search(r"(?:^|\[worker\])session id:\s*([\w-]+)\s*$", clean)
|
|
25
|
+
if match:
|
|
26
|
+
return {match.group(1)}
|
|
27
|
+
except OSError:
|
|
28
|
+
return set()
|
|
29
|
+
return set()
|
|
30
|
+
|
|
31
|
+
|
|
15
32
|
def codex_session_total(jsonl_path: Path) -> dict:
|
|
16
33
|
"""Return last token_count snapshot from a codex rollout jsonl."""
|
|
17
34
|
last: dict | None = None
|