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
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""JSON entrypoint for host catalog, resolution, and launch plans."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from ..application.start_run import start_run
|
|
13
|
+
from ..application.resume_run import resume_run
|
|
14
|
+
from ..domain.host import (
|
|
15
|
+
HostAdapterContractError,
|
|
16
|
+
HostCapabilityMismatch,
|
|
17
|
+
HostNotRegistered,
|
|
18
|
+
HostResolutionContext,
|
|
19
|
+
HostSessionContext,
|
|
20
|
+
HostUnavailable,
|
|
21
|
+
LeadStartRequest,
|
|
22
|
+
LeadResumeRequest,
|
|
23
|
+
ProviderNotRegistered,
|
|
24
|
+
ProviderUnavailable,
|
|
25
|
+
)
|
|
26
|
+
from ..registry.host_registry import HostAdapterRegistry, default_host_registry
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_ENTRYPOINT_ERRORS = (
|
|
30
|
+
HostAdapterContractError,
|
|
31
|
+
HostCapabilityMismatch,
|
|
32
|
+
HostNotRegistered,
|
|
33
|
+
HostUnavailable,
|
|
34
|
+
ProviderNotRegistered,
|
|
35
|
+
ProviderUnavailable,
|
|
36
|
+
ValueError,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def catalog_payload(registry: HostAdapterRegistry) -> dict[str, object]:
|
|
41
|
+
return {
|
|
42
|
+
"schemaVersion": 1,
|
|
43
|
+
"hosts": [asdict(descriptor) for descriptor in registry.catalog()],
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_payload(
|
|
48
|
+
registry: HostAdapterRegistry,
|
|
49
|
+
requested_host: str,
|
|
50
|
+
context: HostResolutionContext,
|
|
51
|
+
) -> dict[str, object]:
|
|
52
|
+
descriptor, host, reason = registry.resolve_request_details(
|
|
53
|
+
requested_host, context
|
|
54
|
+
)
|
|
55
|
+
return {
|
|
56
|
+
"ok": True,
|
|
57
|
+
"requestedRuntime": requested_host.strip().lower() or "auto",
|
|
58
|
+
"resolvedRuntime": descriptor.id,
|
|
59
|
+
"launchMode": descriptor.launch_mode,
|
|
60
|
+
"installTargets": sorted(descriptor.install_targets),
|
|
61
|
+
"host": host,
|
|
62
|
+
"confidence": "high",
|
|
63
|
+
"reason": reason,
|
|
64
|
+
"fallbackFrom": None,
|
|
65
|
+
"warnings": [],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main(
|
|
70
|
+
argv: list[str] | None = None,
|
|
71
|
+
*,
|
|
72
|
+
registry: HostAdapterRegistry | None = None,
|
|
73
|
+
) -> int:
|
|
74
|
+
parser = _build_parser()
|
|
75
|
+
args = parser.parse_args(argv)
|
|
76
|
+
try:
|
|
77
|
+
active_registry = registry or default_host_registry()
|
|
78
|
+
payload = _command_payload(args, active_registry)
|
|
79
|
+
except _ENTRYPOINT_ERRORS as exc:
|
|
80
|
+
sys.stderr.write(f"{exc}\n")
|
|
81
|
+
return 2
|
|
82
|
+
if getattr(args, "output_format", "json") == "null":
|
|
83
|
+
_write_null_launch_plan(payload)
|
|
84
|
+
else:
|
|
85
|
+
public_payload = {
|
|
86
|
+
key: value for key, value in payload.items()
|
|
87
|
+
if not key.startswith("_")
|
|
88
|
+
}
|
|
89
|
+
print(json.dumps(public_payload, default=_json_default, sort_keys=True))
|
|
90
|
+
return 0
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
94
|
+
parser = argparse.ArgumentParser(prog="okstra-hosts")
|
|
95
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
96
|
+
catalog = commands.add_parser("catalog")
|
|
97
|
+
catalog.add_argument("--json", action="store_true")
|
|
98
|
+
resolve = commands.add_parser("resolve")
|
|
99
|
+
resolve.add_argument("--requested", required=True)
|
|
100
|
+
resolve.add_argument("--command", dest="runtime_command", required=True)
|
|
101
|
+
resolve.add_argument("--context-json", required=True)
|
|
102
|
+
launch = commands.add_parser("launch-plan")
|
|
103
|
+
_add_session_plan_args(launch)
|
|
104
|
+
launch.add_argument("--prepared-launch-json", default="")
|
|
105
|
+
resume = commands.add_parser("resume-plan")
|
|
106
|
+
_add_session_plan_args(resume)
|
|
107
|
+
resume.add_argument("--session-id", required=True)
|
|
108
|
+
resume.add_argument("--task-type", default="")
|
|
109
|
+
resume.add_argument("--request-arg", action="append", default=[])
|
|
110
|
+
probe = commands.add_parser("probe")
|
|
111
|
+
probe.add_argument("--host", required=True)
|
|
112
|
+
probe.add_argument(
|
|
113
|
+
"--entry-mode",
|
|
114
|
+
required=True,
|
|
115
|
+
choices=("spawn-process", "current-session"),
|
|
116
|
+
)
|
|
117
|
+
probe.add_argument("--project-root", required=True)
|
|
118
|
+
probe.add_argument("--home-dir", required=True)
|
|
119
|
+
probe.add_argument("--available-function", action="append", default=[])
|
|
120
|
+
return parser
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _add_session_plan_args(parser: argparse.ArgumentParser) -> None:
|
|
124
|
+
parser.add_argument("--host", required=True)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"--entry-mode",
|
|
127
|
+
required=True,
|
|
128
|
+
choices=("spawn-process", "current-session"),
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--format",
|
|
132
|
+
dest="output_format",
|
|
133
|
+
choices=("json", "null"),
|
|
134
|
+
default="json",
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument("--project-root", default=str(Path.cwd()))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _command_payload(
|
|
140
|
+
args: argparse.Namespace, registry: HostAdapterRegistry
|
|
141
|
+
) -> dict[str, object]:
|
|
142
|
+
if args.command == "catalog":
|
|
143
|
+
return catalog_payload(registry)
|
|
144
|
+
if args.command == "resolve":
|
|
145
|
+
context = _parse_resolution_context(args.context_json)
|
|
146
|
+
try:
|
|
147
|
+
return resolve_payload(registry, args.requested, context)
|
|
148
|
+
except HostUnavailable as exc:
|
|
149
|
+
raise HostUnavailable(
|
|
150
|
+
_unclaimed_host_message(registry, args.runtime_command)
|
|
151
|
+
) from exc
|
|
152
|
+
except HostNotRegistered as exc:
|
|
153
|
+
if args.requested.strip().lower() in {"", "auto"} and context.explicit_env_host:
|
|
154
|
+
allowed = "|".join(registry.ids())
|
|
155
|
+
raise HostNotRegistered(
|
|
156
|
+
f"invalid OKSTRA_RUNTIME_HOST {context.explicit_env_host!r} "
|
|
157
|
+
f"(expected: {allowed})."
|
|
158
|
+
) from exc
|
|
159
|
+
raise
|
|
160
|
+
if args.command == "probe":
|
|
161
|
+
return _probe_payload(registry, args)
|
|
162
|
+
if args.command == "resume-plan":
|
|
163
|
+
return _resume_plan_payload(registry, args)
|
|
164
|
+
return _launch_plan_payload(registry, args)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _probe_payload(
|
|
168
|
+
registry: HostAdapterRegistry,
|
|
169
|
+
args: argparse.Namespace,
|
|
170
|
+
) -> dict[str, object]:
|
|
171
|
+
adapter = registry.resolve(args.host)
|
|
172
|
+
context = HostSessionContext(
|
|
173
|
+
host_id=adapter.descriptor.id,
|
|
174
|
+
entry_mode=args.entry_mode,
|
|
175
|
+
available_functions=frozenset(args.available_function),
|
|
176
|
+
interaction_surface="preflight",
|
|
177
|
+
)
|
|
178
|
+
with _probe_paths(args.project_root, args.home_dir):
|
|
179
|
+
readiness = adapter.probe(context)
|
|
180
|
+
statuses = {check.get("status") for check in readiness.checks}
|
|
181
|
+
status = "unknown" if "unavailable" in statuses else (
|
|
182
|
+
"ready" if readiness.ok else "blocked"
|
|
183
|
+
)
|
|
184
|
+
return {
|
|
185
|
+
"runtime": adapter.descriptor.id,
|
|
186
|
+
"status": status,
|
|
187
|
+
"checks": list(readiness.checks),
|
|
188
|
+
"relayContract": adapter.descriptor.relay_contract,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@contextmanager
|
|
193
|
+
def _probe_paths(project_root: str, home_dir: str):
|
|
194
|
+
keys = ("OKSTRA_PROBE_PROJECT_ROOT", "OKSTRA_PROBE_HOME_DIR")
|
|
195
|
+
previous = {key: os.environ.get(key) for key in keys}
|
|
196
|
+
os.environ[keys[0]] = project_root
|
|
197
|
+
os.environ[keys[1]] = home_dir
|
|
198
|
+
try:
|
|
199
|
+
yield
|
|
200
|
+
finally:
|
|
201
|
+
for key, value in previous.items():
|
|
202
|
+
if value is None:
|
|
203
|
+
os.environ.pop(key, None)
|
|
204
|
+
else:
|
|
205
|
+
os.environ[key] = value
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _parse_resolution_context(raw_context: str) -> HostResolutionContext:
|
|
209
|
+
try:
|
|
210
|
+
raw = json.loads(raw_context)
|
|
211
|
+
except json.JSONDecodeError as exc:
|
|
212
|
+
raise ValueError("invalid host resolution context JSON") from exc
|
|
213
|
+
if not isinstance(raw, dict):
|
|
214
|
+
raise ValueError("invalid host resolution context JSON")
|
|
215
|
+
env = raw.get("env", {})
|
|
216
|
+
capabilities = raw.get("capabilities", {})
|
|
217
|
+
if not isinstance(env, dict) or not isinstance(capabilities, dict):
|
|
218
|
+
raise ValueError("invalid host resolution context JSON")
|
|
219
|
+
explicit_env_host = env.get("OKSTRA_RUNTIME_HOST", "")
|
|
220
|
+
if not isinstance(explicit_env_host, str):
|
|
221
|
+
raise ValueError("invalid host resolution context JSON")
|
|
222
|
+
signals = frozenset(
|
|
223
|
+
signal
|
|
224
|
+
for capability, signal in (
|
|
225
|
+
("claudeSkillHandoff", "claude-skill-handoff"),
|
|
226
|
+
("tmuxAvailable", "tmux"),
|
|
227
|
+
)
|
|
228
|
+
if capabilities.get(capability) is True
|
|
229
|
+
)
|
|
230
|
+
return HostResolutionContext(explicit_env_host, signals)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _unclaimed_host_message(registry: HostAdapterRegistry, command: str) -> str:
|
|
234
|
+
runtime_flag = "--runtime" if command in {
|
|
235
|
+
"install", "ensure-installed", "doctor"
|
|
236
|
+
} else "--lead-runtime"
|
|
237
|
+
allowed = "|".join(registry.ids())
|
|
238
|
+
return (
|
|
239
|
+
f"host unknown: set OKSTRA_RUNTIME_HOST={allowed} or pass "
|
|
240
|
+
f"{runtime_flag} explicitly."
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _launch_plan_payload(
|
|
245
|
+
registry: HostAdapterRegistry,
|
|
246
|
+
args: argparse.Namespace,
|
|
247
|
+
) -> dict[str, object]:
|
|
248
|
+
adapter = registry.resolve(args.host)
|
|
249
|
+
context = HostSessionContext(
|
|
250
|
+
host_id=adapter.descriptor.id,
|
|
251
|
+
entry_mode=args.entry_mode,
|
|
252
|
+
available_functions=frozenset(),
|
|
253
|
+
interaction_surface="terminal",
|
|
254
|
+
)
|
|
255
|
+
project_root, request_argv = _launch_request(args, adapter.descriptor.id)
|
|
256
|
+
request = LeadStartRequest(project_root, "", request_argv)
|
|
257
|
+
plan = start_run(request, context, adapter)
|
|
258
|
+
return {"ok": True, "_project_root": str(project_root), **asdict(plan)}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _resume_plan_payload(
|
|
262
|
+
registry: HostAdapterRegistry,
|
|
263
|
+
args: argparse.Namespace,
|
|
264
|
+
) -> dict[str, object]:
|
|
265
|
+
adapter = registry.resolve(args.host)
|
|
266
|
+
context = HostSessionContext(
|
|
267
|
+
host_id=adapter.descriptor.id,
|
|
268
|
+
entry_mode=args.entry_mode,
|
|
269
|
+
available_functions=frozenset(),
|
|
270
|
+
interaction_surface="terminal",
|
|
271
|
+
)
|
|
272
|
+
project_root = Path(args.project_root)
|
|
273
|
+
request = LeadResumeRequest(
|
|
274
|
+
project_root,
|
|
275
|
+
args.task_type,
|
|
276
|
+
args.session_id,
|
|
277
|
+
tuple(args.request_arg),
|
|
278
|
+
)
|
|
279
|
+
plan = resume_run(request, context, adapter)
|
|
280
|
+
return {"ok": True, "_project_root": str(project_root), **asdict(plan)}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _launch_request(
|
|
284
|
+
args: argparse.Namespace,
|
|
285
|
+
canonical_host_id: str,
|
|
286
|
+
) -> tuple[Path, tuple[str, ...]]:
|
|
287
|
+
if not args.prepared_launch_json:
|
|
288
|
+
return Path(args.project_root), ()
|
|
289
|
+
try:
|
|
290
|
+
prepared = json.loads(args.prepared_launch_json)
|
|
291
|
+
except json.JSONDecodeError as exc:
|
|
292
|
+
raise ValueError("invalid prepared launch JSON") from exc
|
|
293
|
+
if not isinstance(prepared, dict):
|
|
294
|
+
raise ValueError("prepared launch JSON must be an object")
|
|
295
|
+
if prepared.get("leadRuntime") != canonical_host_id:
|
|
296
|
+
raise ValueError("prepared launch runtime does not match selected host")
|
|
297
|
+
project_root = prepared.get("projectRoot")
|
|
298
|
+
request_argv = prepared.get("launchRequestArgv")
|
|
299
|
+
if not isinstance(project_root, str) or not project_root:
|
|
300
|
+
raise ValueError("prepared launch is missing projectRoot")
|
|
301
|
+
if not isinstance(request_argv, list) or not all(
|
|
302
|
+
isinstance(arg, str) for arg in request_argv
|
|
303
|
+
):
|
|
304
|
+
raise ValueError("prepared launch is missing launchRequestArgv")
|
|
305
|
+
return Path(project_root), tuple(request_argv)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _write_null_launch_plan(payload: dict[str, object]) -> None:
|
|
309
|
+
fields = [
|
|
310
|
+
payload["_project_root"],
|
|
311
|
+
payload["sandbox_note"],
|
|
312
|
+
str(len(payload["env"])),
|
|
313
|
+
]
|
|
314
|
+
fields.extend(
|
|
315
|
+
f"{key}={value}" for key, value in payload["env"].items()
|
|
316
|
+
)
|
|
317
|
+
fields.extend(payload["argv"])
|
|
318
|
+
for field in fields:
|
|
319
|
+
value = str(field)
|
|
320
|
+
if "\0" in value:
|
|
321
|
+
raise ValueError("launch plan field contains a null byte")
|
|
322
|
+
sys.stdout.write(value + "\0")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _json_default(value: object) -> object:
|
|
326
|
+
if isinstance(value, (frozenset, set)):
|
|
327
|
+
return sorted(value)
|
|
328
|
+
if isinstance(value, Path):
|
|
329
|
+
return str(value)
|
|
330
|
+
raise TypeError(f"not JSON serializable: {type(value).__name__}")
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
raise SystemExit(main())
|
|
@@ -293,20 +293,32 @@ def decide_scope(
|
|
|
293
293
|
if not prev_base_sha or prev_base_sha != cur_base_sha:
|
|
294
294
|
return IncrementalDecision(
|
|
295
295
|
"full", [], [],
|
|
296
|
-
|
|
296
|
+
"the branch this plan was written against has moved on "
|
|
297
|
+
f"({prev_base_sha or 'unrecorded'} -> {cur_base_sha or 'unrecorded'}), "
|
|
298
|
+
"so none of the prior stages can be reused as they stand",
|
|
297
299
|
)
|
|
298
300
|
if not impacted_stages:
|
|
299
|
-
return IncrementalDecision(
|
|
301
|
+
return IncrementalDecision(
|
|
302
|
+
"full", [], [],
|
|
303
|
+
"no stage of the prior plan could be tied to the answers, so there is "
|
|
304
|
+
"nothing to narrow the rework down to",
|
|
305
|
+
)
|
|
300
306
|
all_stages = {num for num, _ in stages}
|
|
301
307
|
closure = downstream_stage_closure(stages, set(impacted_stages))
|
|
302
308
|
if len(closure) * 2 > len(all_stages):
|
|
303
309
|
return IncrementalDecision(
|
|
304
310
|
"full", [], [],
|
|
305
|
-
f"
|
|
311
|
+
f"the answers reach {len(closure)} of the plan's {len(all_stages)} stages — "
|
|
312
|
+
f"past the {int(cutoff_ratio * 100)}% mark where replanning outright costs "
|
|
313
|
+
"less than tracking what carried over",
|
|
306
314
|
)
|
|
307
315
|
reverify = sorted(closure)
|
|
308
316
|
carry = sorted(all_stages - closure)
|
|
309
|
-
return IncrementalDecision(
|
|
317
|
+
return IncrementalDecision(
|
|
318
|
+
"incremental", reverify, carry,
|
|
319
|
+
f"the answers reach {len(reverify)} of the plan's {len(all_stages)} stages; "
|
|
320
|
+
"the rest is reused as written",
|
|
321
|
+
)
|
|
310
322
|
|
|
311
323
|
|
|
312
324
|
def _preview_result(args) -> dict:
|