okstra 0.162.0 → 0.162.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.162.0",
3
+ "version": "0.162.2",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.162.0",
3
- "builtAt": "2026-08-08T17:44:47.668Z",
2
+ "package": "0.162.2",
3
+ "builtAt": "2026-08-08T18:44:32.842Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -59,14 +59,14 @@ resolve_task_root_for_shortcut() {
59
59
  local task_id="$4"
60
60
 
61
61
  local resolved=""
62
- resolved="$(python3 - "$WORKSPACE_ROOT/scripts" "$project_root" "$project_id" "$task_group" "$task_id" <<'PY'
62
+ resolved="$(python3 - "$OKSTRA_PYTHONPATH" "$project_root" "$project_id" "$task_group" "$task_id" <<'PY'
63
63
  import json, os, sys
64
64
  from pathlib import Path
65
65
 
66
66
  # task root 의 slug 경로 구성은 okstra_ctl.paths.task_dir(SSOT) 에 위임한다.
67
67
  # 과거 이 heredoc 은 slugify 와 `.okstra/tasks/<slug>/<slug>` 구조를 자체
68
68
  # 재구현해 규칙 변경 시 silent drift 위험이 있었다. project-resolver.sh 와
69
- # 동일하게 $WORKSPACE_ROOT/scripts 를 sys.path 에 올려 패키지를 import 한다.
69
+ # 동일하게 $OKSTRA_PYTHONPATH 를 sys.path 에 올려 패키지를 import 한다.
70
70
  sys.path.insert(0, sys.argv[1])
71
71
  from okstra_ctl.paths import task_dir
72
72
 
