omnilane 0.14.0 → 0.15.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/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ semantic version tags.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.0] - 2026-08-28
10
+
11
+ ### Added
12
+
13
+ - Timed-out Codex jobs now print the absolute path to the matching
14
+ `rollout-*.jsonl` session file, resolved from the first progress event's
15
+ `thread_id`, so an interrupted conversation remains directly inspectable.
16
+
17
+ ### Changed
18
+
19
+ - Codex runner now invokes `codex exec --json`, streaming JSONL events to
20
+ `out.txt.progress.log` before the run exits or times out. `out.txt` and the
21
+ Jobs display, which reads only `out.txt` and `stderr.log`, remain unchanged.
22
+
23
+ ### Fixed
24
+
25
+ - Timeout diagnostics no longer blame a usage-limit retry loop without evidence.
26
+ They now state that a timeout does not identify its cause, provide a
27
+ three-step check, and clarify that an empty progress log does not prove the
28
+ run made no progress.
29
+
30
+
9
31
  ## [0.14.0] - 2026-08-10
10
32
 
11
33
  ### Added
@@ -594,7 +616,9 @@ work to the wrong model, and records the evidence behind the shipped defaults.
594
616
  - Initial shared routing table, cross-vendor dispatcher, runners, installer,
595
617
  and baseline lint fixes.
596
618
 
