okstra 0.78.2 → 0.80.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.kr.md +3 -3
- package/README.md +3 -3
- package/docs/kr/architecture.md +1 -1
- package/docs/kr/cli.md +18 -1
- package/docs/project-structure-overview.md +15 -2
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +1775 -0
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +404 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-claude-exec.sh +71 -0
- package/runtime/python/okstra_ctl/render.py +11 -0
- package/runtime/python/okstra_ctl/run.py +25 -0
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +4 -1
- package/runtime/skills/okstra-schedule/SKILL.md +1 -1
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/src/cli-registry.mjs +8 -1
- package/src/doctor.mjs +17 -3
- package/src/install.mjs +43 -19
- package/src/render-bundle.mjs +38 -1
- package/src/run.mjs +201 -0
- package/src/runtime-manifest.mjs +49 -12
- package/src/runtime-resolver.mjs +123 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# Host 기반 runtime auto-detection + auto-execution 설계
|
|
2
|
+
|
|
3
|
+
- 날짜: 2026-06-14
|
|
4
|
+
- 상태: 설계 초안
|
|
5
|
+
- 대상: `okstra install`, `okstra doctor`, `okstra run`, lead runtime resolution, runtime manifest
|
|
6
|
+
- 선행 문서:
|
|
7
|
+
- [`2026-06-12-codex-lead-adapter-design.md`](2026-06-12-codex-lead-adapter-design.md)
|
|
8
|
+
- [`2026-06-13-neutral-tmux-lead-adapter-design.md`](2026-06-13-neutral-tmux-lead-adapter-design.md)
|
|
9
|
+
|
|
10
|
+
## 1. 배경
|
|
11
|
+
|
|
12
|
+
현재 runtime 선택은 자동 감지가 아니다. `okstra install` 은 기본값 `claude-code` 를 사용하고, `installed-runtimes.json` 의 `codexAdapter` / `externalAdapter` 는 실제 adapter 실행 가능성이 아니라 install 인자 선택값만 반영한다.
|
|
13
|
+
|
|
14
|
+
확인된 현재 상태:
|
|
15
|
+
|
|
16
|
+
- `installed-runtimes.json` 은 `runtime === "codex" || "all"` 일 때만 `codexAdapter:true`, `runtime === "external" || "all"` 일 때만 `externalAdapter:true` 를 쓴다([runtime-manifest.mjs:8](../../../src/runtime-manifest.mjs:8)).
|
|
17
|
+
- `okstra install` 기본 runtime 은 `claude-code` 다([install.mjs:606](../../../src/install.mjs:606)).
|
|
18
|
+
- `okstra doctor` 기본 runtime 도 `claude-code` 다([doctor.mjs:37](../../../src/doctor.mjs:37)).
|
|
19
|
+
- Python prepare layer 는 `leadRuntime != "claude-code"` 이고 non-render 이면 막는다([run.py:2053](../../../scripts/okstra_ctl/run.py:2053)).
|
|
20
|
+
- `--lead-runtime` 기본값도 `claude-code` 다([run.py:2416](../../../scripts/okstra_ctl/run.py:2416)).
|
|
21
|
+
- `okstra team` 은 `leadRuntime=external` bundle 을 받아 tmux-pane worker dispatch/await/teardown 을 수행하는 Node→Python CLI 경계다([team.mjs:4](../../../src/team.mjs:4)).
|
|
22
|
+
|
|
23
|
+
사용자 결정:
|
|
24
|
+
|
|
25
|
+
1. 자동 감지 범위는 **실행까지 자동**이다.
|
|
26
|
+
2. runtime 우선순위는 **host 기반**이다.
|
|
27
|
+
3. fallback 은 **safe fallback** 이다. 의도와 다른 runtime 으로 조용히 넘어가지 않는다.
|
|
28
|
+
|
|
29
|
+
## 2. 문제
|
|
30
|
+
|
|
31
|
+
현재 이름과 동작이 서로 맞지 않는다.
|
|
32
|
+
|
|
33
|
+
- `codexAdapter:false` / `externalAdapter:false` 는 adapter 미설치 또는 CLI 부재처럼 보이지만, 실제 의미는 "이번 install 선택값이 `claude-code`" 이다.
|
|
34
|
+
- 공유 runtime 은 항상 `~/.okstra/bin` 으로 복사되므로 wrapper 파일 존재와 manifest flag 가 일치하지 않는다.
|
|
35
|
+
- 사용자는 `okstra install` / `okstra run` 이 현재 host 를 이해한다고 기대하지만, 실제로는 `claude-code` 로 고정된다.
|
|
36
|
+
- `external` 실행은 Python non-render gate 때문에 직접 full-run 으로 열 수 없고, Node driver 가 render-only prepare → team dispatch/await 를 orchestration 해야 한다.
|
|
37
|
+
|
|
38
|
+
## 3. 결정
|
|
39
|
+
|
|
40
|
+
`auto` 를 runtime 값 자체가 아니라 **resolver 입력값**으로 도입한다.
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
explicit runtime: claude-code | codex | external | all
|
|
44
|
+
auto request: host/context 를 감지해 위 runtime 중 하나로 resolve
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
새 단일 참조점:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
src/runtime-resolver.mjs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
역할:
|
|
54
|
+
|
|
55
|
+
1. host signal 수집
|
|
56
|
+
2. 설치 자산과 실행 capability 검사
|
|
57
|
+
3. command 별 허용 runtime 계산
|
|
58
|
+
4. safe fallback 적용
|
|
59
|
+
5. 최종 runtime, 이유, 경고를 구조화해서 반환
|
|
60
|
+
|
|
61
|
+
예상 API:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
resolveRuntime({
|
|
65
|
+
requestedRuntime: "auto" | "claude-code" | "codex" | "external" | "all",
|
|
66
|
+
command: "install" | "ensure-installed" | "doctor" | "render" | "run" | "dispatch",
|
|
67
|
+
env: process.env,
|
|
68
|
+
paths,
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
반환:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"requestedRuntime": "auto",
|
|
77
|
+
"resolvedRuntime": "external",
|
|
78
|
+
"host": "generic-terminal",
|
|
79
|
+
"confidence": "high",
|
|
80
|
+
"reason": "No Claude Code or Codex host signal; tmux is available.",
|
|
81
|
+
"fallbackFrom": null,
|
|
82
|
+
"warnings": []
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
핵심 규칙:
|
|
87
|
+
|
|
88
|
+
- 명시 runtime 은 자동 override 하지 않는다.
|
|
89
|
+
- `auto` 일 때만 감지한다.
|
|
90
|
+
- resolver 결과는 install manifest, run manifest, doctor JSON, console output 에 남긴다.
|
|
91
|
+
- `codexAdapter` / `externalAdapter` 는 새 manifest 에서 쓰지 않는다. v1 manifest read compatibility 에서만 해석한다.
|
|
92
|
+
|
|
93
|
+
## 4. 용어
|
|
94
|
+
|
|
95
|
+
| 용어 | 의미 |
|
|
96
|
+
|---|---|
|
|
97
|
+
| install runtime | 어떤 asset set 을 설치/보장할지 결정하는 값 |
|
|
98
|
+
| lead runtime | 이번 run 의 lead adapter 정체성: `claude-code`, `codex`, `external` |
|
|
99
|
+
| host | 현재 `okstra` 를 실행 중인 환경: Claude Code session, Codex CLI/session, generic terminal |
|
|
100
|
+
| dispatch backend | worker 를 어떻게 실행하는지: Claude Teams, CLI wrapper, tmux pane |
|
|
101
|
+
| capability | 현재 시점에서 실행 가능한 조건: CLI binary, tmux, installed assets, phase support |
|
|
102
|
+
| safe fallback | 같은 intent 를 보존할 수 있을 때만 fallback 하는 정책 |
|
|
103
|
+
|
|
104
|
+
## 5. Host 감지
|
|
105
|
+
|
|
106
|
+
Resolver 는 allowlist 기반 signal 만 사용한다. 불명확한 signal 은 runtime 을 결정하지 않고 warning 으로 남긴다.
|
|
107
|
+
|
|
108
|
+
감지 우선순위:
|
|
109
|
+
|
|
110
|
+
1. **Explicit runtime**
|
|
111
|
+
- CLI flag 또는 env override 가 `auto` 가 아니면 그대로 사용한다.
|
|
112
|
+
- 예: `--lead-runtime external`, `OKSTRA_LEAD_RUNTIME=codex`.
|
|
113
|
+
2. **Explicit host override**
|
|
114
|
+
- 새 env `OKSTRA_RUNTIME_HOST=claude-code|codex|external`.
|
|
115
|
+
- 테스트와 wrapper 가 안정적으로 host 를 주입할 수 있는 public contract 다.
|
|
116
|
+
3. **Claude Code host**
|
|
117
|
+
- Claude Code slash skill 또는 agent contract 가 `OKSTRA_RUNTIME_HOST=claude-code` 를 넘기는 방식이 primary signal 이다.
|
|
118
|
+
- 환경 fingerprint 는 보조 signal 로만 사용한다.
|
|
119
|
+
4. **Codex host**
|
|
120
|
+
- Codex wrapper/entrypoint 가 `OKSTRA_RUNTIME_HOST=codex` 를 넘기는 방식이 primary signal 이다.
|
|
121
|
+
- `codex` executable 존재만으로 Codex host 라고 판단하지 않는다. binary availability 는 capability 일 뿐 host 가 아니다.
|
|
122
|
+
5. **External host**
|
|
123
|
+
- 명시 host 가 없고, 일반 terminal 에서 `tmux` 가 사용 가능하면 `external`.
|
|
124
|
+
6. **Unknown**
|
|
125
|
+
- host 와 capability 가 모두 부족하면 fail-fast.
|
|
126
|
+
|
|
127
|
+
이 설계는 "binary가 설치되어 있으니 그 runtime으로 실행" 을 피한다. host 기반 결정과 capability 검사를 분리해야 예측 가능하다.
|
|
128
|
+
|
|
129
|
+
## 6. Safe fallback matrix
|
|
130
|
+
|
|
131
|
+
Fallback 은 `requestedRuntime:auto` 에서만 동작한다. 명시 runtime 은 실패를 사용자에게 보여주고 중단한다.
|
|
132
|
+
|
|
133
|
+
| resolved candidate | 실행 불가 이유 | fallback |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `claude-code` | Claude Code host signal 은 있으나 Claude assets 누락 | `ensure-installed --runtime claude-code` 로 보강 후 재시도 |
|
|
136
|
+
| `claude-code` | Claude Code host 가 아닌 것으로 판정 | `external` if tmux 가능, 아니면 fail |
|
|
137
|
+
| `codex` | Codex host signal 은 있으나 Codex CLI 없음 | fail. Claude/external 로 조용히 전환하지 않음 |
|
|
138
|
+
| `codex` | requested roster 에 unsupported worker 포함 | fail with supported worker/report-writer opt-in 안내 |
|
|
139
|
+
| `external` | tmux 없음 | prepare/render-only 까지는 가능, dispatch 는 fail |
|
|
140
|
+
| `external` | `claude` worker 포함, Claude CLI wrapper 실행 불가 | 해당 worker dispatch fail. roster 를 몰래 줄이지 않음 |
|
|
141
|
+
| unknown | host signal 없음 + tmux 없음 | fail with next-command suggestion |
|
|
142
|
+
|
|
143
|
+
Fallback reason 은 run manifest 에 남긴다.
|
|
144
|
+
|
|
145
|
+
## 7. CLI UX
|
|
146
|
+
|
|
147
|
+
### 7.1 기본값
|
|
148
|
+
|
|
149
|
+
다음 command 의 기본 runtime 을 `auto` 로 바꾼다.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
okstra install
|
|
153
|
+
okstra ensure-installed
|
|
154
|
+
okstra doctor
|
|
155
|
+
okstra render-bundle ...
|
|
156
|
+
okstra run ...
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
명시 선택은 유지한다.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
okstra install --runtime claude-code
|
|
163
|
+
okstra install --runtime codex
|
|
164
|
+
okstra install --runtime external
|
|
165
|
+
okstra install --runtime all
|
|
166
|
+
okstra run --lead-runtime external ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`all` 은 감지값이 아니다. 모든 runtime asset set 을 명시 설치/검사하는 admin mode 로 유지한다.
|
|
170
|
+
|
|
171
|
+
### 7.2 Execution front doors
|
|
172
|
+
|
|
173
|
+
Auto execution has two front doors because Claude Code native tools are not available to a plain Node process.
|
|
174
|
+
|
|
175
|
+
| Front door | Host | Responsibility |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `/okstra-run` skill | Claude Code | Uses resolver context, keeps the existing Claude Code interactive/team path |
|
|
178
|
+
| `okstra run` Node CLI | Codex / generic terminal | Resolves runtime and orchestrates CLI-backed `codex` or `external` execution |
|
|
179
|
+
|
|
180
|
+
New Node driver `okstra run` is still useful, but it must not pretend it can call Claude Code `TeamCreate` / `Agent(...)` from a terminal. If `okstra run` resolves `claude-code` without being invoked by a Claude Code skill handoff, it fails with an instruction to use `/okstra-run` or pass another explicit runtime.
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
okstra run ...
|
|
184
|
+
→ resolveRuntime(requestedRuntime=auto by default, command=run)
|
|
185
|
+
→ if resolvedRuntime=codex: codex-run → codex-dispatch
|
|
186
|
+
→ if resolvedRuntime=external: render-bundle → team dispatch → team await
|
|
187
|
+
→ if resolvedRuntime=claude-code outside skill handoff: fail with /okstra-run guidance
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`external` 은 Python `prepare_task_bundle()` 의 non-render gate 를 열지 않는다. Node `okstra run` 이 render-only prepare 이후 dispatch/await 를 소유한다.
|
|
191
|
+
|
|
192
|
+
### 7.3 기존 command 와의 관계
|
|
193
|
+
|
|
194
|
+
- `okstra codex-run`: 유지. Codex lead prompt materialization 의 explicit low-level command.
|
|
195
|
+
- `okstra codex-dispatch`: 유지. Prepared Codex run 의 explicit dispatch command.
|
|
196
|
+
- `okstra team dispatch/await/teardown`: 유지. External/tmux backend 의 explicit low-level command.
|
|
197
|
+
- `okstra render-bundle`: 유지. Preview/debug 목적의 low-level prepare command.
|
|
198
|
+
- `okstra run`: 위 command 들을 host-aware 하게 묶는 high-level command.
|
|
199
|
+
|
|
200
|
+
## 8. Manifest 변경
|
|
201
|
+
|
|
202
|
+
### 8.1 installed-runtimes.json v2
|
|
203
|
+
|
|
204
|
+
현재 v1 의 `codexAdapter` / `externalAdapter` 는 쓰지 않는다. 새 manifest 는 설치 요청과 설치 자산을 분리한다.
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"schemaVersion": 2,
|
|
209
|
+
"installedAt": "2026-06-14T00:00:00.000Z",
|
|
210
|
+
"installRequest": "auto",
|
|
211
|
+
"runtimeResolution": {
|
|
212
|
+
"requestedRuntime": "auto",
|
|
213
|
+
"resolvedRuntime": "external",
|
|
214
|
+
"host": "generic-terminal",
|
|
215
|
+
"reason": "No Claude Code or Codex host signal; tmux is available.",
|
|
216
|
+
"fallbackFrom": null,
|
|
217
|
+
"warnings": []
|
|
218
|
+
},
|
|
219
|
+
"installedRuntimes": ["external"],
|
|
220
|
+
"installedAssets": {
|
|
221
|
+
"sharedRuntime": true,
|
|
222
|
+
"claudeSkills": false,
|
|
223
|
+
"claudeAgents": false,
|
|
224
|
+
"binWrappers": true,
|
|
225
|
+
"templates": true,
|
|
226
|
+
"schemas": true,
|
|
227
|
+
"validators": true
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Dynamic capability 는 install manifest 에 고정하지 않는다. `tmux`, `codex`, `claude` CLI availability 는 시간이 지나면 바뀌므로 `doctor` 와 `run` 이 매번 검사한다.
|
|
233
|
+
|
|
234
|
+
### 8.2 run manifest additions
|
|
235
|
+
|
|
236
|
+
Run manifest 에 runtime request/resolution 을 기록한다.
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"leadRuntimeRequest": "auto",
|
|
241
|
+
"leadRuntime": "external",
|
|
242
|
+
"runtimeResolution": {
|
|
243
|
+
"requestedRuntime": "auto",
|
|
244
|
+
"resolvedRuntime": "external",
|
|
245
|
+
"host": "generic-terminal",
|
|
246
|
+
"reason": "No Claude Code or Codex host signal; tmux is available.",
|
|
247
|
+
"fallbackFrom": null,
|
|
248
|
+
"warnings": []
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`leadRuntime` 은 기존 consumer 호환을 위해 최종 resolved runtime 을 유지한다.
|
|
254
|
+
|
|
255
|
+
## 9. Install / ensure / doctor 동작
|
|
256
|
+
|
|
257
|
+
### 9.1 install
|
|
258
|
+
|
|
259
|
+
`okstra install` 기본값은 `--runtime auto` 와 같다.
|
|
260
|
+
|
|
261
|
+
- Claude Code host → `claude-code` assets 설치: shared runtime + Claude skills/agents.
|
|
262
|
+
- Codex host → `codex` assets 설치: shared runtime only.
|
|
263
|
+
- Generic terminal + tmux → `external` assets 설치: shared runtime only.
|
|
264
|
+
- `--runtime all` → shared runtime + Claude skills/agents + all installedRuntimes 기록.
|
|
265
|
+
|
|
266
|
+
공유 runtime 은 모든 install mode 에서 복사한다. 이 때문에 `bin/okstra-codex-exec.sh` 같은 wrapper 존재만으로 `codex` runtime 설치를 의미하지 않는다. v2 manifest 는 이 혼동을 `installedAssets` 와 `installedRuntimes` 로 나눠 제거한다.
|
|
267
|
+
|
|
268
|
+
### 9.2 ensure-installed
|
|
269
|
+
|
|
270
|
+
`okstra ensure-installed` 기본값도 auto다.
|
|
271
|
+
|
|
272
|
+
- 현재 host 에 필요한 asset 이 없으면 같은 host-resolved runtime 으로 install 을 수행한다.
|
|
273
|
+
- generic terminal 에서 `external` 로 설치된 뒤 Claude Code 에서 `/okstra-run` 이 실행되면, Claude Code host signal 이 `claude-code` 로 resolve 되고 Claude assets 를 보강 설치한다.
|
|
274
|
+
- v1 manifest 는 읽을 수 있어야 한다. v1 에서 `runtime:"claude-code"` 이면 `installedRuntimes:["claude-code"]` 로 해석한다.
|
|
275
|
+
|
|
276
|
+
### 9.3 doctor
|
|
277
|
+
|
|
278
|
+
`okstra doctor --runtime auto` 는 현재 host 에 필요한 readiness 만 검사한다.
|
|
279
|
+
|
|
280
|
+
`okstra doctor --runtime all` 은 admin check 로 다음을 모두 검사한다.
|
|
281
|
+
|
|
282
|
+
- shared runtime files
|
|
283
|
+
- Claude assets
|
|
284
|
+
- Codex CLI availability
|
|
285
|
+
- external/tmux availability
|
|
286
|
+
- manifest v1/v2 해석
|
|
287
|
+
|
|
288
|
+
JSON output 에 `runtimeResolution` 을 포함한다.
|
|
289
|
+
|
|
290
|
+
## 10. Execution flow
|
|
291
|
+
|
|
292
|
+
### 10.1 Claude Code host
|
|
293
|
+
|
|
294
|
+
```text
|
|
295
|
+
/okstra-run
|
|
296
|
+
→ resolver context = claude-code
|
|
297
|
+
→ ensure-installed claude-code
|
|
298
|
+
→ existing Claude Code interactive/team path
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Bare `okstra run` must not claim Claude Code execution unless it is part of a Claude Code skill handoff. A Node process cannot invoke native `TeamCreate` / `Agent(...)` directly.
|
|
302
|
+
|
|
303
|
+
이 경로는 기존 slash skill UX 를 깨지 않는다.
|
|
304
|
+
|
|
305
|
+
### 10.2 Codex host
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
okstra run ...
|
|
309
|
+
→ resolveRuntime = codex
|
|
310
|
+
→ ensure-installed codex
|
|
311
|
+
→ okstra codex-run ...
|
|
312
|
+
→ okstra codex-dispatch ...
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Report-writer 는 기존 Codex opt-in 정책을 따른다. 필요한 phase 에서 report-writer 가 required 인데 opt-in 이 없으면 fail-fast 한다. Worker roster 를 몰래 줄이지 않는다.
|
|
316
|
+
|
|
317
|
+
### 10.3 External/generic terminal host
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
okstra run ...
|
|
321
|
+
→ resolveRuntime = external
|
|
322
|
+
→ ensure-installed external
|
|
323
|
+
→ okstra render-bundle ... --lead-runtime external
|
|
324
|
+
→ read produced run manifest
|
|
325
|
+
→ okstra team dispatch --run-manifest <path>
|
|
326
|
+
→ okstra team await --run-manifest <path>
|
|
327
|
+
→ post-processing / validation
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Python `prepare_task_bundle()` 의 non-render gate 는 유지한다. Full external execution 은 Node driver 가 orchestration 한다.
|
|
331
|
+
|
|
332
|
+
## 11. Error handling
|
|
333
|
+
|
|
334
|
+
All resolver failures should be actionable.
|
|
335
|
+
|
|
336
|
+
Examples:
|
|
337
|
+
|
|
338
|
+
- `auto resolved codex but codex CLI is unavailable. Install Codex CLI or run with --lead-runtime external.`
|
|
339
|
+
- `auto resolved external but tmux is unavailable. Run render-only or install tmux.`
|
|
340
|
+
- `explicit --lead-runtime codex requested, but worker claude is unsupported by codex-dispatch. Choose supported workers or use claude-code/external.`
|
|
341
|
+
- `host unknown. Set OKSTRA_RUNTIME_HOST=claude-code|codex|external or pass --lead-runtime explicitly.`
|
|
342
|
+
|
|
343
|
+
No fallback may change worker roster, phase, task type, executor, or report-writer role without explicit user input.
|
|
344
|
+
|
|
345
|
+
## 12. Backward compatibility
|
|
346
|
+
|
|
347
|
+
- Existing explicit flags keep working.
|
|
348
|
+
- Existing `okstra codex-run`, `okstra codex-dispatch`, `okstra team *`, `okstra render-bundle` keep working.
|
|
349
|
+
- v1 `installed-runtimes.json` remains readable.
|
|
350
|
+
- New installs write v2.
|
|
351
|
+
- Existing v1 `codexAdapter` / `externalAdapter` are interpreted only while reading old manifests.
|
|
352
|
+
- `leadRuntime` in run manifest remains final resolved runtime, so validators that already read `leadRuntime` do not need a broad schema break.
|
|
353
|
+
|
|
354
|
+
## 13. Tests
|
|
355
|
+
|
|
356
|
+
Required test groups:
|
|
357
|
+
|
|
358
|
+
1. `runtime-resolver` unit tests
|
|
359
|
+
- explicit runtime bypasses auto
|
|
360
|
+
- host override resolves each runtime
|
|
361
|
+
- generic terminal + tmux resolves external
|
|
362
|
+
- binary availability alone does not imply host
|
|
363
|
+
- safe fallback matrix
|
|
364
|
+
2. install tests
|
|
365
|
+
- default install writes v2 auto manifest
|
|
366
|
+
- Claude host installs Claude assets
|
|
367
|
+
- external host skips Claude assets
|
|
368
|
+
- v1 manifest read compatibility
|
|
369
|
+
3. doctor tests
|
|
370
|
+
- `--runtime auto` reports runtimeResolution
|
|
371
|
+
- `--runtime all` checks all readiness categories
|
|
372
|
+
4. run driver tests
|
|
373
|
+
- auto external builds render-only bundle and invokes team dispatch/await in dry-run/fake mode
|
|
374
|
+
- auto codex invokes codex-run/codex-dispatch in dry-run/fake mode
|
|
375
|
+
- bare `okstra run` resolving `claude-code` fails with `/okstra-run` guidance
|
|
376
|
+
- unsupported Codex worker fails without roster shrink
|
|
377
|
+
5. docs contract tests
|
|
378
|
+
- default runtime is auto
|
|
379
|
+
- host-based priority documented
|
|
380
|
+
- safe fallback documented
|
|
381
|
+
- manifest v2 no longer documents `codexAdapter` / `externalAdapter` as live fields
|
|
382
|
+
|
|
383
|
+
## 14. Rollout plan
|
|
384
|
+
|
|
385
|
+
Recommended implementation order:
|
|
386
|
+
|
|
387
|
+
1. Add `runtime-resolver.mjs` and tests.
|
|
388
|
+
2. Add v2 runtime manifest writer/reader while preserving v1 read path.
|
|
389
|
+
3. Switch install/ensure/doctor defaults from `claude-code` to `auto`.
|
|
390
|
+
4. Add `okstra run` high-level driver with dry-run/fake subprocess tests.
|
|
391
|
+
5. Wire external execution path through render-bundle → team dispatch → team await.
|
|
392
|
+
6. Wire codex execution path through codex-run → codex-dispatch.
|
|
393
|
+
7. Update docs and CLI help.
|
|
394
|
+
8. Keep explicit low-level commands unchanged.
|
|
395
|
+
|
|
396
|
+
## 15. Non-goals
|
|
397
|
+
|
|
398
|
+
- Do not remove explicit runtime flags.
|
|
399
|
+
- Do not remove Claude Code slash skill UX.
|
|
400
|
+
- Do not make binary availability alone choose a host.
|
|
401
|
+
- Do not open Python `prepare_task_bundle()` non-render for `external` in this change.
|
|
402
|
+
- Do not silently shrink worker roster for fallback.
|
|
403
|
+
- Do not implement non-tmux external dispatch backends.
|
|
404
|
+
- Do not change phase profile/schema/validator contracts by provider fork.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okstra-claude-exec.sh — wrapper around Claude Code non-interactive print mode.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
if [[ $# -lt 3 || $# -gt 6 ]]; then
|
|
6
|
+
printf 'usage: %s <project-root> <model-execution-value> <prompt-path> [worktree-path] [role] [idle-timeout-seconds]\n' "$(basename "$0")" >&2
|
|
7
|
+
exit 64
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
project_root="$1"
|
|
11
|
+
model="$2"
|
|
12
|
+
prompt_path="$3"
|
|
13
|
+
worktree_path="${4-}"
|
|
14
|
+
role="${5:-worker}"
|
|
15
|
+
idle_timeout_secs="${6:-600}"
|
|
16
|
+
|
|
17
|
+
if [[ ! -d "$project_root" ]]; then
|
|
18
|
+
printf 'okstra-claude-exec.sh: project root not found: %s\n' "$project_root" >&2
|
|
19
|
+
exit 65
|
|
20
|
+
fi
|
|
21
|
+
if [[ -z "$model" ]]; then
|
|
22
|
+
printf 'okstra-claude-exec.sh: model is required\n' >&2
|
|
23
|
+
exit 66
|
|
24
|
+
fi
|
|
25
|
+
if [[ ! -f "$prompt_path" ]]; then
|
|
26
|
+
printf 'okstra-claude-exec.sh: prompt not found: %s\n' "$prompt_path" >&2
|
|
27
|
+
exit 67
|
|
28
|
+
fi
|
|
29
|
+
if [[ -n "$worktree_path" && ! -d "$worktree_path" ]]; then
|
|
30
|
+
printf 'okstra-claude-exec.sh: worktree not found: %s\n' "$worktree_path" >&2
|
|
31
|
+
exit 68
|
|
32
|
+
fi
|
|
33
|
+
if ! [[ "$idle_timeout_secs" =~ ^[0-9]+$ ]]; then
|
|
34
|
+
printf 'okstra-claude-exec.sh: idle timeout must be a non-negative integer\n' >&2
|
|
35
|
+
exit 69
|
|
36
|
+
fi
|
|
37
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
38
|
+
printf 'okstra-claude-exec.sh: claude CLI not found\n' >&2
|
|
39
|
+
exit 127
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
log_path="${prompt_path%.md}.log"
|
|
43
|
+
[[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
|
|
44
|
+
: > "$log_path"
|
|
45
|
+
status_path="${prompt_path}.status.json"
|
|
46
|
+
started_ts=$(date +%s)
|
|
47
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
48
|
+
python3 "$script_dir/okstra-wrapper-status.py" \
|
|
49
|
+
init "$status_path" "$(basename "$0")" "$role" "$$" "$started_ts" "$log_path" \
|
|
50
|
+
>>"$log_path" 2>&1 || true
|
|
51
|
+
|
|
52
|
+
_okstra_status_finish() {
|
|
53
|
+
local exit_code=$?
|
|
54
|
+
local ended_ts
|
|
55
|
+
ended_ts=$(date +%s)
|
|
56
|
+
local duration_ms=$(( (ended_ts - started_ts) * 1000 ))
|
|
57
|
+
python3 "$script_dir/okstra-wrapper-status.py" \
|
|
58
|
+
finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
|
|
59
|
+
>>"$log_path" 2>&1 || true
|
|
60
|
+
}
|
|
61
|
+
trap _okstra_status_finish EXIT
|
|
62
|
+
|
|
63
|
+
cd "$project_root"
|
|
64
|
+
set +e
|
|
65
|
+
claude -p --model "$model" --output-format=stream-json < "$prompt_path" \
|
|
66
|
+
> >(tee -a "$log_path") \
|
|
67
|
+
2> >(tee -a "$log_path" >&2)
|
|
68
|
+
claude_exit=$?
|
|
69
|
+
set -e
|
|
70
|
+
|
|
71
|
+
exit "$claude_exit"
|
|
@@ -91,6 +91,15 @@ def _lead_adapter(ctx: dict) -> dict:
|
|
|
91
91
|
return _lead_info(ctx).adapter_payload()
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
def _runtime_resolution(ctx: dict) -> dict:
|
|
95
|
+
raw = ctx.get("RUNTIME_RESOLUTION_JSON", "") or "{}"
|
|
96
|
+
try:
|
|
97
|
+
payload = json.loads(raw)
|
|
98
|
+
except Exception:
|
|
99
|
+
return {}
|
|
100
|
+
return payload if isinstance(payload, dict) else {}
|
|
101
|
+
|
|
102
|
+
|
|
94
103
|
_PHASE_BLOCK_RE = re.compile(
|
|
95
104
|
r"\{% if header\.taskType == '(implementation-planning|release-handoff|implementation|final-verification)' %\}\n(.*?)\{% endif %\}\n",
|
|
96
105
|
re.DOTALL,
|
|
@@ -1185,6 +1194,8 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1185
1194
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
1186
1195
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
1187
1196
|
"leadRuntime": _lead_runtime(ctx),
|
|
1197
|
+
"leadRuntimeRequest": ctx.get("LEAD_RUNTIME_REQUEST", "") or _lead_runtime(ctx),
|
|
1198
|
+
"runtimeResolution": _runtime_resolution(ctx),
|
|
1188
1199
|
"leadAdapter": _lead_adapter(ctx),
|
|
1189
1200
|
"workCategory": task_manifest.get(
|
|
1190
1201
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
@@ -296,6 +296,8 @@ class PrepareInputs:
|
|
|
296
296
|
gemini_model: str = ""
|
|
297
297
|
report_writer_model: str = ""
|
|
298
298
|
lead_runtime: str = "claude-code"
|
|
299
|
+
lead_runtime_request: str = ""
|
|
300
|
+
runtime_resolution_json: str = ""
|
|
299
301
|
executor: str = ""
|
|
300
302
|
critic: str = ""
|
|
301
303
|
related_tasks_raw: str = ""
|
|
@@ -2055,6 +2057,12 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2055
2057
|
workspace_root = Path(inp.workspace_root)
|
|
2056
2058
|
project_root = Path(inp.project_root)
|
|
2057
2059
|
lead_runtime = _normalize_lead_runtime(inp.lead_runtime)
|
|
2060
|
+
lead_runtime_request = (inp.lead_runtime_request or lead_runtime).strip()
|
|
2061
|
+
runtime_resolution_json = (inp.runtime_resolution_json or "{}").strip()
|
|
2062
|
+
try:
|
|
2063
|
+
json.loads(runtime_resolution_json or "{}")
|
|
2064
|
+
except json.JSONDecodeError as exc:
|
|
2065
|
+
raise PrepareError(f"invalid --runtime-resolution-json: {exc}") from exc
|
|
2058
2066
|
if lead_runtime != "claude-code" and not inp.render_only:
|
|
2059
2067
|
raise PrepareError(
|
|
2060
2068
|
f"lead runtime `{lead_runtime}` is currently render-only; "
|
|
@@ -2247,6 +2255,8 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
2247
2255
|
"ANALYSIS_PROFILE": inp.task_type,
|
|
2248
2256
|
"RECOMMENDED_ANALYSERS": selected_reviewers,
|
|
2249
2257
|
"LEAD_RUNTIME": lead_runtime,
|
|
2258
|
+
"LEAD_RUNTIME_REQUEST": lead_runtime_request,
|
|
2259
|
+
"RUNTIME_RESOLUTION_JSON": runtime_resolution_json or "{}",
|
|
2250
2260
|
"PR_TEMPLATE_PATH": pr_template_path_str,
|
|
2251
2261
|
"PR_TEMPLATE_SOURCE": pr_template_source,
|
|
2252
2262
|
**handoff_tokens,
|
|
@@ -2424,6 +2434,18 @@ def main(argv: list[str]) -> int:
|
|
|
2424
2434
|
"bundles for manifest inspection until a dispatch backend is enabled."
|
|
2425
2435
|
),
|
|
2426
2436
|
)
|
|
2437
|
+
p.add_argument(
|
|
2438
|
+
"--lead-runtime-request",
|
|
2439
|
+
default="",
|
|
2440
|
+
dest="lead_runtime_request",
|
|
2441
|
+
help="Original lead runtime request before Node-side resolution, e.g. auto.",
|
|
2442
|
+
)
|
|
2443
|
+
p.add_argument(
|
|
2444
|
+
"--runtime-resolution-json",
|
|
2445
|
+
default="",
|
|
2446
|
+
dest="runtime_resolution_json",
|
|
2447
|
+
help="Node-side runtime resolution payload serialized as JSON.",
|
|
2448
|
+
)
|
|
2427
2449
|
p.add_argument("--executor", default="")
|
|
2428
2450
|
p.add_argument("--critic", default="")
|
|
2429
2451
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
@@ -2561,6 +2583,8 @@ def main(argv: list[str]) -> int:
|
|
|
2561
2583
|
gemini_model=args.gemini_model,
|
|
2562
2584
|
report_writer_model=args.report_writer_model,
|
|
2563
2585
|
lead_runtime=args.lead_runtime,
|
|
2586
|
+
lead_runtime_request=args.lead_runtime_request,
|
|
2587
|
+
runtime_resolution_json=args.runtime_resolution_json,
|
|
2564
2588
|
executor=args.executor,
|
|
2565
2589
|
critic=args.critic,
|
|
2566
2590
|
related_tasks_raw=args.related_tasks_raw,
|
|
@@ -2591,6 +2615,7 @@ def main(argv: list[str]) -> int:
|
|
|
2591
2615
|
print(f"okstra latest task discovery file: {ctx['OKSTRA_LATEST_TASK_FILE']}")
|
|
2592
2616
|
print(f"okstra task catalog file: {ctx['OKSTRA_TASK_CATALOG_FILE']}")
|
|
2593
2617
|
print(f"okstra instruction-set: {ctx['INSTRUCTION_SET_PATH']}")
|
|
2618
|
+
print(f"okstra run manifest: {ctx['RUN_MANIFEST_PATH']}")
|
|
2594
2619
|
print(f"okstra reference expectations: {ctx['REFERENCE_EXPECTATIONS_FILE']}")
|
|
2595
2620
|
print(f"okstra final report template: {ctx['FINAL_REPORT_TEMPLATE_PATH']}")
|
|
2596
2621
|
cr = out.extras.get("concurrent_run", {})
|
|
@@ -21,7 +21,7 @@ Single read-side entry point for okstra runtime inspection plus the one write-si
|
|
|
21
21
|
|
|
22
22
|
Before any sub-command, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
|
|
23
23
|
|
|
24
|
-
1. `okstra ensure-installed`
|
|
24
|
+
1. `okstra ensure-installed --runtime claude-code`
|
|
25
25
|
If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
|
|
26
26
|
|
|
27
27
|
2. `okstra check-project --json`
|
|
@@ -55,7 +55,7 @@ Never invent additional questions. Never reorder. **Never drop, hide, or merge a
|
|
|
55
55
|
|
|
56
56
|
Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&` — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's JSON output and decides what to do next in natural language — never in shell.
|
|
57
57
|
|
|
58
|
-
1. `okstra ensure-installed`
|
|
58
|
+
1. `okstra ensure-installed --runtime claude-code`
|
|
59
59
|
If this exits non-zero, tell the user: "okstra runtime missing — run `npx okstra@latest install` once and retry." Then stop.
|
|
60
60
|
|
|
61
61
|
2. `okstra paths --json`
|
|
@@ -155,8 +155,11 @@ Output: `{ok: true, args: {"project-root": "...", "task-type": "...", ...}}`. Bu
|
|
|
155
155
|
|
|
156
156
|
**Escaping rule**: inside a double-quoted value, escape any literal `"` as `\"`. Do not collapse `--key ""` into `--key` even when the value is empty.
|
|
157
157
|
|
|
158
|
+
The okstra-run skill is the Claude Code execution front door, so it marks the host explicitly with `--runtime claude-code` / `--lead-runtime claude-code`; bare `okstra run` cannot invoke Claude Code `TeamCreate` / `Agent(...)` from a terminal.
|
|
159
|
+
|
|
158
160
|
```bash
|
|
159
161
|
okstra render-bundle \
|
|
162
|
+
--lead-runtime claude-code \
|
|
160
163
|
--project-root "<args.project-root>" \
|
|
161
164
|
--project-id "<args.project-id>" \
|
|
162
165
|
--task-group "<args.task-group>" \
|
|
@@ -39,7 +39,7 @@ If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadF
|
|
|
39
39
|
|
|
40
40
|
Before anything else in this skill, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
|
|
41
41
|
|
|
42
|
-
1. `okstra ensure-installed`
|
|
42
|
+
1. `okstra ensure-installed --runtime claude-code`
|
|
43
43
|
If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
|
|
44
44
|
|
|
45
45
|
2. `okstra check-project --json`
|
|
@@ -34,7 +34,7 @@ Inform the user up-front and confirm before continuing:
|
|
|
34
34
|
## Step 1: Install okstra
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
npx -y okstra@latest install
|
|
37
|
+
npx -y okstra@latest install --runtime claude-code
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
This single command populates everything the user needs:
|
|
@@ -316,7 +316,7 @@ If the user picks 1/2/3, run:
|
|
|
316
316
|
## Step 5: Verify
|
|
317
317
|
|
|
318
318
|
```bash
|
|
319
|
-
okstra doctor
|
|
319
|
+
okstra doctor --runtime claude-code
|
|
320
320
|
```
|
|
321
321
|
|
|
322
322
|
If all checks return `OK`, the setup is complete. If any check fails, surface
|
package/src/cli-registry.mjs
CHANGED
|
@@ -127,6 +127,13 @@ export const COMMAND_REGISTRY = [
|
|
|
127
127
|
"python3 -m okstra_ctl.run --render-only)",
|
|
128
128
|
],
|
|
129
129
|
},
|
|
130
|
+
{
|
|
131
|
+
name: "run",
|
|
132
|
+
module: "./run.mjs",
|
|
133
|
+
export: "run",
|
|
134
|
+
category: "introspection",
|
|
135
|
+
summary: ["Host-aware auto execution front door"],
|
|
136
|
+
},
|
|
130
137
|
{
|
|
131
138
|
name: "codex-run",
|
|
132
139
|
module: "./codex-run.mjs",
|
|
@@ -232,7 +239,7 @@ This CLI is the installer/admin tool. Day-to-day usage happens inside a
|
|
|
232
239
|
Claude Code session via slash commands (/okstra-setup, /okstra-run, ...).
|
|
233
240
|
|
|
234
241
|
Quick start (CLI):
|
|
235
|
-
1. npx -y okstra@latest install
|
|
242
|
+
1. npx -y okstra@latest install --runtime claude-code # one-time, this machine
|
|
236
243
|
2. cd <your project>
|
|
237
244
|
3. npx -y okstra@latest setup --project-id <id> # one-time, this project
|
|
238
245
|
4. open a Claude Code session and run /okstra-run # start a task
|