okstra 0.163.2 → 0.164.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 +7 -5
- package/docs/architecture.md +12 -7
- package/docs/cli.md +8 -5
- package/docs/project-structure-overview.md +7 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -0
- 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.sh +87 -91
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -0
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- 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} +111 -0
- 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} +52 -0
- 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 +35 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +55 -0
- package/runtime/python/okstra_ctl/adapters/providers/claude/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +43 -0
- package/runtime/python/okstra_ctl/adapters/providers/codex/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +32 -0
- package/runtime/python/okstra_ctl/adapters/providers/grok/manifest.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +38 -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 +240 -29
- package/runtime/python/okstra_ctl/dispatch_state.py +17 -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 +66 -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/entrypoints/__init__.py +1 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +334 -0
- 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/run.py +80 -58
- package/runtime/python/okstra_ctl/session.py +1 -1
- package/runtime/python/okstra_ctl/team.py +44 -32
- package/runtime/python/okstra_ctl/wizard.py +162 -64
- package/runtime/python/okstra_ctl/worker_audit_ledger.py +29 -4
- package/runtime/python/okstra_token_usage/collect.py +34 -6
- package/runtime/schemas/final-report-v1.0.schema.json +3989 -1085
- package/runtime/schemas/final-report-v2.0.schema.json +5622 -1451
- package/runtime/skills/okstra-run/SKILL.md +73 -28
- package/runtime/templates/implementation-worker-preamble.md +1 -1
- package/runtime/templates/reports/final-report.template.md +3 -3
- package/runtime/templates/reports/html/i18n/en.json +3 -1
- package/runtime/templates/reports/html/i18n/ko.json +3 -1
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +1 -1
- 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 +8 -7
- package/runtime/validators/validate_session_conformance.py +7 -2
- 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/python/okstra_ctl/lead_runtime.py +0 -115
- package/runtime/python/okstra_ctl/runner_resolution.py +0 -103
- package/src/lib/runtime-readiness.mjs +0 -90
- package/src/lib/runtime-resolver.mjs +0 -123
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"""Lead runtime metadata shared by render and prepare paths."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from dataclasses import dataclass, replace
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@dataclass(frozen=True)
|
|
8
|
-
class LeadRuntimeInfo:
|
|
9
|
-
runtime: str
|
|
10
|
-
agent: str
|
|
11
|
-
agent_label: str
|
|
12
|
-
role: str
|
|
13
|
-
adapter_name: str
|
|
14
|
-
adapter_dispatch_mode: str
|
|
15
|
-
session_accounting: str
|
|
16
|
-
has_claude_session: bool
|
|
17
|
-
adapter_contract_relative_path: str
|
|
18
|
-
|
|
19
|
-
def adapter_payload(self) -> dict[str, str]:
|
|
20
|
-
return {
|
|
21
|
-
"name": self.adapter_name,
|
|
22
|
-
"dispatchMode": self.adapter_dispatch_mode,
|
|
23
|
-
"sessionAccounting": self.session_accounting,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
ARTIFACT_ONLY_LEAD_RUNTIMES = frozenset({"codex", "antigravity", "external"})
|
|
28
|
-
|
|
29
|
-
# Not a lead runtime — an adapter selected by the environment, so it is absent
|
|
30
|
-
# from ALLOWED_LEAD_RUNTIMES and from _LEAD_RUNTIMES on purpose. `--lead-runtime
|
|
31
|
-
# cmux` is not a thing a user can ask for; see `with_cmux_dispatch`.
|
|
32
|
-
CMUX_ADAPTER_NAME = "cmux"
|
|
33
|
-
CMUX_ADAPTER_RELATIVE_PATH = "lead/adapters/cmux.md"
|
|
34
|
-
|
|
35
|
-
_LEAD_RUNTIMES = {
|
|
36
|
-
"claude-code": LeadRuntimeInfo(
|
|
37
|
-
runtime="claude-code",
|
|
38
|
-
agent="claude",
|
|
39
|
-
agent_label="Claude Code",
|
|
40
|
-
role="Claude lead",
|
|
41
|
-
adapter_name="claude-code",
|
|
42
|
-
adapter_dispatch_mode="team",
|
|
43
|
-
session_accounting="claude-jsonl",
|
|
44
|
-
has_claude_session=True,
|
|
45
|
-
adapter_contract_relative_path="lead/adapters/claude-code.md",
|
|
46
|
-
),
|
|
47
|
-
"codex": LeadRuntimeInfo(
|
|
48
|
-
runtime="codex",
|
|
49
|
-
agent="codex",
|
|
50
|
-
agent_label="Codex CLI",
|
|
51
|
-
role="Codex lead",
|
|
52
|
-
adapter_name="codex",
|
|
53
|
-
adapter_dispatch_mode="render-only",
|
|
54
|
-
session_accounting="artifact-only",
|
|
55
|
-
has_claude_session=False,
|
|
56
|
-
adapter_contract_relative_path="lead/adapters/codex.md",
|
|
57
|
-
),
|
|
58
|
-
"antigravity": LeadRuntimeInfo(
|
|
59
|
-
runtime="antigravity",
|
|
60
|
-
agent="antigravity",
|
|
61
|
-
agent_label="Antigravity CLI",
|
|
62
|
-
role="Antigravity lead",
|
|
63
|
-
adapter_name="antigravity",
|
|
64
|
-
adapter_dispatch_mode="render-only",
|
|
65
|
-
session_accounting="artifact-only",
|
|
66
|
-
has_claude_session=False,
|
|
67
|
-
adapter_contract_relative_path="lead/adapters/antigravity.md",
|
|
68
|
-
),
|
|
69
|
-
"external": LeadRuntimeInfo(
|
|
70
|
-
runtime="external",
|
|
71
|
-
agent="external",
|
|
72
|
-
agent_label="External harness",
|
|
73
|
-
role="Okstra lead",
|
|
74
|
-
adapter_name="external",
|
|
75
|
-
adapter_dispatch_mode="render-only",
|
|
76
|
-
session_accounting="artifact-only",
|
|
77
|
-
has_claude_session=False,
|
|
78
|
-
adapter_contract_relative_path="lead/adapters/external.md",
|
|
79
|
-
),
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# Derived rather than restated: a hand-written copy of the registry's own keys
|
|
84
|
-
# is a list that can disagree with the thing it describes.
|
|
85
|
-
ALLOWED_LEAD_RUNTIMES = tuple(_LEAD_RUNTIMES)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def lead_runtime_info(runtime: str) -> LeadRuntimeInfo:
|
|
89
|
-
try:
|
|
90
|
-
return _LEAD_RUNTIMES[runtime]
|
|
91
|
-
except KeyError as exc:
|
|
92
|
-
allowed = ", ".join(ALLOWED_LEAD_RUNTIMES)
|
|
93
|
-
raise ValueError(f"unsupported lead runtime: {runtime} (allowed: {allowed})") from exc
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def is_artifact_only_runtime(runtime: str) -> bool:
|
|
97
|
-
return runtime in ARTIFACT_ONLY_LEAD_RUNTIMES
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def with_cmux_dispatch(info: LeadRuntimeInfo) -> LeadRuntimeInfo:
|
|
101
|
-
"""The same lead, minus the two fields cmux takes over.
|
|
102
|
-
|
|
103
|
-
On the cmux path okstra owns the worker panes rather than the host, so
|
|
104
|
-
"which adapter" and "does this lead dispatch or only render" stop depending
|
|
105
|
-
on who the lead is — they are the same for every runtime. Overriding the two
|
|
106
|
-
fields keeps that answer in one adapter file rather than repeating it in
|
|
107
|
-
four. Everything else (agent, label, role, session accounting) still
|
|
108
|
-
describes the lead itself and is left untouched.
|
|
109
|
-
"""
|
|
110
|
-
return replace(
|
|
111
|
-
info,
|
|
112
|
-
adapter_name=CMUX_ADAPTER_NAME,
|
|
113
|
-
adapter_dispatch_mode="team",
|
|
114
|
-
adapter_contract_relative_path=CMUX_ADAPTER_RELATIVE_PATH,
|
|
115
|
-
)
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
"""Resolve native versus CLI execution from host and provider capabilities."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
|
|
6
|
-
from .models import (
|
|
7
|
-
UnknownProviderError,
|
|
8
|
-
provider_spec,
|
|
9
|
-
provider_supports_role,
|
|
10
|
-
provider_wrappers,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
NATIVE_SESSION = "native-session"
|
|
14
|
-
CLI_WRAPPER = "cli-wrapper"
|
|
15
|
-
RUNNER_VALUES = frozenset({NATIVE_SESSION, CLI_WRAPPER})
|
|
16
|
-
|
|
17
|
-
_HOST_NATIVE_PROVIDERS = {
|
|
18
|
-
"claude-code": frozenset({"claude"}),
|
|
19
|
-
"codex": frozenset({"codex"}),
|
|
20
|
-
"antigravity": frozenset({"antigravity"}),
|
|
21
|
-
"external": frozenset(),
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def native_provider_for_host(host_runtime: str) -> str:
|
|
26
|
-
"""Return the single provider owned by an in-session host, if any."""
|
|
27
|
-
providers = _HOST_NATIVE_PROVIDERS.get(host_runtime)
|
|
28
|
-
if providers is None:
|
|
29
|
-
allowed = ", ".join(_HOST_NATIVE_PROVIDERS)
|
|
30
|
-
raise RunnerResolutionError(
|
|
31
|
-
f"unknown host runtime {host_runtime!r}. Allowed values: {allowed}"
|
|
32
|
-
)
|
|
33
|
-
return next(iter(providers), "")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class RunnerResolutionError(ValueError):
|
|
37
|
-
"""Raised when an assignment cannot run on the selected host."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass(frozen=True)
|
|
41
|
-
class RunnerResolution:
|
|
42
|
-
runner: str
|
|
43
|
-
wrapper: str
|
|
44
|
-
reason: str
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def resolve_runner(
|
|
48
|
-
host_runtime: str,
|
|
49
|
-
provider: str,
|
|
50
|
-
role: str,
|
|
51
|
-
requested_runner: str = "",
|
|
52
|
-
) -> RunnerResolution:
|
|
53
|
-
"""Resolve an assignment without inferring the host from installed CLIs."""
|
|
54
|
-
native_providers = _HOST_NATIVE_PROVIDERS.get(host_runtime)
|
|
55
|
-
if native_providers is None:
|
|
56
|
-
allowed = ", ".join(_HOST_NATIVE_PROVIDERS)
|
|
57
|
-
raise RunnerResolutionError(
|
|
58
|
-
f"unknown host runtime {host_runtime!r}. Allowed values: {allowed}"
|
|
59
|
-
)
|
|
60
|
-
try:
|
|
61
|
-
spec = provider_spec(provider)
|
|
62
|
-
except UnknownProviderError as exc:
|
|
63
|
-
raise RunnerResolutionError(str(exc)) from exc
|
|
64
|
-
if not provider_supports_role(provider, role):
|
|
65
|
-
raise RunnerResolutionError(
|
|
66
|
-
f"provider {provider!r} does not support role {role!r}"
|
|
67
|
-
)
|
|
68
|
-
requested = (requested_runner or "").strip()
|
|
69
|
-
if requested and requested not in RUNNER_VALUES:
|
|
70
|
-
allowed = ", ".join(sorted(RUNNER_VALUES))
|
|
71
|
-
raise RunnerResolutionError(
|
|
72
|
-
f"unknown runner {requested!r}. Allowed values: {allowed}"
|
|
73
|
-
)
|
|
74
|
-
if requested == NATIVE_SESSION and provider not in native_providers:
|
|
75
|
-
raise RunnerResolutionError(
|
|
76
|
-
f"host runtime {host_runtime!r} does not provide native {provider!r} execution"
|
|
77
|
-
)
|
|
78
|
-
if requested == NATIVE_SESSION or (not requested and provider in native_providers):
|
|
79
|
-
return RunnerResolution(
|
|
80
|
-
runner=NATIVE_SESSION,
|
|
81
|
-
wrapper="",
|
|
82
|
-
reason=f"{host_runtime} provides native {provider} execution",
|
|
83
|
-
)
|
|
84
|
-
if not spec.wrapper:
|
|
85
|
-
raise RunnerResolutionError(
|
|
86
|
-
f"provider {provider!r} has no CLI wrapper for role {role!r}"
|
|
87
|
-
)
|
|
88
|
-
return RunnerResolution(
|
|
89
|
-
runner=CLI_WRAPPER,
|
|
90
|
-
wrapper=spec.wrapper,
|
|
91
|
-
reason=f"{host_runtime} uses the registered {provider} CLI wrapper",
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def worker_wrapper_registry(report_writer_provider: str = "claude") -> dict[str, str]:
|
|
96
|
-
"""Worker-id wrapper map for dispatch front ends."""
|
|
97
|
-
wrappers = provider_wrappers("analyser")
|
|
98
|
-
report_writer = provider_spec(report_writer_provider)
|
|
99
|
-
if not provider_supports_role(report_writer_provider, "report-writer"):
|
|
100
|
-
raise RunnerResolutionError(
|
|
101
|
-
f"provider {report_writer_provider!r} does not support report-writer"
|
|
102
|
-
)
|
|
103
|
-
return {**wrappers, "report-writer": report_writer.wrapper}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join, resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
const WORKSPACE_TRUST_CHECK_ID = "workspace-trust";
|
|
6
|
-
|
|
7
|
-
function ready(runtime, check) {
|
|
8
|
-
return { runtime, status: "ready", checks: check ? [check] : [] };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function blocked(runtime) {
|
|
12
|
-
return {
|
|
13
|
-
runtime,
|
|
14
|
-
status: "blocked",
|
|
15
|
-
checks: [{
|
|
16
|
-
id: WORKSPACE_TRUST_CHECK_ID,
|
|
17
|
-
status: "required",
|
|
18
|
-
action: "restart-and-trust",
|
|
19
|
-
}],
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function unknown(runtime) {
|
|
24
|
-
return {
|
|
25
|
-
runtime,
|
|
26
|
-
status: "unknown",
|
|
27
|
-
checks: [{
|
|
28
|
-
id: WORKSPACE_TRUST_CHECK_ID,
|
|
29
|
-
status: "unavailable",
|
|
30
|
-
action: "open-project-and-trust",
|
|
31
|
-
}],
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isJsonObject(value) {
|
|
36
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function hasProjectSettings(projectRoot) {
|
|
40
|
-
try {
|
|
41
|
-
await fs.lstat(join(projectRoot, ".claude", "settings.local.json"));
|
|
42
|
-
return true;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
if (error?.code === "ENOENT") return false;
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function checkClaudeCodeReadiness({ projectRoot, homeDir }) {
|
|
50
|
-
const runtime = "claude-code";
|
|
51
|
-
try {
|
|
52
|
-
if (!(await hasProjectSettings(projectRoot))) {
|
|
53
|
-
return ready(runtime, {
|
|
54
|
-
id: WORKSPACE_TRUST_CHECK_ID,
|
|
55
|
-
status: "not-applicable",
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const rawState = await fs.readFile(join(homeDir, ".claude.json"), "utf8");
|
|
60
|
-
const state = JSON.parse(rawState);
|
|
61
|
-
if (!isJsonObject(state?.projects)) return unknown(runtime);
|
|
62
|
-
|
|
63
|
-
const projectState = state.projects[resolve(projectRoot)];
|
|
64
|
-
if (projectState === undefined) return blocked(runtime);
|
|
65
|
-
if (!isJsonObject(projectState)) return unknown(runtime);
|
|
66
|
-
if (projectState.hasTrustDialogAccepted === true) {
|
|
67
|
-
return ready(runtime, { id: WORKSPACE_TRUST_CHECK_ID, status: "accepted" });
|
|
68
|
-
}
|
|
69
|
-
if (projectState.hasTrustDialogAccepted === false
|
|
70
|
-
|| projectState.hasTrustDialogAccepted === null
|
|
71
|
-
|| projectState.hasTrustDialogAccepted === undefined) {
|
|
72
|
-
return blocked(runtime);
|
|
73
|
-
}
|
|
74
|
-
return unknown(runtime);
|
|
75
|
-
} catch {
|
|
76
|
-
return unknown(runtime);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function checkRuntimeReadiness({
|
|
81
|
-
runtime,
|
|
82
|
-
projectRoot,
|
|
83
|
-
homeDir = homedir(),
|
|
84
|
-
}) {
|
|
85
|
-
const selectedRuntime = runtime ?? "auto";
|
|
86
|
-
if (selectedRuntime !== "claude-code") {
|
|
87
|
-
return ready(selectedRuntime);
|
|
88
|
-
}
|
|
89
|
-
return checkClaudeCodeReadiness({ projectRoot, homeDir });
|
|
90
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
export const RUNTIME_AUTO = "auto";
|
|
2
|
-
export const EXPLICIT_RUNTIMES = ["claude-code", "codex", "antigravity", "external"];
|
|
3
|
-
export const VALID_RUNTIME_REQUESTS = [RUNTIME_AUTO, ...EXPLICIT_RUNTIMES, "all"];
|
|
4
|
-
|
|
5
|
-
const VALID_HOSTS = new Set(EXPLICIT_RUNTIMES);
|
|
6
|
-
const VALID_REQUESTS = new Set(VALID_RUNTIME_REQUESTS);
|
|
7
|
-
|
|
8
|
-
function clean(value) {
|
|
9
|
-
return String(value ?? "").trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function normalizeRuntimeRequest(value) {
|
|
13
|
-
const runtime = clean(value) || RUNTIME_AUTO;
|
|
14
|
-
if (!VALID_REQUESTS.has(runtime)) {
|
|
15
|
-
throw new Error(`unknown runtime '${runtime}' (expected: ${VALID_RUNTIME_REQUESTS.join(", ")})`);
|
|
16
|
-
}
|
|
17
|
-
return runtime;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizeCapabilities(capabilities = {}) {
|
|
21
|
-
return {
|
|
22
|
-
claudeAssetsInstalled: capabilities.claudeAssetsInstalled === true,
|
|
23
|
-
codexCliAvailable: capabilities.codexCliAvailable === true,
|
|
24
|
-
tmuxAvailable: capabilities.tmuxAvailable === true,
|
|
25
|
-
claudeSkillHandoff: capabilities.claudeSkillHandoff === true,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function explicitResult(runtime) {
|
|
30
|
-
return {
|
|
31
|
-
ok: true,
|
|
32
|
-
requestedRuntime: runtime,
|
|
33
|
-
resolvedRuntime: runtime,
|
|
34
|
-
host: "explicit",
|
|
35
|
-
confidence: "high",
|
|
36
|
-
reason: `Explicit runtime '${runtime}' requested.`,
|
|
37
|
-
fallbackFrom: null,
|
|
38
|
-
warnings: [],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const RUNTIME_FLAG_COMMANDS = new Set(["install", "ensure-installed", "doctor"]);
|
|
43
|
-
|
|
44
|
-
function explicitRuntimeFlag(command) {
|
|
45
|
-
return RUNTIME_FLAG_COMMANDS.has(command) ? "--runtime" : "--lead-runtime";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function okResult({ requestedRuntime, resolvedRuntime, host, reason, warnings = [] }) {
|
|
49
|
-
return {
|
|
50
|
-
ok: true,
|
|
51
|
-
requestedRuntime,
|
|
52
|
-
resolvedRuntime,
|
|
53
|
-
host,
|
|
54
|
-
confidence: "high",
|
|
55
|
-
reason,
|
|
56
|
-
fallbackFrom: null,
|
|
57
|
-
warnings,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function failResult(requestedRuntime, reason, warnings = []) {
|
|
62
|
-
return {
|
|
63
|
-
ok: false,
|
|
64
|
-
requestedRuntime,
|
|
65
|
-
resolvedRuntime: null,
|
|
66
|
-
host: "unknown",
|
|
67
|
-
confidence: "low",
|
|
68
|
-
reason,
|
|
69
|
-
fallbackFrom: null,
|
|
70
|
-
warnings,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function resolveRuntime({
|
|
75
|
-
requestedRuntime = RUNTIME_AUTO,
|
|
76
|
-
command = "run",
|
|
77
|
-
env = {},
|
|
78
|
-
capabilities = {},
|
|
79
|
-
} = {}) {
|
|
80
|
-
const request = normalizeRuntimeRequest(requestedRuntime);
|
|
81
|
-
const caps = normalizeCapabilities(capabilities);
|
|
82
|
-
|
|
83
|
-
if (request !== RUNTIME_AUTO) return explicitResult(request);
|
|
84
|
-
|
|
85
|
-
const rawHost = clean(env?.OKSTRA_RUNTIME_HOST);
|
|
86
|
-
if (rawHost) {
|
|
87
|
-
if (!VALID_HOSTS.has(rawHost)) {
|
|
88
|
-
return failResult(
|
|
89
|
-
request,
|
|
90
|
-
`invalid OKSTRA_RUNTIME_HOST '${rawHost}' (expected: ${EXPLICIT_RUNTIMES.join("|")}).`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
return okResult({
|
|
94
|
-
requestedRuntime: request,
|
|
95
|
-
resolvedRuntime: rawHost,
|
|
96
|
-
host: rawHost,
|
|
97
|
-
reason: `OKSTRA_RUNTIME_HOST=${rawHost} selected runtime '${rawHost}'.`,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (caps.claudeSkillHandoff) {
|
|
102
|
-
return okResult({
|
|
103
|
-
requestedRuntime: request,
|
|
104
|
-
resolvedRuntime: "claude-code",
|
|
105
|
-
host: "claude-code",
|
|
106
|
-
reason: "Claude Code skill handoff selected runtime 'claude-code'.",
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (caps.tmuxAvailable) {
|
|
111
|
-
return okResult({
|
|
112
|
-
requestedRuntime: request,
|
|
113
|
-
resolvedRuntime: "external",
|
|
114
|
-
host: "generic-terminal",
|
|
115
|
-
reason: "No Claude Code or Codex host signal; tmux is available.",
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return failResult(
|
|
120
|
-
request,
|
|
121
|
-
`host unknown: set OKSTRA_RUNTIME_HOST=claude-code|codex|antigravity|external or pass ${explicitRuntimeFlag(command)} explicitly.`,
|
|
122
|
-
);
|
|
123
|
-
}
|