okstra 0.163.2 → 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 +8 -6
- package/docs/architecture.md +24 -15
- package/docs/cli.md +15 -8
- 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 +22 -14
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +9 -7
- package/runtime/agents/workers/claude-worker.md +1 -0
- 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/lib/okstra/cli.sh +5 -0
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/usage.sh +5 -5
- 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/bin/okstra.sh +87 -91
- package/runtime/prompts/lead/adapters/cmux.md +2 -2
- package/runtime/prompts/lead/convergence.md +36 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +24 -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 +6 -4
- package/runtime/python/okstra_ctl/adapters/accounting/__init__.py +11 -0
- package/runtime/python/okstra_ctl/adapters/accounting/claude_jsonl.py +17 -0
- package/runtime/python/okstra_ctl/adapters/accounting/cli_artifact.py +17 -0
- package/runtime/python/okstra_ctl/adapters/accounting/unavailable.py +19 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +92 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +54 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +68 -0
- package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +60 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/manifest.json +1 -0
- package/runtime/{prompts/lead/adapters/antigravity.md → python/okstra_ctl/adapters/hosts/antigravity/relay.md} +52 -0
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +292 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +120 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -0
- package/runtime/{prompts/lead/adapters/claude-code.md → python/okstra_ctl/adapters/hosts/claude-code/relay.md} +112 -1
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +60 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/manifest.json +1 -0
- package/runtime/{prompts/lead/adapters/codex.md → python/okstra_ctl/adapters/hosts/codex/relay.md} +52 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +72 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/manifest.json +1 -0
- package/runtime/{prompts/lead/adapters/external.md → python/okstra_ctl/adapters/hosts/external/relay.md} +53 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +63 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +90 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +63 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +90 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +183 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +110 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +84 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +76 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +80 -0
- package/runtime/python/okstra_ctl/adapters/providers/kimi/manifest.json +1 -0
- package/runtime/python/okstra_ctl/application/__init__.py +1 -0
- package/runtime/python/okstra_ctl/application/advance_wizard.py +25 -0
- package/runtime/python/okstra_ctl/application/collect_usage.py +15 -0
- package/runtime/python/okstra_ctl/application/dispatch_assignments.py +15 -0
- package/runtime/python/okstra_ctl/application/resolve_assignment.py +93 -0
- package/runtime/python/okstra_ctl/application/resume_run.py +21 -0
- package/runtime/python/okstra_ctl/application/start_run.py +21 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +49 -826
- package/runtime/python/okstra_ctl/dispatch_core.py +244 -29
- package/runtime/python/okstra_ctl/dispatch_state.py +27 -0
- package/runtime/python/okstra_ctl/domain/__init__.py +34 -0
- package/runtime/python/okstra_ctl/domain/host.py +100 -0
- package/runtime/python/okstra_ctl/domain/provider.py +70 -0
- package/runtime/python/okstra_ctl/domain/wizard/__init__.py +19 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +140 -0
- 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/entrypoints/__init__.py +1 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +334 -0
- package/runtime/python/okstra_ctl/incremental_scope.py +16 -4
- package/runtime/python/okstra_ctl/models.py +54 -269
- package/runtime/python/okstra_ctl/ports/__init__.py +15 -0
- package/runtime/python/okstra_ctl/ports/host.py +32 -0
- package/runtime/python/okstra_ctl/ports/interaction.py +15 -0
- package/runtime/python/okstra_ctl/ports/lead_session.py +25 -0
- package/runtime/python/okstra_ctl/ports/usage_accounting.py +23 -0
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +32 -0
- package/runtime/python/okstra_ctl/registry/__init__.py +13 -0
- package/runtime/python/okstra_ctl/registry/factory_loader.py +32 -0
- package/runtime/python/okstra_ctl/registry/host_discovery.py +124 -0
- package/runtime/python/okstra_ctl/registry/host_registry.py +365 -0
- package/runtime/python/okstra_ctl/registry/provider_registry.py +149 -0
- package/runtime/python/okstra_ctl/render.py +145 -47
- 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/run.py +80 -58
- package/runtime/python/okstra_ctl/session.py +1 -1
- package/runtime/python/okstra_ctl/stage_citations.py +52 -15
- package/runtime/python/okstra_ctl/team.py +44 -32
- package/runtime/python/okstra_ctl/user_response.py +45 -29
- package/runtime/python/okstra_ctl/wizard.py +175 -73
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +29 -4
- 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 +42 -7
- 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 +4040 -1066
- package/runtime/schemas/final-report-v2.0.schema.json +5673 -1412
- 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 +74 -29
- package/runtime/skills/okstra-user-response/SKILL.md +15 -5
- package/runtime/templates/implementation-worker-preamble.md +1 -1
- package/runtime/templates/report-writer-prompt-preamble.md +1 -0
- package/runtime/templates/reports/final-report.template.md +3 -3
- 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 +32 -7
- package/runtime/templates/reports/html/i18n/ko.json +32 -7
- 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/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +17 -22
- package/runtime/validators/validate-report-views.py +0 -39
- package/runtime/validators/validate-run.py +96 -14
- package/runtime/validators/validate_session_conformance.py +69 -3
- package/src/cli-registry.mjs +0 -7
- package/src/commands/execute/render-bundle.mjs +4 -4
- package/src/commands/execute/run.mjs +8 -25
- package/src/commands/execute/wizard.mjs +33 -13
- package/src/commands/lifecycle/doctor.mjs +10 -10
- package/src/commands/lifecycle/install.mjs +53 -30
- package/src/commands/lifecycle/preflight.mjs +14 -4
- package/src/lib/host-registry-client.mjs +176 -0
- package/src/lib/runtime-manifest.mjs +6 -8
- 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/python/okstra_ctl/lead_runtime.py +0 -115
- package/runtime/python/okstra_ctl/runner_resolution.py +0 -103
- package/runtime/skills/okstra-inspect/facets/error-issue.md +0 -77
- package/src/commands/inspect/error-issue.mjs +0 -27
- package/src/lib/runtime-readiness.mjs +0 -90
- package/src/lib/runtime-resolver.mjs +0 -123
|
@@ -6,7 +6,7 @@ import { getPackageRoot } from "../../lib/version.mjs";
|
|
|
6
6
|
import { resolvePaths } from "../../lib/paths.mjs";
|
|
7
7
|
import { fileExists } from "../../lib/proc.mjs";
|
|
8
8
|
import { buildRuntimeManifest, RUNTIMES_MANIFEST_REL } from "../../lib/runtime-manifest.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { normalizeHostRequest, resolveRuntime } from "../../lib/host-registry-client.mjs";
|
|
10
10
|
import { OBSOLETE_SKILL_NAMES, USER_SKILL_NAMES } from "../../lib/skill-catalog.mjs";
|
|
11
11
|
import {
|
|
12
12
|
AGENTS_MANIFEST_REL,
|
|
@@ -52,10 +52,6 @@ const BIN_ENTRYPOINTS = [
|
|
|
52
52
|
// depend on. Intentionally an exact allowlist, not a directory wildcard.
|
|
53
53
|
const REQUIRED_PROMPT_RESOURCE_FILES = Object.freeze([
|
|
54
54
|
["prompts", "lead", "okstra-lead-contract.md"],
|
|
55
|
-
["prompts", "lead", "adapters", "claude-code.md"],
|
|
56
|
-
["prompts", "lead", "adapters", "codex.md"],
|
|
57
|
-
["prompts", "lead", "adapters", "antigravity.md"],
|
|
58
|
-
["prompts", "lead", "adapters", "external.md"],
|
|
59
55
|
["prompts", "lead", "context-loader.md"],
|
|
60
56
|
["prompts", "lead", "team-contract.md"],
|
|
61
57
|
["prompts", "lead", "convergence.md"],
|
|
@@ -77,7 +73,7 @@ const INSTALL_USAGE = `okstra install — install runtime into ~/.okstra
|
|
|
77
73
|
|
|
78
74
|
Usage:
|
|
79
75
|
okstra install Install/update runtime (copy mode)
|
|
80
|
-
okstra install --runtime <
|
|
76
|
+
okstra install --runtime <host-id|alias|all>
|
|
81
77
|
okstra install --link <p> Dev only: symlink ~/.okstra/{lib/python,bin}
|
|
82
78
|
to the source repo at <p>. agents/ resolves
|
|
83
79
|
to <p>/agents. Not advertised to end users.
|
|
@@ -128,19 +124,14 @@ const ENSURE_USAGE = `okstra ensure-installed — idempotent install check
|
|
|
128
124
|
|
|
129
125
|
Usage:
|
|
130
126
|
okstra ensure-installed Verify install; reinstall if version/files drift
|
|
131
|
-
okstra ensure-installed --runtime <
|
|
127
|
+
okstra ensure-installed --runtime <host-id|alias|all>
|
|
132
128
|
okstra ensure-installed -q Same, but suppress success output
|
|
133
129
|
|
|
134
130
|
Skills call this on every run. Returns 0 quickly when the install is fresh.
|
|
135
131
|
`;
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
export function runtimeIncludesClaudeAssets(runtime, resolution = null) {
|
|
142
|
-
if (runtime === "claude-code" || runtime === "all") return true;
|
|
143
|
-
return runtime === "auto" && resolution?.resolvedRuntime === "claude-code";
|
|
133
|
+
export function runtimeIncludesClaudeAssets(resolution) {
|
|
134
|
+
return resolution?.installTargets?.includes("claude") === true;
|
|
144
135
|
}
|
|
145
136
|
|
|
146
137
|
function readRuntimeFlag(args, index) {
|
|
@@ -150,21 +141,17 @@ function readRuntimeFlag(args, index) {
|
|
|
150
141
|
}
|
|
151
142
|
const next = args[index + 1];
|
|
152
143
|
if (!next || next.startsWith("--")) {
|
|
153
|
-
throw new Error("--runtime requires
|
|
144
|
+
throw new Error("--runtime requires a host ID, alias, or all");
|
|
154
145
|
}
|
|
155
146
|
return { value: next, nextIndex: index + 1 };
|
|
156
147
|
}
|
|
157
148
|
|
|
158
149
|
export function validateInstallRuntime(value) {
|
|
159
|
-
|
|
160
|
-
if (!INSTALL_RUNTIMES.has(runtime)) {
|
|
161
|
-
throw new Error(`unknown runtime '${runtime}' (expected: auto, claude-code, codex, antigravity, external, all)`);
|
|
162
|
-
}
|
|
163
|
-
return runtime;
|
|
150
|
+
return normalizeHostRequest(value);
|
|
164
151
|
}
|
|
165
152
|
|
|
166
|
-
function resolveInstallRuntime(runtime, command) {
|
|
167
|
-
const resolution = resolveRuntime({
|
|
153
|
+
async function resolveInstallRuntime(runtime, command) {
|
|
154
|
+
const resolution = await resolveRuntime({
|
|
168
155
|
requestedRuntime: runtime,
|
|
169
156
|
command,
|
|
170
157
|
env: process.env,
|
|
@@ -172,13 +159,8 @@ function resolveInstallRuntime(runtime, command) {
|
|
|
172
159
|
});
|
|
173
160
|
if (resolution.ok || runtime !== "auto") return resolution;
|
|
174
161
|
return {
|
|
175
|
-
|
|
176
|
-
requestedRuntime: "auto",
|
|
177
|
-
resolvedRuntime: null,
|
|
178
|
-
host: "unknown",
|
|
162
|
+
...resolution,
|
|
179
163
|
confidence: "none",
|
|
180
|
-
reason: resolution.reason,
|
|
181
|
-
fallbackFrom: null,
|
|
182
164
|
warnings: [resolution.reason],
|
|
183
165
|
};
|
|
184
166
|
}
|
|
@@ -792,7 +774,7 @@ export async function runInstall(args) {
|
|
|
792
774
|
const pkgRoot = getPackageRoot();
|
|
793
775
|
const runtimeRoot = join(pkgRoot, "runtime");
|
|
794
776
|
const paths = await resolvePaths();
|
|
795
|
-
const runtimeResolution = resolveInstallRuntime(opts.runtime, "install");
|
|
777
|
+
const runtimeResolution = await resolveInstallRuntime(opts.runtime, "install");
|
|
796
778
|
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
797
779
|
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
798
780
|
return 2;
|
|
@@ -968,6 +950,44 @@ async function promptResourceDriftReasons(paths) {
|
|
|
968
950
|
return reasons;
|
|
969
951
|
}
|
|
970
952
|
|
|
953
|
+
async function hostRelayDriftReasons(paths) {
|
|
954
|
+
const reasons = [];
|
|
955
|
+
const hostsRoot = join(paths.pythonpath, "okstra_ctl", "adapters", "hosts");
|
|
956
|
+
let entries;
|
|
957
|
+
try {
|
|
958
|
+
entries = await fs.readdir(hostsRoot, { withFileTypes: true });
|
|
959
|
+
} catch (err) {
|
|
960
|
+
if (err.code === "ENOENT") return [`missing ${hostsRoot}`];
|
|
961
|
+
throw err;
|
|
962
|
+
}
|
|
963
|
+
for (const entry of entries) {
|
|
964
|
+
if (!entry.isDirectory()) continue;
|
|
965
|
+
const adapterRoot = join(hostsRoot, entry.name);
|
|
966
|
+
const manifestPath = join(adapterRoot, "manifest.json");
|
|
967
|
+
if (!(await fileExists(manifestPath))) continue;
|
|
968
|
+
let manifest;
|
|
969
|
+
try {
|
|
970
|
+
manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
971
|
+
} catch {
|
|
972
|
+
reasons.push(`invalid ${manifestPath}`);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
const relayContract = manifest?.relayContract;
|
|
976
|
+
if (typeof relayContract !== "string" || !relayContract) {
|
|
977
|
+
reasons.push(`invalid relayContract in ${manifestPath}`);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
const relayPath = resolveAbs(adapterRoot, relayContract);
|
|
981
|
+
const relayRelative = relative(adapterRoot, relayPath);
|
|
982
|
+
if (relayRelative.startsWith("..") || resolveAbs(adapterRoot) === relayPath) {
|
|
983
|
+
reasons.push(`invalid relayContract in ${manifestPath}`);
|
|
984
|
+
} else if (!(await fileExists(relayPath))) {
|
|
985
|
+
reasons.push(`missing ${relayPath}`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return reasons;
|
|
989
|
+
}
|
|
990
|
+
|
|
971
991
|
async function agentDriftReasons(paths) {
|
|
972
992
|
const reasons = [];
|
|
973
993
|
const workersDir = join(paths.agents, "workers");
|
|
@@ -1051,7 +1071,7 @@ export async function runEnsureInstalled(args) {
|
|
|
1051
1071
|
return 2;
|
|
1052
1072
|
}
|
|
1053
1073
|
const paths = await resolvePaths();
|
|
1054
|
-
const runtimeResolution = resolveInstallRuntime(opts.runtime, "ensure-installed");
|
|
1074
|
+
const runtimeResolution = await resolveInstallRuntime(opts.runtime, "ensure-installed");
|
|
1055
1075
|
if (!runtimeResolution.ok && opts.runtime !== "auto") {
|
|
1056
1076
|
process.stderr.write(`error: ${runtimeResolution.reason}\n`);
|
|
1057
1077
|
return 2;
|
|
@@ -1093,6 +1113,9 @@ export async function runEnsureInstalled(args) {
|
|
|
1093
1113
|
for (const reason of await promptResourceDriftReasons(paths)) {
|
|
1094
1114
|
reasons.push(reason);
|
|
1095
1115
|
}
|
|
1116
|
+
for (const reason of await hostRelayDriftReasons(paths)) {
|
|
1117
|
+
reasons.push(reason);
|
|
1118
|
+
}
|
|
1096
1119
|
const skillTargets = await detectSkillTargets();
|
|
1097
1120
|
for (const reason of await skillTargetDriftReasons(skillTargets)) {
|
|
1098
1121
|
reasons.push(reason);
|
|
@@ -3,7 +3,7 @@ import { runEnsureInstalled } from "./install.mjs";
|
|
|
3
3
|
import { missingHelperScripts } from "../../lib/helper-scripts.mjs";
|
|
4
4
|
import { resolveInstalledScript } from "../../lib/python-helper.mjs";
|
|
5
5
|
import { resolvePaths } from "../../lib/paths.mjs";
|
|
6
|
-
import { checkRuntimeReadiness } from "../../lib/
|
|
6
|
+
import { checkRuntimeReadiness } from "../../lib/host-registry-client.mjs";
|
|
7
7
|
|
|
8
8
|
const USAGE = `okstra preflight — one-call skill preflight (ensure-installed + check-project)
|
|
9
9
|
|
|
@@ -17,7 +17,7 @@ verifies the target project has .okstra/project.json. Prints one JSON object:
|
|
|
17
17
|
ok: false {ok, stage, reason, ...}
|
|
18
18
|
stage: install | helper_scripts_missing | resolve |
|
|
19
19
|
project_json_missing | project_json_invalid |
|
|
20
|
-
python | parse
|
|
20
|
+
runtime_readiness | python | parse
|
|
21
21
|
|
|
22
22
|
Options:
|
|
23
23
|
--runtime <name> Runtime whose assets must be present (e.g. claude-code);
|
|
@@ -26,8 +26,8 @@ Options:
|
|
|
26
26
|
--json Default output format (kept for symmetry).
|
|
27
27
|
|
|
28
28
|
Exit codes:
|
|
29
|
-
0 runtime fresh
|
|
30
|
-
1 runtime install failed,
|
|
29
|
+
0 runtime fresh, project registered, and current host ready
|
|
30
|
+
1 runtime install failed, project setup missing/invalid, or host not ready
|
|
31
31
|
2 PROJECT_ROOT could not be resolved from cwd (pass --cwd), or bad flags
|
|
32
32
|
`;
|
|
33
33
|
|
|
@@ -110,6 +110,16 @@ export async function run(args) {
|
|
|
110
110
|
runtime: opts.runtime,
|
|
111
111
|
projectRoot: payload.projectRoot,
|
|
112
112
|
});
|
|
113
|
+
if (["blocked", "unknown"].includes(runtimeReadiness.status)) {
|
|
114
|
+
emit({
|
|
115
|
+
...payload,
|
|
116
|
+
ok: false,
|
|
117
|
+
stage: "runtime_readiness",
|
|
118
|
+
reason: `current host readiness is ${runtimeReadiness.status}`,
|
|
119
|
+
runtimeReadiness,
|
|
120
|
+
});
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
113
123
|
emit({ ...payload, runtimeReadiness });
|
|
114
124
|
return code;
|
|
115
125
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
|
|
3
|
+
import { runPythonModule } from "./python-helper.mjs";
|
|
4
|
+
|
|
5
|
+
export const RUNTIME_AUTO = "auto";
|
|
6
|
+
|
|
7
|
+
const HOST_REQUEST_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
8
|
+
const PLAIN_TEXT_INPUT = "plain_text_input";
|
|
9
|
+
|
|
10
|
+
export class HostRegistryClientError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "HostRegistryClientError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function callHosts(runPython, args) {
|
|
18
|
+
const result = await runPython({
|
|
19
|
+
module: "okstra_ctl.entrypoints.hosts",
|
|
20
|
+
args,
|
|
21
|
+
stdio: "capture",
|
|
22
|
+
});
|
|
23
|
+
if (result.code !== 0) {
|
|
24
|
+
const detail = String(result.stderr || "").trim() || `Python exited ${result.code}`;
|
|
25
|
+
throw new HostRegistryClientError(detail);
|
|
26
|
+
}
|
|
27
|
+
const stdout = String(result.stdout || "").trim();
|
|
28
|
+
if (!stdout) throw new HostRegistryClientError("host registry returned empty stdout");
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(stdout);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new HostRegistryClientError(`host registry returned invalid JSON: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolutionContext(env = {}, capabilities = {}) {
|
|
37
|
+
return {
|
|
38
|
+
env: { OKSTRA_RUNTIME_HOST: String(env?.OKSTRA_RUNTIME_HOST ?? "") },
|
|
39
|
+
capabilities: {
|
|
40
|
+
claudeSkillHandoff: capabilities?.claudeSkillHandoff === true,
|
|
41
|
+
tmuxAvailable: capabilities?.tmuxAvailable === true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createHostRegistryClient({ runPython = runPythonModule } = {}) {
|
|
47
|
+
return {
|
|
48
|
+
async catalog() {
|
|
49
|
+
return await callHosts(runPython, ["catalog", "--json"]);
|
|
50
|
+
},
|
|
51
|
+
async resolveHostRuntime({ requestedRuntime, command, env, capabilities }) {
|
|
52
|
+
return await callHosts(runPython, [
|
|
53
|
+
"resolve",
|
|
54
|
+
"--requested", requestedRuntime,
|
|
55
|
+
"--command", command,
|
|
56
|
+
"--context-json", JSON.stringify(resolutionContext(env, capabilities)),
|
|
57
|
+
]);
|
|
58
|
+
},
|
|
59
|
+
async probeHost({
|
|
60
|
+
hostRuntime,
|
|
61
|
+
entryMode,
|
|
62
|
+
projectRoot,
|
|
63
|
+
homeDir,
|
|
64
|
+
availableFunctions = [],
|
|
65
|
+
}) {
|
|
66
|
+
const args = [
|
|
67
|
+
"probe",
|
|
68
|
+
"--host", hostRuntime,
|
|
69
|
+
"--entry-mode", entryMode,
|
|
70
|
+
"--project-root", projectRoot,
|
|
71
|
+
"--home-dir", homeDir,
|
|
72
|
+
];
|
|
73
|
+
for (const availableFunction of availableFunctions) {
|
|
74
|
+
args.push("--available-function", availableFunction);
|
|
75
|
+
}
|
|
76
|
+
return await callHosts(runPython, args);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const hostRegistryClient = createHostRegistryClient();
|
|
82
|
+
|
|
83
|
+
export function normalizeHostRequest(value) {
|
|
84
|
+
const runtime = String(value ?? "").trim() || RUNTIME_AUTO;
|
|
85
|
+
if (!HOST_REQUEST_PATTERN.test(runtime)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`invalid runtime '${runtime}' (expected auto, all, or a lowercase host ID)`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return runtime;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function failedResolution(requestedRuntime, error) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
requestedRuntime,
|
|
97
|
+
resolvedRuntime: null,
|
|
98
|
+
launchMode: null,
|
|
99
|
+
installTargets: [],
|
|
100
|
+
host: "unknown",
|
|
101
|
+
confidence: "low",
|
|
102
|
+
reason: error.message,
|
|
103
|
+
fallbackFrom: null,
|
|
104
|
+
warnings: [],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function catalogResolution(catalog) {
|
|
109
|
+
const hosts = Array.isArray(catalog?.hosts) ? catalog.hosts : [];
|
|
110
|
+
const catalogHostIds = hosts.map((host) => host.id);
|
|
111
|
+
const installTargets = Array.from(new Set(
|
|
112
|
+
hosts.flatMap((host) => host.install_targets ?? host.installTargets ?? []),
|
|
113
|
+
)).sort();
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
requestedRuntime: "all",
|
|
117
|
+
resolvedRuntime: "all",
|
|
118
|
+
launchMode: "catalog",
|
|
119
|
+
installTargets,
|
|
120
|
+
catalogHostIds,
|
|
121
|
+
host: "explicit",
|
|
122
|
+
confidence: "high",
|
|
123
|
+
reason: "All registered host runtimes requested.",
|
|
124
|
+
fallbackFrom: null,
|
|
125
|
+
warnings: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function loadHostCatalog() {
|
|
130
|
+
return await hostRegistryClient.catalog();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function resolveHostRuntime(options) {
|
|
134
|
+
return await hostRegistryClient.resolveHostRuntime(options);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function probeHost(options) {
|
|
138
|
+
return await hostRegistryClient.probeHost(options);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function resolveRuntime({
|
|
142
|
+
requestedRuntime = RUNTIME_AUTO,
|
|
143
|
+
command = "run",
|
|
144
|
+
env = {},
|
|
145
|
+
capabilities = {},
|
|
146
|
+
} = {}) {
|
|
147
|
+
const request = normalizeHostRequest(requestedRuntime);
|
|
148
|
+
try {
|
|
149
|
+
if (request === "all") return catalogResolution(await loadHostCatalog());
|
|
150
|
+
return await resolveHostRuntime({
|
|
151
|
+
requestedRuntime: request,
|
|
152
|
+
command,
|
|
153
|
+
env,
|
|
154
|
+
capabilities,
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (!(error instanceof HostRegistryClientError)) throw error;
|
|
158
|
+
return failedResolution(request, error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function checkRuntimeReadiness({
|
|
163
|
+
runtime,
|
|
164
|
+
projectRoot,
|
|
165
|
+
homeDir = homedir(),
|
|
166
|
+
hostRegistryClient = { probeHost },
|
|
167
|
+
}) {
|
|
168
|
+
if (!runtime) return { runtime: "auto", status: "ready", checks: [] };
|
|
169
|
+
return await hostRegistryClient.probeHost({
|
|
170
|
+
hostRuntime: runtime,
|
|
171
|
+
entryMode: "current-session",
|
|
172
|
+
projectRoot,
|
|
173
|
+
homeDir,
|
|
174
|
+
availableFunctions: [PLAIN_TEXT_INPUT],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export const RUNTIMES_MANIFEST_REL = "installed-runtimes.json";
|
|
2
2
|
export const RUNTIME_MANIFEST_SCHEMA_VERSION = 2;
|
|
3
|
-
export const EXPLICIT_RUNTIME_VALUES = ["claude-code", "codex", "antigravity", "external"];
|
|
4
|
-
|
|
5
3
|
export function runtimeListForSelection(runtime, resolution = null) {
|
|
6
|
-
if (runtime === "all") return [...
|
|
4
|
+
if (runtime === "all") return [...(resolution?.catalogHostIds ?? [])];
|
|
7
5
|
if (runtime === "auto") {
|
|
8
6
|
const resolved = resolution?.resolvedRuntime;
|
|
9
7
|
return resolved && resolved !== "all" ? [resolved] : [];
|
|
@@ -11,10 +9,8 @@ export function runtimeListForSelection(runtime, resolution = null) {
|
|
|
11
9
|
return [runtime];
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
if (runtime === "claude-code") return true;
|
|
17
|
-
return runtime === "auto" && resolution?.resolvedRuntime === "claude-code";
|
|
12
|
+
function includesClaudeAssets(resolution) {
|
|
13
|
+
return resolution?.installTargets?.includes("claude") === true;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
function defaultResolution(runtime) {
|
|
@@ -22,6 +18,8 @@ function defaultResolution(runtime) {
|
|
|
22
18
|
return {
|
|
23
19
|
requestedRuntime: runtime,
|
|
24
20
|
resolvedRuntime: runtime,
|
|
21
|
+
launchMode: null,
|
|
22
|
+
installTargets: [],
|
|
25
23
|
host: "explicit",
|
|
26
24
|
confidence: "high",
|
|
27
25
|
reason: `Explicit runtime '${runtime}' requested.`,
|
|
@@ -37,7 +35,7 @@ export function buildRuntimeManifest(
|
|
|
37
35
|
installedAssets = null,
|
|
38
36
|
) {
|
|
39
37
|
const runtimeResolution = resolution ?? defaultResolution(runtime);
|
|
40
|
-
const includesClaude =
|
|
38
|
+
const includesClaude = includesClaudeAssets(runtimeResolution);
|
|
41
39
|
const defaultAssets = {
|
|
42
40
|
sharedRuntime: true,
|
|
43
41
|
claudeSkills: includesClaude,
|
|
@@ -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())
|