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.
Files changed (167) hide show
  1. package/README.md +8 -6
  2. package/docs/architecture.md +24 -15
  3. package/docs/cli.md +15 -8
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-inspect.md +2 -2
  6. package/docs/for-ai/skills/okstra-user-response.md +2 -2
  7. package/docs/project-structure-overview.md +22 -14
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/workers/antigravity-worker.md +9 -7
  11. package/runtime/agents/workers/claude-worker.md +1 -0
  12. package/runtime/agents/workers/codex-worker.md +9 -7
  13. package/runtime/agents/workers/grok-worker.md +6 -4
  14. package/runtime/agents/workers/kimi-worker.md +6 -4
  15. package/runtime/bin/lib/okstra/cli.sh +5 -0
  16. package/runtime/bin/lib/okstra/globals.sh +2 -0
  17. package/runtime/bin/lib/okstra/usage.sh +5 -5
  18. package/runtime/bin/okstra-antigravity-exec.sh +1 -340
  19. package/runtime/bin/okstra-claude-exec.sh +1 -178
  20. package/runtime/bin/okstra-codex-exec.sh +1 -467
  21. package/runtime/bin/okstra-provider-exec.py +165 -190
  22. package/runtime/bin/okstra-trace-cleanup.sh +14 -7
  23. package/runtime/bin/okstra-wrapper-status.py +26 -19
  24. package/runtime/bin/okstra.sh +87 -91
  25. package/runtime/prompts/lead/adapters/cmux.md +2 -2
  26. package/runtime/prompts/lead/convergence.md +36 -8
  27. package/runtime/prompts/lead/okstra-lead-contract.md +24 -1
  28. package/runtime/prompts/lead/plan-body-verification.md +9 -1
  29. package/runtime/prompts/lead/report-writer.md +1 -0
  30. package/runtime/prompts/lead/team-contract.md +3 -3
  31. package/runtime/prompts/profiles/_common-contract.md +9 -1
  32. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  33. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  34. package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
  35. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  36. package/runtime/prompts/profiles/implementation-planning.md +6 -4
  37. package/runtime/python/okstra_ctl/adapters/accounting/__init__.py +11 -0
  38. package/runtime/python/okstra_ctl/adapters/accounting/claude_jsonl.py +17 -0
  39. package/runtime/python/okstra_ctl/adapters/accounting/cli_artifact.py +17 -0
  40. package/runtime/python/okstra_ctl/adapters/accounting/unavailable.py +19 -0
  41. package/runtime/python/okstra_ctl/adapters/dispatch/__init__.py +92 -0
  42. package/runtime/python/okstra_ctl/adapters/dispatch/cli_wrapper.py +54 -0
  43. package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +68 -0
  44. package/runtime/python/okstra_ctl/adapters/dispatch/native_team.py +13 -0
  45. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +60 -0
  46. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/manifest.json +1 -0
  47. package/runtime/{prompts/lead/adapters/antigravity.md → python/okstra_ctl/adapters/hosts/antigravity/relay.md} +52 -0
  48. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +292 -0
  49. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +120 -0
  50. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/manifest.json +1 -0
  51. package/runtime/{prompts/lead/adapters/claude-code.md → python/okstra_ctl/adapters/hosts/claude-code/relay.md} +112 -1
  52. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +60 -0
  53. package/runtime/python/okstra_ctl/adapters/hosts/codex/manifest.json +1 -0
  54. package/runtime/{prompts/lead/adapters/codex.md → python/okstra_ctl/adapters/hosts/codex/relay.md} +52 -0
  55. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +72 -0
  56. package/runtime/python/okstra_ctl/adapters/hosts/external/manifest.json +1 -0
  57. package/runtime/{prompts/lead/adapters/external.md → python/okstra_ctl/adapters/hosts/external/relay.md} +53 -1
  58. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +63 -0
  59. package/runtime/python/okstra_ctl/adapters/hosts/grok/manifest.json +1 -0
  60. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +90 -0
  61. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +63 -0
  62. package/runtime/python/okstra_ctl/adapters/hosts/kimi/manifest.json +1 -0
  63. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +90 -0
  64. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +183 -0
  65. package/runtime/python/okstra_ctl/adapters/providers/antigravity/manifest.json +1 -0
  66. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +110 -0
  67. package/runtime/python/okstra_ctl/adapters/providers/claude/manifest.json +1 -0
  68. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +84 -0
  69. package/runtime/python/okstra_ctl/adapters/providers/codex/manifest.json +1 -0
  70. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +76 -0
  71. package/runtime/python/okstra_ctl/adapters/providers/grok/manifest.json +1 -0
  72. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +80 -0
  73. package/runtime/python/okstra_ctl/adapters/providers/kimi/manifest.json +1 -0
  74. package/runtime/python/okstra_ctl/application/__init__.py +1 -0
  75. package/runtime/python/okstra_ctl/application/advance_wizard.py +25 -0
  76. package/runtime/python/okstra_ctl/application/collect_usage.py +15 -0
  77. package/runtime/python/okstra_ctl/application/dispatch_assignments.py +15 -0
  78. package/runtime/python/okstra_ctl/application/resolve_assignment.py +93 -0
  79. package/runtime/python/okstra_ctl/application/resume_run.py +21 -0
  80. package/runtime/python/okstra_ctl/application/start_run.py +21 -0
  81. package/runtime/python/okstra_ctl/codex_dispatch.py +49 -826
  82. package/runtime/python/okstra_ctl/dispatch_core.py +244 -29
  83. package/runtime/python/okstra_ctl/dispatch_state.py +27 -0
  84. package/runtime/python/okstra_ctl/domain/__init__.py +34 -0
  85. package/runtime/python/okstra_ctl/domain/host.py +100 -0
  86. package/runtime/python/okstra_ctl/domain/provider.py +70 -0
  87. package/runtime/python/okstra_ctl/domain/wizard/__init__.py +19 -0
  88. package/runtime/python/okstra_ctl/domain/wizard/interaction.py +140 -0
  89. package/runtime/python/okstra_ctl/domain/worker_exec.py +102 -0
  90. package/runtime/python/okstra_ctl/domain/worker_role.py +34 -0
  91. package/runtime/python/okstra_ctl/domain/worker_stream.py +261 -0
  92. package/runtime/python/okstra_ctl/entrypoints/__init__.py +1 -0
  93. package/runtime/python/okstra_ctl/entrypoints/hosts.py +334 -0
  94. package/runtime/python/okstra_ctl/incremental_scope.py +16 -4
  95. package/runtime/python/okstra_ctl/models.py +54 -269
  96. package/runtime/python/okstra_ctl/ports/__init__.py +15 -0
  97. package/runtime/python/okstra_ctl/ports/host.py +32 -0
  98. package/runtime/python/okstra_ctl/ports/interaction.py +15 -0
  99. package/runtime/python/okstra_ctl/ports/lead_session.py +25 -0
  100. package/runtime/python/okstra_ctl/ports/usage_accounting.py +23 -0
  101. package/runtime/python/okstra_ctl/ports/worker_dispatch.py +32 -0
  102. package/runtime/python/okstra_ctl/registry/__init__.py +13 -0
  103. package/runtime/python/okstra_ctl/registry/factory_loader.py +32 -0
  104. package/runtime/python/okstra_ctl/registry/host_discovery.py +124 -0
  105. package/runtime/python/okstra_ctl/registry/host_registry.py +365 -0
  106. package/runtime/python/okstra_ctl/registry/provider_registry.py +149 -0
  107. package/runtime/python/okstra_ctl/render.py +145 -47
  108. package/runtime/python/okstra_ctl/report_html/common.py +71 -25
  109. package/runtime/python/okstra_ctl/report_html/models.py +5 -0
  110. package/runtime/python/okstra_ctl/report_html/render.py +1 -1
  111. package/runtime/python/okstra_ctl/report_html/run_usage.py +19 -0
  112. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -0
  113. package/runtime/python/okstra_ctl/report_views.py +44 -16
  114. package/runtime/python/okstra_ctl/run.py +80 -58
  115. package/runtime/python/okstra_ctl/session.py +1 -1
  116. package/runtime/python/okstra_ctl/stage_citations.py +52 -15
  117. package/runtime/python/okstra_ctl/team.py +44 -32
  118. package/runtime/python/okstra_ctl/user_response.py +45 -29
  119. package/runtime/python/okstra_ctl/wizard.py +175 -73
  120. package/runtime/python/okstra_ctl/worker_audit_ledger.py +29 -4
  121. package/runtime/python/okstra_ctl/worker_prompt_policy.py +10 -3
  122. package/runtime/python/okstra_ctl/worker_request.py +140 -0
  123. package/runtime/python/okstra_ctl/worker_runner.py +622 -0
  124. package/runtime/python/okstra_token_usage/collect.py +42 -7
  125. package/runtime/python/okstra_token_usage/report.py +42 -0
  126. package/runtime/python/okstra_token_usage/task_totals.py +88 -0
  127. package/runtime/schemas/final-report-v1.0.schema.json +4040 -1066
  128. package/runtime/schemas/final-report-v2.0.schema.json +5673 -1412
  129. package/runtime/skills/okstra-inspect/SKILL.md +1 -2
  130. package/runtime/skills/okstra-inspect/facets/logs.md +5 -5
  131. package/runtime/skills/okstra-inspect/facets/run-audit.md +3 -3
  132. package/runtime/skills/okstra-run/SKILL.md +74 -29
  133. package/runtime/skills/okstra-user-response/SKILL.md +15 -5
  134. package/runtime/templates/implementation-worker-preamble.md +1 -1
  135. package/runtime/templates/report-writer-prompt-preamble.md +1 -0
  136. package/runtime/templates/reports/final-report.template.md +3 -3
  137. package/runtime/templates/reports/html/assets/base.css +8 -4
  138. package/runtime/templates/reports/html/base.template.html +12 -6
  139. package/runtime/templates/reports/html/i18n/en.json +32 -7
  140. package/runtime/templates/reports/html/i18n/ko.json +32 -7
  141. package/runtime/templates/reports/html/macros/forms.html +9 -3
  142. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +14 -19
  143. package/runtime/templates/reports/report.js +59 -26
  144. package/runtime/templates/reports/user-response.template.md +12 -8
  145. package/runtime/templates/worker-prompt-preamble.md +1 -1
  146. package/runtime/validators/validate-implementation-plan-stages.py +17 -22
  147. package/runtime/validators/validate-report-views.py +0 -39
  148. package/runtime/validators/validate-run.py +96 -14
  149. package/runtime/validators/validate_session_conformance.py +69 -3
  150. package/src/cli-registry.mjs +0 -7
  151. package/src/commands/execute/render-bundle.mjs +4 -4
  152. package/src/commands/execute/run.mjs +8 -25
  153. package/src/commands/execute/wizard.mjs +33 -13
  154. package/src/commands/lifecycle/doctor.mjs +10 -10
  155. package/src/commands/lifecycle/install.mjs +53 -30
  156. package/src/commands/lifecycle/preflight.mjs +14 -4
  157. package/src/lib/host-registry-client.mjs +176 -0
  158. package/src/lib/runtime-manifest.mjs +6 -8
  159. package/runtime/bin/okstra-wrapper-agy-stream.py +0 -61
  160. package/runtime/python/okstra_ctl/error_issue.py +0 -640
  161. package/runtime/python/okstra_ctl/issue_signals.py +0 -186
  162. package/runtime/python/okstra_ctl/lead_runtime.py +0 -115
  163. package/runtime/python/okstra_ctl/runner_resolution.py +0 -103
  164. package/runtime/skills/okstra-inspect/facets/error-issue.md +0 -77
  165. package/src/commands/inspect/error-issue.mjs +0 -27
  166. package/src/lib/runtime-readiness.mjs +0 -90
  167. package/src/lib/runtime-resolver.mjs +0 -123