@@ -176,7 +176,7 @@ autofill_from_manifest() {
176
176
  return 0
177
177
  fi
178
178
 
179
- PYTHONPATH="$WORKSPACE_ROOT/scripts:${PYTHONPATH-}" python3 - "$manifest_project_root" "$manifest_task_root" <<'PY' || true
179
+ PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - "$manifest_project_root" "$manifest_task_root" <<'PY' || true
180
180
  import sys
181
181
  from pathlib import Path
182
182
 
@@ -298,7 +298,7 @@ find_latest_final_report() {
298
298
  }
299
299
 
300
300
  local found=""
301
- found="$(PYTHONPATH="$WORKSPACE_ROOT/scripts:${PYTHONPATH-}" python3 - \
301
+ found="$(PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - \
302
302
  "$task_root" "$task_type_filter" <<'PY'
303
303
  import sys
304
304
  from okstra_ctl.paths import RunRef
@@ -397,7 +397,7 @@ PY
397
397
  local resume_extra_args=()
398
398
  while IFS= read -r -d '' _arg; do
399
399
  resume_extra_args+=("$_arg")
400
- done < <(PYTHONPATH="$WORKSPACE_ROOT/scripts:${PYTHONPATH-}" python3 - \
400
+ done < <(PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 - \
401
401
  "$PROJECT_ROOT" "$TASK_GROUP" "$TASK_ID" "$resolved_type" <<'PY'
402
402
  import sys
403
403
  from okstra_ctl.run_context import latest_run_inputs
@@ -9,7 +9,7 @@
9
9
  # strict/safe 두 종류를 분리한다.
10
10
  resolve_project_root_strict() {
11
11
  local explicit="${1-}"
12
- python3 - "$WORKSPACE_ROOT/scripts" "$explicit" <<'PY'
12
+ python3 - "$OKSTRA_PYTHONPATH" "$explicit" <<'PY'
13
13
  import os, sys
14
14
  sys.path.insert(0, sys.argv[1])
15
15
  from okstra_project import resolve_project_root, ResolverError
@@ -26,7 +26,7 @@ PY
26
26
  # 실패해도 0 으로 종료하고 stdout 은 빈 문자열(autofill 등 best-effort 경로용).
27
27
  resolve_project_root_safe() {
28
28
  local explicit="${1-}"
29
- python3 - "$WORKSPACE_ROOT/scripts" "$explicit" <<'PY'
29
+ python3 - "$OKSTRA_PYTHONPATH" "$explicit" <<'PY'
30
30
  import os, sys
31
31
  sys.path.insert(0, sys.argv[1])
32
32
  from okstra_project import resolve_project_root, ResolverError
@@ -43,7 +43,7 @@ PY
43
43
  upsert_project_json() {
44
44
  local project_root="$1"
45
45
  local project_id="$2"
46
- python3 - "$WORKSPACE_ROOT/scripts" "$project_root" "$project_id" <<'PY'
46
+ python3 - "$OKSTRA_PYTHONPATH" "$project_root" "$project_id" <<'PY'
47
47
  import sys
48
48
  sys.path.insert(0, sys.argv[1])
49
49
  from pathlib import Path
@@ -55,3 +55,31 @@ except ResolverError as exc:
55
55
  sys.exit(1)
56
56
  PY
57
57
  }
58
+
59
+ # `okstra setup` 이 이미 기록한 값을 다시 묻지 않기 위한 자동 채움. 프로젝트
60
+ # 안에서 실행하는 한 project-id 와 project-root 는 물어볼 것이 아니라 읽을 것이다.
61
+ autofill_from_project_json() {
62
+ [[ -n "$PROJECT_ID" && -n "$PROJECT_ROOT" ]] && return 0
63
+ local resolved
64
+ resolved="$(python3 - "$OKSTRA_PYTHONPATH" "$PROJECT_ROOT_OVERRIDE" <<'PY' || true
65
+ import json, sys
66
+ from pathlib import Path
67
+ sys.path.insert(0, sys.argv[1])
68
+ from okstra_project import project_json_path
69
+ from okstra_project.resolver import resolve_project_root
70
+ try:
71
+ root = resolve_project_root(explicit_root=sys.argv[2] or "")
72
+ except Exception:
73
+ sys.exit(0)
74
+ path = project_json_path(Path(root))
75
+ if not path.is_file():
76
+ sys.exit(0)
77
+ data = json.loads(path.read_text(encoding="utf-8"))
78
+ print(f"{data.get('projectId', '')}\t{root}")
79
+ PY
80
+ )"
81
+ [[ -z "$resolved" ]] && return 0
82
+ [[ -z "$PROJECT_ID" ]] && PROJECT_ID="${resolved%%$'\t'*}"
83
+ [[ -z "$PROJECT_ROOT" ]] && PROJECT_ROOT="${resolved##*$'\t'}"
84
+ return 0
85
+ }
@@ -37,8 +37,25 @@ source "$SCRIPT_DIR/lib/okstra/interactive.sh"
37
37
  # shellcheck disable=SC1090
38
38
  source "$SCRIPT_DIR/lib/okstra/project-resolver.sh"
39
39
 
40
+ # The python packages live at `scripts/` in the repo and at `lib/python/` once
41
+ # installed, and this script runs from both. Assuming the repo layout made the
42
+ # installed launcher die on its first import; that went unnoticed because
43
+ # in-host runs reach python through the Node CLI instead of through here.
44
+ OKSTRA_PYTHONPATH=""
45
+ for candidate in "scripts" "python" "lib/python"; do
46
+ if [[ -d "$WORKSPACE_ROOT/$candidate/okstra_project" ]]; then
47
+ OKSTRA_PYTHONPATH="$WORKSPACE_ROOT/$candidate"
48
+ break
49
+ fi
50
+ done
51
+ if [[ -z "$OKSTRA_PYTHONPATH" ]]; then
52
+ printf 'okstra: python package not found under %s — run '"'"'okstra install'"'"'\n' \
53
+ "$WORKSPACE_ROOT" >&2
54
+ exit 1
55
+ fi
56
+
40
57
  okstra_py() {
41
- PYTHONPATH="$WORKSPACE_ROOT/scripts:${PYTHONPATH-}" python3 "$@"
58
+ PYTHONPATH="$OKSTRA_PYTHONPATH:${PYTHONPATH-}" python3 "$@"
42
59
  }
43
60
 
44
61
  parse_cli_arguments "$@"
@@ -53,6 +70,7 @@ if [[ "$RESUME_CLARIFICATION_MODE" == "true" ]]; then
53
70
  run_resume_clarification
54
71
  exit 0
55
72
  fi
73
+ autofill_from_project_json
56
74
  autofill_from_manifest
57
75
  collect_required_arguments
58
76
 
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { createInterface } from "node:readline/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
3
4
 
4
5
  import { runEnsureInstalled } from "../lifecycle/install.mjs";
5
6
  import { runPythonModule } from "../../lib/python-helper.mjs";
@@ -106,9 +107,16 @@ export function buildRunPlan({ args, paths, resolution, runManifestPath = "" })
106
107
  ok: true,
107
108
  kind: "launch-lead",
108
109
  reason: resolution.reason,
109
- // Not render-only: the lead is actually started, so prepare must commit
110
- // the run and emit the launch metadata rather than preview it.
111
- commands: [{ name: "prepare", args: renderArgs(args, resolution), capture: true }],
110
+ // Handed to okstra.sh, which owns input collection and the launch. Only
111
+ // the runtime is decided here; everything else is passed through so the
112
+ // launcher can prompt for whatever is still missing.
113
+ commands: [
114
+ {
115
+ name: "launcher",
116
+ args: ["--lead-runtime", resolution.resolvedRuntime, ...withoutRuntimeSelection(args)],
117
+ capture: false,
118
+ },
119
+ ],
112
120
  };
113
121
  }
114
122
 
@@ -130,68 +138,22 @@ async function runRenderCommand(command, paths) {
130
138
  return result;
131
139
  }
132
140
 
133
- export function parseLaunchMetadata(stdout) {
134
- const marker = "__OKSTRA_LAUNCH__ ";
135
- const line = String(stdout || "")
136
- .split("\n")
137
- .reverse()
138
- .find((item) => item.startsWith(marker));
139
- return line ? JSON.parse(line.slice(marker.length)) : null;
140
- }
141
-
142
- export function withoutLaunchMetadata(stdout) {
143
- return String(stdout || "")
144
- .split("\n")
145
- .filter((line) => !line.startsWith("__OKSTRA_LAUNCH__ "))
146
- .join("\n");
147
- }
148
-
149
- async function confirmSandboxWaiver(launch, args) {
150
- if (!launch.sandboxWaiverNote) return true;
151
- process.stderr.write(`\n${launch.sandboxWaiverNote}\n`);
152
- if (args.includes("--yes")) {
153
- process.stderr.write("Proceeding without the sandbox (--yes).\n\n");
154
- return true;
155
- }
156
- const rl = createInterface({ input: process.stdin, output: process.stderr });
157
- try {
158
- const answer = await rl.question(
159
- `Start ${launch.leadExecutable} without its sandbox? [y/N] `,
160
- );
161
- return answer.trim().toLowerCase() === "y";
162
- } finally {
163
- rl.close();
164
- }
165
- }
166
-
167
- async function launchLead(command, paths, args) {
168
- const prepared = await runPythonModule({
169
- module: "okstra_ctl.run",
170
- args: ["--workspace-root", paths.workspace, ...command.args],
171
- stdio: "capture",
172
- });
173
- process.stdout.write(withoutLaunchMetadata(prepared.stdout));
174
- if (prepared.stderr) process.stderr.write(prepared.stderr);
175
- if (prepared.code !== 0) return prepared.code ?? 1;
176
-
177
- const launch = parseLaunchMetadata(prepared.stdout);
178
- if (!launch) {
179
- process.stderr.write("error: prepare did not emit launch metadata\n");
180
- return 1;
181
- }
182
- if (!(await confirmSandboxWaiver(launch, args))) {
141
+ async function launchLead(command, paths) {
142
+ // okstra.sh already collects missing task inputs, prepares the bundle, asks
143
+ // about a sandbox waiver, and execs the lead. Reimplementing any of that here
144
+ // would give the two front doors separate answers to the same questions —
145
+ // most visibly, this one had no way to ask for a task id and simply forwarded
146
+ // the gap to python's argument parser.
147
+ const launcher = join(paths.bin, "okstra.sh");
148
+ if (!existsSync(launcher)) {
183
149
  process.stderr.write(
184
- "okstra: cancelled. The task bundle is prepared; rerun to resume it.\n",
150
+ `error: launcher not found at ${launcher} run 'okstra install'\n`,
185
151
  );
186
152
  return 1;
187
153
  }
188
- const [executable, ...rest] = launch.launchArgv;
189
- const started = spawnSync(executable, rest, {
190
- cwd: launch.projectRoot,
191
- stdio: "inherit",
192
- });
154
+ const started = spawnSync(launcher, command.args, { stdio: "inherit" });
193
155
  if (started.error) {
194
- process.stderr.write(`error: could not start ${executable}: ${started.error.message}\n`);
156
+ process.stderr.write(`error: could not start the launcher: ${started.error.message}\n`);
195
157
  return 1;
196
158
  }
197
159
  return started.status ?? 1;
@@ -231,7 +193,7 @@ export async function run(args) {
231
193
  if (ensureCode !== 0) return ensureCode;
232
194
 
233
195
  if (firstPlan.kind === "launch-lead") {
234
- return await launchLead(firstPlan.commands[0], paths, args);
196
+ return await launchLead(firstPlan.commands[0], paths);
235
197
  }
236
198
 
237
199
  const render = firstPlan.commands[0];