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.
Files changed (112) hide show
  1. package/README.md +7 -5
  2. package/docs/architecture.md +12 -7
  3. package/docs/cli.md +8 -5
  4. package/docs/project-structure-overview.md +7 -5
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/agents/workers/claude-worker.md +1 -0
  8. package/runtime/bin/lib/okstra/cli.sh +5 -0
  9. package/runtime/bin/lib/okstra/globals.sh +2 -0
  10. package/runtime/bin/lib/okstra/usage.sh +5 -5
  11. package/runtime/bin/okstra.sh +87 -91
  12. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  13. package/runtime/prompts/lead/okstra-lead-contract.md +1 -0
  14. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  15. package/runtime/python/okstra_ctl/adapters/accounting/__init__.py +11 -0
  16. package/runtime/python/okstra_ctl/adapters/accounting/claude_jsonl.py +17 -0
  17. package/runtime/python/okstra_ctl/adapters/accounting/cli_artifact.py +17 -0
  18. package/runtime/python/okstra_ctl/adapters/accounting/unavailable.py +19 -0
  19. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +92 -0
  20. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +54 -0
  21. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +68 -0
  22. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  23. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +60 -0
  24. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/manifest.json +1 -0
  25. package/runtime/{prompts/lead/adapters/antigravity.md → python/okstra_ctl/adapters/hosts/antigravity/relay.md} +52 -0
  26. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +292 -0
  27. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +120 -0
  28. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -0
  29. package/runtime/{prompts/lead/adapters/claude-code.md → python/okstra_ctl/adapters/hosts/claude-code/relay.md} +111 -0
  30. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +60 -0
  31. package/runtime/python/okstra_ctl/adapters/hosts/codex/manifest.json +1 -0
  32. package/runtime/{prompts/lead/adapters/codex.md → python/okstra_ctl/adapters/hosts/codex/relay.md} +52 -0
  33. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +72 -0
  34. package/runtime/python/okstra_ctl/adapters/hosts/external/manifest.json +1 -0
  35. package/runtime/{prompts/lead/adapters/external.md → python/okstra_ctl/adapters/hosts/external/relay.md} +52 -0
  36. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +63 -0
  37. package/runtime/python/okstra_ctl/adapters/hosts/grok/manifest.json +1 -0
  38. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +90 -0
  39. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +63 -0
  40. package/runtime/python/okstra_ctl/adapters/hosts/kimi/manifest.json +1 -0
  41. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +90 -0
  42. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +35 -0
  43. package/runtime/python/okstra_ctl/adapters/providers/antigravity/manifest.json +1 -0
  44. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +55 -0
  45. package/runtime/python/okstra_ctl/adapters/providers/claude/manifest.json +1 -0
  46. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +43 -0
  47. package/runtime/python/okstra_ctl/adapters/providers/codex/manifest.json +1 -0
  48. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +32 -0
  49. package/runtime/python/okstra_ctl/adapters/providers/grok/manifest.json +1 -0
  50. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +38 -0
  51. package/runtime/python/okstra_ctl/adapters/providers/kimi/manifest.json +1 -0
  52. package/runtime/python/okstra_ctl/application/__init__.py +1 -0
  53. package/runtime/python/okstra_ctl/application/advance_wizard.py +25 -0
  54. package/runtime/python/okstra_ctl/application/collect_usage.py +15 -0
  55. package/runtime/python/okstra_ctl/application/dispatch_assignments.py +15 -0
  56. package/runtime/python/okstra_ctl/application/resolve_assignment.py +93 -0
  57. package/runtime/python/okstra_ctl/application/resume_run.py +21 -0
  58. package/runtime/python/okstra_ctl/application/start_run.py +21 -0
  59. package/runtime/python/okstra_ctl/codex_dispatch.py +49 -826
  60. package/runtime/python/okstra_ctl/dispatch_core.py +240 -29
  61. package/runtime/python/okstra_ctl/dispatch_state.py +17 -0
  62. package/runtime/python/okstra_ctl/domain/__init__.py +34 -0
  63. package/runtime/python/okstra_ctl/domain/host.py +100 -0
  64. package/runtime/python/okstra_ctl/domain/provider.py +66 -0
  65. package/runtime/python/okstra_ctl/domain/wizard/__init__.py +19 -0
  66. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +140 -0
  67. package/runtime/python/okstra_ctl/entrypoints/__init__.py +1 -0
  68. package/runtime/python/okstra_ctl/entrypoints/hosts.py +334 -0
  69. package/runtime/python/okstra_ctl/models.py +54 -269
  70. package/runtime/python/okstra_ctl/ports/__init__.py +15 -0
  71. package/runtime/python/okstra_ctl/ports/host.py +32 -0
  72. package/runtime/python/okstra_ctl/ports/interaction.py +15 -0
  73. package/runtime/python/okstra_ctl/ports/lead_session.py +25 -0
  74. package/runtime/python/okstra_ctl/ports/usage_accounting.py +23 -0
  75. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +32 -0
  76. package/runtime/python/okstra_ctl/registry/__init__.py +13 -0
  77. package/runtime/python/okstra_ctl/registry/factory_loader.py +32 -0
  78. package/runtime/python/okstra_ctl/registry/host_discovery.py +124 -0
  79. package/runtime/python/okstra_ctl/registry/host_registry.py +365 -0
  80. package/runtime/python/okstra_ctl/registry/provider_registry.py +149 -0
  81. package/runtime/python/okstra_ctl/render.py +145 -47
  82. package/runtime/python/okstra_ctl/run.py +80 -58
  83. package/runtime/python/okstra_ctl/session.py +1 -1
  84. package/runtime/python/okstra_ctl/team.py +44 -32
  85. package/runtime/python/okstra_ctl/wizard.py +162 -64
  86. package/runtime/python/okstra_ctl/worker_audit_ledger.py +29 -4
  87. package/runtime/python/okstra_token_usage/collect.py +34 -6
  88. package/runtime/schemas/final-report-v1.0.schema.json +3989 -1085
  89. package/runtime/schemas/final-report-v2.0.schema.json +5622 -1451
  90. package/runtime/skills/okstra-run/SKILL.md +73 -28
  91. package/runtime/templates/implementation-worker-preamble.md +1 -1
  92. package/runtime/templates/reports/final-report.template.md +3 -3
  93. package/runtime/templates/reports/html/i18n/en.json +3 -1
  94. package/runtime/templates/reports/html/i18n/ko.json +3 -1
  95. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +1 -1
  96. package/runtime/templates/worker-prompt-preamble.md +1 -1
  97. package/runtime/validators/validate-implementation-plan-stages.py +17 -22
  98. package/runtime/validators/validate-report-views.py +0 -39
  99. package/runtime/validators/validate-run.py +8 -7
  100. package/runtime/validators/validate_session_conformance.py +7 -2
  101. package/src/commands/execute/render-bundle.mjs +4 -4
  102. package/src/commands/execute/run.mjs +8 -25
  103. package/src/commands/execute/wizard.mjs +33 -13
  104. package/src/commands/lifecycle/doctor.mjs +10 -10
  105. package/src/commands/lifecycle/install.mjs +53 -30
  106. package/src/commands/lifecycle/preflight.mjs +14 -4
  107. package/src/lib/host-registry-client.mjs +176 -0
  108. package/src/lib/runtime-manifest.mjs +6 -8
  109. package/runtime/python/okstra_ctl/lead_runtime.py +0 -115
  110. package/runtime/python/okstra_ctl/runner_resolution.py +0 -103
  111. package/src/lib/runtime-readiness.mjs +0 -90
  112. package/src/lib/runtime-resolver.mjs +0 -123