@@ -0,0 +1,120 @@
1
+ """Bundled Claude Code host strategy."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import shutil
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+
10
+ from okstra_ctl.adapters.accounting import ClaudeJsonlUsageAccountingPort
11
+ from okstra_ctl.adapters.hosts.capability_adapter import (
12
+ INTERACTION_FUNCTIONS,
13
+ PENDING_HOST_PORT,
14
+ CapabilityHostAdapter,
15
+ numbered_interaction_port,
16
+ )
17
+ from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
18
+ from okstra_ctl.registry.provider_registry import ProviderRegistry
19
+
20
+
21
+ DESCRIPTOR = HostDescriptor(
22
+ id="claude-code",
23
+ aliases=("claude",),
24
+ native_provider_id="claude",
25
+ required_executables=("claude",),
26
+ launch_mode="lead",
27
+ install_targets=frozenset({"agents", "claude"}),
28
+ agent_id="claude",
29
+ agent_label="Claude Code",
30
+ role="Claude lead",
31
+ dispatch_mode="team",
32
+ session_accounting="claude-jsonl",
33
+ has_claude_session=True,
34
+ relay_contract=str(Path(__file__).with_name("relay.md").resolve()),
35
+ )
36
+
37
+
38
+ def _detect_claude_skill_handoff(
39
+ context: HostResolutionContext,
40
+ ) -> HostClaim | None:
41
+ if "claude-skill-handoff" not in context.signals:
42
+ return None
43
+ return HostClaim(
44
+ DESCRIPTOR.id,
45
+ 100,
46
+ "Claude Code skill handoff selected runtime 'claude-code'.",
47
+ )
48
+
49
+
50
+ def _workspace_trust_checks(context) -> tuple[dict[str, object], ...]:
51
+ project_root = Path(
52
+ os.path.abspath(
53
+ os.environ.get("OKSTRA_PROBE_PROJECT_ROOT", str(Path.cwd()))
54
+ )
55
+ )
56
+ home_dir = Path(os.environ.get("OKSTRA_PROBE_HOME_DIR", str(Path.home())))
57
+ if not (project_root / ".claude" / "settings.local.json").exists():
58
+ return ({"id": "workspace-trust", "status": "not-applicable"},)
59
+ try:
60
+ state = json.loads((home_dir / ".claude.json").read_text(encoding="utf-8"))
61
+ projects = state.get("projects") if isinstance(state, dict) else None
62
+ if not isinstance(projects, dict):
63
+ return _workspace_trust_unavailable()
64
+ project_key = str(project_root)
65
+ if project_key not in projects:
66
+ return _workspace_trust_required()
67
+ project_state = projects[project_key]
68
+ if not isinstance(project_state, dict):
69
+ return _workspace_trust_unavailable()
70
+ accepted = project_state.get("hasTrustDialogAccepted")
71
+ if accepted is True:
72
+ return ({"id": "workspace-trust", "status": "accepted"},)
73
+ if accepted is False or accepted is None:
74
+ return _workspace_trust_required()
75
+ return _workspace_trust_unavailable()
76
+ except (OSError, json.JSONDecodeError):
77
+ return _workspace_trust_unavailable()
78
+
79
+
80
+ def _workspace_trust_required() -> tuple[dict[str, object], ...]:
81
+ return ({
82
+ "id": "workspace-trust",
83
+ "status": "required",
84
+ "action": "restart-and-trust",
85
+ },)
86
+
87
+
88
+ def _workspace_trust_unavailable() -> tuple[dict[str, object], ...]:
89
+ return ({
90
+ "id": "workspace-trust",
91
+ "status": "unavailable",
92
+ "action": "open-project-and-trust",
93
+ },)
94
+
95
+
96
+ def create_adapter(
97
+ *,
98
+ executable_finder: Callable[[str], str | None] = shutil.which,
99
+ interaction_port=None,
100
+ lead_session_port=PENDING_HOST_PORT,
101
+ worker_dispatch_port=PENDING_HOST_PORT,
102
+ usage_accounting_port=ClaudeJsonlUsageAccountingPort(),
103
+ provider_registry: ProviderRegistry | None = None,
104
+ ) -> CapabilityHostAdapter:
105
+ return CapabilityHostAdapter(
106
+ DESCRIPTOR,
107
+ executable_finder=executable_finder,
108
+ interaction_port=(
109
+ interaction_port
110
+ if interaction_port is not None
111
+ else numbered_interaction_port()
112
+ ),
113
+ lead_session_port=lead_session_port,
114
+ worker_dispatch_port=worker_dispatch_port,
115
+ usage_accounting_port=usage_accounting_port,
116
+ supported_functions=INTERACTION_FUNCTIONS,
117
+ detector=_detect_claude_skill_handoff,
118
+ provider_registry=provider_registry,
119
+ readiness_probe=_workspace_trust_checks,
120
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "claude-code", "factory": "adapter.py:create_adapter", "nativeProviderId": "claude", "requiredExecutables": ["claude"], "relayContract": "relay.md"}
@@ -18,6 +18,117 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
18
18
  | `teardownMode` | `teammate-shutdown` |
19
19
  | `leadEventSource` | `claude-jsonl+artifact` |
20
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. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it.
24
+
25
+ ```json
26
+ {
27
+ "schemaVersion": 1,
28
+ "runtime": "claude-code",
29
+ "semanticFunctions": [
30
+ "plain_text_input",
31
+ "native_single_select",
32
+ "native_multi_select",
33
+ "native_question_group"
34
+ ],
35
+ "interactions": {
36
+ "native-single": {
37
+ "function": "AskUserQuestion",
38
+ "input": {
39
+ "callCount": 1,
40
+ "questions": "one",
41
+ "question": "label-with-progress",
42
+ "header": "Q1",
43
+ "options": "all-in-original-order-as-label-description",
44
+ "multiSelect": false
45
+ },
46
+ "response": {
47
+ "envelope": "answers",
48
+ "key": "rendered-question-text",
49
+ "selection": "selected-label",
50
+ "submit": "matching-option-value"
51
+ }
52
+ },
53
+ "native-multi": {
54
+ "function": "AskUserQuestion",
55
+ "input": {
56
+ "callCount": 1,
57
+ "questions": "one",
58
+ "question": "label-with-progress",
59
+ "header": "Q1",
60
+ "options": "all-in-original-order-as-label-description",
61
+ "multiSelect": true
62
+ },
63
+ "response": {
64
+ "envelope": "answers",
65
+ "key": "rendered-question-text",
66
+ "selection": "selected-labels-joined-comma-space",
67
+ "submit": "csv-matching-option-values-in-option-order"
68
+ }
69
+ },
70
+ "native-group": {
71
+ "function": "AskUserQuestion",
72
+ "input": {
73
+ "callCount": 1,
74
+ "questions": "all-in-original-order",
75
+ "question": "label-with-progress",
76
+ "header": "Q<one-based-question-position>",
77
+ "options": "all-in-original-order-as-label-description",
78
+ "multiSelect": "questions[].multi"
79
+ },
80
+ "response": {
81
+ "envelope": "answers",
82
+ "key": "rendered-question-text",
83
+ "selection": "selected-label-or-labels-joined-comma-space",
84
+ "multiValue": "csv-matching-option-values-in-option-order",
85
+ "submit": "compact-step-json-values"
86
+ }
87
+ },
88
+ "numbered-single": {
89
+ "function": "host-text",
90
+ "input": {
91
+ "questions": "one",
92
+ "question": "label-with-progress",
93
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
94
+ },
95
+ "response": { "source": "next-message", "submit": "raw" }
96
+ },
97
+ "numbered-multi": {
98
+ "function": "host-text",
99
+ "input": {
100
+ "questions": "one",
101
+ "question": "label-with-progress",
102
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
103
+ },
104
+ "response": { "source": "next-message", "submit": "raw" }
105
+ },
106
+ "sequential-group": {
107
+ "function": "host-text",
108
+ "input": {
109
+ "questions": "all-one-at-a-time-in-original-order",
110
+ "question": "label-with-progress",
111
+ "options": "all-in-original-order-as-numbered-markdown-label-and-description"
112
+ },
113
+ "response": {
114
+ "source": "next-message-by-question-position",
115
+ "submit": "compact-step-json-raw"
116
+ }
117
+ },
118
+ "plain-text": {
119
+ "function": "host-text",
120
+ "input": { "questions": "one", "question": "label-with-progress" },
121
+ "response": { "source": "next-message", "submit": "raw" }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ For `AskUserQuestion`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix; the `answers` keys are question text, not positions or headers. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only for one to four questions whose option labels are unique and whose complete option lists each contain two to four entries; other prompts use the text mapping so no option is dropped. For a group, key the compact JSON object by the corresponding `questions[].step`; a multi-select question stores its ordered value CSV as one string, not a JSON array. Never use the display header as an answer key. The `Q1`…`Q4` headers only satisfy the host tool's short-header input and carry no domain meaning.
128
+
129
+ For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer. Do not translate numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.
130
+
131
+
21
132
  ## Semantic operation mapping
22
133
 
23
134
  | Operation | Mapping |
@@ -46,7 +157,7 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
46
157
  - Every CLI-wrapper agent remains `inherit` at the Agent layer because its exact `modelExecutionValue` is applied by `okstra-claude-exec.sh`, `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh`, `okstra-grok-exec.sh`, or `okstra-kimi-exec.sh` according to the assignment provider.
47
158
  - Missing or unsupported family-token mapping is a pre-dispatch contract failure. Never inherit the lead model, choose a nearby alias, or switch provider silently.
48
159
  - Every analysis dispatch sets `name: "<workerId>-worker"`; convergence retries append `-reverify-r<N>`, implementation uses the functional `-executor` / `-verifier` suffix, and report writing uses `report-writer`. These values are retained as `agentName` in session JSONL for usage attribution.
49
- - Every Codex / Antigravity prompt includes `**Pane role:** <functional-role>` so the wrapper's optional fifth argument names both its caller pane and trace pane.
160
+ - Every CLI-worker prompt includes `**Pane role:** <functional-role>` so the entrypoint's optional fifth argument carries the dispatched role. That argument selects the dispatch's idle budget — `executor` and `verifier` run silent build+test suites and get 1500s, every other role 600s — and is recorded in the run's status sidecar. Omitting it defaults to `worker`, i.e. the short budget, which reaps a healthy build mid-suite.
50
161
  - The Agent SDK may supply transport metadata through the in-process worker definition, but the persisted semantic prompt body and primary analysis-packet input remain identical to the CLI-wrapper workers after permitted identity/path normalization.
51
162
  - A retry keeps the same Agent `name`. When logging a twice-failed CLI-wrapper attempt, reference both attempts' `bash_ids` and prompt-history paths.
52
163
  - An internally detected contract violation without a specific worker uses `--agent "claude-lead"` in the error-log event.
@@ -0,0 +1,60 @@
1
+ """Bundled Codex 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 CliArtifactUsageAccountingPort
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="codex",
22
+ aliases=("codex",),
23
+ native_provider_id="codex",
24
+ required_executables=("codex",),
25
+ launch_mode="lead",
26
+ install_targets=frozenset({"agents"}),
27
+ agent_id="codex",
28
+ agent_label="Codex CLI",
29
+ role="Codex 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=CliArtifactUsageAccountingPort(),
44
+ provider_registry: ProviderRegistry | None = None,
45
+ ) -> CapabilityHostAdapter:
46
+ return CapabilityHostAdapter(
47
+ DESCRIPTOR,
48
+ executable_finder=executable_finder,
49
+ interaction_port=(
50
+ interaction_port
51
+ if interaction_port is not None
52
+ else numbered_interaction_port()
53
+ ),
54
+ lead_session_port=lead_session_port,
55
+ worker_dispatch_port=worker_dispatch_port,
56
+ usage_accounting_port=usage_accounting_port,
57
+ supported_functions=INTERACTION_FUNCTIONS,
58
+ detector=no_automatic_claim,
59
+ provider_registry=provider_registry,
60
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "codex", "factory": "adapter.py:create_adapter", "nativeProviderId": "codex", "requiredExecutables": ["codex"], "relayContract": "relay.md"}
@@ -18,6 +18,58 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
18
18
  | `teardownMode` | `process-cleanup` |
