okstra 0.170.2 → 0.170.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/docs/cli.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +23 -0
- package/runtime/python/okstra_ctl/domain/worker_stream.py +21 -1
- package/runtime/python/okstra_ctl/run.py +15 -3
- package/runtime/python/okstra_ctl/wizard.py +8 -1
package/docs/cli.md
CHANGED
|
@@ -742,7 +742,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
742
742
|
| `okstra doctor [--runtime claude-code\|codex\|antigravity\|external\|all] [--phase <phase>] [--json]` | Diagnose the runtime, Python imports, and skill/agent installation. The `codex`, `antigravity`, and `external` runtimes omit Claude skill checks. `--phase` adds readiness checks for `implementation`, `final-verification`, `release-handoff`, or `improvement-discovery` |
|
|
743
743
|
| `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project |
|
|
744
744
|
| `okstra check-project [--json]` | Verify that the current project is registered |
|
|
745
|
-
| `okstra preflight [--runtime <name>] [--cwd <dir>] [--json]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, `check-project`, and host-specific `runtimeReadiness` into one JSON response. A `claude-code` host checks project workspace trust
|
|
745
|
+
| `okstra preflight [--runtime <name>] [--cwd <dir>] [--json]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, `check-project`, and host-specific `runtimeReadiness` into one JSON response. A `claude-code` host checks project workspace trust. A `codex` current-session host verifies write access to `~/.okstra/worktrees/registry.lock`; a sandbox denial blocks before the wizard with the `switch-codex-to-full-access-and-rerun` action. `antigravity` and `external` hosts return ready without reading Claude Code state. Step 0 of every project-scoped skill converges on this command |
|
|
746
746
|
| `okstra convergence seed --groups <path> --work-state <path> --final-state <path> --migration-dir <dir> [--restart-from-round0]` | Create, resume, reuse, or explicitly recover deterministic convergence state |
|
|
747
747
|
| `okstra convergence plan-round --work-state <path> --plan <path>` | Persist the next roster-aware dispatch plan without mutating working state |
|
|
748
748
|
| `okstra convergence collect-results --plan <round-plan.json> --mode <adversarial\|collaborative> --result <worker>=<path>… --dispatch <worker>=<status>:<durationMs>… --output <round-results.json>` | Read one round's worker responses into the `apply-round --results` shape. `--mode` picks the verdict vocabulary — the adversarial prompt answers `REFUTED` / `SURVIVES` / `SURVIVES-WITH-CAVEAT` / `UNVERIFIABLE`, which this maps to `disagree` / `agree` / `supplement` / `unverifiable`, and copies `**Basis**` into `disagreeBasis`. `--dispatch` supplies the terminal status and duration, which live in the dispatch rather than the response; a worker that never returned gets a `--dispatch` and no `--result`. Exits 2 on a dispatched finding with no verdict, a verdict for a finding the plan did not dispatch to that worker, a planned worker with no recorded outcome, or a vote with no explanation |
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Bundled Codex host strategy."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import os
|
|
4
5
|
import shutil
|
|
5
6
|
from collections.abc import Callable
|
|
6
7
|
from pathlib import Path
|
|
@@ -35,6 +36,27 @@ DESCRIPTOR = HostDescriptor(
|
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
def _okstra_home_write_checks(context) -> tuple[dict[str, object], ...]:
|
|
40
|
+
if context.entry_mode != "current-session":
|
|
41
|
+
return ({"id": "okstra-home-write", "status": "not-applicable"},)
|
|
42
|
+
|
|
43
|
+
home_dir = Path(os.environ.get("OKSTRA_PROBE_HOME_DIR", str(Path.home())))
|
|
44
|
+
registry_lock = home_dir / ".okstra" / "worktrees" / "registry.lock"
|
|
45
|
+
try:
|
|
46
|
+
registry_lock.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
if not registry_lock.exists():
|
|
48
|
+
registry_lock.touch()
|
|
49
|
+
with registry_lock.open("r+"):
|
|
50
|
+
pass
|
|
51
|
+
except PermissionError:
|
|
52
|
+
return ({
|
|
53
|
+
"id": "okstra-home-write",
|
|
54
|
+
"status": "required",
|
|
55
|
+
"action": "switch-codex-to-full-access-and-rerun",
|
|
56
|
+
},)
|
|
57
|
+
return ({"id": "okstra-home-write", "status": "accepted"},)
|
|
58
|
+
|
|
59
|
+
|
|
38
60
|
def create_adapter(
|
|
39
61
|
*,
|
|
40
62
|
executable_finder: Callable[[str], str | None] = shutil.which,
|
|
@@ -59,6 +81,7 @@ def create_adapter(
|
|
|
59
81
|
supported_functions=INTERACTION_FUNCTIONS,
|
|
60
82
|
detector=no_automatic_claim,
|
|
61
83
|
provider_registry=provider_registry,
|
|
84
|
+
readiness_probe=_okstra_home_write_checks,
|
|
62
85
|
host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
|
|
63
86
|
DESCRIPTOR.id,
|
|
64
87
|
DESCRIPTOR.native_provider_id,
|
|
@@ -255,7 +255,27 @@ def _content_blocks(event: Mapping[str, Any]) -> list[Mapping[str, Any]]:
|
|
|
255
255
|
return [block for block in content if isinstance(block, Mapping)]
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
# A tool call's detail identifies itself at both ends and neither end alone. A
|
|
259
|
+
# path's run-directory prefix is shared by every file a worker touches, so the
|
|
260
|
+
# leaf is what tells two calls apart; a command's program name is at the front.
|
|
261
|
+
# Cutting the tail served only the second, and on a project whose run directory
|
|
262
|
+
# alone is 195 characters it rendered every file as the same visible string —
|
|
263
|
+
# `→ Read: /Volumes/…/tasks/analysis-…` for all of them — leaving the reader
|
|
264
|
+
# unable to tell one call from another. The head holds the tool name plus enough
|
|
265
|
+
# of the detail to read a command; the rest of the budget goes to the tail.
|
|
266
|
+
_HEAD_BUDGET = 32
|
|
267
|
+
|
|
268
|
+
|
|
258
269
|
def _truncate(line: str, limit: int | None) -> str:
|
|
270
|
+
"""Fold the middle, not the end.
|
|
271
|
+
|
|
272
|
+
The end is what distinguishes one line from the next, so it is the part the
|
|
273
|
+
screen must keep. Falls back to a tail cut only when the limit is too small
|
|
274
|
+
to hold a head, an ellipsis, and any tail at all.
|
|
275
|
+
"""
|
|
259
276
|
if limit is None or len(line) <= limit:
|
|
260
277
|
return line
|
|
261
|
-
|
|
278
|
+
tail_budget = limit - _HEAD_BUDGET - 1
|
|
279
|
+
if tail_budget < 1:
|
|
280
|
+
return line[: limit - 1] + "…"
|
|
281
|
+
return line[:_HEAD_BUDGET] + "…" + line[-tail_budget:]
|
|
@@ -1418,10 +1418,22 @@ class _ModelBindings:
|
|
|
1418
1418
|
invocation_assignments: dict[str, dict[str, object]]
|
|
1419
1419
|
|
|
1420
1420
|
|
|
1421
|
-
def recommended_role_models() -> dict[str, str]:
|
|
1421
|
+
def recommended_role_models(*, lead_provider: str = "") -> dict[str, str]:
|
|
1422
1422
|
"""역할 → 추천 모델 display 값 (env override 반영). prepare 의 모델 해소와
|
|
1423
|
-
wizard 의 안내 표기가 공유하는 단일 기준점.
|
|
1424
|
-
|
|
1423
|
+
wizard 의 안내 표기가 공유하는 단일 기준점.
|
|
1424
|
+
|
|
1425
|
+
lead 는 호스트가 provider 를 정한다 — codex 호스트의 in-session lead 는
|
|
1426
|
+
codex 이고 다른 provider 요청은 거부된다(`resolve_lead_provider`). 그래서
|
|
1427
|
+
`lead_provider` 를 받아 그 provider 의 기본값으로 해소한다. 받지 않으면
|
|
1428
|
+
role 별 레거시 기본값(claude 계열)으로 떨어지는데, 그 값을 codex 호스트
|
|
1429
|
+
화면에 그대로 쓰면 안내는 `opus` 인데 prepare 는 `gpt-5.6-sol` 을 배정한다.
|
|
1430
|
+
"""
|
|
1431
|
+
lead_default = _default(
|
|
1432
|
+
"OKSTRA_DEFAULT_LEAD_MODEL",
|
|
1433
|
+
provider_default_model(lead_provider, "lead")
|
|
1434
|
+
if lead_provider
|
|
1435
|
+
else default_model("lead"),
|
|
1436
|
+
)
|
|
1425
1437
|
recommendations = {
|
|
1426
1438
|
"lead": lead_default,
|
|
1427
1439
|
"claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", default_model("claude")),
|
|
@@ -3445,7 +3445,14 @@ def _role_model_lines(state: WizardState) -> str:
|
|
|
3445
3445
|
"""이번 run 에서 실제로 모델을 고르게 되는 역할만, 추천 모델과 함께 나열한다.
|
|
3446
3446
|
뒤따르는 *_model 단계의 등장 조건과 1:1 로 맞춰 안내와 실제 화면이 어긋나지
|
|
3447
3447
|
않게 한다 (그래서 분석에 참여하지 않는 antigravity 는 executor 일 때만 나온다)."""
|
|
3448
|
-
|
|
3448
|
+
# 이 화면의 lead 줄은 뒤따르는 lead-model picker 와 같은 provider 를
|
|
3449
|
+
# 봐야 한다 — picker 는 호스트의 native provider 만 제시한다.
|
|
3450
|
+
rec = recommended_role_models(
|
|
3451
|
+
lead_provider=default_host_registry()
|
|
3452
|
+
.resolve(state.host_runtime)
|
|
3453
|
+
.descriptor.native_provider_id
|
|
3454
|
+
or "",
|
|
3455
|
+
)
|
|
3449
3456
|
roster = _resolved_roster(state)
|
|
3450
3457
|
impl = state.task_type == "implementation"
|
|
3451
3458
|
|