okstra 0.164.0 → 0.165.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 +1 -1
- package/docs/architecture.md +12 -8
- package/docs/cli.md +7 -3
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-inspect.md +2 -2
- package/docs/for-ai/skills/okstra-user-response.md +2 -2
- package/docs/project-structure-overview.md +15 -9
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +9 -7
- package/runtime/agents/workers/codex-worker.md +9 -7
- package/runtime/agents/workers/grok-worker.md +6 -4
- package/runtime/agents/workers/kimi-worker.md +6 -4
- package/runtime/bin/okstra-antigravity-exec.sh +1 -340
- package/runtime/bin/okstra-claude-exec.sh +1 -178
- package/runtime/bin/okstra-codex-exec.sh +1 -467
- package/runtime/bin/okstra-provider-exec.py +165 -190
- package/runtime/bin/okstra-trace-cleanup.sh +14 -7
- package/runtime/bin/okstra-wrapper-status.py +26 -19
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/convergence.md +36 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +23 -1
- package/runtime/prompts/lead/plan-body-verification.md +9 -1
- package/runtime/prompts/lead/report-writer.md +1 -0
- package/runtime/prompts/lead/team-contract.md +3 -3
- package/runtime/prompts/profiles/_common-contract.md +9 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
- package/runtime/prompts/profiles/implementation-planning.md +5 -3
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +148 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +55 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +41 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +44 -0
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +42 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +5 -1
- package/runtime/python/okstra_ctl/dispatch_state.py +10 -0
- package/runtime/python/okstra_ctl/domain/provider.py +5 -1
- package/runtime/python/okstra_ctl/domain/worker_exec.py +102 -0
- package/runtime/python/okstra_ctl/domain/worker_role.py +34 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +261 -0
- package/runtime/python/okstra_ctl/incremental_scope.py +16 -4
- package/runtime/python/okstra_ctl/report_html/common.py +71 -25
- package/runtime/python/okstra_ctl/report_html/models.py +5 -0
- package/runtime/python/okstra_ctl/report_html/render.py +1 -1
- package/runtime/python/okstra_ctl/report_html/run_usage.py +19 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +44 -16
- package/runtime/python/okstra_ctl/stage_citations.py +52 -15
- package/runtime/python/okstra_ctl/user_response.py +45 -29
- package/runtime/python/okstra_ctl/wizard.py +13 -9
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +10 -3
- package/runtime/python/okstra_ctl/worker_request.py +140 -0
- package/runtime/python/okstra_ctl/worker_runner.py +622 -0
- package/runtime/python/okstra_token_usage/collect.py +8 -1
- package/runtime/python/okstra_token_usage/report.py +42 -0
- package/runtime/python/okstra_token_usage/task_totals.py +88 -0
- package/runtime/schemas/final-report-v1.0.schema.json +70 -0
- package/runtime/schemas/final-report-v2.0.schema.json +90 -0
- package/runtime/skills/okstra-inspect/SKILL.md +1 -2
- package/runtime/skills/okstra-inspect/facets/logs.md +5 -5
- package/runtime/skills/okstra-inspect/facets/run-audit.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +15 -5
- package/runtime/templates/report-writer-prompt-preamble.md +1 -0
- package/runtime/templates/reports/html/assets/base.css +8 -4
- package/runtime/templates/reports/html/base.template.html +12 -6
- package/runtime/templates/reports/html/i18n/en.json +29 -6
- package/runtime/templates/reports/html/i18n/ko.json +29 -6
- package/runtime/templates/reports/html/macros/forms.html +9 -3
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +14 -19
- package/runtime/templates/reports/report.js +59 -26
- package/runtime/templates/reports/user-response.template.md +12 -8
- package/runtime/validators/validate-run.py +88 -7
- package/runtime/validators/validate_session_conformance.py +62 -1
- package/src/cli-registry.mjs +0 -7
- package/runtime/bin/okstra-wrapper-agy-stream.py +0 -61
- package/runtime/python/okstra_ctl/error_issue.py +0 -640
- package/runtime/python/okstra_ctl/issue_signals.py +0 -186
- package/runtime/skills/okstra-inspect/facets/error-issue.md +0 -77
- package/src/commands/inspect/error-issue.mjs +0 -27
|
@@ -24,6 +24,8 @@ from okstra_ctl.render_final_report import ( # noqa: E402
|
|
|
24
24
|
render_to_file,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
from .task_totals import task_cumulative_usage # noqa: E402
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
class SubstituteRefusedError(RuntimeError):
|
|
29
31
|
"""Raised when substitution would write zero-only token cells.
|
|
@@ -89,6 +91,8 @@ def _populate_execution_row(row: dict, source: dict) -> None:
|
|
|
89
91
|
return
|
|
90
92
|
if "totalTokens" in usage:
|
|
91
93
|
row["totalTokens"] = usage["totalTokens"]
|
|
94
|
+
if "cacheReadTokens" in usage:
|
|
95
|
+
row["cacheReadTokens"] = usage["cacheReadTokens"]
|
|
92
96
|
if "billableEquivalentTokens" in usage:
|
|
93
97
|
row["billableTokens"] = usage["billableEquivalentTokens"]
|
|
94
98
|
if "estimatedCostUsd" in usage:
|
|
@@ -114,6 +118,7 @@ def _worker_detail_row(label: str, usage: dict) -> dict:
|
|
|
114
118
|
return {
|
|
115
119
|
"label": label,
|
|
116
120
|
"totalTokens": None,
|
|
121
|
+
"cacheReadTokens": None,
|
|
117
122
|
"billableTokens": None,
|
|
118
123
|
"costUsd": None,
|
|
119
124
|
"cliTotalTokens": None,
|
|
@@ -122,6 +127,7 @@ def _worker_detail_row(label: str, usage: dict) -> dict:
|
|
|
122
127
|
return {
|
|
123
128
|
"label": label,
|
|
124
129
|
"totalTokens": usage.get("totalTokens"),
|
|
130
|
+
"cacheReadTokens": usage.get("cacheReadTokens"),
|
|
125
131
|
"billableTokens": usage.get("billableEquivalentTokens"),
|
|
126
132
|
"costUsd": usage.get("estimatedCostUsd"),
|
|
127
133
|
"cliTotalTokens": usage.get("cliTotalTokens"),
|
|
@@ -129,6 +135,35 @@ def _worker_detail_row(label: str, usage: dict) -> dict:
|
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
|
|
138
|
+
def _cache_reads(summary: dict, team_state: dict) -> dict[str, int]:
|
|
139
|
+
"""Lead / worker / grand cache-read totals for this run.
|
|
140
|
+
|
|
141
|
+
The figure joined `usageSummary` after most team-states on disk were
|
|
142
|
+
written, and re-rendering one of those reports is exactly when the reader
|
|
143
|
+
goes looking for the corrected table — so a state without the summary keys
|
|
144
|
+
is added up from the usage blocks it does carry.
|
|
145
|
+
"""
|
|
146
|
+
lead = summary.get("leadCacheReadTokens")
|
|
147
|
+
worker = summary.get("workerCacheReadTokens")
|
|
148
|
+
if isinstance(lead, int) and isinstance(worker, int):
|
|
149
|
+
return {"lead": lead, "worker": worker, "grand": lead + worker}
|
|
150
|
+
lead = _cache_read_tokens(team_state.get("leadUsage"))
|
|
151
|
+
worker = sum(
|
|
152
|
+
_cache_read_tokens(entry.get("usage"))
|
|
153
|
+
for entry in team_state.get("workers") or []
|
|
154
|
+
if isinstance(entry, dict)
|
|
155
|
+
)
|
|
156
|
+
worker += _cache_read_tokens(summary.get("unattributedWorkerUsage"))
|
|
157
|
+
return {"lead": lead, "worker": worker, "grand": lead + worker}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _cache_read_tokens(usage: object) -> int:
|
|
161
|
+
if not isinstance(usage, dict):
|
|
162
|
+
return 0
|
|
163
|
+
value = usage.get("cacheReadTokens")
|
|
164
|
+
return value if isinstance(value, int) else 0
|
|
165
|
+
|
|
166
|
+
|
|
132
167
|
def _worker_detail_rows(team_state: dict) -> list[dict]:
|
|
133
168
|
rows = []
|
|
134
169
|
for worker in team_state.get("workers") or []:
|
|
@@ -178,19 +213,23 @@ def populate_data_token_cells(data_path: Path, team_state: dict) -> int:
|
|
|
178
213
|
worker_cost = cost.get("claudeWorkers") or 0
|
|
179
214
|
cli_cost = cost.get("cliWorkers") or 0
|
|
180
215
|
|
|
216
|
+
cache_reads = _cache_reads(summary, team_state)
|
|
181
217
|
token_usage = data.setdefault("tokenUsage", {})
|
|
182
218
|
token_usage.setdefault("lead", {}).update({
|
|
183
219
|
"totalTokens": summary.get("leadTotalTokens"),
|
|
220
|
+
"cacheReadTokens": cache_reads["lead"],
|
|
184
221
|
"billableTokens": summary.get("leadBillableEquivalentTokens"),
|
|
185
222
|
"costUsd": lead_cost,
|
|
186
223
|
})
|
|
187
224
|
token_usage.setdefault("worker", {}).update({
|
|
188
225
|
"totalTokens": summary.get("workerTotalTokens"),
|
|
226
|
+
"cacheReadTokens": cache_reads["worker"],
|
|
189
227
|
"billableTokens": summary.get("workerBillableEquivalentTokens"),
|
|
190
228
|
"costUsd": worker_cost,
|
|
191
229
|
})
|
|
192
230
|
token_usage.setdefault("grand", {}).update({
|
|
193
231
|
"totalTokens": summary.get("grandTotalTokens"),
|
|
232
|
+
"cacheReadTokens": cache_reads["grand"],
|
|
194
233
|
"billableTokens": summary.get("grandBillableEquivalentTokens"),
|
|
195
234
|
# CLI tracked on its own row per the template — grand here means
|
|
196
235
|
# lead + claudeWorkers, not lead + claudeWorkers + cli.
|
|
@@ -198,6 +237,9 @@ def populate_data_token_cells(data_path: Path, team_state: dict) -> int:
|
|
|
198
237
|
})
|
|
199
238
|
token_usage.setdefault("cli", {})["costUsd"] = cli_cost
|
|
200
239
|
token_usage["workerDetails"] = _worker_detail_rows(team_state)
|
|
240
|
+
cumulative = task_cumulative_usage(data_path)
|
|
241
|
+
if cumulative is not None:
|
|
242
|
+
token_usage["taskCumulative"] = cumulative
|
|
201
243
|
changes += 4
|
|
202
244
|
|
|
203
245
|
# Execution Status by Agent — per-row token / cost / duration.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""What a whole task has spent, across every run of every phase.
|
|
2
|
+
|
|
3
|
+
A report's own figures cover one run, but a task reaches its plan through
|
|
4
|
+
several: the first pass, a clarification re-run, a conformance fix. Each of
|
|
5
|
+
those bills separately, and a reader who only ever sees the last one has no way
|
|
6
|
+
to learn what the task cost. The per-run team-states are the only record of the
|
|
7
|
+
earlier passes, so the total is summed from them here rather than carried
|
|
8
|
+
forward inside any single run's state.
|
|
9
|
+
|
|
10
|
+
Every team-state is read directly rather than through its `usageSummary`: the
|
|
11
|
+
summary predates the cache-read figure, so an older run would drop out of that
|
|
12
|
+
column while still counting in the others.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
_TEAM_STATE_GLOB = "runs/*/state/team-state-*.json"
|
|
20
|
+
_SUMMED_KEYS = (
|
|
21
|
+
("totalTokens", "totalTokens"),
|
|
22
|
+
("cacheReadTokens", "cacheReadTokens"),
|
|
23
|
+
("billableTokens", "billableEquivalentTokens"),
|
|
24
|
+
("costUsd", "estimatedCostUsd"),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _task_root(data_path: Path) -> Path | None:
|
|
29
|
+
"""The task-bundle directory a report lives under.
|
|
30
|
+
|
|
31
|
+
Report path shape: `<task-id>/runs/<task-type>/reports/<name>.data.json`.
|
|
32
|
+
A path that does not match is not inside a task bundle — a fixture under a
|
|
33
|
+
tmp dir, most often — and has no sibling runs to total.
|
|
34
|
+
"""
|
|
35
|
+
parents = data_path.parents
|
|
36
|
+
if len(parents) < 4:
|
|
37
|
+
return None
|
|
38
|
+
if parents[0].name != "reports" or parents[2].name != "runs":
|
|
39
|
+
return None
|
|
40
|
+
return parents[3]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _usage_blocks(team_state: dict) -> list[dict]:
|
|
44
|
+
blocks = [team_state.get("leadUsage") or {}]
|
|
45
|
+
for worker in team_state.get("workers") or []:
|
|
46
|
+
if isinstance(worker, dict):
|
|
47
|
+
blocks.append(worker.get("usage") or {})
|
|
48
|
+
unattributed = (team_state.get("usageSummary") or {}).get("unattributedWorkerUsage")
|
|
49
|
+
if isinstance(unattributed, dict):
|
|
50
|
+
blocks.append(unattributed)
|
|
51
|
+
return [block for block in blocks if isinstance(block, dict)]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _number(value: object) -> int | float:
|
|
55
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
56
|
+
return 0
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def task_cumulative_usage(data_path: Path) -> dict | None:
|
|
61
|
+
"""Sum every measured run of this report's task, or None when there is none.
|
|
62
|
+
|
|
63
|
+
A run whose collector found no session contributes zero tokens and is left
|
|
64
|
+
out of `runCount` — counting it would make the average per run look cheaper
|
|
65
|
+
than the runs that were actually measured.
|
|
66
|
+
"""
|
|
67
|
+
root = _task_root(data_path)
|
|
68
|
+
if root is None:
|
|
69
|
+
return None
|
|
70
|
+
totals = {name: 0 for name, _ in _SUMMED_KEYS}
|
|
71
|
+
run_count = 0
|
|
72
|
+
for state_path in sorted(root.glob(_TEAM_STATE_GLOB)):
|
|
73
|
+
try:
|
|
74
|
+
team_state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
75
|
+
except (OSError, ValueError):
|
|
76
|
+
continue
|
|
77
|
+
if not isinstance(team_state, dict):
|
|
78
|
+
continue
|
|
79
|
+
blocks = _usage_blocks(team_state)
|
|
80
|
+
measured = sum(_number(block.get("totalTokens")) for block in blocks)
|
|
81
|
+
if measured <= 0:
|
|
82
|
+
continue
|
|
83
|
+
run_count += 1
|
|
84
|
+
for name, source_key in _SUMMED_KEYS:
|
|
85
|
+
totals[name] += sum(_number(block.get(source_key)) for block in blocks)
|
|
86
|
+
if not run_count:
|
|
87
|
+
return None
|
|
88
|
+
return {**totals, "runCount": run_count}
|
|
@@ -398,6 +398,9 @@
|
|
|
398
398
|
]
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
|
+
},
|
|
402
|
+
"taskCumulative": {
|
|
403
|
+
"$ref": "#/$defs/TokenUsageTaskCumulative"
|
|
401
404
|
}
|
|
402
405
|
}
|
|
403
406
|
},
|
|
@@ -3322,6 +3325,17 @@
|
|
|
3322
3325
|
}
|
|
3323
3326
|
]
|
|
3324
3327
|
},
|
|
3328
|
+
"cacheReadTokens": {
|
|
3329
|
+
"oneOf": [
|
|
3330
|
+
{
|
|
3331
|
+
"type": "integer",
|
|
3332
|
+
"minimum": 0
|
|
3333
|
+
},
|
|
3334
|
+
{
|
|
3335
|
+
"type": "null"
|
|
3336
|
+
}
|
|
3337
|
+
]
|
|
3338
|
+
},
|
|
3325
3339
|
"billableTokens": {
|
|
3326
3340
|
"oneOf": [
|
|
3327
3341
|
{
|
|
@@ -3399,6 +3413,17 @@
|
|
|
3399
3413
|
}
|
|
3400
3414
|
]
|
|
3401
3415
|
},
|
|
3416
|
+
"cacheReadTokens": {
|
|
3417
|
+
"oneOf": [
|
|
3418
|
+
{
|
|
3419
|
+
"type": "integer",
|
|
3420
|
+
"minimum": 0
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
"type": "null"
|
|
3424
|
+
}
|
|
3425
|
+
]
|
|
3426
|
+
},
|
|
3402
3427
|
"billableTokens": {
|
|
3403
3428
|
"oneOf": [
|
|
3404
3429
|
{
|
|
@@ -3450,6 +3475,17 @@
|
|
|
3450
3475
|
}
|
|
3451
3476
|
]
|
|
3452
3477
|
},
|
|
3478
|
+
"cacheReadTokens": {
|
|
3479
|
+
"oneOf": [
|
|
3480
|
+
{
|
|
3481
|
+
"type": "integer",
|
|
3482
|
+
"minimum": 0
|
|
3483
|
+
},
|
|
3484
|
+
{
|
|
3485
|
+
"type": "null"
|
|
3486
|
+
}
|
|
3487
|
+
]
|
|
3488
|
+
},
|
|
3453
3489
|
"billableTokens": {
|
|
3454
3490
|
"oneOf": [
|
|
3455
3491
|
{
|
|
@@ -6287,6 +6323,40 @@
|
|
|
6287
6323
|
}
|
|
6288
6324
|
}
|
|
6289
6325
|
]
|
|
6326
|
+
},
|
|
6327
|
+
"TokenUsageTaskCumulative": {
|
|
6328
|
+
"type": "object",
|
|
6329
|
+
"description": "Every measured run of this task added up — this report's run plus every earlier one. Written by the Phase 7 token-usage step from the per-run team-states.",
|
|
6330
|
+
"required": [
|
|
6331
|
+
"totalTokens",
|
|
6332
|
+
"cacheReadTokens",
|
|
6333
|
+
"billableTokens",
|
|
6334
|
+
"costUsd",
|
|
6335
|
+
"runCount"
|
|
6336
|
+
],
|
|
6337
|
+
"additionalProperties": false,
|
|
6338
|
+
"properties": {
|
|
6339
|
+
"totalTokens": {
|
|
6340
|
+
"type": "integer",
|
|
6341
|
+
"minimum": 0
|
|
6342
|
+
},
|
|
6343
|
+
"cacheReadTokens": {
|
|
6344
|
+
"type": "integer",
|
|
6345
|
+
"minimum": 0
|
|
6346
|
+
},
|
|
6347
|
+
"billableTokens": {
|
|
6348
|
+
"type": "integer",
|
|
6349
|
+
"minimum": 0
|
|
6350
|
+
},
|
|
6351
|
+
"costUsd": {
|
|
6352
|
+
"type": "number",
|
|
6353
|
+
"minimum": 0
|
|
6354
|
+
},
|
|
6355
|
+
"runCount": {
|
|
6356
|
+
"type": "integer",
|
|
6357
|
+
"minimum": 1
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6290
6360
|
}
|
|
6291
6361
|
}
|
|
6292
6362
|
}
|
|
@@ -368,6 +368,9 @@
|
|
|
368
368
|
]
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
+
},
|
|
372
|
+
"taskCumulative": {
|
|
373
|
+
"$ref": "#/$defs/TokenUsageTaskCumulative"
|
|
371
374
|
}
|
|
372
375
|
}
|
|
373
376
|
},
|
|
@@ -3435,6 +3438,20 @@
|
|
|
3435
3438
|
"deferrable"
|
|
3436
3439
|
]
|
|
3437
3440
|
},
|
|
3441
|
+
"ClarificationOrigin": {
|
|
3442
|
+
"enum": [
|
|
3443
|
+
"worker-finding",
|
|
3444
|
+
"material-gap",
|
|
3445
|
+
"lead-directed"
|
|
3446
|
+
]
|
|
3447
|
+
},
|
|
3448
|
+
"ClarificationUserConfirmation": {
|
|
3449
|
+
"enum": [
|
|
3450
|
+
"asked-and-answered",
|
|
3451
|
+
"asked-awaiting",
|
|
3452
|
+
"deferred-no-interactive-session"
|
|
3453
|
+
]
|
|
3454
|
+
},
|
|
3438
3455
|
"ClarificationOption": {
|
|
3439
3456
|
"type": "object",
|
|
3440
3457
|
"required": [
|
|
@@ -5380,6 +5397,17 @@
|
|
|
5380
5397
|
}
|
|
5381
5398
|
]
|
|
5382
5399
|
},
|
|
5400
|
+
"cacheReadTokens": {
|
|
5401
|
+
"oneOf": [
|
|
5402
|
+
{
|
|
5403
|
+
"type": "integer",
|
|
5404
|
+
"minimum": 0
|
|
5405
|
+
},
|
|
5406
|
+
{
|
|
5407
|
+
"type": "null"
|
|
5408
|
+
}
|
|
5409
|
+
]
|
|
5410
|
+
},
|
|
5383
5411
|
"billableTokens": {
|
|
5384
5412
|
"oneOf": [
|
|
5385
5413
|
{
|
|
@@ -5457,6 +5485,17 @@
|
|
|
5457
5485
|
}
|
|
5458
5486
|
]
|
|
5459
5487
|
},
|
|
5488
|
+
"cacheReadTokens": {
|
|
5489
|
+
"oneOf": [
|
|
5490
|
+
{
|
|
5491
|
+
"type": "integer",
|
|
5492
|
+
"minimum": 0
|
|
5493
|
+
},
|
|
5494
|
+
{
|
|
5495
|
+
"type": "null"
|
|
5496
|
+
}
|
|
5497
|
+
]
|
|
5498
|
+
},
|
|
5460
5499
|
"billableTokens": {
|
|
5461
5500
|
"oneOf": [
|
|
5462
5501
|
{
|
|
@@ -5508,6 +5547,17 @@
|
|
|
5508
5547
|
}
|
|
5509
5548
|
]
|
|
5510
5549
|
},
|
|
5550
|
+
"cacheReadTokens": {
|
|
5551
|
+
"oneOf": [
|
|
5552
|
+
{
|
|
5553
|
+
"type": "integer",
|
|
5554
|
+
"minimum": 0
|
|
5555
|
+
},
|
|
5556
|
+
{
|
|
5557
|
+
"type": "null"
|
|
5558
|
+
}
|
|
5559
|
+
]
|
|
5560
|
+
},
|
|
5511
5561
|
"billableTokens": {
|
|
5512
5562
|
"oneOf": [
|
|
5513
5563
|
{
|
|
@@ -8249,6 +8299,12 @@
|
|
|
8249
8299
|
"userInput": {
|
|
8250
8300
|
"type": "string"
|
|
8251
8301
|
},
|
|
8302
|
+
"origin": {
|
|
8303
|
+
"$ref": "#/$defs/ClarificationOrigin"
|
|
8304
|
+
},
|
|
8305
|
+
"userConfirmation": {
|
|
8306
|
+
"$ref": "#/$defs/ClarificationUserConfirmation"
|
|
8307
|
+
},
|
|
8252
8308
|
"options": {
|
|
8253
8309
|
"type": "array",
|
|
8254
8310
|
"minItems": 2,
|
|
@@ -8371,6 +8427,40 @@
|
|
|
8371
8427
|
}
|
|
8372
8428
|
}
|
|
8373
8429
|
]
|
|
8430
|
+
},
|
|
8431
|
+
"TokenUsageTaskCumulative": {
|
|
8432
|
+
"type": "object",
|
|
8433
|
+
"description": "Every measured run of this task added up — this report's run plus every earlier one. Written by the Phase 7 token-usage step from the per-run team-states.",
|
|
8434
|
+
"required": [
|
|
8435
|
+
"totalTokens",
|
|
8436
|
+
"cacheReadTokens",
|
|
8437
|
+
"billableTokens",
|
|
8438
|
+
"costUsd",
|
|
8439
|
+
"runCount"
|
|
8440
|
+
],
|
|
8441
|
+
"additionalProperties": false,
|
|
8442
|
+
"properties": {
|
|
8443
|
+
"totalTokens": {
|
|
8444
|
+
"type": "integer",
|
|
8445
|
+
"minimum": 0
|
|
8446
|
+
},
|
|
8447
|
+
"cacheReadTokens": {
|
|
8448
|
+
"type": "integer",
|
|
8449
|
+
"minimum": 0
|
|
8450
|
+
},
|
|
8451
|
+
"billableTokens": {
|
|
8452
|
+
"type": "integer",
|
|
8453
|
+
"minimum": 0
|
|
8454
|
+
},
|
|
8455
|
+
"costUsd": {
|
|
8456
|
+
"type": "number",
|
|
8457
|
+
"minimum": 0
|
|
8458
|
+
},
|
|
8459
|
+
"runCount": {
|
|
8460
|
+
"type": "integer",
|
|
8461
|
+
"minimum": 1
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8374
8464
|
}
|
|
8375
8465
|
}
|
|
8376
8466
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-inspect
|
|
3
3
|
description: >-
|
|
4
|
-
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also turns cross-project okstra errors into an anonymized zip (error feedback)
|
|
4
|
+
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (verdict / pass); its elapsed time or context/read cost; its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (error report). Also turns cross-project okstra errors into an anonymized zip (error feedback), and audits run health across tasks (run audit). NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule-gen), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA Inspect
|
|
@@ -18,7 +18,6 @@ Single read-side entry point for okstra runtime inspection plus the one status m
|
|
|
18
18
|
| `cost` | `facets/cost.md` | Estimate file/read context cost for a task bundle. |
|
|
19
19
|
| `errors` | `facets/errors.md` | Aggregate okstra-run error logs for a task into a timestamped markdown report; print a summary. |
|
|
20
20
|
| `error-zip` | `facets/error-zip.md` | Collect cross-project okstra error logs into an anonymized zip (report + raw) and summarize clusters. |
|
|
21
|
-
| `error-issue` | `facets/error-issue.md` | Turn cross-project okstra anomalies into GitHub issue candidates; file the approved ones after explicit user approval. |
|
|
22
21
|
| `run-audit` | `facets/run-audit.md` | Check every run's artifacts against progress invariants; report what went wrong without an error ever being logged. |
|
|
23
22
|
| `recap` | `facets/recap.md` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl`, and writes agent-authored notes to `notes/` for feeding into later runs. |
|
|
24
23
|
|
|
@@ -6,17 +6,17 @@ Loaded lazily by the dispatch table in `SKILL.md` (core). Shared rules — Step
|
|
|
6
6
|
|
|
7
7
|
Trigger phrases: "okstra logs", "log status", "log files", "log size", "log cleanup".
|
|
8
8
|
|
|
9
|
-
Read-only inventory of
|
|
9
|
+
Read-only inventory of the worker log files written next to each prompt history file (`<prompt>.log`). Reports sizes, ages, totals, and suggests cleanup commands. **Does not delete** — the user runs whichever `find … -delete` line they like.
|
|
10
10
|
|
|
11
|
-
**Background:**
|
|
11
|
+
**Background:** every CLI-worker dispatch writes a sidecar log next to its prompt history file. All five providers go through the same shared runner (`~/.okstra/lib/python/okstra_ctl/worker_runner.py`), so the file is there whichever provider ran:
|
|
12
12
|
|
|
13
13
|
```
|
|
14
14
|
.okstra/tasks/<task-group>/<task-id>/runs/<phase>/prompts/
|
|
15
15
|
<worker>-worker-prompt-<phase>-<seq>.md <-- prompt (git-tracked)
|
|
16
|
-
<worker>-worker-prompt-<phase>-<seq>.log <--
|
|
16
|
+
<worker>-worker-prompt-<phase>-<seq>.log <-- the run's archive of what the worker did
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The log is
|
|
19
|
+
The log is rewritten from scratch at each dispatch — only the latest run for a given seq is preserved. Different seqs keep separate files. It holds one row per tool call and per tool result plus the worker's closing text, and drops thinking; a provider that emits plain text instead of an event stream has its progress copy capped at 5000 lines per run, with `[okstra log-cap] N progress line(s) elided` marking what was dropped. Long-running implementation dispatches can still produce multi-MB logs; analysis-phase dispatches are typically smaller.
|
|
20
20
|
|
|
21
21
|
### logs.1 — Inventory
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ Scans `<projectRoot>/.okstra/tasks/**/runs/*/prompts/*.log` and parses task/phas
|
|
|
29
29
|
- `perTask[]` — `{taskKey, fileCount, totalBytes, oldestEpoch, newestEpoch}`, total-size desc
|
|
30
30
|
- `totals` — `{fileCount, totalBytes, taskCount}`
|
|
31
31
|
|
|
32
|
-
If `totals.fileCount` is 0, report `No
|
|
32
|
+
If `totals.fileCount` is 0, report `No worker log files found under <projectRoot>` and stop.
|
|
33
33
|
|
|
34
34
|
### logs.2 — Summary tables
|
|
35
35
|
|
|
@@ -28,7 +28,7 @@ Under each, list the affected `taskKey` values with their `detail` and `source`.
|
|
|
28
28
|
State plainly which violations are this task's own state versus a repeated okstra defect:
|
|
29
29
|
|
|
30
30
|
- an invariant broken in **one** task → that task's own state; tell the user what to do about that task
|
|
31
|
-
- an invariant broken across **two or more** tasks →
|
|
32
|
-
- `approval-not-forgotten` is the exception
|
|
31
|
+
- an invariant broken across **two or more** tasks → a repeated okstra defect; say so and cite every affected `taskKey` so the user can decide what to do with it
|
|
32
|
+
- `approval-not-forgotten` is the exception no matter how far it spreads. It means a human has not approved yet, not that okstra did something wrong. Report it as a to-do list of tasks awaiting the user's approval instead.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
This facet reports; it never files anything anywhere.
|
|
@@ -183,7 +183,7 @@ That is the entire interactive flow. The wizard handles:
|
|
|
183
183
|
- brief path — **asked only for entry task-types (requirements-discovery / error-analysis / improvement-discovery / project-analysis / feature-analysis / change-impact-analysis)** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, sorted by the newer of file-created/modified time and latest task-catalog use; direct input last; `Keep / Change` for existing entry tasks). `project-analysis`, `feature-analysis`, and `change-impact-analysis` are brief entry task types. A downstream lifecycle task-type auto carries in the manifest's brief, and when no registered brief exists a `brief_carry` 3-option prompt appears (recommend switching to entry / Enter directly / Abort). `release-handoff` has no brief step at all — prepare generates the input document that cites the verification report,
|
|
184
184
|
- analysis-input sub-flow — `project-analysis` has no target or evidence step. `feature-analysis` runs `project_evidence_pick` / `project_evidence`, then `analysis_target_pick` / `analysis_target`; the target is required even when the user skips project evidence. `change-impact-analysis` runs `feature_evidence_pick` / `feature_evidence`, then `project_evidence_pick` / `project_evidence`. The evidence steps show accepted compatible reports first and retain direct-path entry; do not invent a different relation or reorder these steps,
|
|
185
185
|
- base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
|
|
186
|
-
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number) + executor pick. When an approved plan is selected and a `##
|
|
186
|
+
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number) + executor pick. When an approved plan is selected and a `## PLAN DECISION` sidecar carrying `Status: approved`, exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
|
|
187
187
|
- `release-handoff`-only sub-flow: after the approved plan auto-resolves, a `handoff_stage_pick` multi-select — choose an eligible stage bundle (stage-group) or the whole task (when an accepted whole-task verification report exists); the result goes out as render-args' `stages` key (csv, empty when whole-task),
|
|
188
188
|
- `Use defaults / Customize` branch with profile-aware worker/model questions,
|
|
189
189
|
- **resume-clarification (in-session equivalent)** — there is no separate mode or flag matching the shell's `okstra.sh --resume-clarification`; two steps of the standard flow carry out its substance. (1) `reuse_previous` (yes/no to reuse the previous run's settings — in `requirements-discovery` / `error-analysis` / `implementation-planning`, only when prior run-inputs exist): YES prefills workers·model·directive·related-tasks at once. (2) `clarification_pick`: if the **task-type's own** previous `final-report` exists it is auto-recommended as the carry-in input (falling back to the newest by mtime across all phases when absent), and the same run's `user-responses/` sidecar (answers the user filled in) is attached alongside. The chosen path is passed to prepare as `--clarification-response` — the user makes the sidecar via the report's `Export user response`, places it in `runs/<task-type>/user-responses/`, and re-runs the same phase,
|
|
@@ -147,26 +147,36 @@ Once the loop has covered every open item, **before** calling `write`, **echo ba
|
|
|
147
147
|
- If the user changes any item, show the echo-back again with the changed value and re-confirm.
|
|
148
148
|
- Every `value` must be what the user picked or typed, never a wording you settled on for them — this gate guarantees that.
|
|
149
149
|
|
|
150
|
-
## Step 5: (Optional) Record
|
|
150
|
+
## Step 5: (Optional) Record the plan decision
|
|
151
151
|
|
|
152
|
-
Record
|
|
152
|
+
Record a decision alongside the answers only when the user stated one outright. Never infer it from the tone of the conversation.
|
|
153
|
+
|
|
154
|
+
Approval also requires that the report's approval-blocking clarification items are **all filled with an answer**:
|
|
153
155
|
|
|
154
156
|
```
|
|
155
|
-
--
|
|
157
|
+
--plan-decision '{"status":"approved","implementationOption":"<the option the user chose>"}'
|
|
156
158
|
```
|
|
157
159
|
|
|
158
160
|
If any approval-target item is not filled with an answer, or is a reframe/hold, do not approve — tell the user the gate is still open.
|
|
159
161
|
|
|
162
|
+
A user who turns the plan down gets the same treatment, and the reason is mandatory — the next planning run reads it to know what to change:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
--plan-decision '{"status":"rejected","reason":"<the user's own words>"}'
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use `revision-requested` instead of `rejected` when the user wants the same plan reworked rather than dropped. Either way, quote the user's reason; do not summarise it into your own.
|
|
169
|
+
|
|
160
170
|
## Step 6: Write the sidecar
|
|
161
171
|
|
|
162
172
|
Run only after the user has confirmed the echo-back with "confirmed":
|
|
163
173
|
|
|
164
174
|
```bash
|
|
165
|
-
okstra user-response write --report <reportPath> --answers '<answers-json>' --
|
|
175
|
+
okstra user-response write --report <reportPath> --answers '<answers-json>' --plan-decision '<plan-decision-json>' --task-key <taskKey>
|
|
166
176
|
```
|
|
167
177
|
|
|
168
178
|
- `--answers` = the JSON array of `{id, kind, value, rationale?, disposition}` objects collected in Step 3.
|
|
169
|
-
- `--
|
|
179
|
+
- `--plan-decision` is attached only when recording a decision in Step 5 (otherwise omit it).
|
|
170
180
|
- `--task-key` is the value carried forward from Step 1/2.
|
|
171
181
|
- Report the path from the returned JSON `{sidecar: <path>}` to the user. (`write` merges items when a sidecar already exists — the same `id` is overwritten with the new value.)
|
|
172
182
|
|
|
@@ -25,6 +25,7 @@ Write `- PROGRESS: <stage> <ISO-8601-UTC>` there before reading and at least eve
|
|
|
25
25
|
- Follow the task-type schema excerpt and Phase 6 report-writer contract. Do not perform independent analysis, edit source code, or load implementation coding-preflight resources.
|
|
26
26
|
- Invoke `okstra render-final-report <Result Path>` after writing data.json and verify the markdown sibling exists before returning.
|
|
27
27
|
- Preserve source item IDs, convergence classifications, round history, and unresolved dissent; do not recompute them from intuition.
|
|
28
|
+
- Every `clarificationItems[]` row you leave `status: open` with `blocks: approval` carries `origin` and `userConfirmation`. `origin` is `worker-finding` only when an analyser or verifier reached it on its own evidence — when the lead's dispatch prompt told you to raise it, it is `lead-directed`, whatever the workers then agreed. `userConfirmation` is what the lead reports having done about it (`asked-and-answered` / `asked-awaiting` / `deferred-no-interactive-session`); if the dispatch does not say, write `asked-awaiting` and name the gap in your return message rather than guessing. An open approval blocker stops the whole task, so a row that misreports where it came from costs a re-run. **Enforced:** `validators/validate-run.py` `_validate_open_approval_blocker_provenance`.
|
|
28
29
|
|
|
29
30
|
## Anchor headers
|
|
30
31
|
|
|
@@ -77,8 +77,9 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
|
|
|
77
77
|
.ledger-key { display: flex; flex-wrap: wrap; gap: .6rem; font-weight: 600; }
|
|
78
78
|
.ledger-kind { font-weight: 400; color: GrayText; }
|
|
79
79
|
.ledger-text { max-width: 78ch; }
|
|
80
|
-
.ledger-source { font-size: .9rem; color: GrayText; }
|
|
81
|
-
.ledger-source > span:first-child::after
|
|
80
|
+
.ledger-source, .ledger-confidence { font-size: .9rem; color: GrayText; }
|
|
81
|
+
.ledger-source > span:first-child::after,
|
|
82
|
+
.ledger-confidence > span:first-child::after { content: ":"; }
|
|
82
83
|
.status { display: inline-block; white-space: nowrap; border-radius: 999px; padding: .1rem .5rem; background: color-mix(in srgb, GrayText 15%, Canvas); }
|
|
83
84
|
.status-gap, .status-risk, .status-error, .status-timeout { background: color-mix(in srgb, #d94b4b 18%, Canvas); }
|
|
84
85
|
/* Figures are read by comparing them down the column, which only works when
|
|
@@ -86,13 +87,16 @@ th, td { text-align: left; vertical-align: top; border-bottom: 1px solid color-m
|
|
|
86
87
|
the right edge is the one they share. */
|
|
87
88
|
.run-usage-lede { max-width: 78ch; }
|
|
88
89
|
.figure { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
89
|
-
/*
|
|
90
|
+
/* Five nowrap number columns need almost none of the width, and the default
|
|
90
91
|
even split spent it on them while the agent names wrapped mid-word. */
|
|
91
|
-
[data-report-section="run-usage"] .row-key { width:
|
|
92
|
+
[data-report-section="run-usage"] .row-key { width: 40%; }
|
|
92
93
|
.cli-extra { display: block; color: GrayText; font-size: .85em; }
|
|
93
94
|
tfoot th { font-weight: 600; }
|
|
94
95
|
tfoot tr:first-child > * { border-top: 2px solid color-mix(in srgb, CanvasText 30%, transparent); }
|
|
95
96
|
tfoot .grand-total > * { font-weight: 700; }
|
|
97
|
+
/* The task total counts runs this report is not about, so it is ruled off from
|
|
98
|
+
the run's own figures rather than reading as one more line of them. */
|
|
99
|
+
tfoot .task-cumulative > * { border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent); color: GrayText; }
|
|
96
100
|
.human-report-footer { width: min(1120px, calc(100% - 2rem)); margin: 0 auto 2rem; display: flex; flex-wrap: wrap; gap: .6rem; }
|
|
97
101
|
.human-report-footer pre { flex-basis: 100%; white-space: pre-wrap; max-height: 14em; overflow: auto; }
|
|
98
102
|
fieldset, label { display: block; margin: .7rem 0; }
|