19
19
  | `leadEventSource` | `lead-events-jsonl` |
20
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": "codex",
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. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.
71
+
72
+
21
73
  ## Semantic operation mapping
22
74
 
23
75
  | Operation | Mapping |
@@ -0,0 +1,72 @@
1
+ """Bundled external 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
+ numbered_interaction_port,
14
+ )
15
+ from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
16
+ from okstra_ctl.registry.provider_registry import ProviderRegistry
17
+
18
+
19
+ DESCRIPTOR = HostDescriptor(
20
+ id="external",
21
+ aliases=("external",),
22
+ native_provider_id="",
23
+ required_executables=(),
24
+ launch_mode="team",
25
+ install_targets=frozenset({"agents"}),
26
+ agent_id="external",
27
+ agent_label="External harness",
28
+ role="Okstra lead",
29
+ dispatch_mode="render-only",
30
+ session_accounting="artifact-only",
31
+ has_claude_session=False,
32
+ relay_contract=str(Path(__file__).with_name("relay.md").resolve()),
33
+ )
34
+
35
+
36
+ def _detect_tmux(context: HostResolutionContext) -> HostClaim | None:
37
+ if "tmux" not in context.signals:
38
+ return None
39
+ return HostClaim(
40
+ DESCRIPTOR.id,
41
+ 10,
42
+ "No Claude Code host signal; tmux is available.",
43
+ )
44
+
45
+
46
+ def create_adapter(
47
+ *,
48
+ executable_finder: Callable[[str], str | None] = shutil.which,
49
+ interaction_port=None,
50
+ lead_session_port=PENDING_HOST_PORT,
51
+ worker_dispatch_port=PENDING_HOST_PORT,
52
+ usage_accounting_port=UnavailableUsageAccountingPort(
53
+ "External host usage accounting is unavailable because no session "
54
+ "transcript or CLI usage artifact contract is registered."
55
+ ),
56
+ provider_registry: ProviderRegistry | None = None,
57
+ ) -> CapabilityHostAdapter:
58
+ return CapabilityHostAdapter(
59
+ DESCRIPTOR,
60
+ executable_finder=executable_finder,
61
+ interaction_port=(
62
+ interaction_port
63
+ if interaction_port is not None
64
+ else numbered_interaction_port()
65
+ ),
66
+ lead_session_port=lead_session_port,
67
+ worker_dispatch_port=worker_dispatch_port,
68
+ usage_accounting_port=usage_accounting_port,
69
+ supported_functions=INTERACTION_FUNCTIONS,
70
+ detector=_detect_tmux,
71
+ provider_registry=provider_registry,
72
+ )
@@ -0,0 +1 @@
1
+ {"schemaVersion": 1, "id": "external", "factory": "adapter.py:create_adapter", "nativeProviderId": "", "requiredExecutables": [], "relayContract": "relay.md"}
@@ -18,6 +18,58 @@ This adapter maps the neutral Okstra lead operations to a generic host using Oks
18
18
  | `teardownMode` | `pane-teardown` |
19
19
  | `leadEventSource` | `lead-events-jsonl` |
20
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": "external",
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. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.
71
+
72
+
21
73
  ## Semantic operation mapping
22
74
 
23
75
  | Operation | Mapping |
@@ -39,7 +91,7 @@ This adapter maps the neutral Okstra lead operations to a generic host using Oks
39
91
  - Worker completion is valid only from `workerDispatches[]`, terminal status sidecars, and required Result Paths. Pane creation alone is not completion.
40
92
  - Reverify uses a fresh jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json`, sets `dispatchKind: "reverify-r<N>"`, and dispatches with `okstra team dispatch --project-root <root> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>`.
41
93
  - Report-writer uses a fresh one-job jobs file with `dispatchKind: "report-writer"` and the same schema, then dispatches through `okstra team dispatch --project-root <root> --run-manifest <path> --jobs-file <jobs-file>`.
42
- - Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`. For reverify, set `role` to `worker-reverify-r<N>` for the pane title. The report-writer completion paths include both data.json and the worker-results audit file.
94
+ - Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`. For reverify, set `role` to `worker-reverify-r<N>` — the role selects the dispatch's idle budget and is recorded in the run's status sidecar, so it must name the actual assignment. The report-writer completion paths include both data.json and the worker-results audit file.
43
95
  - After either dispatch, run `okstra team await --project-root <root> --run-manifest <path>` before evaluating terminal status or completion paths.
44
96
 
45
97
  ## Completion, cleanup, and resume
@@ -0,0 +1,63 @@
1
+ """Bundled Grok 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="grok",
22
+ aliases=("grok",),
23
+ native_provider_id="grok",
24
+ required_executables=("grok",),
25
+ launch_mode="lead",
26
+ install_targets=frozenset({"agents"}),
27
+ agent_id="grok",
28
+ agent_label="Grok CLI",
29
+ role="Grok 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
+ "Grok 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": "grok", "factory": "adapter.py:create_adapter", "nativeProviderId": "grok", "requiredExecutables": ["grok"], "relayContract": "relay.md"}