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,90 @@
|
|
|
1
|
+
# Grok Lead Runtime Adapter
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only when the rendered launch prompt selects `leadRuntime=grok`.
|
|
6
|
+
|
|
7
|
+
## Capability declaration
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `runtime` | `grok` |
|
|
12
|
+
| `leadRoleLabel` | `Grok lead` |
|
|
13
|
+
| `userPromptMode` | `host-text` |
|
|
14
|
+
| `workerDispatchBackend` | `mixed` |
|
|
15
|
+
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
|
+
| `sessionAccounting` | `artifact-only` |
|
|
17
|
+
| `resumeMode` | `native-session-id` |
|
|
18
|
+
| `teardownMode` | `process-cleanup` |
|
|
19
|
+
| `leadEventSource` | `lead-events-jsonl` |
|
|
20
|
+
|
|
21
|
+
## Wizard interaction relay
|
|
22
|
+
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. This relay exposes only text input; do not invent a native picker function.
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"schemaVersion": 1,
|
|
28
|
+
"runtime": "grok",
|
|
29
|
+
"semanticFunctions": ["plain_text_input"],
|
|
30
|
+
"interactions": {
|
|
31
|
+
"numbered-single": {
|
|
32
|
+
"function": "host-text",
|
|
33
|
+
"input": {
|
|
34
|
+
"questions": "one",
|
|
35
|
+
"question": "label-with-progress",
|
|
36
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
37
|
+
},
|
|
38
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
39
|
+
},
|
|
40
|
+
"numbered-multi": {
|
|
41
|
+
"function": "host-text",
|
|
42
|
+
"input": {
|
|
43
|
+
"questions": "one",
|
|
44
|
+
"question": "label-with-progress",
|
|
45
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
46
|
+
},
|
|
47
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
48
|
+
},
|
|
49
|
+
"sequential-group": {
|
|
50
|
+
"function": "host-text",
|
|
51
|
+
"input": {
|
|
52
|
+
"questions": "all-one-at-a-time-in-original-order",
|
|
53
|
+
"question": "label-with-progress",
|
|
54
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
55
|
+
},
|
|
56
|
+
"response": {
|
|
57
|
+
"source": "next-message-by-question-position",
|
|
58
|
+
"submit": "compact-step-json-raw"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"plain-text": {
|
|
62
|
+
"function": "host-text",
|
|
63
|
+
"input": { "questions": "one", "question": "label-with-progress" },
|
|
64
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Render every numbered item as its option label followed by its description verbatim, preserving every item and its original order. Submit the next user message unchanged. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`.
|
|
71
|
+
|
|
72
|
+
## Semantic operation mapping
|
|
73
|
+
|
|
74
|
+
| Operation | Mapping |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `read_artifacts` | Read the manifest-provided paths through the current Grok host file interface. |
|
|
77
|
+
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
78
|
+
| `prompt_user` | Ask through the current host text interface and wait for an explicit answer. |
|
|
79
|
+
| `dispatch_worker` | Follow each persisted assignment's `runner` and use the common host dispatch boundary. |
|
|
80
|
+
| `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
|
|
81
|
+
| `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
|
|
82
|
+
| `shutdown_workers` | Clean up only host or process resources owned by this run. |
|
|
83
|
+
| `record_lead_event` | Append the required structured event to `leadEventsPath` and emit the matching progress line. |
|
|
84
|
+
| `collect_usage` | Return explicit unavailable lead usage until Grok registers a session transcript or CLI usage artifact contract. |
|
|
85
|
+
|
|
86
|
+
## Completion, cleanup, and resume
|
|
87
|
+
|
|
88
|
+
- Do not infer the current host from an installed `grok` executable. The runtime must come from an explicit request or current-session declaration.
|
|
89
|
+
- Keep persisted provider, model, runner, and dispatch-kind assignments unchanged.
|
|
90
|
+
- Resume with the persisted Grok session ID when one exists; otherwise resume from Okstra run artifacts.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Bundled Kimi host strategy."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import shutil
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from okstra_ctl.adapters.accounting import UnavailableUsageAccountingPort
|
|
9
|
+
from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
10
|
+
INTERACTION_FUNCTIONS,
|
|
11
|
+
PENDING_HOST_PORT,
|
|
12
|
+
CapabilityHostAdapter,
|
|
13
|
+
no_automatic_claim,
|
|
14
|
+
numbered_interaction_port,
|
|
15
|
+
)
|
|
16
|
+
from okstra_ctl.domain.host import HostDescriptor
|
|
17
|
+
from okstra_ctl.registry.provider_registry import ProviderRegistry
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DESCRIPTOR = HostDescriptor(
|
|
21
|
+
id="kimi",
|
|
22
|
+
aliases=("kimi",),
|
|
23
|
+
native_provider_id="kimi",
|
|
24
|
+
required_executables=("kimi",),
|
|
25
|
+
launch_mode="lead",
|
|
26
|
+
install_targets=frozenset({"agents"}),
|
|
27
|
+
agent_id="kimi",
|
|
28
|
+
agent_label="Kimi CLI",
|
|
29
|
+
role="Kimi lead",
|
|
30
|
+
dispatch_mode="render-only",
|
|
31
|
+
session_accounting="artifact-only",
|
|
32
|
+
has_claude_session=False,
|
|
33
|
+
relay_contract=str(Path(__file__).with_name("relay.md").resolve()),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_adapter(
|
|
38
|
+
*,
|
|
39
|
+
executable_finder: Callable[[str], str | None] = shutil.which,
|
|
40
|
+
interaction_port=None,
|
|
41
|
+
lead_session_port=PENDING_HOST_PORT,
|
|
42
|
+
worker_dispatch_port=PENDING_HOST_PORT,
|
|
43
|
+
usage_accounting_port=UnavailableUsageAccountingPort(
|
|
44
|
+
"Kimi host usage accounting is unavailable because no session "
|
|
45
|
+
"transcript or CLI usage artifact contract is registered."
|
|
46
|
+
),
|
|
47
|
+
provider_registry: ProviderRegistry | None = None,
|
|
48
|
+
) -> CapabilityHostAdapter:
|
|
49
|
+
return CapabilityHostAdapter(
|
|
50
|
+
DESCRIPTOR,
|
|
51
|
+
executable_finder=executable_finder,
|
|
52
|
+
interaction_port=(
|
|
53
|
+
interaction_port
|
|
54
|
+
if interaction_port is not None
|
|
55
|
+
else numbered_interaction_port()
|
|
56
|
+
),
|
|
57
|
+
lead_session_port=lead_session_port,
|
|
58
|
+
worker_dispatch_port=worker_dispatch_port,
|
|
59
|
+
usage_accounting_port=usage_accounting_port,
|
|
60
|
+
supported_functions=INTERACTION_FUNCTIONS,
|
|
61
|
+
detector=no_automatic_claim,
|
|
62
|
+
provider_registry=provider_registry,
|
|
63
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": 1, "id": "kimi", "factory": "adapter.py:create_adapter", "nativeProviderId": "kimi", "requiredExecutables": ["kimi"], "relayContract": "relay.md"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Kimi Lead Runtime Adapter
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
This adapter maps neutral Okstra lead operations to the Kimi CLI. Read it only when the rendered launch prompt selects `leadRuntime=kimi`.
|
|
6
|
+
|
|
7
|
+
## Capability declaration
|
|
8
|
+
|
|
9
|
+
| Field | Value |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `runtime` | `kimi` |
|
|
12
|
+
| `leadRoleLabel` | `Kimi lead` |
|
|
13
|
+
| `userPromptMode` | `host-text` |
|
|
14
|
+
| `workerDispatchBackend` | `mixed` |
|
|
15
|
+
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
|
+
| `sessionAccounting` | `artifact-only` |
|
|
17
|
+
| `resumeMode` | `native-session-id` |
|
|
18
|
+
| `teardownMode` | `process-cleanup` |
|
|
19
|
+
| `leadEventSource` | `lead-events-jsonl` |
|
|
20
|
+
|
|
21
|
+
## Wizard interaction relay
|
|
22
|
+
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. This relay exposes only text input; do not invent a native picker function.
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"schemaVersion": 1,
|
|
28
|
+
"runtime": "kimi",
|
|
29
|
+
"semanticFunctions": ["plain_text_input"],
|
|
30
|
+
"interactions": {
|
|
31
|
+
"numbered-single": {
|
|
32
|
+
"function": "host-text",
|
|
33
|
+
"input": {
|
|
34
|
+
"questions": "one",
|
|
35
|
+
"question": "label-with-progress",
|
|
36
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
37
|
+
},
|
|
38
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
39
|
+
},
|
|
40
|
+
"numbered-multi": {
|
|
41
|
+
"function": "host-text",
|
|
42
|
+
"input": {
|
|
43
|
+
"questions": "one",
|
|
44
|
+
"question": "label-with-progress",
|
|
45
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
46
|
+
},
|
|
47
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
48
|
+
},
|
|
49
|
+
"sequential-group": {
|
|
50
|
+
"function": "host-text",
|
|
51
|
+
"input": {
|
|
52
|
+
"questions": "all-one-at-a-time-in-original-order",
|
|
53
|
+
"question": "label-with-progress",
|
|
54
|
+
"options": "all-in-original-order-as-numbered-markdown-label-and-description"
|
|
55
|
+
},
|
|
56
|
+
"response": {
|
|
57
|
+
"source": "next-message-by-question-position",
|
|
58
|
+
"submit": "compact-step-json-raw"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"plain-text": {
|
|
62
|
+
"function": "host-text",
|
|
63
|
+
"input": { "questions": "one", "question": "label-with-progress" },
|
|
64
|
+
"response": { "source": "next-message", "submit": "raw" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Render every numbered item as its option label followed by its description verbatim, preserving every item and its original order. Submit the next user message unchanged. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`.
|
|
71
|
+
|
|
72
|
+
## Semantic operation mapping
|
|
73
|
+
|
|
74
|
+
| Operation | Mapping |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `read_artifacts` | Read the manifest-provided paths through the current Kimi host file interface. |
|
|
77
|
+
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
78
|
+
| `prompt_user` | Ask through the current host text interface and wait for an explicit answer. |
|
|
79
|
+
| `dispatch_worker` | Follow each persisted assignment's `runner` and use the common host dispatch boundary. |
|
|
80
|
+
| `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
|
|
81
|
+
| `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
|
|
82
|
+
| `shutdown_workers` | Clean up only host or process resources owned by this run. |
|
|
83
|
+
| `record_lead_event` | Append the required structured event to `leadEventsPath` and emit the matching progress line. |
|
|
84
|
+
| `collect_usage` | Return explicit unavailable lead usage until Kimi registers a session transcript or CLI usage artifact contract. |
|
|
85
|
+
|
|
86
|
+
## Completion, cleanup, and resume
|
|
87
|
+
|
|
88
|
+
- Do not infer the current host from an installed `kimi` executable. The runtime must come from an explicit request or current-session declaration.
|
|
89
|
+
- Keep persisted provider, model, runner, and dispatch-kind assignments unchanged.
|
|
90
|
+
- Resume with the persisted Kimi session ID when one exists; otherwise resume from Okstra run artifacts.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Bundled Antigravity provider catalog."""
|
|
2
|
+
from typing import Any, Mapping
|
|
3
|
+
|
|
4
|
+
from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
|
|
5
|
+
from okstra_ctl.domain.worker_exec import (
|
|
6
|
+
STREAM_JSON,
|
|
7
|
+
ExecCommand,
|
|
8
|
+
PolicySupport,
|
|
9
|
+
WorkerExecRequest,
|
|
10
|
+
)
|
|
11
|
+
from okstra_ctl.domain.worker_stream import (
|
|
12
|
+
Result,
|
|
13
|
+
StreamEvent,
|
|
14
|
+
Text,
|
|
15
|
+
ToolCall,
|
|
16
|
+
ToolResult,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# `agy` needs a tier suffix at dispatch time; model_discovery owns that host
|
|
21
|
+
# specific normalization so catalog display values never reach the CLI.
|
|
22
|
+
ANTIGRAVITY = {
|
|
23
|
+
"gemini-3.1-pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro", in_picker=True),
|
|
24
|
+
"gemini 3.1 pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro"),
|
|
25
|
+
"gemini-3.6-flash": ModelSpec(
|
|
26
|
+
"Gemini 3.6 Flash", "gemini-3.6-flash", in_picker=True
|
|
27
|
+
),
|
|
28
|
+
"gemini 3.6 flash": ModelSpec("Gemini 3.6 Flash", "gemini-3.6-flash"),
|
|
29
|
+
"gemini-3.5-flash": ModelSpec(
|
|
30
|
+
"Gemini 3.5 Flash", "gemini-3.5-flash", in_picker=True
|
|
31
|
+
),
|
|
32
|
+
"gemini 3.5 flash": ModelSpec("Gemini 3.5 Flash", "gemini-3.5-flash"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# The wall-clock cap on one print run, carried over from the wrapper. It is
|
|
37
|
+
# deliberately not derived from the idle budget: an analyser legitimately works
|
|
38
|
+
# for far longer than it goes quiet, so folding the idle budget in here would
|
|
39
|
+
# kill a healthy worker mid-run. Idle is the runner's watchdog to enforce.
|
|
40
|
+
_PRINT_TIMEOUT = "7200s"
|
|
41
|
+
|
|
42
|
+
_STEP_UPDATE = "step_update"
|
|
43
|
+
_RESULT = "result"
|
|
44
|
+
_CALL_STARTED = "ACTIVE"
|
|
45
|
+
_CALL_FINISHED = "DONE"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def step_update_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
49
|
+
"""Normalise this CLI's stream-json, which shares no key with the other four.
|
|
50
|
+
|
|
51
|
+
Measured 2026-08-11 against agy 1.1.11. Events are keyed on `event` rather
|
|
52
|
+
than `type`, and progress arrives as one `step_update` per step transition:
|
|
53
|
+
|
|
54
|
+
- `step_type: agent_response` carries the worker's prose in `text_delta`
|
|
55
|
+
(absent on the turns that only planned a tool call).
|
|
56
|
+
- `step_type: tool` arrives twice for one call — `ACTIVE` with the
|
|
57
|
+
arguments, then `DONE` with the tool's output.
|
|
58
|
+
- the closing `result` carries `response`, and an `error` string when
|
|
59
|
+
`status` is anything but SUCCESS. A failed run's `response` is empty, so
|
|
60
|
+
without the error text such a run would leave nothing behind at all.
|
|
61
|
+
"""
|
|
62
|
+
kind = event.get("event")
|
|
63
|
+
if kind == _RESULT:
|
|
64
|
+
return _result_events(event.get(_RESULT))
|
|
65
|
+
if kind != _STEP_UPDATE:
|
|
66
|
+
return ()
|
|
67
|
+
step = event.get(_STEP_UPDATE)
|
|
68
|
+
if not isinstance(step, Mapping):
|
|
69
|
+
return ()
|
|
70
|
+
if step.get("step_type") == "agent_response":
|
|
71
|
+
text = step.get("text_delta")
|
|
72
|
+
return (Text(body=text),) if isinstance(text, str) and text.strip() else ()
|
|
73
|
+
if step.get("step_type") == "tool":
|
|
74
|
+
return _tool_events(step)
|
|
75
|
+
return ()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _tool_events(step: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
79
|
+
info = step.get("tool_info")
|
|
80
|
+
info = info if isinstance(info, Mapping) else {}
|
|
81
|
+
name = str(step.get("tool_name") or info.get("name") or "tool")
|
|
82
|
+
if step.get("state") == _CALL_STARTED:
|
|
83
|
+
return (ToolCall(name=name, detail=_argument(info.get("parameters"))),)
|
|
84
|
+
if step.get("state") != _CALL_FINISHED:
|
|
85
|
+
return ()
|
|
86
|
+
output = str(info.get("output", ""))
|
|
87
|
+
# No outcome field: a `run_command` whose command exited non-zero was
|
|
88
|
+
# measured closing as `DONE` with the failure text in `output` and nothing
|
|
89
|
+
# else to distinguish it, so the outcome is left unreported rather than
|
|
90
|
+
# rendered as success.
|
|
91
|
+
return (ToolResult(body=output, size_bytes=len(output.encode("utf-8"))),)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _argument(parameters: Any) -> str:
|
|
95
|
+
"""The one argument worth a row, without guessing at the parameter names.
|
|
96
|
+
|
|
97
|
+
Parameter keys belong to each tool, not to this CLI — `view_file` names its
|
|
98
|
+
path `AbsolutePath` while `run_command` names its command `CommandLine` —
|
|
99
|
+
so an allowlist of key names would cover only the tools that happened to be
|
|
100
|
+
observed. The first string argument is the subject of every tool measured.
|
|
101
|
+
"""
|
|
102
|
+
if not isinstance(parameters, Mapping):
|
|
103
|
+
return ""
|
|
104
|
+
return next(
|
|
105
|
+
(str(value) for value in parameters.values() if isinstance(value, str) and value),
|
|
106
|
+
"",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _result_events(result: Any) -> tuple[StreamEvent, ...]:
|
|
111
|
+
if not isinstance(result, Mapping):
|
|
112
|
+
return ()
|
|
113
|
+
error = result.get("error")
|
|
114
|
+
failure = (Text(body=error),) if isinstance(error, str) and error.strip() else ()
|
|
115
|
+
response = result.get("response")
|
|
116
|
+
if not isinstance(response, str):
|
|
117
|
+
return failure
|
|
118
|
+
return (*failure, Result(text=response))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class AntigravityExecution:
|
|
122
|
+
"""agy CLI invocation.
|
|
123
|
+
|
|
124
|
+
`--add-dir` defines the workspace rather than widening a default one, so the
|
|
125
|
+
project root is included instead of skipped.
|
|
126
|
+
|
|
127
|
+
The prompt is an argument, not stdin: this CLI does not read stdin.
|
|
128
|
+
|
|
129
|
+
`--dangerously-skip-permissions` is required rather than convenient. Headless
|
|
130
|
+
`--print` has nobody to answer a permission request, so without it every
|
|
131
|
+
command tool is auto-denied and the run still reports SUCCESS with an empty
|
|
132
|
+
response.
|
|
133
|
+
|
|
134
|
+
No flag bounds what this worker may write. `--sandbox` was measured on
|
|
135
|
+
2026-08-10 (agy 1.1.11) and did not block writes outside the `--add-dir`
|
|
136
|
+
workspace through either the shell tool or the file tool — identically to a
|
|
137
|
+
control run without it. It is therefore not passed, and `policy_support`
|
|
138
|
+
reports the gap rather than claiming a boundary that was not observed.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
142
|
+
argv = ["agy", "--print", request.prompt_text, "--model", request.model]
|
|
143
|
+
for directory in request.policy.write_scope:
|
|
144
|
+
argv += ["--add-dir", str(directory)]
|
|
145
|
+
argv += ["--output-format", "stream-json"]
|
|
146
|
+
argv += ["--print-timeout", _PRINT_TIMEOUT]
|
|
147
|
+
if request.policy.auto_approve:
|
|
148
|
+
argv.append("--dangerously-skip-permissions")
|
|
149
|
+
return ExecCommand(
|
|
150
|
+
argv=tuple(argv),
|
|
151
|
+
stdin_text=None,
|
|
152
|
+
stream_format=STREAM_JSON,
|
|
153
|
+
normalise=step_update_events,
|
|
154
|
+
cwd=request.project_root,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def policy_support(self) -> PolicySupport:
|
|
158
|
+
return PolicySupport(
|
|
159
|
+
can_auto_approve=True,
|
|
160
|
+
can_bound_write_scope=False,
|
|
161
|
+
note=(
|
|
162
|
+
"measured 2026-08-10 (agy 1.1.11): --sandbox did not block writes "
|
|
163
|
+
"outside the --add-dir workspace, so no flag bounds this provider"
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def create_provider() -> ProviderSpec:
|
|
169
|
+
return ProviderSpec(
|
|
170
|
+
provider="antigravity",
|
|
171
|
+
display_label="Antigravity",
|
|
172
|
+
models=ANTIGRAVITY,
|
|
173
|
+
default_models={
|
|
174
|
+
role: "gemini-3.1-pro"
|
|
175
|
+
for role in ("lead", "analyser", "critic", "executor", "verifier")
|
|
176
|
+
},
|
|
177
|
+
wrapper="okstra-antigravity-exec.sh",
|
|
178
|
+
supported_roles=frozenset({"lead", "analyser", "critic", "executor", "verifier"}),
|
|
179
|
+
lead_launch=LeadLaunchSpec(
|
|
180
|
+
executable="agy", model_flag="--model", prompt_flag="--prompt-interactive"
|
|
181
|
+
),
|
|
182
|
+
exec_strategy=AntigravityExecution(),
|
|
183
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": 1, "id": "antigravity", "factory": "adapter.py:create_provider"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Bundled Claude provider catalog."""
|
|
2
|
+
from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
|
|
3
|
+
from okstra_ctl.domain.worker_exec import (
|
|
4
|
+
STREAM_JSON,
|
|
5
|
+
ExecCommand,
|
|
6
|
+
PolicySupport,
|
|
7
|
+
WorkerExecRequest,
|
|
8
|
+
)
|
|
9
|
+
from okstra_ctl.domain.worker_stream import content_block_events
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
CLAUDE = {
|
|
13
|
+
"fable": ModelSpec("fable", "fable", in_picker=True),
|
|
14
|
+
"fable-5": ModelSpec("fable-5", "claude-fable-5"),
|
|
15
|
+
"claude-fable-5": ModelSpec("fable-5", "claude-fable-5"),
|
|
16
|
+
"opus": ModelSpec("opus", "opus", in_picker=True),
|
|
17
|
+
"opus-5": ModelSpec("opus-5", "claude-opus-5"),
|
|
18
|
+
"claude-opus-5": ModelSpec("opus-5", "claude-opus-5"),
|
|
19
|
+
"opus-4-8": ModelSpec("opus-4-8", "claude-opus-4-8"),
|
|
20
|
+
"claude-opus-4-8": ModelSpec("opus-4-8", "claude-opus-4-8"),
|
|
21
|
+
"opus-4-7": ModelSpec("opus-4-7", "claude-opus-4-7"),
|
|
22
|
+
"claude-opus-4-7": ModelSpec("opus-4-7", "claude-opus-4-7"),
|
|
23
|
+
"opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
|
|
24
|
+
"claude-opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
|
|
25
|
+
"sonnet": ModelSpec("sonnet", "sonnet", in_picker=True),
|
|
26
|
+
"sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5", in_picker=True),
|
|
27
|
+
"claude-sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5"),
|
|
28
|
+
"sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
|
|
29
|
+
"claude-sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
|
|
30
|
+
"haiku": ModelSpec("haiku", "haiku", in_picker=True),
|
|
31
|
+
"haiku-4-5": ModelSpec("haiku-4-5", "claude-haiku-4-5"),
|
|
32
|
+
"claude-haiku-4-5": ModelSpec("haiku-4-5", "claude-haiku-4-5"),
|
|
33
|
+
"claude-haiku-4-5-20251001": ModelSpec(
|
|
34
|
+
"haiku-4-5", "claude-haiku-4-5-20251001"
|
|
35
|
+
),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Opening the approval gate for a non-interactive run. Without it this CLI
|
|
40
|
+
# auto-denies any tool call outside the seeded allowlist and the worker silently
|
|
41
|
+
# routes around the denial, which reads as a thinner analysis rather than as a
|
|
42
|
+
# failure. The value is taken from the CLI's own help text; it has not been
|
|
43
|
+
# observed end to end, which is why policy_support claims no write boundary.
|
|
44
|
+
_APPROVAL_ARGS = ("--permission-mode", "bypassPermissions")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ClaudeExecution:
|
|
48
|
+
"""claude CLI invocation.
|
|
49
|
+
|
|
50
|
+
Working directory is the project root even when a stage worktree exists:
|
|
51
|
+
this CLI attaches the worktree with `--add-dir` and runs from the root,
|
|
52
|
+
unlike grok/kimi which run inside the worktree.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
56
|
+
argv = ["claude", "-p", "--model", request.model]
|
|
57
|
+
for directory in request.policy.write_scope:
|
|
58
|
+
if directory != request.project_root:
|
|
59
|
+
argv += ["--add-dir", str(directory)]
|
|
60
|
+
if request.policy.auto_approve:
|
|
61
|
+
argv += list(_APPROVAL_ARGS)
|
|
62
|
+
# `--verbose` is mandatory, not cosmetic: Claude Code rejects `--print`
|
|
63
|
+
# combined with `--output-format=stream-json` without it and exits 1 in
|
|
64
|
+
# under a second.
|
|
65
|
+
argv += ["--output-format=stream-json", "--verbose"]
|
|
66
|
+
return ExecCommand(
|
|
67
|
+
argv=tuple(argv),
|
|
68
|
+
stdin_text=request.prompt_text,
|
|
69
|
+
stream_format=STREAM_JSON,
|
|
70
|
+
normalise=content_block_events,
|
|
71
|
+
cwd=request.project_root,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def policy_support(self) -> PolicySupport:
|
|
75
|
+
return PolicySupport(
|
|
76
|
+
can_auto_approve=True,
|
|
77
|
+
can_bound_write_scope=False,
|
|
78
|
+
note=(
|
|
79
|
+
"the approval flag is taken from help text and has not been "
|
|
80
|
+
"observed end to end; no flag is known to bound this provider's "
|
|
81
|
+
"writes, so none is claimed"
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def create_provider() -> ProviderSpec:
|
|
87
|
+
return ProviderSpec(
|
|
88
|
+
provider="claude",
|
|
89
|
+
display_label="Claude",
|
|
90
|
+
models=CLAUDE,
|
|
91
|
+
default_models={
|
|
92
|
+
"lead": "opus",
|
|
93
|
+
"analyser": "opus",
|
|
94
|
+
"critic": "opus",
|
|
95
|
+
"executor": "opus",
|
|
96
|
+
"verifier": "opus",
|
|
97
|
+
"report-writer": "sonnet",
|
|
98
|
+
},
|
|
99
|
+
wrapper="okstra-claude-exec.sh",
|
|
100
|
+
supported_roles=frozenset(
|
|
101
|
+
{"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
|
|
102
|
+
),
|
|
103
|
+
lead_launch=LeadLaunchSpec(
|
|
104
|
+
executable="claude",
|
|
105
|
+
model_flag="--model",
|
|
106
|
+
start_session_id_flag="--session-id",
|
|
107
|
+
resume_session_id_flag="--resume",
|
|
108
|
+
),
|
|
109
|
+
exec_strategy=ClaudeExecution(),
|
|
110
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": 1, "id": "claude", "factory": "adapter.py:create_provider"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Bundled Codex provider catalog."""
|
|
2
|
+
from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
|
|
3
|
+
from okstra_ctl.domain.worker_exec import (
|
|
4
|
+
TEXT,
|
|
5
|
+
ExecCommand,
|
|
6
|
+
PolicySupport,
|
|
7
|
+
WorkerExecRequest,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
CODEX = {
|
|
12
|
+
# ChatGPT-account hosts serve this model without per-token billing.
|
|
13
|
+
"gpt-5.6-sol": ModelSpec("gpt-5.6-sol", "gpt-5.6-sol", None, in_picker=True),
|
|
14
|
+
"gpt-5.6": ModelSpec("gpt-5.6", "gpt-5.6", (5.00, 0.50, 30.0), in_picker=True),
|
|
15
|
+
"gpt-5.5": ModelSpec("gpt-5.5", "gpt-5.5", (5.00, 0.50, 30.0), in_picker=True),
|
|
16
|
+
"gpt-5.4": ModelSpec("gpt-5.4", "gpt-5.4", (2.50, 0.25, 15.00), in_picker=True),
|
|
17
|
+
"gpt-5.4-mini": ModelSpec(
|
|
18
|
+
"gpt-5.4-mini", "gpt-5.4-mini", (0.75, 0.075, 4.50), in_picker=True
|
|
19
|
+
),
|
|
20
|
+
"gpt-5.3-codex": ModelSpec("gpt-5.4", "gpt-5.4"),
|
|
21
|
+
"gpt-5.2": ModelSpec("gpt-5.2", "gpt-5.2", (1.75, 0.175, 14.0)),
|
|
22
|
+
"codex-auto-review": ModelSpec("codex-auto-review", "codex-auto-review"),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CodexExecution:
|
|
27
|
+
"""codex CLI invocation.
|
|
28
|
+
|
|
29
|
+
The sandbox stays `workspace-write` even when the approval gate is opened:
|
|
30
|
+
removing the gate is not the same as removing the boundary. A non-interactive
|
|
31
|
+
codex run under the default `on-request` policy blocks on the first approval
|
|
32
|
+
it wants, and with no TTY to answer it ends the turn with exit 0 and no
|
|
33
|
+
output — a success that produced nothing.
|
|
34
|
+
|
|
35
|
+
Working directory is the project root even when a stage worktree exists:
|
|
36
|
+
this CLI attaches the worktree with `--add-dir` and runs from the root,
|
|
37
|
+
unlike grok/kimi which run inside the worktree.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
41
|
+
argv = ["codex", "exec", "-C", str(request.project_root)]
|
|
42
|
+
for directory in request.policy.write_scope:
|
|
43
|
+
if directory != request.project_root:
|
|
44
|
+
argv += ["--add-dir", str(directory)]
|
|
45
|
+
argv += ["--model", request.model, "--sandbox", "workspace-write"]
|
|
46
|
+
if request.policy.auto_approve:
|
|
47
|
+
argv += ["-c", "approval_policy=never"]
|
|
48
|
+
argv.append("-")
|
|
49
|
+
return ExecCommand(
|
|
50
|
+
argv=tuple(argv),
|
|
51
|
+
stdin_text=request.prompt_text,
|
|
52
|
+
stream_format=TEXT,
|
|
53
|
+
cwd=request.project_root,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def policy_support(self) -> PolicySupport:
|
|
57
|
+
return PolicySupport(can_auto_approve=True, can_bound_write_scope=True)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def create_provider() -> ProviderSpec:
|
|
61
|
+
return ProviderSpec(
|
|
62
|
+
provider="codex",
|
|
63
|
+
display_label="Codex",
|
|
64
|
+
models=CODEX,
|
|
65
|
+
default_models={
|
|
66
|
+
role: "gpt-5.6-sol"
|
|
67
|
+
for role in ("lead", "analyser", "critic", "executor", "verifier", "report-writer")
|
|
68
|
+
},
|
|
69
|
+
wrapper="okstra-codex-exec.sh",
|
|
70
|
+
supported_roles=frozenset(
|
|
71
|
+
{"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
|
|
72
|
+
),
|
|
73
|
+
lead_launch=LeadLaunchSpec(
|
|
74
|
+
executable="codex",
|
|
75
|
+
model_flag="-m",
|
|
76
|
+
# A sandboxed lead cannot reach cmux or write its worker CLI config.
|
|
77
|
+
sandbox_waiver=("-s", "danger-full-access"),
|
|
78
|
+
sandbox_waiver_note=(
|
|
79
|
+
"codex will start without its filesystem and network sandbox, "
|
|
80
|
+
"which okstra needs so the lead can reach cmux and start worker CLIs."
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
exec_strategy=CodexExecution(),
|
|
84
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": 1, "id": "codex", "factory": "adapter.py:create_provider"}
|