@@ -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,35 @@
1
+ """Bundled Antigravity provider catalog."""
2
+ from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
3
+
4
+
5
+ # `agy` needs a tier suffix at dispatch time; model_discovery owns that host
6
+ # specific normalization so catalog display values never reach the CLI.
7
+ ANTIGRAVITY = {
8
+ "gemini-3.1-pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro", in_picker=True),
9
+ "gemini 3.1 pro": ModelSpec("Gemini 3.1 Pro", "gemini-3.1-pro"),
10
+ "gemini-3.6-flash": ModelSpec(
11
+ "Gemini 3.6 Flash", "gemini-3.6-flash", in_picker=True
12
+ ),
13
+ "gemini 3.6 flash": ModelSpec("Gemini 3.6 Flash", "gemini-3.6-flash"),
14
+ "gemini-3.5-flash": ModelSpec(
15
+ "Gemini 3.5 Flash", "gemini-3.5-flash", in_picker=True
16
+ ),
17
+ "gemini 3.5 flash": ModelSpec("Gemini 3.5 Flash", "gemini-3.5-flash"),
18
+ }
19
+
20
+
21
+ def create_provider() -> ProviderSpec:
22
+ return ProviderSpec(
23
+ provider="antigravity",
24
+ display_label="Antigravity",
25
+ models=ANTIGRAVITY,
26
+ default_models={
27
+ role: "gemini-3.1-pro"
28
+ for role in ("lead", "analyser", "critic", "executor", "verifier")
29
+ },
30
+ wrapper="okstra-antigravity-exec.sh",
31
+ supported_roles=frozenset({"lead", "analyser", "critic", "executor", "verifier"}),
32
+ lead_launch=LeadLaunchSpec(
33
+ executable="agy", model_flag="--model", prompt_flag="--prompt-interactive"
34
+ ),
35
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "antigravity", "factory": "adapter.py:create_provider"}
@@ -0,0 +1,55 @@
1
+ """Bundled Claude provider catalog."""
2
+ from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
3
+
4
+
5
+ CLAUDE = {
6
+ "fable": ModelSpec("fable", "fable", in_picker=True),
7
+ "fable-5": ModelSpec("fable-5", "claude-fable-5"),
8
+ "claude-fable-5": ModelSpec("fable-5", "claude-fable-5"),
9
+ "opus": ModelSpec("opus", "opus", in_picker=True),
10
+ "opus-5": ModelSpec("opus-5", "claude-opus-5"),
11
+ "claude-opus-5": ModelSpec("opus-5", "claude-opus-5"),
12
+ "opus-4-8": ModelSpec("opus-4-8", "claude-opus-4-8"),
13
+ "claude-opus-4-8": ModelSpec("opus-4-8", "claude-opus-4-8"),
14
+ "opus-4-7": ModelSpec("opus-4-7", "claude-opus-4-7"),
15
+ "claude-opus-4-7": ModelSpec("opus-4-7", "claude-opus-4-7"),
16
+ "opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
17
+ "claude-opus-4-6": ModelSpec("opus-4-6", "claude-opus-4-6"),
18
+ "sonnet": ModelSpec("sonnet", "sonnet", in_picker=True),
19
+ "sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5", in_picker=True),
20
+ "claude-sonnet-5": ModelSpec("sonnet-5", "claude-sonnet-5"),
21
+ "sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
22
+ "claude-sonnet-4-6": ModelSpec("sonnet-4-6", "claude-sonnet-4-6"),
23
+ "haiku": ModelSpec("haiku", "haiku", in_picker=True),
24
+ "haiku-4-5": ModelSpec("haiku-4-5", "claude-haiku-4-5"),
25
+ "claude-haiku-4-5": ModelSpec("haiku-4-5", "claude-haiku-4-5"),
26
+ "claude-haiku-4-5-20251001": ModelSpec(
27
+ "haiku-4-5", "claude-haiku-4-5-20251001"
28
+ ),
29
+ }
30
+
31
+
32
+ def create_provider() -> ProviderSpec:
33
+ return ProviderSpec(
34
+ provider="claude",
35
+ display_label="Claude",
36
+ models=CLAUDE,
37
+ default_models={
38
+ "lead": "opus",
39
+ "analyser": "opus",
40
+ "critic": "opus",
41
+ "executor": "opus",
42
+ "verifier": "opus",
43
+ "report-writer": "sonnet",
44
+ },
45
+ wrapper="okstra-claude-exec.sh",
46
+ supported_roles=frozenset(
47
+ {"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
48
+ ),
49
+ lead_launch=LeadLaunchSpec(
50
+ executable="claude",
51
+ model_flag="--model",
52
+ start_session_id_flag="--session-id",
53
+ resume_session_id_flag="--resume",
54
+ ),
55
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "claude", "factory": "adapter.py:create_provider"}
@@ -0,0 +1,43 @@
1
+ """Bundled Codex provider catalog."""
2
+ from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
3
+
4
+
5
+ CODEX = {
6
+ # ChatGPT-account hosts serve this model without per-token billing.
7
+ "gpt-5.6-sol": ModelSpec("gpt-5.6-sol", "gpt-5.6-sol", None, in_picker=True),
8
+ "gpt-5.6": ModelSpec("gpt-5.6", "gpt-5.6", (5.00, 0.50, 30.0), in_picker=True),
9
+ "gpt-5.5": ModelSpec("gpt-5.5", "gpt-5.5", (5.00, 0.50, 30.0), in_picker=True),
10
+ "gpt-5.4": ModelSpec("gpt-5.4", "gpt-5.4", (2.50, 0.25, 15.00), in_picker=True),
11
+ "gpt-5.4-mini": ModelSpec(
12
+ "gpt-5.4-mini", "gpt-5.4-mini", (0.75, 0.075, 4.50), in_picker=True
13
+ ),
14
+ "gpt-5.3-codex": ModelSpec("gpt-5.4", "gpt-5.4"),
15
+ "gpt-5.2": ModelSpec("gpt-5.2", "gpt-5.2", (1.75, 0.175, 14.0)),
16
+ "codex-auto-review": ModelSpec("codex-auto-review", "codex-auto-review"),
17
+ }
18
+
19
+
20
+ def create_provider() -> ProviderSpec:
21
+ return ProviderSpec(
22
+ provider="codex",
23
+ display_label="Codex",
24
+ models=CODEX,
25
+ default_models={
26
+ role: "gpt-5.6-sol"
27
+ for role in ("lead", "analyser", "critic", "executor", "verifier", "report-writer")
28
+ },
29
+ wrapper="okstra-codex-exec.sh",
30
+ supported_roles=frozenset(
31
+ {"lead", "analyser", "critic", "executor", "verifier", "report-writer"}
32
+ ),
33
+ lead_launch=LeadLaunchSpec(
34
+ executable="codex",
35
+ model_flag="-m",
36
+ # A sandboxed lead cannot reach cmux or write its worker CLI config.
37
+ sandbox_waiver=("-s", "danger-full-access"),
38
+ sandbox_waiver_note=(
39
+ "codex will start without its filesystem and network sandbox, "
40
+ "which okstra needs so the lead can reach cmux and start worker CLIs."
41
+ ),
42
+ ),
43
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "codex", "factory": "adapter.py:create_provider"}
@@ -0,0 +1,32 @@
1
+ """Bundled Grok provider catalog."""
2
+ from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
3
+
4
+
5
+ GROK = {
6
+ "grok-build-0.1": ModelSpec(
7
+ "grok-build-0.1", "grok-build-0.1", (1.00, 0.20, 2.00), in_picker=True
8
+ ),
9
+ "grok-4.5": ModelSpec("grok-4.5", "grok-4.5", (2.00, 0.30, 6.00), in_picker=True),
10
+ }
11
+
12
+ GROK_LEAD_LAUNCH = LeadLaunchSpec(
13
+ executable="grok",
14
+ model_flag="--model",
15
+ resume_session_id_flag="--resume",
16
+ )
17
+
18
+
19
+ def create_provider() -> ProviderSpec:
20
+ return ProviderSpec(
21
+ provider="grok",
22
+ display_label="Grok",
23
+ models=GROK,
24
+ default_models={
25
+ "lead": "grok-build-0.1",
26
+ "analyser": "grok-build-0.1",
27
+ "critic": "grok-4.5",
28
+ },
29
+ wrapper="okstra-grok-exec.sh",
30
+ supported_roles=frozenset({"lead", "analyser", "critic"}),
31
+ lead_launch=GROK_LEAD_LAUNCH,
32
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "grok", "factory": "adapter.py:create_provider"}
@@ -0,0 +1,38 @@
1
+ """Bundled Kimi provider catalog."""
2
+ from okstra_ctl.domain.provider import LeadLaunchSpec, ModelSpec, ProviderSpec
3
+
4
+
5
+ KIMI = {
6
+ "kimi-k2.7-code": ModelSpec(
7
+ "Kimi K2.7 Code", "kimi-k2.7-code", (0.95, 0.19, 4.00), in_picker=True
8
+ ),
9
+ "kimi k2.7 code": ModelSpec("Kimi K2.7 Code", "kimi-k2.7-code"),
10
+ "kimi-for-coding": ModelSpec("Kimi K2.7 Code", "kimi-for-coding", in_picker=True),
11
+ "kimi-k3": ModelSpec("Kimi K3", "kimi-k3", (3.00, 0.30, 15.00), in_picker=True),
12
+ "kimi k3": ModelSpec("Kimi K3", "kimi-k3"),
13
+ "k3": ModelSpec("Kimi K3", "k3", in_picker=True),
14
+ "k3-256k": ModelSpec("Kimi K3 256K", "k3-256k", in_picker=True),
15
+ "kimi k3 256k": ModelSpec("Kimi K3 256K", "k3-256k"),
16
+ }
17
+
18
+ KIMI_LEAD_LAUNCH = LeadLaunchSpec(
19
+ executable="kimi",
20
+ model_flag="--model",
21
+ resume_session_id_flag="--session",
22
+ )
23
+
24
+
25
+ def create_provider() -> ProviderSpec:
26
+ return ProviderSpec(
27
+ provider="kimi",
28
+ display_label="Kimi",
29
+ models=KIMI,
30
+ default_models={
31
+ "lead": "kimi-k2.7-code",
32
+ "analyser": "kimi-k2.7-code",
33
+ "critic": "kimi-k3",
34
+ },
35
+ wrapper="okstra-kimi-exec.sh",
36
+ supported_roles=frozenset({"lead", "analyser", "critic"}),
37
+ lead_launch=KIMI_LEAD_LAUNCH,
38
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "kimi", "factory": "adapter.py:create_provider"}
@@ -0,0 +1 @@
1
+ """Host-neutral application services."""
@@ -0,0 +1,25 @@
1
+ """Plan a wizard prompt through the selected host interaction boundary."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+ from okstra_ctl.domain.host import HostSessionContext
7
+ from okstra_ctl.domain.wizard.interaction import InteractionPlan, WizardPrompt
8
+ from okstra_ctl.ports import InteractionPort
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class PlannedWizardPrompt:
13
+ prompt: WizardPrompt
14
+ interaction: InteractionPlan
15
+
16
+
17
+ def plan_prompt(
18
+ prompt: WizardPrompt,
19
+ context: HostSessionContext,
20
+ interaction_port: InteractionPort,
21
+ ) -> PlannedWizardPrompt:
22
+ return PlannedWizardPrompt(
23
+ prompt=prompt,
24
+ interaction=interaction_port.plan(prompt, context),
25
+ )
@@ -0,0 +1,15 @@
1
+ """Collect a usage snapshot through the selected host boundary."""
2
+ from __future__ import annotations
3
+
4
+ from okstra_ctl.ports.usage_accounting import (
5
+ UsageAccountingPort,
6
+ UsageRequest,
7
+ UsageSnapshot,
8
+ )
9
+
10
+
11
+ def collect_usage(
12
+ request: UsageRequest,
13
+ port: UsageAccountingPort,
14
+ ) -> UsageSnapshot:
15
+ return port.collect(request)
@@ -0,0 +1,15 @@
1
+ """Route one normalized worker-dispatch request through its host port."""
2
+ from __future__ import annotations
3
+
4
+ from ..ports.worker_dispatch import (
5
+ WorkerDispatchPlan,
6
+ WorkerDispatchPort,
7
+ WorkerDispatchRequest,
8
+ )
9
+
10
+
11
+ def dispatch_assignments(
12
+ request: WorkerDispatchRequest,
13
+ port: WorkerDispatchPort,
14
+ ) -> WorkerDispatchPlan:
15
+ return port.build_plan(request)
@@ -0,0 +1,93 @@
1
+ """Resolve provider assignments from host and provider capabilities."""
2
+ from __future__ import annotations
3
+
4
+ from okstra_ctl.domain.host import (
5
+ HostCapabilityMismatch,
6
+ HostDescriptor,
7
+ ProviderUnavailable,
8
+ )
9
+ from okstra_ctl.domain.provider import (
10
+ LeadProviderAssignment,
11
+ ProviderSpec,
12
+ RunnerResolution,
13
+ )
14
+
15
+
16
+ NATIVE_SESSION = "native-session"
17
+ CLI_WRAPPER = "cli-wrapper"
18
+ RUNNER_VALUES = frozenset({NATIVE_SESSION, CLI_WRAPPER})
19
+
20
+
21
+ def resolve_assignment_runner(
22
+ *,
23
+ host: HostDescriptor,
24
+ provider: ProviderSpec,
25
+ role: str,
26
+ requested_runner: str = "",
27
+ ) -> RunnerResolution:
28
+ if role not in provider.supported_roles:
29
+ raise ProviderUnavailable(
30
+ f"provider {provider.provider!r} does not support role {role!r}"
31
+ )
32
+ requested = (requested_runner or "").strip()
33
+ if requested and requested not in RUNNER_VALUES:
34
+ allowed = ", ".join(sorted(RUNNER_VALUES))
35
+ raise HostCapabilityMismatch(
36
+ f"unknown runner {requested!r}. Allowed values: {allowed}"
37
+ )
38
+ is_native = provider.provider == host.native_provider_id
39
+ if requested == NATIVE_SESSION and not is_native:
40
+ raise HostCapabilityMismatch(
41
+ f"host {host.id!r} does not provide native "
42
+ f"{provider.provider!r} execution"
43
+ )
44
+ if requested == NATIVE_SESSION or (not requested and is_native):
45
+ return RunnerResolution(
46
+ NATIVE_SESSION,
47
+ "",
48
+ f"{host.id} provides native {provider.provider} execution",
49
+ )
50
+ if not provider.wrapper:
51
+ raise ProviderUnavailable(
52
+ f"provider {provider.provider!r} has no CLI wrapper for role {role!r}"
53
+ )
54
+ return RunnerResolution(
55
+ CLI_WRAPPER,
56
+ provider.wrapper,
57
+ f"{host.id} uses the registered {provider.provider} CLI wrapper",
58
+ )
59
+
60
+
61
+ def resolve_lead_provider(
62
+ *,
63
+ host_id: str,
64
+ requested_provider: str,
65
+ host_registry,
66
+ provider_registry,
67
+ ) -> LeadProviderAssignment:
68
+ host = host_registry.resolve(host_id).descriptor
69
+ requested = (requested_provider or "").strip().lower()
70
+ if not requested and not host.native_provider_id:
71
+ raise HostCapabilityMismatch(
72
+ f"host {host.id!r} has no native lead provider; select an explicit "
73
+ "provider with lead capability"
74
+ )
75
+ if host.native_provider_id and requested not in {"", host.native_provider_id}:
76
+ raise HostCapabilityMismatch(
77
+ f"{host.agent_label.removesuffix(' CLI')} host requires provider "
78
+ f"{host.native_provider_id!r} for the in-session lead; provider "
79
+ f"{requested!r} must be selected as a worker instead"
80
+ )
81
+ provider_id = requested or host.native_provider_id
82
+ provider = provider_registry.resolve(provider_id)
83
+ resolution = resolve_assignment_runner(
84
+ host=host,
85
+ provider=provider,
86
+ role="lead",
87
+ )
88
+ return LeadProviderAssignment(
89
+ provider=provider.provider,
90
+ runner=resolution.runner,
91
+ wrapper=resolution.wrapper,
92
+ reason=resolution.reason,
93
+ )
@@ -0,0 +1,21 @@
1
+ """Prepare a lead resume plan through a ready host adapter."""
2
+ from __future__ import annotations
3
+
4
+ from okstra_ctl.domain.host import (
5
+ HostSessionContext,
6
+ HostUnavailable,
7
+ LeadResumeRequest,
8
+ LeadSessionPlan,
9
+ )
10
+ from okstra_ctl.ports import HostAdapter
11
+
12
+
13
+ def resume_run(
14
+ request: LeadResumeRequest,
15
+ context: HostSessionContext,
16
+ adapter: HostAdapter,
17
+ ) -> LeadSessionPlan:
18
+ readiness = adapter.probe(context)
19
+ if not readiness.ok:
20
+ raise HostUnavailable(readiness.reason)
21
+ return adapter.lead_session().build_resume(request, context)
@@ -0,0 +1,21 @@
1
+ """Prepare a lead start plan through a ready host adapter."""
2
+ from __future__ import annotations
3
+
4
+ from okstra_ctl.domain.host import (
5
+ HostSessionContext,
6
+ HostUnavailable,
7
+ LeadSessionPlan,
8
+ LeadStartRequest,
9
+ )
10
+ from okstra_ctl.ports import HostAdapter
11
+
12
+
13
+ def start_run(
14
+ request: LeadStartRequest,
15
+ context: HostSessionContext,
16
+ adapter: HostAdapter,
17
+ ) -> LeadSessionPlan:
18
+ readiness = adapter.probe(context)
19
+ if not readiness.ok:
20
+ raise HostUnavailable(readiness.reason)
21
+ return adapter.lead_session().build_start(request, context)