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
|
@@ -66,7 +66,7 @@ from okstra_ctl.report_translation import ( # noqa: E402
|
|
|
66
66
|
HANGUL_PROSE_LIMIT,
|
|
67
67
|
hangul_share,
|
|
68
68
|
)
|
|
69
|
-
from okstra_ctl.stage_citations import
|
|
69
|
+
from okstra_ctl.stage_citations import enumerated_stage_numbers # noqa: E402
|
|
70
70
|
from okstra_ctl.incremental_scope import ( # noqa: E402
|
|
71
71
|
coverage_row_blocked_on,
|
|
72
72
|
stages_for_clarification,
|
|
@@ -3133,6 +3133,7 @@ def validate_final_report_data(
|
|
|
3133
3133
|
# Called here rather than from a task-type branch: four profiles raise
|
|
3134
3134
|
# clarification rows, and the gate scopes itself by task type internally.
|
|
3135
3135
|
_validate_clarification_options(data, failures)
|
|
3136
|
+
_validate_open_approval_blocker_provenance(data, failures)
|
|
3136
3137
|
|
|
3137
3138
|
task_type = (data.get("header") or {}).get("taskType")
|
|
3138
3139
|
_validate_verifier_fail_blocks_verdict(data, failures)
|
|
@@ -4126,6 +4127,80 @@ def _validate_option_set(
|
|
|
4126
4127
|
)
|
|
4127
4128
|
|
|
4128
4129
|
|
|
4130
|
+
def _validate_open_approval_blocker_provenance(
|
|
4131
|
+
data: dict, failures: list[str]
|
|
4132
|
+
) -> None:
|
|
4133
|
+
"""An open approval blocker records who raised it and what the lead did about it.
|
|
4134
|
+
|
|
4135
|
+
A lead can foresee a blocker, write it into the report as a
|
|
4136
|
+
`blocks: approval` row, and let the run finish — the row then withholds
|
|
4137
|
+
approval until a separate user-response cycle answers it. That path cost a
|
|
4138
|
+
full planning run: the user pointed at a reduction document and said "remove
|
|
4139
|
+
what it flags", the lead read that document's own "user decision required"
|
|
4140
|
+
note as outranking the instruction, and told the report writer to raise the
|
|
4141
|
+
item instead of applying it. The two verifiers then agreed unanimously that
|
|
4142
|
+
it was a user decision — not an independent finding, but the lead's own
|
|
4143
|
+
instruction returning to it. The run spent its whole self-fix budget on a
|
|
4144
|
+
gate no round could clear, because the thing blocking it was already
|
|
4145
|
+
answered before the run started.
|
|
4146
|
+
|
|
4147
|
+
`origin` names who produced the row, so a lead-authored blocker can no
|
|
4148
|
+
longer arrive dressed as a worker consensus. `userConfirmation` records what
|
|
4149
|
+
the lead did before writing it. Neither is required on a row the user has
|
|
4150
|
+
already answered (`answered` / `resolved`) — the record is in `userInput`.
|
|
4151
|
+
|
|
4152
|
+
The one shape rejected outright is a lead-directed blocker raised with no
|
|
4153
|
+
interactive session: with nobody to ask, a defect the lead itself surfaced
|
|
4154
|
+
belongs in `## 5. Missing Information and Risks` as a Working Assumption,
|
|
4155
|
+
the same outlet the surviving planner-fixable items already use. Blocking a
|
|
4156
|
+
plan on the lead's own judgment, in a run where that judgment cannot be put
|
|
4157
|
+
to the user, only defers the work to a re-run.
|
|
4158
|
+
"""
|
|
4159
|
+
for row in data.get("clarificationItems") or []:
|
|
4160
|
+
if not isinstance(row, dict):
|
|
4161
|
+
continue
|
|
4162
|
+
if row.get("blocks") != "approval" or row.get("status") != "open":
|
|
4163
|
+
continue
|
|
4164
|
+
row_id = str(row.get("id") or "<unknown>")
|
|
4165
|
+
origin = row.get("origin")
|
|
4166
|
+
confirmation = row.get("userConfirmation")
|
|
4167
|
+
if not origin:
|
|
4168
|
+
failures.append(
|
|
4169
|
+
f"final-report data.json: clarification `{row_id}` is an open "
|
|
4170
|
+
"approval blocker with no `origin`. Record who raised it — "
|
|
4171
|
+
"`worker-finding` (an analyser or verifier reached it "
|
|
4172
|
+
"independently), `material-gap` (the brief and the codebase "
|
|
4173
|
+
"both leave it unanswered), or `lead-directed` (the lead's own "
|
|
4174
|
+
"judgment, including anything the lead instructed a worker to "
|
|
4175
|
+
"raise). A lead-authored blocker recorded as a worker consensus "
|
|
4176
|
+
"is how a run blocks on a question the user already answered."
|
|
4177
|
+
)
|
|
4178
|
+
if not confirmation:
|
|
4179
|
+
failures.append(
|
|
4180
|
+
f"final-report data.json: clarification `{row_id}` is an open "
|
|
4181
|
+
"approval blocker with no `userConfirmation`. Record what "
|
|
4182
|
+
"happened before the row was written — `asked-and-answered`, "
|
|
4183
|
+
"`asked-awaiting`, or `deferred-no-interactive-session`. "
|
|
4184
|
+
"Withholding approval is the most expensive thing a report can "
|
|
4185
|
+
"do to a run; doing it without recording whether the user was "
|
|
4186
|
+
"ever asked leaves the next run unable to tell a real blocker "
|
|
4187
|
+
"from a question nobody put to them."
|
|
4188
|
+
)
|
|
4189
|
+
if (
|
|
4190
|
+
origin == "lead-directed"
|
|
4191
|
+
and confirmation == "deferred-no-interactive-session"
|
|
4192
|
+
):
|
|
4193
|
+
failures.append(
|
|
4194
|
+
f"final-report data.json: clarification `{row_id}` blocks "
|
|
4195
|
+
"approval on the lead's own judgment (`origin: lead-directed`) "
|
|
4196
|
+
"in a run with no interactive session to answer it. Record it "
|
|
4197
|
+
"as a Working Assumption in `## 5. Missing Information and "
|
|
4198
|
+
"Risks` and let the plan proceed — that is the outlet surviving "
|
|
4199
|
+
"planner-fixable items already use. A blocker nobody can answer "
|
|
4200
|
+
"inside this run only moves the work to a re-run."
|
|
4201
|
+
)
|
|
4202
|
+
|
|
4203
|
+
|
|
4129
4204
|
def _has_clarification_backtrace(
|
|
4130
4205
|
row_id: str, plan_items: object, coverage: object
|
|
4131
4206
|
) -> bool:
|
|
@@ -6347,10 +6422,14 @@ def _validate_requirement_provenance(
|
|
|
6347
6422
|
# Deliberately not `_COVERED_BY_STAGE_REF_RE`: the two directions disagree on
|
|
6348
6423
|
# what "no match" means — forward treats it as nothing to verify and passes,
|
|
6349
6424
|
# reverse treats it as nothing cited and orphans every stage — so the reverse
|
|
6350
|
-
# reader must cover `Stages 1, 2
|
|
6351
|
-
#
|
|
6352
|
-
#
|
|
6353
|
-
|
|
6425
|
+
# reader must cover `Stages 1, 2` and `Stage 1 and 2` too.
|
|
6426
|
+
#
|
|
6427
|
+
# It reads hand-enumerated numbers only. A range's interior is deliberately NOT
|
|
6428
|
+
# coverage evidence: one `Stages 1-64` cell would otherwise stamp every stage in
|
|
6429
|
+
# the map while the planner confirmed none of them. The incremental-scope
|
|
6430
|
+
# back-trace keeps the widening reader (`cited_stage_numbers`) because "could
|
|
6431
|
+
# this answer reach stage 5" is the opposite question and must over-approximate.
|
|
6432
|
+
_enumerated_stage_numbers = enumerated_stage_numbers
|
|
6354
6433
|
|
|
6355
6434
|
|
|
6356
6435
|
# Statuses under which a row asserts the plan does work for the requirement.
|
|
@@ -6387,14 +6466,16 @@ def _validate_stage_has_requirement(data: dict, failures: list[str]) -> None:
|
|
|
6387
6466
|
continue
|
|
6388
6467
|
if not _COVERAGE_CLAIMING_STATUS_RE.match(str(row.get("status") or "").strip()):
|
|
6389
6468
|
continue
|
|
6390
|
-
cited |=
|
|
6469
|
+
cited |= _enumerated_stage_numbers(str(row.get("coveredBy") or ""))
|
|
6391
6470
|
|
|
6392
6471
|
for orphan in sorted(stage_numbers - cited):
|
|
6393
6472
|
failures.append(
|
|
6394
6473
|
f"final-report data.json: Stage {orphan} is cited by no requirementCoverage "
|
|
6395
6474
|
"row — it traces back to nothing the brief asked for. Either cite the "
|
|
6396
6475
|
"requirement it serves, or drop it and raise it as a `Blocks=approval` "
|
|
6397
|
-
"clarification (profile: scope provenance)."
|
|
6476
|
+
"clarification (profile: scope provenance). A range cites only its "
|
|
6477
|
+
"endpoints here: write `Stages 1, 2, 3` rather than `Stages 1-3`, so each "
|
|
6478
|
+
"stage this requirement covers is one the planner named."
|
|
6398
6479
|
)
|
|
6399
6480
|
|
|
6400
6481
|
|
|
@@ -457,6 +457,61 @@ def _plan_body_rounds_ran(run_dir: Path, suffix: str | None) -> int:
|
|
|
457
457
|
return rounds if isinstance(rounds, int) and rounds > 0 else 0
|
|
458
458
|
|
|
459
459
|
|
|
460
|
+
def _ids_reported_as_asked(report_path: Path) -> list[str]:
|
|
461
|
+
"""리포트가 "사용자에게 물었다"고 기록한 열린 승인 차단 행의 id."""
|
|
462
|
+
name = report_path.name
|
|
463
|
+
if not name.endswith(".md"):
|
|
464
|
+
return []
|
|
465
|
+
data_path = report_path.with_name(name.removesuffix(".md") + ".data.json")
|
|
466
|
+
try:
|
|
467
|
+
doc = json.loads(data_path.read_text())
|
|
468
|
+
except (OSError, json.JSONDecodeError):
|
|
469
|
+
return []
|
|
470
|
+
if not isinstance(doc, dict):
|
|
471
|
+
return []
|
|
472
|
+
asked: list[str] = []
|
|
473
|
+
for row in doc.get("clarificationItems") or []:
|
|
474
|
+
if not isinstance(row, dict):
|
|
475
|
+
continue
|
|
476
|
+
if row.get("blocks") != "approval" or row.get("status") != "open":
|
|
477
|
+
continue
|
|
478
|
+
if not str(row.get("userConfirmation") or "").startswith("asked-"):
|
|
479
|
+
continue
|
|
480
|
+
row_id = str(row.get("id") or "").strip()
|
|
481
|
+
if row_id:
|
|
482
|
+
asked.append(row_id)
|
|
483
|
+
return asked
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _check_user_confirm_checkpoints(
|
|
487
|
+
by_phase: dict[str, list[tuple[str, str]]],
|
|
488
|
+
asked_ids: list[str],
|
|
489
|
+
errors: list[str],
|
|
490
|
+
) -> None:
|
|
491
|
+
""""물어봤다"는 리포트의 기록에는 물어본 흔적이 세션에 남아 있어야 한다.
|
|
492
|
+
|
|
493
|
+
승인을 막는 행은 run 하나를 통째로 대기 상태로 만든다. 그런 행을 세우기 전에
|
|
494
|
+
사용자에게 그 자리에서 묻는 것이 계약인데, 그 이행 여부를 리포트의 자기 신고
|
|
495
|
+
말고는 확인할 길이 없었다. 한 번은 리드가 차단이 생길 것을 미리 예측해 말해
|
|
496
|
+
놓고도 묻지 않은 채 행으로 만들었고, run 은 사용자가 이미 답해 둔 질문 앞에서
|
|
497
|
+
self-fix 예산을 전부 썼다. 체크포인트가 그 격차를 좁힌다 — 세션에 질문이 남지
|
|
498
|
+
않았다면 리드는 묻지 않은 것이다."""
|
|
499
|
+
if not asked_ids:
|
|
500
|
+
return
|
|
501
|
+
detail = "prompts/lead/okstra-lead-contract.md 'User confirmation before an approval blocker'"
|
|
502
|
+
asked_lines = " ".join(line for _ts, line in by_phase.get("user-confirm", []))
|
|
503
|
+
for row_id in asked_ids:
|
|
504
|
+
if row_id in asked_lines:
|
|
505
|
+
continue
|
|
506
|
+
errors.append(
|
|
507
|
+
f"PROGRESS checkpoint missing: `user-confirm {row_id}` — the final "
|
|
508
|
+
f"report records `{row_id}` as an approval blocker the user was "
|
|
509
|
+
"asked about, but no such line exists in this run's evidence. Either "
|
|
510
|
+
"ask before writing the row, or record what actually happened "
|
|
511
|
+
f"(`deferred-no-interactive-session`) ({detail})."
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
460
515
|
def _phase_mentions_worker(lines: list[tuple[str, str]], needles: list[str]) -> bool:
|
|
461
516
|
return any(needle in _norm(line) for _ts, line in lines for needle in needles)
|
|
462
517
|
|
|
@@ -560,6 +615,7 @@ def _check_progress_checkpoints(
|
|
|
560
615
|
team_state: dict,
|
|
561
616
|
run_dir: Path,
|
|
562
617
|
suffix: str | None,
|
|
618
|
+
report_path: Path,
|
|
563
619
|
errors: list[str],
|
|
564
620
|
) -> None:
|
|
565
621
|
by_phase: dict[str, list[tuple[str, str]]] = {}
|
|
@@ -622,6 +678,9 @@ def _check_progress_checkpoints(
|
|
|
622
678
|
_check_plan_verify_cleanup_checkpoints(
|
|
623
679
|
by_phase, _plan_body_rounds_ran(run_dir, suffix), errors
|
|
624
680
|
)
|
|
681
|
+
_check_user_confirm_checkpoints(
|
|
682
|
+
by_phase, _ids_reported_as_asked(report_path), errors
|
|
683
|
+
)
|
|
625
684
|
|
|
626
685
|
|
|
627
686
|
def _parse_iso(ts: str) -> datetime | None:
|
|
@@ -874,7 +933,9 @@ def validate_session_conformance(
|
|
|
874
933
|
if error:
|
|
875
934
|
result.errors.append(error)
|
|
876
935
|
return result
|
|
877
|
-
_check_progress_checkpoints(
|
|
936
|
+
_check_progress_checkpoints(
|
|
937
|
+
evidence, team_state, run_dir, suffix, report_path, result.errors
|
|
938
|
+
)
|
|
878
939
|
_check_cmux_adapter_read(evidence, team_state, result.errors)
|
|
879
940
|
if task_type == "implementation":
|
|
880
941
|
_check_implementation_entry_guard(
|
package/src/cli-registry.mjs
CHANGED
|
@@ -218,13 +218,6 @@ export const COMMAND_REGISTRY = [
|
|
|
218
218
|
category: "introspection",
|
|
219
219
|
summary: ["Bundle cross-project okstra error logs into a zip"],
|
|
220
220
|
},
|
|
221
|
-
{
|
|
222
|
-
name: "error-issue",
|
|
223
|
-
module: "./commands/inspect/error-issue.mjs",
|
|
224
|
-
export: "run",
|
|
225
|
-
category: "introspection",
|
|
226
|
-
summary: ["Turn okstra run anomalies into GitHub issue candidates"],
|
|
227
|
-
},
|
|
228
221
|
{
|
|
229
222
|
name: "run-audit",
|
|
230
223
|
module: "./commands/inspect/run-audit.mjs",
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Split `agy --output-format stream-json` output for okstra-antigravity-exec.sh.
|
|
3
|
-
|
|
4
|
-
Everything agy emits is appended to the live log, so the log becomes an audit
|
|
5
|
-
trail of what the worker actually did — which tools it called, on which paths,
|
|
6
|
-
for how long. In the default `text` format agy prints only its closing summary,
|
|
7
|
-
so an antigravity dispatch left ~2KB of self-report against codex's ~487KB of
|
|
8
|
-
tool trace, and whether the worker ever opened the evidence it was asked to
|
|
9
|
-
verify could not be checked from outside.
|
|
10
|
-
|
|
11
|
-
stdout stays what the dispatcher's contract expects: the final response text
|
|
12
|
-
only, so the subagent's captured output is unchanged by the format switch.
|
|
13
|
-
"""
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import json
|
|
17
|
-
import sys
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def main() -> int:
|
|
21
|
-
if len(sys.argv) < 2:
|
|
22
|
-
print("usage: okstra-wrapper-agy-stream.py <log-path>", file=sys.stderr)
|
|
23
|
-
return 2
|
|
24
|
-
log_path = sys.argv[1]
|
|
25
|
-
final: str | None = None
|
|
26
|
-
# Lines agy emitted that are not stream-json events. Normally empty; kept so
|
|
27
|
-
# a non-JSON emitter (a stub, an older agy, a crash banner) still reaches
|
|
28
|
-
# stdout instead of being silently swallowed.
|
|
29
|
-
passthrough: list[str] = []
|
|
30
|
-
with open(log_path, "a", encoding="utf-8") as log:
|
|
31
|
-
for line in sys.stdin:
|
|
32
|
-
log.write(line)
|
|
33
|
-
log.flush()
|
|
34
|
-
stripped = line.strip()
|
|
35
|
-
if not stripped:
|
|
36
|
-
continue
|
|
37
|
-
try:
|
|
38
|
-
event = json.loads(stripped)
|
|
39
|
-
except json.JSONDecodeError:
|
|
40
|
-
passthrough.append(line)
|
|
41
|
-
continue
|
|
42
|
-
if isinstance(event, dict) and event.get("event") == "result":
|
|
43
|
-
result = event.get("result")
|
|
44
|
-
if isinstance(result, dict):
|
|
45
|
-
final = result.get("response") or ""
|
|
46
|
-
if final is not None:
|
|
47
|
-
sys.stdout.write(final)
|
|
48
|
-
elif passthrough:
|
|
49
|
-
sys.stdout.writelines(passthrough)
|
|
50
|
-
else:
|
|
51
|
-
print(
|
|
52
|
-
"okstra-wrapper-agy-stream: no result event in agy output — "
|
|
53
|
-
f"see {log_path}",
|
|
54
|
-
file=sys.stderr,
|
|
55
|
-
)
|
|
56
|
-
sys.stdout.flush()
|
|
57
|
-
return 0
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if __name__ == "__main__":
|
|
61
|
-
raise SystemExit(main())
|