okstra 0.32.1 → 0.33.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.
@@ -880,7 +880,7 @@ Phase 7 step 1.5 가 final-report MD 한 본을 입력으로 두 view 를 결정
880
880
  ### Live-log mirror (codex / gemini wrapper)
881
881
 
882
882
  - `scripts/okstra-codex-exec.sh`, `scripts/okstra-gemini-exec.sh` 는 dispatch 마다 prompt path 옆에 `<prompt>.log` sidecar 를 만들고 stdout 을 거기로 mirror 합니다 (`tee`, `PIPESTATUS[0]` 로 종료코드 보존). stderr 은 같은 파일에 append (subagent stderr 캡처 contract 보존), 매 dispatch 시 truncate. 호출 subagent 의 `BashOutput` 폴링은 60s 간격이라 long-running run (analysis 의 large-codebase scan, implementation 의 cargo / pytest) 동안 사용자가 stalled state 를 탐지할 수 없는 문제를 해소합니다.
883
- - `$TMUX` 가 셋팅된 lead 환경이면 wrapper 가 sibling pane 을 자동 분할해 `tail -F <log-path>` 를 띄웁니다. pane title 은 `<cli>-<role>-trace` (e.g. `codex-worker-trace`, `gemini-worker-trace`); role 은 wrapper 의 5번째 optional positional 인자이며, 누락 시 기본값 `worker` 로 떨어집니다. caller 가 다른 라벨(예: `executor`)을 원하면 5번째 인자로 명시해야 합니다. focus 는 caller pane 으로 복귀하고, CLI 종료 후 pane 은 유지돼 스크롤백 가능. `$TMUX` 미설정, split 실패, 구버전 tmux 등 모든 경로는 silent degrade.
883
+ - `$TMUX` 가 셋팅된 lead 환경이면 wrapper 가 sibling pane 을 자동 분할해 `tail -F <log-path>` 를 띄웁니다. trace pane title 은 `<cli>-<role>-<pid>-trace` (e.g. `codex-worker-93421-trace`, `gemini-executor-93422-trace`); 동일 시점에 caller (worker) pane title 도 `<cli>-<role>-<pid>` 로 셋팅돼 worker ↔ trace 쌍을 한 눈에 매칭할 수 있습니다. `<pid>` 는 wrapper 자기 자신의 PID 라서 동일 role 의 worker 가 둘 이상 동시에 spawn 돼도 서로 구분됩니다. role 은 wrapper 의 5번째 optional positional 인자이며, 누락 시 기본값 `worker` 로 떨어집니다. caller 가 다른 라벨(예: `executor`)을 원하면 5번째 인자로 명시해야 합니다. wrapper 진입 직전의 caller pane title 은 capture 해두고 EXIT trap 에서 복원하므로, dispatch 사이의 stale title 이 남지 않습니다. focus 는 caller pane 으로 복귀하고, CLI 종료 후 pane 은 유지돼 스크롤백 가능. `$TMUX` 미설정, split 실패, 구버전 tmux 등 모든 경로는 silent degrade.
884
884
  - **Claude `/exit` 시 자동 정리**: trace pane 의 `tail -F` 는 tmux 셸의 자식이라 Claude 가 종료돼도 살아남는 문제를 막기 위해, wrapper 는 spawn 한 pane id 를 caller `$TMUX_PANE` 으로 키된 registry (`${TMPDIR:-/tmp}/okstra-trace-panes/<caller-pane>.list`) 에 append 합니다. `templates/reports/settings.template.json` 의 `hooks.SessionEnd` 가 `$HOME/.okstra/bin/okstra-trace-cleanup.sh` 를 호출해 자신의 caller pane registry 만 읽어 `tmux kill-pane` 합니다. caller pane 단위로 scope 가 잡혀 있어 같은 tmux 세션에 Claude 인스턴스가 여러 개 떠 있어도 서로의 trace pane 을 죽이지 않습니다. tmux 가 없거나 stale pane id 인 경우 silent degrade.
885
885
  - **Phase 종료 시 사용자 확인**: 매 phase 의 마지막 단계로 lead 가 `okstra-trace-cleanup.sh --list` 로 등록된 pane 목록을 출력한 뒤 사용자에게 "모두 닫기 / 그대로 두기" 양자택일을 묻고 응답대로 처리합니다 (`prompts/profiles/_common-contract.md` 의 *Phase wrap-up* 항목). `$TMUX_PANE` 미설정 환경에서는 단계 자체가 silent-skip. `--list` 모드는 pane 을 죽이지 않고 `<pane_id>\t<pane_title>` 만 출력하므로 사용자가 무엇이 닫힐지 시각적으로 확인할 수 있습니다.