597
- [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.14.0...HEAD
619
+ [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.15.0...HEAD
620
+
621
+ [0.15.0]: https://github.com/Seraphim0916/omnilane/compare/v0.14.0...v0.15.0
598
622
 
599
623
  [0.14.0]: https://github.com/Seraphim0916/omnilane/compare/v0.13.0...v0.14.0
600
624
  [0.13.0]: https://github.com/Seraphim0916/omnilane/compare/v0.12.0...v0.13.0
package/README.ja.md CHANGED
@@ -516,6 +516,16 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 解決済みプラン、
516
516
 
517
517
  ## 📜 リリース履歴
518
518
 
519
+ ## v0.15.0 の新機能
520
+
521
+ - **Codex の進捗証跡をストリーミング保存** — `codex exec --json` が JSONL イベントを
522
+ `out.txt.progress.log` へ逐次書き込むため、タイムアウト時にも最後に到達した手順を残せます。
523
+ `out.txt` と Jobs の表示は変わりません。
524
+ - **証拠に基づくタイムアウト診断** — タイムアウト自体では原因を特定できないことを明示し、
525
+ 三段階の確認手順と、空の進捗ログが Codex の未進行を示す証拠ではないことを案内します。
526
+ - **rollout 記録への直接パス** — 最初の進捗イベントの `thread_id` から対応する
527
+ `rollout-*.jsonl` の絶対パスをタイムアウト出力に表示し、中断された会話履歴を確認できます。
528
+
519
529
  ## v0.14.0 の新機能
520
530
 
521
531
  - **根拠に基づくルーティング提案**:`jobs recommend` と MCP `jobs_recommend` は公開ジョブメタデータだけを読み、ルーティングを自動変更しません。
package/README.ko.md CHANGED
@@ -499,6 +499,16 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 완전히 해석된 계
499
499
 
500
500
  ## 📜 릴리스 기록
501
501
 
502
+ ## v0.15.0 새 기능
503
+
504
+ - **Codex 진행 증거의 스트리밍 보존** — `codex exec --json`이 JSONL 이벤트를
505
+ `out.txt.progress.log`에 순차 기록하므로 시간 초과가 나도 마지막으로 도달한 단계를 남깁니다.
506
+ `out.txt`와 Jobs 표시는 그대로입니다.
507
+ - **증거 기반 시간 초과 진단** — 시간 초과 자체로 원인을 특정하지 않는다고 밝히고,
508
+ 세 단계 점검 목록과 빈 진행 로그가 Codex 미진행의 증거가 아니라는 점을 안내합니다.
509
+ - **rollout 기록으로 가는 직접 경로** — 첫 진행 이벤트의 `thread_id`로 찾은
510
+ `rollout-*.jsonl`의 절대 경로를 시간 초과 출력에 표시하여, 중단된 대화 이력을 확인할 수 있습니다.
511
+
502
512
  ## v0.14.0 새 기능
503
513
 
504
514
  - **근거 기반 라우팅 제안**: `jobs recommend`와 MCP `jobs_recommend`는 공개 작업 메타데이터만 읽고 라우팅을 자동 변경하지 않습니다.
package/README.md CHANGED
@@ -205,6 +205,10 @@ header overrides that and the choice is remembered locally.
205
205
 
206
206
  Core routing does not need Python; only this UI requires Python 3.9 or newer.
207
207
 
208
+ ### Foreman completion inbox
209
+
210
+ Finished dispatches write a private completion record under `$OMNILANE_HOME/inbox/`. The bundled Claude Code `UserPromptSubmit` hook atomically delivers up to ten matching records on the foreman's next prompt, scoped by the dispatch `workdir`; claimed records move to `inbox/consumed/`. Set `OMNILANE_INBOX=0` on dispatch to disable record creation. The default is enabled.
211
+
208
212
  ## 📦 Install
209
213
 
210
214
  Requirements: the vendor CLIs you want to route to, logged in (`codex`,
@@ -565,6 +569,18 @@ working notes, including per-benchmark caveats, live in
565
569
 
566
570
  ## 📜 Release history
567
571
 
572
+ ## What's new in v0.15.0
573
+
574
+ - **Streaming Codex progress evidence** — `codex exec --json` now streams JSONL
575
+ events into `out.txt.progress.log`, so a timeout still records its last known
576
+ step. `out.txt` and the Jobs display remain unchanged.
577
+ - **Evidence-led timeout diagnostics** — timeouts now state that they do not
578
+ identify the cause, list a three-step check, and clarify that an empty
579
+ progress log does not prove Codex made no progress.
580
+ - **Direct rollout recovery path** — timeout output now prints the absolute path
581
+ to the matching `rollout-*.jsonl`, found from the first progress event's
582
+ `thread_id`, so the interrupted conversation history can be inspected.
583
+
568
584
  ## What's new in v0.14.0
569
585
 
570
586
  - **Evidence-based routing recommendations** — `jobs recommend` and MCP
package/README.zh-CN.md CHANGED
@@ -473,6 +473,16 @@ vendor 一律当成 `work`,而且它只能逐次明确指定,永远不是 lane
473
473
 
474
474
  ## 📜 版本历程
475
475
 
476
+ ## v0.15.0 新功能
477
+
478
+ - **流式保留 Codex 进度证据**:`codex exec --json` 会将 JSONL 事件逐条写入
479
+ `out.txt.progress.log`,即使超时也会留下最后执行到哪一步。`out.txt` 与任务列表显示保持不变。
480
+ - **超时诊断回归证据**:超时提示明确说明它本身不足以判定原因,提供三步检查清单,并说明空的
481
+ 进度日志不是 Codex 从未推进的证据。
482
+ - **直接给出 rollout 记录位置**:超时输出会根据第一条进度事件的 `thread_id`,打印
483
+ ${CODEX_HOME:-$HOME/.codex}/sessions 下对应 `rollout-*.jsonl` 的绝对路径,便于查看
484
+ 被中断而未回报的完整对话历程。
485
+
476
486
  ## v0.14.0 新功能
477
487
 
478
488
  - **基于证据的路由建议**:`jobs recommend` 与 MCP `jobs_recommend` 只读取公开作业元数据,并且不会自动修改路由。
package/README.zh-TW.md CHANGED
@@ -497,6 +497,16 @@ vendor 一律當成 `work`,而且它只能逐次明確指定,永遠不是 lane
497
497
 
498
498
  ## 📜 版本歷程
499
499
 
500
+ ## v0.15.0 新功能
501
+
502
+ - **串流保留 Codex 進度證據**:`codex exec --json` 會把 JSONL 事件逐筆寫入
503
+ `out.txt.progress.log`,即使逾時也能留下最後做到哪一步。`out.txt` 與工作清單顯示維持不變。
504
+ - **逾時診斷回到證據**:逾時訊息明示它本身不足以判定原因,提供三步檢查清單,並說明空的
505
+ 進度日誌不是 Codex 未曾前進的證據。
506
+ - **直接指出 rollout 記錄位置**:逾時輸出會依第一筆進度事件的 `thread_id`,印出
507
+ ${CODEX_HOME:-$HOME/.codex}/sessions 下對應 `rollout-*.jsonl` 的絕對路徑,方便查看
508
+ 被中斷而未回報的完整對話歷程。
509
+
500
510
  ## v0.14.0 新功能
501
511
 
502
512
  - **依證據產生路由建議**:`jobs recommend` 與 MCP `jobs_recommend` 只用已完成工作的
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.14.0
1
+ 0.15.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "One routing table, every harness — classify subtasks into lanes and dispatch each lane to the best vendor's agentic CLI (Codex, Claude, Gemini, Grok) using your existing subscription logins.",
5
5
  "bin": {
6
6
  "omnilane": "bin/omnilane"
@@ -655,10 +655,58 @@ secure_job_files() {
655
655
  find "$JOB_DIR" -type f -exec chmod 600 {} +
656
656
  }
657
657
 
658
+ completion_tail() {
659
+ local path="$1" size=0 keep value="" sentinel=$'\001'
660
+ local note=$'[truncated: leading output omitted]\n'
661
+ local LC_ALL=C
662
+
663
+ if [[ -f "$path" && ! -L "$path" ]]; then
664
+ size="$(wc -c < "$path" 2>/dev/null | tr -d '[:space:]')" || return 1
665
+ [[ "$size" =~ ^[0-9]+$ ]] || return 1
666
+ if [[ "$size" -gt 2000 ]]; then
667
+ keep=$((2000 - ${#note}))
668
+ value="$(printf '%s' "$note"; tail -c "$keep" "$path"; printf '%s' "$sentinel")" || return 1
669
+ else
670
+ value="$(cat "$path"; printf '%s' "$sentinel")" || return 1
671
+ fi
672
+ value="${value%"$sentinel"}"
673
+ fi
674
+ printf '%s' "$value"
675
+ }
676
+
677
+ write_completion_record() {
678
+ local rc="$1" inbox="$OMNILANE_HOME/inbox"
679
+ local final="$inbox/$JOB_ID.json" tmp="$inbox/.$JOB_ID.tmp.$$-$RANDOM"
680
+ local tail_value finished old_umask write_rc=0 sentinel=$'\001'
681
+
682
+ prepare_inbox_store || return 1
683
+ tail_value="$(completion_tail "$JOB_DIR/out.txt"; printf '%s' "$sentinel")" || return 1
684
+ tail_value="${tail_value%"$sentinel"}"
685
+ finished="$(date -u +%FT%TZ)" || return 1
686
+ old_umask="$(umask)"
687
+ umask 077
688
+ printf '{"job_id":"%s","lane":"%s","vendor":"%s","model":"%s","mode":"%s","workdir":"%s","exit":%s,"finished":"%s","tail":"%s"}\n' \
689
+ "$(json_escape "$JOB_ID")" "$(json_escape "$LANE")" "$(json_escape "$VENDOR")" \
690
+ "$(json_escape "$MODEL")" "$(json_escape "$MODE")" "$(json_escape "$WORKDIR")" \
691
+ "$rc" "$(json_escape "$finished")" "$(json_escape "$tail_value")" > "$tmp" || write_rc=$?
692
+ if [[ "$write_rc" -eq 0 ]]; then
693
+ chmod 600 "$tmp" || write_rc=$?
694
+ fi
695
+ if [[ "$write_rc" -eq 0 ]]; then
696
+ mv "$tmp" "$final" || write_rc=$?
697
+ fi
698
+ umask "$old_umask"
699
+ rm -f "$tmp" 2>/dev/null || true
700
+ return "$write_rc"
701
+ }
702
+
658
703
  finish_job() {
659
704
  local rc="$1"
660
705
  secure_job_files
661
706
  (umask 077; printf '%s\n' "$rc" > "$JOB_DIR/exit")
707
+ if [[ "${OMNILANE_INBOX:-1}" != "0" ]]; then
708
+ write_completion_record "$rc" >/dev/null 2>&1 || true
709
+ fi
662
710
  }
663
711
 
664
712
  run_job() {
@@ -181,6 +181,25 @@ read_lock_owner() {
181
181
  LOCK_OWNER_VALUE="$value"
182
182
  }
183
183
 
184
+ prepare_private_store() { # path, diagnostic label
185
+ local store_root="$1" store_label="$2"
186
+ mkdir -p "$OMNILANE_HOME" || return 1
187
+ if [[ -L "$store_root" || ( -e "$store_root" && ! -d "$store_root" ) ]]; then
188
+ echo "omnilane: unsafe $store_label path (want real directory): $store_root" >&2
189
+ return 1
190
+ fi
191
+ [[ -d "$store_root" ]] || mkdir -m 700 "$store_root" || return 1
192
+ [[ -d "$store_root" && ! -L "$store_root" ]] || {
193
+ echo "omnilane: $store_label changed while preparing it" >&2
194
+ return 1
195
+ }
196
+ chmod 700 "$store_root" || return 1
197
+ }
198
+
199
+ prepare_inbox_store() {
200
+ prepare_private_store "$OMNILANE_HOME/inbox" "inbox store"
201
+ }
202
+
184
203
  prepare_lock_store() {
185
204
  local lock_root="$OMNILANE_HOME/locks"
186
205
  mkdir -p "$OMNILANE_HOME"
@@ -16,7 +16,9 @@ RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
16
16
 
17
17
  # --skip-git-repo-check: the operator chose WORKDIR explicitly; codex would
18
18
  # otherwise refuse any directory that is not a trusted git repo.
19
- ARGS=(exec -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
19
+ # --json: without it codex writes nothing until it exits, so a watchdog kill
20
+ # leaves an empty progress log that looks identical to a run that never started.
21
+ ARGS=(exec --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
20
22
  [[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(-c "model_reasoning_effort=\"$EFFORT\"")
21
23
  if [[ "$MODE" == "advise" ]]; then
22
24
  ARGS+=(--ephemeral -s read-only)
@@ -41,10 +43,25 @@ set +e
41
43
  RC=$?
42
44
  set -e
43
45
 
44
- # Watchdog kill with an empty progress log = codex produced nothing at all;
45
- # the usual cause is a quota-window 429 retry loop, which codex retries silently.
46
- if [[ ("$RC" -eq 142 || "$RC" -eq 124) && ! -s "${OUTPUT_FILE}.progress.log" ]]; then
47
- echo "omnilane: codex timed out after ${RUN_TIMEOUT}s with no output — likely a usage-limit retry loop; check your codex quota window and retry" >> "${OUTPUT_FILE}.stderr.log"
46
+ if [[ "$RC" -eq 142 || "$RC" -eq 124 ]]; then
47
+ {
48
+ echo "omnilane: codex timed out after ${RUN_TIMEOUT}s. This does NOT identify the cause."
49
+ echo "omnilane: an empty progress log is NOT evidence of a stall check, in order:"
50
+ echo " 1. the streamed events in ${OUTPUT_FILE}.progress.log — the last event says how far the run got"
51
+ echo " 2. the rollout file below — if it stops at task_started, the request never left the CLI"
52
+ echo " 3. your provider proxy's log for the run window: rate-limit responses, and whether a"
53
+ echo " request was ever sent upstream at all"
54
+ # The rollout holds the full turn history a killed run never got to report;
55
+ # thread_id is the only handle onto it once the process is gone.
56
+ thread_id="$(head -1 "${OUTPUT_FILE}.progress.log" 2>/dev/null |
57
+ sed -n 's/.*"thread_id":"\([^"]*\)".*/\1/p')"
58
+ if [[ -n "$thread_id" ]]; then
59
+ rollout="$(find "${CODEX_HOME:-$HOME/.codex}/sessions" -name "rollout-*-${thread_id}.jsonl" 2>/dev/null | head -1)"
60
+ echo "omnilane: rollout: ${rollout:-not found (thread ${thread_id})}"
61
+ else
62
+ echo "omnilane: rollout: unknown — no thread_id in the progress log, so codex died before its first event"
63
+ fi
64
+ } >> "${OUTPUT_FILE}.stderr.log"
48
65
  fi
49
66
 
50
67
  if [[ -f "${OUTPUT_FILE}.tmp" ]]; then