okstra 0.209.1 → 0.209.3
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/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/python/okstra_ctl/agent/activity.py +5 -0
- package/runtime/python/okstra_ctl/dispatch_checkpoints.py +6 -4
- package/runtime/python/okstra_ctl/dispatch_core.py +12 -6
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +35 -24
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -33,7 +33,7 @@ Keep the selected host relay's dispatch permission guidance when calling `okstra
|
|
|
33
33
|
| `await_workers` | Run `okstra team await --project-root <root> --run-manifest <path>` through the host's asynchronous shell facility. |
|
|
34
34
|
| `redispatch_worker` | Create the core-specified fresh jobs file and dispatch it with a new `dispatchKind`; never reuse a live worker conversation. |
|
|
35
35
|
| `shutdown_workers` | Run `okstra team teardown --project-root <root> --run-manifest <path>` only after the user-approved cleanup gate. |
|
|
36
|
-
| `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Use `okstra lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id>` for a checkpoint the command performing that step does not record itself (the lead contract "Progress reporting" lists the ones `team dispatch`, `team await`, `team reclaim`, `worker-dispatch` and `report-finalize` record; emit the `PROGRESS:` lines they print instead) and `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> ...` for an activity record; both resolve the ledger path from the run manifest. Emit the matching `PROGRESS:` line — the command prints it as `progressLine` — and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
|
|
36
|
+
| `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Use `okstra lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id>` for a checkpoint the command performing that step does not record itself (the lead contract "Progress reporting" lists the ones `team dispatch`, `team await`, `team reclaim`, `worker-dispatch` and `report-finalize` record; emit the `PROGRESS:` lines they print instead) and `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> ...` for an activity record — never for the `worker-dispatched` / `worker-completed` rows `team dispatch` and `team await` append themselves; both resolve the ledger path from the run manifest. Emit the matching `PROGRESS:` line — the command prints it as `progressLine` — and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
|
|
37
37
|
| `collect_usage` | Collect artifact/CLI-log-backed usage through the existing Okstra token-usage path; never substitute another runtime's session log. |
|
|
38
38
|
|
|
39
39
|
An `implementation` run calls `dispatch_worker` twice: once for the Executor, then — after `await_workers` settles it — once for the verifiers. That second call's `--workers` list must omit the Executor's worker ID: it is materialized as the Executor on every dispatch, so a batch still carrying it is refused again. A verifier started beside the Executor observes base HEAD instead of the stage diff, so a single batch holding both is refused by `scripts/okstra_ctl/dispatch_core.py` `_validate_implementation_phase_order`, `--dry-run` included.
|
|
@@ -66,7 +66,7 @@ Every `okstra` command the lead documents cite, grouped by phase, each spelled w
|
|
|
66
66
|
| Command | Use when | Procedure |
|
|
67
67
|
|---|---|---|
|
|
68
68
|
| `okstra lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id>` | Every `PROGRESS` checkpoint the command performing that step does not record itself; print the `progressLine` it returns. `--phase` accepts only the listed phase ids; use `--worker`, `--field NAME=VALUE`, and `--detail <text>` for the rest | this contract "Progress reporting" |
|
|
69
|
-
| `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <agent> --outcome <outcome> --summary <text>` | Before each activity boundary when the run manifest declares `activityContractVersion: 1` | launch prompt "Progress reporting" |
|
|
69
|
+
| `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <agent> --outcome <outcome> --summary <text>` | Before each activity boundary when the run manifest declares `activityContractVersion: 1`, except a `worker-dispatched` / `worker-completed` row the dispatching command already appended (see "Progress reporting") | launch prompt "Progress reporting" |
|
|
70
70
|
| `okstra error-log append-observed --out <errors-path> --task-key <key> --phase <phase> --agent <provider-worker> --agent-role <role> --model <model> --error-type <type> --command-kind <kind> --command <command> --message <text>` | Record an observed failure. `--agent` takes the provider worker id (`codex-worker`); an execution label goes in `--execution-label` | this contract "Errors log path wiring"; `team-contract` "Worker error-log command" |
|
|
71
71
|
| `okstra approval-decision open --ledger <path> --task-key <key> --task-type <type> --run-seq <seq> --clarification-id <C-NNN> --ticket-id <id> --statement <text> --expected-form <form> --classification <class> --origin <origin> --user-confirmation <text> --unblock-condition <text> --recommended-disposition <disposition> [--supersedes <C-NNN>]` | Open an approval blocker after the user confirmed it | this contract "User confirmation before an approval blocker" |
|
|
72
72
|
| `okstra approval-decision carry --ledger <path> --clarification-id <C-NNN> --from-responses <path>` | Carry a decision answered in an earlier run | `report-writer` "Role-owned inputs" |
|
|
@@ -166,6 +166,9 @@ def _activity_row(event: LeadEvent) -> dict[str, Any]:
|
|
|
166
166
|
from okstra_ctl.execution_identity import stored_identity
|
|
167
167
|
|
|
168
168
|
row = {key: details[key] for key in ACTIVITY_FIELDS}
|
|
169
|
+
if row["resultPath"] is None:
|
|
170
|
+
# 0.209.1 의 team dispatch/await 가 null 로 기록한 행. 원장은 고치지 않고 읽을 때 맞춘다.
|
|
171
|
+
row["resultPath"] = ""
|
|
169
172
|
if "clarificationRefs" in details:
|
|
170
173
|
refs = details["clarificationRefs"]
|
|
171
174
|
if not isinstance(refs, list) or any(
|
|
@@ -201,6 +204,8 @@ def record_activity(
|
|
|
201
204
|
raise ActivityProjectionError(
|
|
202
205
|
"run manifest does not declare activityContractVersion 1"
|
|
203
206
|
)
|
|
207
|
+
if not isinstance(details.get("resultPath"), str):
|
|
208
|
+
raise ActivityProjectionError("activity resultPath must be a string")
|
|
204
209
|
_validate_activity_ownership(project_root, manifest, details)
|
|
205
210
|
event = LeadEvent(
|
|
206
211
|
event_type="activity",
|
|
@@ -74,7 +74,8 @@ def record_dispatch_checkpoints(
|
|
|
74
74
|
project_root, run_manifest, source=source,
|
|
75
75
|
kind="worker-dispatched", agent=job.worker_id,
|
|
76
76
|
summary=f"Dispatched {job.worker_id} as an initial worker",
|
|
77
|
-
result_path=
|
|
77
|
+
result_path=_project_relative(project_root, str(job.worker_result_path)),
|
|
78
|
+
outcome="pending",
|
|
78
79
|
))
|
|
79
80
|
dispatches_writer = any(job.worker_id == REPORT_WRITER_WORKER_ID for job in jobs)
|
|
80
81
|
writer_dispatched_before = bool(
|
|
@@ -165,9 +166,10 @@ def _declares_activity_contract(
|
|
|
165
166
|
return manifest.get("activityContractVersion") == 1
|
|
166
167
|
|
|
167
168
|
|
|
168
|
-
def _project_relative(project_root: Path, value: Any) -> str
|
|
169
|
+
def _project_relative(project_root: Path, value: Any) -> str:
|
|
170
|
+
# 보고서 스키마의 resultPath 는 문자열이다. 리드 CLI 도 경로가 없으면 "" 를 쓴다.
|
|
169
171
|
if not isinstance(value, str) or not value:
|
|
170
|
-
return
|
|
172
|
+
return ""
|
|
171
173
|
path = Path(value)
|
|
172
174
|
root = project_root.resolve()
|
|
173
175
|
return str(path.relative_to(root)) if path.is_relative_to(root) else value
|
|
@@ -181,7 +183,7 @@ def _record_worker_activity(
|
|
|
181
183
|
kind: str,
|
|
182
184
|
agent: str,
|
|
183
185
|
summary: str,
|
|
184
|
-
result_path: str
|
|
186
|
+
result_path: str,
|
|
185
187
|
outcome: str,
|
|
186
188
|
) -> str | None:
|
|
187
189
|
"""실패 처리는 `record_checkpoint` 와 같다: stderr 에만 남기고, 누락은 검증기가 보고한다."""
|
|
@@ -115,6 +115,7 @@ from .seeding import installed_version
|
|
|
115
115
|
from .worker_audit_ledger import (
|
|
116
116
|
check_worker_results_audit,
|
|
117
117
|
parse_worker_result_name,
|
|
118
|
+
result_file_audit_failures,
|
|
118
119
|
)
|
|
119
120
|
from .worker_artifacts import worker_provider_id
|
|
120
121
|
from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
@@ -122,7 +123,7 @@ from .worker_prompt_headers import (
|
|
|
122
123
|
WorkerPromptHeaderError,
|
|
123
124
|
resolve_errors_log_path,
|
|
124
125
|
)
|
|
125
|
-
from .worker_artifact_paths import audit_sidecar_rel
|
|
126
|
+
from .worker_artifact_paths import WorkerArtifactPathError, audit_sidecar_rel
|
|
126
127
|
from .wrapper_status import (
|
|
127
128
|
log_path_for_prompt,
|
|
128
129
|
mutation_snapshot_path_for_prompt,
|
|
@@ -2732,11 +2733,16 @@ def _audit_sidecar_failures(job: WorkerJob) -> tuple[str, ...]:
|
|
|
2732
2733
|
"""
|
|
2733
2734
|
parsed = parse_worker_result_name(job.worker_result_path.name)
|
|
2734
2735
|
if parsed is None:
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2736
|
+
# reverify·critic 결과는 `<role>-worker-reverify-r<N>-…` 처럼 run 단위
|
|
2737
|
+
# 스캔이 못 읽는 이름이다. 사이드카 이름은 같은 규칙으로 나오므로 이 파일만 본다.
|
|
2738
|
+
try:
|
|
2739
|
+
audit_path = Path(audit_sidecar_rel(str(job.worker_result_path)))
|
|
2740
|
+
except WorkerArtifactPathError:
|
|
2741
|
+
return (
|
|
2742
|
+
f"worker result `{job.worker_result_path.name}` has no "
|
|
2743
|
+
f"`-worker-` token, so the audit-sidecar contract could not be checked",
|
|
2744
|
+
)
|
|
2745
|
+
return tuple(result_file_audit_failures(job.worker_result_path, audit_path))
|
|
2740
2746
|
return tuple(
|
|
2741
2747
|
check_worker_results_audit(
|
|
2742
2748
|
job.worker_result_path.parent.parent,
|
|
@@ -344,6 +344,36 @@ def worker_result_files(
|
|
|
344
344
|
yield path, match.worker_role, match.seq
|
|
345
345
|
|
|
346
346
|
|
|
347
|
+
def result_file_audit_failures(result_path: Path, audit_path: Path) -> list[str]:
|
|
348
|
+
"""The blocking audit-sidecar findings for one worker result and its sidecar."""
|
|
349
|
+
rel = result_path.name
|
|
350
|
+
try:
|
|
351
|
+
content = result_path.read_text()
|
|
352
|
+
except OSError as exc:
|
|
353
|
+
return [f"worker-results file unreadable: {rel} ({exc})"]
|
|
354
|
+
failures: list[str] = []
|
|
355
|
+
if READING_CONFIRMATION_HEADING_RE.search(content) is not None:
|
|
356
|
+
failures.append(
|
|
357
|
+
f"worker-results file `{rel}` contains a `## 0. Reading "
|
|
358
|
+
f"Confirmation` heading — that block moved to the audit "
|
|
359
|
+
f"sidecar (`{audit_path.name}`). "
|
|
360
|
+
f"Remove the §0 heading + body from the main file and "
|
|
361
|
+
f"write a fresh sidecar."
|
|
362
|
+
)
|
|
363
|
+
if not audit_path.exists():
|
|
364
|
+
worker_role = rel.partition("-worker-")[0] + "-worker"
|
|
365
|
+
failures.append(
|
|
366
|
+
f"worker `{worker_role}` produced `{rel}` but no audit sidecar "
|
|
367
|
+
f"at `{audit_path.name}` — the sidecar must carry the Reading "
|
|
368
|
+
f"Confirmation block (one short line per input file). Workers "
|
|
369
|
+
f"write this in the same step as the main worker-results file."
|
|
370
|
+
)
|
|
371
|
+
return failures
|
|
372
|
+
_, command_failures = read_evidence_commands(audit_path)
|
|
373
|
+
failures.extend(command_failures)
|
|
374
|
+
return failures
|
|
375
|
+
|
|
376
|
+
|
|
347
377
|
def check_worker_results_audit(
|
|
348
378
|
run_dir: Path,
|
|
349
379
|
task_type: str,
|
|
@@ -399,36 +429,17 @@ def worker_results_audit_findings(
|
|
|
399
429
|
|
|
400
430
|
for path, worker_role, result_seq in worker_result_files(run_dir, task_type, seq, worker):
|
|
401
431
|
rel = path.name
|
|
402
|
-
try:
|
|
403
|
-
content = path.read_text()
|
|
404
|
-
except OSError as exc:
|
|
405
|
-
failures.append(f"worker-results file unreadable: {rel} ({exc})")
|
|
406
|
-
continue
|
|
407
|
-
|
|
408
|
-
if READING_CONFIRMATION_HEADING_RE.search(content) is not None:
|
|
409
|
-
failures.append(
|
|
410
|
-
f"worker-results file `{rel}` contains a `## 0. Reading "
|
|
411
|
-
f"Confirmation` heading — that block moved to the audit "
|
|
412
|
-
f"sidecar (`{worker_role}-audit-{task_type}-{result_seq}.md`). "
|
|
413
|
-
f"Remove the §0 heading + body from the main file and "
|
|
414
|
-
f"write a fresh sidecar."
|
|
415
|
-
)
|
|
416
|
-
|
|
417
432
|
audit_path = (
|
|
418
433
|
run_dir / "worker-results"
|
|
419
434
|
/ f"{worker_role}-audit-{task_type}-{result_seq}.md"
|
|
420
435
|
)
|
|
436
|
+
failures.extend(result_file_audit_failures(path, audit_path))
|
|
437
|
+
try:
|
|
438
|
+
content = path.read_text()
|
|
439
|
+
except OSError:
|
|
440
|
+
continue
|
|
421
441
|
if not audit_path.exists():
|
|
422
|
-
failures.append(
|
|
423
|
-
f"worker `{worker_role}` produced `{rel}` but no audit sidecar "
|
|
424
|
-
f"at `{audit_path.name}` — the sidecar must carry the Reading "
|
|
425
|
-
f"Confirmation block (one short line per input file). Workers "
|
|
426
|
-
f"write this in the same step as the main worker-results file."
|
|
427
|
-
)
|
|
428
442
|
continue
|
|
429
|
-
|
|
430
|
-
_, command_failures = read_evidence_commands(audit_path)
|
|
431
|
-
failures.extend(command_failures)
|
|
432
443
|
advisories.extend(_evidence_read_ledger_failures(
|
|
433
444
|
run_dir=run_dir,
|
|
434
445
|
worker_role=worker_role,
|