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
package/runtime/BUILD.json
CHANGED
|
@@ -59,14 +59,14 @@ resolve_task_root_for_shortcut() {
|
|
|
59
59
|
local task_id="$4"
|
|
60
60
|
|
|
61
61
|
local resolved=""
|
|
62
|
-
resolved="$(python3 - "$
|
|
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
|
-
# 동일하게 $
|
|
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="$
|
|
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="$
|
|
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="$
|
|
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 - "$
|
|
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 - "$
|
|
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 - "$
|
|
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
|
+
}
|
package/runtime/bin/okstra.sh
CHANGED
|
@@ -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="$
|
|
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 {
|
|
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
|
-
//
|
|
110
|
-
// the
|
|
111
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
150
|
+
`error: launcher not found at ${launcher} — run 'okstra install'\n`,
|
|
185
151
|
);
|
|
186
152
|
return 1;
|
|
187
153
|
}
|
|
188
|
-
const
|
|
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
|
|
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
|
|
196
|
+
return await launchLead(firstPlan.commands[0], paths);
|
|
235
197
|
}
|
|
236
198
|
|
|
237
199
|
const render = firstPlan.commands[0];
|