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 +25 -1
- package/README.ja.md +10 -0
- package/README.ko.md +10 -0
- package/README.md +16 -0
- package/README.zh-CN.md +10 -0
- package/README.zh-TW.md +10 -0
- package/VERSION +1 -1
- package/package.json +1 -1
- package/scripts/dispatch.sh +48 -0
- package/scripts/lib/common.sh +19 -0
- package/scripts/runners/run-codex.sh +22 -5
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.
|
|
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.
|
|
1
|
+
0.15.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnilane",
|
|
3
|
-
"version": "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"
|
package/scripts/dispatch.sh
CHANGED
|
@@ -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() {
|
package/scripts/lib/common.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|