886
886
  - 디스크 누적은 `okstra-logs` skill 이 read-only 로 인벤토리 + cleanup 명령을 제안합니다 (실행은 사용자 copy-paste).
package/docs/kr/cli.md CHANGED
@@ -546,5 +546,5 @@ chmod +x ~/.local/bin/okstra-ctl
546
546
 
547
547
  ### Live-log sidecar
548
548
 
549
- codex / gemini wrapper 는 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar 를 만들고 stdout / stderr 를 mirror 합니다. tmux 안에서 lead 를 띄우면 wrapper 가 자동으로 `tail -F` pane 을 분할합니다 (title: `<cli>-<role>-trace`). 분할된 trace pane 은 caller `$TMUX_PANE` 으로 키된 registry 에 등록돼, Claude `/exit` 시 `SessionEnd` 훅이 `okstra-trace-cleanup.sh` 로 자동 정리합니다. 사용량 인벤토리와 `find … -delete` cleanup 명령은 `okstra-logs` skill 이 read-only 로 제안합니다. 자세한 와이어링은 [`docs/kr/architecture.md`](architecture.md) 의 *Live-log mirror* 절 참고.
549
+ codex / gemini wrapper 는 매 dispatch 마다 `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar 를 만들고 stdout / stderr 를 mirror 합니다. tmux 안에서 lead 를 띄우면 wrapper 가 자동으로 `tail -F` pane 을 분할합니다 (trace pane title: `<cli>-<role>-<pid>-trace`, caller (worker) pane title: `<cli>-<role>-<pid>` — wrapper PID 가 동일 role 의 동시 dispatch 를 구분합니다). 분할된 trace pane 은 caller `$TMUX_PANE` 으로 키된 registry 에 등록돼, Claude `/exit` 시 `SessionEnd` 훅이 `okstra-trace-cleanup.sh` 로 자동 정리합니다. 사용량 인벤토리와 `find … -delete` cleanup 명령은 `okstra-logs` skill 이 read-only 로 제안합니다. 자세한 와이어링은 [`docs/kr/architecture.md`](architecture.md) 의 *Live-log mirror* 절 참고.
550
550
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.32.1",
3
+ "version": "0.33.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.32.1",
3
- "builtAt": "2026-05-19T14:15:13.766Z",
2
+ "package": "0.33.0",
3
+ "builtAt": "2026-05-19T16:04:42.528Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -30,7 +30,7 @@ You are a Codex worker agent. Your job is to execute the OpenAI Codex CLI and re
30
30
  $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is the trace-pane label suffix (`codex-<role>-trace`); pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
33
+ The fifth argument `<role>` is folded into both the caller (worker) pane title `codex-<role>-<pid>` and the sibling trace-pane title `codex-<role>-<pid>-trace` (`<pid>` = the wrapper's PID, present so concurrent dispatches of the same role can be told apart). Pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
34
34
 
35
35
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper forwards it to codex as `--add-dir`, which grants the codex sandbox write access to the worktree (where all implementation-phase mutations occur). Without it, codex's `workspace-write` sandbox is anchored only at `<project-root>` and rejects every Edit/Write that targets the worktree (EPERM), which is the failure pattern that originally motivated this argument.
36
36
 
@@ -30,7 +30,7 @@ You are a Gemini worker agent. Your job is to execute the Google Gemini CLI and
30
30
  $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is the trace-pane label suffix (`gemini-<role>-trace`); pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
33
+ The fifth argument `<role>` is folded into both the caller (worker) pane title `gemini-<role>-<pid>` and the sibling trace-pane title `gemini-<role>-<pid>-trace` (`<pid>` = the wrapper's PID, present so concurrent dispatches of the same role can be told apart). Pass the literal string `worker` for every dispatch from this subagent. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the dispatch is self-describing.
34
34
 
35
35
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper appends it to gemini's `--include-directories` list so the model can both read and operate on the worktree alongside project-root.
36
36
 
@@ -170,6 +170,21 @@ python3 "$script_dir/okstra-wrapper-status.py" \
170
170
  init "$status_path" "$(basename "$0")" "$role" "$$" "$started_ts" "$log_path" \
171
171
  >>"$log_path" 2>&1 || true
172
172
 
173
+ # Pane titles: worker (caller) pane gets `codex-<role>-<pid>`; the sibling
174
+ # trace pane appends `-trace`. The wrapper PID disambiguates concurrent
175
+ # dispatches of the same role (e.g. two `codex-worker` panes spawned in
176
+ # parallel) so the operator can match worker ↔ trace at a glance.
177
+ pane_label="codex-${role}-$$"
178
+ trace_label="${pane_label}-trace"
179
+
180
+ # Capture the caller pane's current title so the EXIT trap can restore it
181
+ # once the wrapper returns. Empty when not in tmux or capture fails — the
182
+ # restore step degrades to a no-op in that case.
183
+ original_caller_title=""
184
+ if [[ -n "${TMUX_PANE:-}" ]]; then
185
+ original_caller_title=$(tmux display-message -p -t "$TMUX_PANE" '#{pane_title}' 2>/dev/null || true)
186
+ fi
187
+
173
188
  _okstra_status_finish() {
174
189
  local exit_code=$?
175
190
  local ended_ts
@@ -178,17 +193,29 @@ _okstra_status_finish() {
178
193
  python3 "$script_dir/okstra-wrapper-status.py" \
179
194
  finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
180
195
  >>"$log_path" 2>&1 || true
196
+ if [[ -n "${TMUX_PANE:-}" && -n "$original_caller_title" ]]; then
197
+ tmux select-pane -t "$TMUX_PANE" -T "$original_caller_title" 2>/dev/null || true
198
+ fi
181
199
  }
182
200
  trap _okstra_status_finish EXIT
183
201
 
202
+ # Label the caller (worker) pane now that the restore trap is armed. Any
203
+ # failure after this point still rewinds the title to its prior value.
204
+ if [[ -n "${TMUX_PANE:-}" ]]; then
205
+ tmux select-pane -t "$TMUX_PANE" -T "$pane_label" 2>/dev/null || true
206
+ fi
207
+
184
208
  # When a tmux session is reachable, split a sibling pane that tails the live
185
209
  # log so the operator can watch codex's progress in real time without waiting
186
210
  # for the wrapper to exit. This fires in every phase the wrapper is invoked
187
211
  # from (analysis, error-analysis, implementation-planning, implementation,
188
212
  # …) — long-running codex dispatches are not implementation-specific. The
189
- # new pane carries the title `codex-<role>-trace` `role` is the optional
190
- # 5th positional arg (defaults to `worker`); callers that dispatch a
191
- # different role (e.g. `executor`) must pass it explicitly. The pane uses
213
+ # new pane carries the title `codex-<role>-<pid>-trace` (matching the
214
+ # caller pane's `codex-<role>-<pid>` label so worker trace pairs are
215
+ # greppable); `role` is the optional 5th positional arg (defaults to
216
+ # `worker`); callers that dispatch a different role (e.g. `executor`) must
217
+ # pass it explicitly. The `<pid>` suffix is the wrapper's PID and
218
+ # disambiguates concurrent dispatches of the same role. The pane uses
192
219
  # `tail -F`
193
220
  # (follow-by-name) so it survives any truncation a re-dispatch performs on
194
221
  # the same log path. Failures are tolerated silently: missing $TMUX, a tmux
@@ -201,7 +228,7 @@ if [[ -n "${TMUX:-}" ]]; then
201
228
  -c "$(dirname "$log_path")" \
202
229
  "tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
203
230
  if [[ -n "$trace_pane" ]]; then
204
- tmux select-pane -t "$trace_pane" -T "codex-${role}-trace" 2>/dev/null || true
231
+ tmux select-pane -t "$trace_pane" -T "$trace_label" 2>/dev/null || true
205
232
  tmux last-pane 2>/dev/null || true
206
233
  # Register the spawned pane so the `SessionEnd` hook (see
207
234
  # `okstra-trace-cleanup.sh`) can kill it when the caller's Claude
@@ -121,6 +121,21 @@ python3 "$script_dir/okstra-wrapper-status.py" \
121
121
  init "$status_path" "$(basename "$0")" "$role" "$$" "$started_ts" "$log_path" \
122
122
  >>"$log_path" 2>&1 || true
123
123
 
124
+ # Pane titles: worker (caller) pane gets `gemini-<role>-<pid>`; the sibling
125
+ # trace pane appends `-trace`. The wrapper PID disambiguates concurrent
126
+ # dispatches of the same role (e.g. two `gemini-worker` panes spawned in
127
+ # parallel) so the operator can match worker ↔ trace at a glance.
128
+ pane_label="gemini-${role}-$$"
129
+ trace_label="${pane_label}-trace"
130
+
131
+ # Capture the caller pane's current title so the EXIT trap can restore it
132
+ # once the wrapper returns. Empty when not in tmux or capture fails — the
133
+ # restore step degrades to a no-op in that case.
134
+ original_caller_title=""
135
+ if [[ -n "${TMUX_PANE:-}" ]]; then
136
+ original_caller_title=$(tmux display-message -p -t "$TMUX_PANE" '#{pane_title}' 2>/dev/null || true)
137
+ fi
138
+
124
139
  _okstra_status_finish() {
125
140
  local exit_code=$?
126
141
  local ended_ts
@@ -129,23 +144,34 @@ _okstra_status_finish() {
129
144
  python3 "$script_dir/okstra-wrapper-status.py" \
130
145
  finish "$status_path" "$exit_code" "$ended_ts" "$duration_ms" \
131
146
  >>"$log_path" 2>&1 || true
147
+ if [[ -n "${TMUX_PANE:-}" && -n "$original_caller_title" ]]; then
148
+ tmux select-pane -t "$TMUX_PANE" -T "$original_caller_title" 2>/dev/null || true
149
+ fi
132
150
  }
133
151
  trap _okstra_status_finish EXIT
134
152
 
153
+ # Label the caller (worker) pane now that the restore trap is armed. Any
154
+ # failure after this point still rewinds the title to its prior value.
155
+ if [[ -n "${TMUX_PANE:-}" ]]; then
156
+ tmux select-pane -t "$TMUX_PANE" -T "$pane_label" 2>/dev/null || true
157
+ fi
158
+
135
159
  # When a tmux session is reachable, split a sibling pane tailing the log so
136
160
  # the operator can watch progress live. This fires in every phase the
137
161
  # wrapper is invoked from — long-running gemini dispatches are not
138
- # implementation-specific. Title `gemini-<role>-trace` `role` is the
139
- # optional 5th positional arg (defaults to `worker`); callers that
140
- # dispatch a different role must pass it explicitly. See the codex
141
- # wrapper for the full design rationale and the silent-degrade failure
142
- # model.
162
+ # implementation-specific. Title `gemini-<role>-<pid>-trace` (matching the
163
+ # caller pane's `gemini-<role>-<pid>` label so worker trace pairs are
164
+ # greppable). `role` is the optional 5th positional arg (defaults to
165
+ # `worker`); callers that dispatch a different role must pass it
166
+ # explicitly. The `<pid>` suffix is the wrapper's PID and disambiguates
167
+ # concurrent dispatches of the same role. See the codex wrapper for the
168
+ # full design rationale and the silent-degrade failure model.
143
169
  if [[ -n "${TMUX:-}" ]]; then
144
170
  trace_pane=$(tmux split-window -h -P -F '#{pane_id}' \
145
171
  -c "$(dirname "$log_path")" \
146
172
  "tail -F $(printf '%q' "$log_path")" 2>/dev/null || true)
147
173
  if [[ -n "$trace_pane" ]]; then
148
- tmux select-pane -t "$trace_pane" -T "gemini-${role}-trace" 2>/dev/null || true
174
+ tmux select-pane -t "$trace_pane" -T "$trace_label" 2>/dev/null || true
149
175
  tmux last-pane 2>/dev/null || true
150
176
  # See `okstra-codex-exec.sh` for the registry rationale — kept in lock-step.
151
177
  if [[ -n "${TMUX_PANE:-}" ]]; then
@@ -26,7 +26,7 @@ profile document.
26
26
  - Anti-escalation rule (shared):
27
27
  - treating "다음 단계 진행해" or equivalent user phrases as authorisation to start a *different* lifecycle phase is forbidden. The next phase begins only in a separate okstra run launched with the new `--task-type`. Per-profile documents may further restrict this within their own scope.
28
28
  - Phase wrap-up — worker trace pane disposition (shared, MUST be the *last* step before returning control to the user):
29
- - Codex / Gemini worker wrappers spawn `tail -F` trace panes in the lead's tmux session (`codex-<role>-trace`, `gemini-<role>-trace`). They survive every worker invocation by design so the operator can scroll back through the final output, but accumulate across phases and clutter the screen.
29
+ - Codex / Gemini worker wrappers spawn `tail -F` trace panes in the lead's tmux session, titled `<cli>-<role>-<pid>-trace` (e.g. `codex-worker-93421-trace`, `gemini-executor-93422-trace`) — the matching caller (worker) pane is titled `<cli>-<role>-<pid>` so worker ↔ trace pairs can be matched by the shared `<pid>` suffix. They survive every worker invocation by design so the operator can scroll back through the final output, but accumulate across phases and clutter the screen.
30
30
  - When `$TMUX_PANE` is set, after the final-report file has been written and the routing recommendation has been issued, the lead MUST run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list` exactly once. The output is a tab-separated `<pane_id>\t<pane_title>` list of every trace pane registered for this Claude session.
31
31
  - If the list is empty, skip the question — there is nothing to ask about.
32
32
  - Otherwise the lead MUST present the user with a strict binary choice **before** declaring the phase complete. Use one prompt of this shape (Korean preferred, English acceptable if the rest of the run is in English):