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,140 @@
1
+ """Assemble the request every provider strategy then takes on trust.
2
+
3
+ A strategy translates a ``WorkerExecRequest`` into its CLI's syntax and nothing
4
+ more: it does not resolve paths, does not decide what may be written, and does
5
+ not know which role it is running. Whoever builds the request answers all
6
+ three, so the answers live here — importable — rather than inside a dispatch
7
+ entrypoint where the next caller would have to copy them.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import subprocess
13
+ from pathlib import Path
14
+
15
+ from .domain.worker_exec import ExecutionPolicy, WorkerExecRequest
16
+ from .domain.worker_role import role_spec
17
+
18
+ # Carried over from the codex wrapper, where it was one provider's rule: cargo's
19
+ # package-cache flock and its registry live outside every workspace, so a
20
+ # verifier bounded to the tree alone fails each `cargo build/test/clippy` on the
21
+ # global lock. The rule follows the role rather than the provider now — every
22
+ # provider's verifier receives these — so the name no longer says codex. Set it
23
+ # to a colon-separated list of absolute paths to override, or to empty to
24
+ # disable.
25
+ VERIFIER_EXTRA_DIRS_ENV = "OKSTRA_VERIFIER_EXTRA_DIRS"
26
+
27
+ _VERIFIER_ROLE = "verifier"
28
+
29
+
30
+ def build_request(
31
+ *,
32
+ prompt_text: str,
33
+ model: str,
34
+ project_root: Path,
35
+ worktree_path: Path | None,
36
+ role: str,
37
+ idle_timeout_seconds: int,
38
+ ) -> WorkerExecRequest:
39
+ """One dispatch, with every value the strategies will not re-derive.
40
+
41
+ Paths are resolved here because nothing downstream resolves them: the
42
+ strategies hand what they are given straight to a CLI, so an unresolved
43
+ `..` or symlink would survive into the sandbox boundary itself.
44
+ """
45
+ root = project_root.resolve()
46
+ worktree = worktree_path.resolve() if worktree_path is not None else None
47
+ return WorkerExecRequest(
48
+ prompt_text=prompt_text,
49
+ model=model,
50
+ project_root=root,
51
+ worktree_path=worktree,
52
+ # Nobody is at the keyboard to answer an approval prompt, so the gate is
53
+ # open and the boundary comes from the write scope instead.
54
+ policy=ExecutionPolicy(
55
+ auto_approve=True, write_scope=write_scope(root, worktree, role)
56
+ ),
57
+ idle_timeout_seconds=idle_timeout_seconds,
58
+ )
59
+
60
+
61
+ def write_scope(
62
+ project_root: Path, worktree: Path | None, role: str
63
+ ) -> tuple[Path, ...]:
64
+ """What this worker may write, in the order the CLIs are told it.
65
+
66
+ The order is contract, not taste: strategies translate this tuple into
67
+ repeated `--add-dir` positionally, so it mirrors what the shell wrappers
68
+ claimed. The project root leads (the antigravity wrapper's first
69
+ `--add-dir`; codex names it with `-C` and its strategy skips the repeat),
70
+ then the stage tree, then the git directory that tree commits through.
71
+
72
+ The worktree belongs here as well as in the request: passing it as the
73
+ working directory alone produces an argv with no `--add-dir`, and the worker
74
+ then fails by not writing files rather than by erroring.
75
+ """
76
+ scope = [project_root]
77
+ if worktree is not None:
78
+ scope.append(worktree)
79
+ common_git_dir = git_common_dir(worktree)
80
+ if common_git_dir is not None:
81
+ scope.append(common_git_dir)
82
+ scope.extend(verifier_extra_dirs(role))
83
+ return tuple(scope)
84
+
85
+
86
+ def git_common_dir(worktree: Path) -> Path | None:
87
+ """The main repository's `.git`, which a linked worktree commits through.
88
+
89
+ A linked worktree keeps its index and refs under the main repo's `.git` and
90
+ shares its object database, so a worker bounded to the tree alone cannot
91
+ `git commit` from it. A directory that is no worktree at all simply has
92
+ nothing extra to grant.
93
+ """
94
+ try:
95
+ probe = subprocess.run(
96
+ ["git", "-C", str(worktree), "rev-parse", "--git-common-dir"],
97
+ capture_output=True,
98
+ text=True,
99
+ check=False,
100
+ )
101
+ except OSError:
102
+ return None
103
+ if probe.returncode != 0 or not probe.stdout.strip():
104
+ return None
105
+ candidate = Path(probe.stdout.strip())
106
+ if not candidate.is_absolute():
107
+ candidate = worktree / candidate
108
+ return candidate.resolve() if candidate.is_dir() else None
109
+
110
+
111
+ def verifier_extra_dirs(role: str) -> tuple[Path, ...]:
112
+ """Toolchain directories granted to the verifier role only.
113
+
114
+ Role decides this, not the provider, so it is assembled here rather than in
115
+ a strategy — and not in `WorkerRoleSpec`, which holds values that hold for
116
+ every machine while these paths exist only where the toolchain is.
117
+ """
118
+ if role != _VERIFIER_ROLE:
119
+ return ()
120
+ home = Path.home()
121
+ raw = os.environ.get(VERIFIER_EXTRA_DIRS_ENV, f"{home}/.cargo:{home}/.rustup")
122
+ candidates = (Path(entry) for entry in raw.split(":") if entry)
123
+ return tuple(path.resolve() for path in candidates if path.is_dir())
124
+
125
+
126
+ def idle_timeout(raw: str, role: str) -> int:
127
+ """The explicit budget, or the one the role's spec owns.
128
+
129
+ The default is not written here. The same 1500/600 pair used to live in
130
+ every wrapper and in the python entrypoint, and each copy could drift from
131
+ the policy. Raises ``ValueError`` on a malformed budget; the exit code that
132
+ failure earns belongs to the entrypoint, not to this module.
133
+ """
134
+ if not raw:
135
+ return role_spec(role).idle_timeout_seconds
136
+ if not raw.isdigit():
137
+ raise ValueError(
138
+ f"idle-timeout-seconds must be a non-negative integer: {raw!r}"
139
+ )
140
+ return int(raw)