omnilane 0.13.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,59 @@ 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
+
31
+ ## [0.14.0] - 2026-08-10
32
+
33
+ ### Added
34
+
35
+ - Optional `omnilane doctor --probe V` and read-only MCP `provider_probe` perform
36
+ one bounded advise-mode live capability check only when explicitly requested;
37
+ reports include routing/timing metadata but never the provider response body.
38
+ - Live Board retained-history search and state filters now drive a local
39
+ **Export visible** JSON download that excludes tokens, task/result bodies,
40
+ workdirs, and logs.
41
+ - `omnilane benchmark` provides a versioned fixed-workload quality/timing report,
42
+ transparent user-supplied per-call cost estimates, default no-call dry-run,
43
+ and an explicit `--run` provider-call gate.
44
+ - CI runs `omnilane doctor --strict --json` against an isolated, offline fixture
45
+ as a post-install acceptance contract.
46
+
47
+ - `omnilane jobs recommend` and the read-only MCP `jobs_recommend` tool derive
48
+ per-lane vendor suggestions from bounded completed-job metadata. A configurable
49
+ minimum sample gate prevents one-off successes from outranking established
50
+ evidence; neither surface reads task/result bodies or changes routing.
51
+ - `omnilane doctor --strict` keeps warning diagnostics intact but exits nonzero
52
+ when any warning is present, so local automation and CI can enforce a complete
53
+ runtime setup. JSON output now includes the applied `strict` policy and makes
54
+ `ok` reflect it.
55
+
56
+ ### Fixed
57
+
58
+ - `doctor` now validates macOS `stat -f` output before falling back to GNU
59
+ `stat -c`, so owner-only job-store permissions are classified consistently on
60
+ Linux CI instead of becoming a false strict-mode warning.
61
+
9
62
  ## [0.13.0] - 2026-08-03
10
63
 
11
64
  ### Changed
@@ -563,7 +616,11 @@ work to the wrong model, and records the evidence behind the shipped defaults.
563
616
  - Initial shared routing table, cross-vendor dispatcher, runners, installer,
564
617
  and baseline lint fixes.
565
618
 
566
- [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.13.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
622
+
623
+ [0.14.0]: https://github.com/Seraphim0916/omnilane/compare/v0.13.0...v0.14.0
567
624
  [0.13.0]: https://github.com/Seraphim0916/omnilane/compare/v0.12.0...v0.13.0
568
625
  [0.12.0]: https://github.com/Seraphim0916/omnilane/compare/v0.11.0...v0.12.0
569
626
  [0.11.0]: https://github.com/Seraphim0916/omnilane/compare/v0.10.4...v0.11.0
package/README.ja.md CHANGED
@@ -516,6 +516,24 @@ 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
+
529
+ ## v0.14.0 の新機能
530
+
531
+ - **根拠に基づくルーティング提案**:`jobs recommend` と MCP `jobs_recommend` は公開ジョブメタデータだけを読み、ルーティングを自動変更しません。
532
+ - **任意の実動プローブ**:`doctor --probe V` と MCP `provider_probe` は明示された場合だけプロバイダーを呼び出し、応答本文を報告しません。
533
+ - **Live Board の履歴検索・絞り込み・エクスポート**:直近 50 件を対象に、表示中の公開メタデータだけを出力します。
534
+ - **固定品質/コストベンチマーク**:`omnilane benchmark` は既定で dry-run、実呼び出しには `--run` が必要です。
535
+ - **厳格なインストール検証**:CI で隔離された `doctor --strict --json` を実行し、macOS/GNU `stat` の権限判定差も修正しました。
536
+
519
537
  ## v0.13.0 の新機能
520
538
 
521
539
  - **`long-context` を AA-LCR で順序付け** — Artificial Analysis の長文脈推論
package/README.ko.md CHANGED
@@ -499,6 +499,24 @@ 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
+
512
+ ## v0.14.0 새 기능
513
+
514
+ - **근거 기반 라우팅 제안**: `jobs recommend`와 MCP `jobs_recommend`는 공개 작업 메타데이터만 읽고 라우팅을 자동 변경하지 않습니다.
515
+ - **선택형 실제 기능 탐지**: `doctor --probe V`와 MCP `provider_probe`는 명시적으로 요청할 때만 공급자를 호출하며 응답 본문을 보고하지 않습니다.
516
+ - **Live Board 기록 검색·필터·내보내기**: 최근 50개 작업을 검색하고 현재 표시된 공개 메타데이터만 내보냅니다.
517
+ - **고정 품질/비용 벤치마크**: `omnilane benchmark`는 기본적으로 dry-run이며 실제 호출에는 `--run`이 필요합니다.
518
+ - **엄격한 설치 검증**: CI에서 격리된 `doctor --strict --json`을 실행하고 macOS/GNU `stat` 권한 판정 차이도 수정했습니다.
519
+
502
520
  ## v0.13.0 새 기능
503
521
 
504
522
  - **`long-context` 를 AA-LCR 기준으로 정렬** — Artificial Analysis 의 장문맥 추론
package/README.md CHANGED
@@ -195,12 +195,20 @@ The reference is memory-only and disappears when the page closes. The board
195
195
  binds only to `127.0.0.1`, uses a random token, and is read-only. It shows
196
196
  `task.txt` and the public `out.txt`, but never raw worker or vendor logs.
197
197
 
198
+ Search and state filters apply to the latest 50 retained jobs. **Export visible**
199
+ downloads only the currently visible public metadata as local JSON; it excludes
200
+ tokens, task text, result bodies, workdirs, and raw logs.
201
+
198
202
  The board reads in English, Japanese, Korean, Traditional Chinese and Simplified
199
203
  Chinese. It follows the browser language on first load; the switcher in the
200
204
  header overrides that and the choice is remembered locally.
201
205
 
202
206
  Core routing does not need Python; only this UI requires Python 3.9 or newer.
203
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
+
204
212
  ## 📦 Install
205
213
 
206
214
  Requirements: the vendor CLIs you want to route to, logged in (`codex`,
@@ -255,7 +263,8 @@ adding a routing reminder. Configure the host to launch the installed CLI:
255
263
 
256
264
  The server exposes `route` plus read-only introspection: `list_lanes`,
257
265
  `explain`, `validate`, `dry_run`, `jobs_list`, `jobs_status`, `jobs_result`,
258
- `jobs_stats`, `jobs_audit`, and `doctor`. `route` defaults to read-only `advise` mode. Calls that select `work` must also
266
+ `jobs_stats`, `jobs_recommend`, `jobs_audit`, `doctor`, and the explicitly opt-in
267
+ `provider_probe`. `route` defaults to read-only `advise` mode. Calls that select `work` must also
259
268
  provide an explicit `workdir`.
260
269
 
261
270
  Node.js is the only runtime requirement (no npm packages). If you prefer
@@ -299,7 +308,8 @@ omnilane ui start # start/reuse the local Live UI;
299
308
  omnilane ui status # report whether the Live UI is running
300
309
  omnilane ui url # print the current authenticated local URL
301
310
  omnilane ui stop # stop the Live UI
302
- omnilane doctor [--json] # read-only routing and runtime health report
311
+ omnilane doctor [--json] [--strict] [--probe V] [--probe-timeout SEC] # live probe is opt-in
312
+ omnilane benchmark [--json] [--run] [--vendor V] [--cost-per-call V=USD] # dry-run by default
303
313
  dispatch.sh [--background] [--dry-run] [--mode advise|work|sysops] [--workdir DIR]
304
314
  [--vendor V] [--model M] [--effort E] [--timeout SEC] [--job-timeout SEC]
305
315
  LANE "TASK" # "-" reads task from stdin
@@ -312,12 +322,24 @@ jobs.sh wait ID [--timeout N] # job exit; 124 timeout; 125 d
312
322
  jobs.sh cancel ID # stop a running job: group SIGTERM, then SIGKILL
313
323
  jobs.sh rm ID # delete one finished/dead job (refuses a running job)
314
324
  jobs.sh [--json] stats [--last N] [--lane L] [--vendor V] # local success and routing aggregates
325
+ jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N] # evidence-gated vendor suggestion
315
326
  jobs.sh audit [--last N] [--json] # read-only job integrity/privacy check
316
327
  jobs.sh prune [--keep N] [--apply] # preview by default; completed jobs only
317
328
  configure.sh # interactive lane menu
318
329
  configure.sh set|get|unset|list|diff LANE [SPEC] # script/inspect routing.local.yaml, no tty
319
330
  ```
320
331
 
332
+ `jobs recommend` reads only validated public metadata and exit codes. It ranks
333
+ eligible vendors by success rate, sample count, then name; the default minimum
334
+ is three completed jobs. It never reads task/result bodies or changes routing.
335
+
336
+ `doctor --probe V` makes one bounded advise-mode provider call and returns only
337
+ availability, selected model, timing, and response byte count—not the response
338
+ body. Without `--probe`, doctor remains offline. `benchmark` uses the fixed TSV
339
+ suite in `benchmarks/workloads.tsv`; its default dry-run resolves every route
340
+ without provider calls. `--run` is the explicit call gate, and cost totals are
341
+ estimates based only on values supplied with `--cost-per-call`.
342
+
321
343
  **Big decisions can get a panel, not a person.** The `arbitrate` lane ships
322
344
  **disabled** — a panel costs one call per voter per round, so it is opt-in.
323
345
  Enable it with `arbitrate: vote codex,claude,grok -` in `routing.local.yaml`,
@@ -547,6 +569,36 @@ working notes, including per-benchmark caveats, live in
547
569
 
548
570
  ## 📜 Release history
549
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
+
584
+ ## What's new in v0.14.0
585
+
586
+ - **Evidence-based routing recommendations** — `jobs recommend` and MCP
587
+ `jobs_recommend` rank vendors from completed public job metadata, enforce a
588
+ minimum sample gate, and never change routing automatically.
589
+ - **Opt-in live capability probes** — `doctor --probe V` and MCP
590
+ `provider_probe` distinguish an installed CLI from a working provider call.
591
+ Default doctor remains offline; probe reports never include response bodies.
592
+ - **Search, filter, and export Live Board history** — search and state filters
593
+ cover the latest 50 jobs, while **Export visible** downloads only filtered
594
+ public metadata.
595
+ - **Repeatable quality/cost benchmark** — `omnilane benchmark` ships a fixed
596
+ workload suite, defaults to provider-free dry-run, and requires `--run` for
597
+ actual calls. Cost estimates use only explicit `--cost-per-call` values.
598
+ - **Strict installation acceptance** — CI runs isolated
599
+ `omnilane doctor --strict --json`, catching incomplete runtime wiring without
600
+ contacting providers.
601
+
550
602
  ## What's new in v0.13.0
551
603
 
552
604
  - **`long-context` is ordered on AA-LCR** — Artificial Analysis's long-context
package/README.zh-CN.md CHANGED
@@ -473,6 +473,24 @@ 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
+
486
+ ## v0.14.0 新功能
487
+
488
+ - **基于证据的路由建议**:`jobs recommend` 与 MCP `jobs_recommend` 只读取公开作业元数据,并且不会自动修改路由。
489
+ - **可选的真实能力探测**:`doctor --probe V` 与 MCP `provider_probe` 仅在明确请求时调用供应商,报告不包含回答正文。
490
+ - **Live Board 历史搜索、筛选与导出**:支持最近 50 条作业,并且只导出当前可见的公开元数据。
491
+ - **固定质量/成本基准**:`omnilane benchmark` 默认只做 dry-run,`--run` 才实际调用供应商。
492
+ - **严格安装验收**:CI 在隔离环境执行 `doctor --strict --json`,并修正 macOS/GNU `stat` 权限检测差异。
493
+
476
494
  ## v0.13.0 新功能
477
495
 
478
496
  - **`long-context` 改用 AA-LCR 排序**——那是 Artificial Analysis 的长脉络推理基准,
package/README.zh-TW.md CHANGED
@@ -178,6 +178,9 @@ omnilane ui stop # 正常停止
178
178
  `127.0.0.1`、用隨機 token 保護、全程唯讀。畫面只顯示 `task.txt` 與公開的
179
179
  `out.txt`,不顯示工作端或廠商原始 log。
180
180
 
181
+ 搜尋與狀態篩選會套用到最近保留的 50 筆工作歷史。按「匯出目前結果」只會把畫面上
182
+ 目前可見的公開中繼資料下載成 JSON;不含 token、任務本文、結果本文、工作目錄與原始 log。
183
+
181
184
  畫面提供英文、日文、韓文、繁體中文與簡體中文。首次載入依瀏覽器語言決定,可用標題列
182
185
  的切換器覆寫,選擇會記在本機。
183
186
 
@@ -228,7 +231,7 @@ omnilane ui stop # 正常停止
228
231
  }
229
232
  ```
230
233
 
231
- Server 提供 `route`,以及一組唯讀查詢工具:`list_lanes`、`explain`、`validate`、`dry_run`、`jobs_list`、`jobs_status`、`jobs_result`、`jobs_stats`、`jobs_audit`、`doctor`。
234
+ Server 提供 `route`,以及一組唯讀查詢工具:`list_lanes`、`explain`、`validate`、`dry_run`、`jobs_list`、`jobs_status`、`jobs_result`、`jobs_stats`、`jobs_recommend`、`jobs_audit`、`doctor`,以及必須明確選用的 `provider_probe`。
232
235
  `route` 預設唯讀 `advise` 模式;選 `work` 的呼叫必須同時提供明確的
233
236
  `workdir`。
234
237
 
@@ -266,7 +269,8 @@ omnilane ui start # 啟動或沿用本機 Live UI,
266
269
  omnilane ui status # 查看 Live UI 是否運作中
267
270
  omnilane ui url # 印出目前通過驗證的本機網址
268
271
  omnilane ui stop # 停止 Live UI
269
- omnilane doctor [--json] # 唯讀檢查路由與本機執行環境
272
+ omnilane doctor [--json] [--strict] [--probe V] [--probe-timeout SEC] # 實際探測必須明確選用
273
+ omnilane benchmark [--json] [--run] [--vendor V] [--cost-per-call V=USD] # 預設只乾跑
270
274
  dispatch.sh [--background] [--dry-run] [--mode advise|work|sysops] [--workdir 目錄]
271
275
  [--vendor V] [--model M] [--effort E] [--timeout SEC] [--job-timeout SEC]
272
276
  通道 "任務" # "-" 表示從 stdin 讀任務
@@ -279,6 +283,7 @@ jobs.sh wait 工作ID [--timeout N] # 工作結束碼;124 逾
279
283
  jobs.sh cancel 工作ID # 停止執行中的工作:整組 SIGTERM,再 SIGKILL
280
284
  jobs.sh rm 工作ID # 刪除單一已完成/已死工作(執行中會被拒絕)
281
285
  jobs.sh [--json] stats [--last N] [--lane L] [--vendor V] # 本機成功率與路由彙整
286
+ jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N] # 有證據門檻的廠商建議
282
287
  jobs.sh audit [--last N] [--json] # 唯讀檢查工作完整性與隱私
283
288
  jobs.sh prune [--keep N] [--apply] # 預設只預覽;只清理已完成工作
284
289
  omnilane mcp # MCP stdio server(需 Node.js)
@@ -287,6 +292,15 @@ configure.sh # 互動通道選單
287
292
  configure.sh set|get|unset|list|diff LANE [SPEC] # 非互動編輯/檢視 routing.local.yaml
288
293
  ```
289
294
 
295
+ `jobs recommend` 只讀取通過驗證的公開中繼資料與退出碼。候選達到最低樣本數後,
296
+ 依成功率、樣本數、廠商名稱排序;預設至少三筆已完成工作。它不讀任務/結果本文,
297
+ 也不修改路由。
298
+
299
+ `doctor --probe V` 只會做一次有逾時上限的 advise 模式供應商呼叫,回傳可用性、
300
+ 選到的模型、耗時與回應位元組數,不回傳回答本文;未帶 `--probe` 時仍完全離線。
301
+ `benchmark` 使用 `benchmarks/workloads.tsv` 的固定題組,預設只解析路由、不呼叫供應商;
302
+ `--run` 才是實際呼叫閘門,成本總額也只依 `--cost-per-call` 明確提供的估值計算。
303
+
290
304
  **重大決定可以開評審團,不是問一個人。**`arbitrate` 通道**預設關閉**——
291
305
  評審團每評審每輪燒一次額度,所以做成選配。要用就在 `routing.local.yaml`
292
306
  寫 `arbitrate: vote codex,claude,grok -`,或跑設定選單,從
@@ -483,6 +497,29 @@ vendor 一律當成 `work`,而且它只能逐次明確指定,永遠不是 lane
483
497
 
484
498
  ## 📜 版本歷程
485
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
+
510
+ ## v0.14.0 新功能
511
+
512
+ - **依證據產生路由建議**:`jobs recommend` 與 MCP `jobs_recommend` 只用已完成工作的
513
+ 公開中繼資料排序供應商,設有最低樣本門檻,也不會自動修改路由。
514
+ - **選配實際能力探測**:`doctor --probe V` 與 MCP `provider_probe` 可分辨「CLI 已安裝」
515
+ 和「供應商呼叫真的可用」。doctor 預設仍離線,探測報告不含回答本文。
516
+ - **Live Board 歷史搜尋、篩選與匯出**:搜尋與狀態篩選涵蓋最近 50 筆工作;
517
+ 「匯出目前結果」只下載篩選後的公開中繼資料。
518
+ - **可重現的品質/成本基準**:`omnilane benchmark` 內建固定題組,預設不呼叫供應商;
519
+ `--run` 才會實際執行,成本也只依明確提供的 `--cost-per-call` 估值計算。
520
+ - **嚴格安裝驗收**:CI 以隔離環境執行 `omnilane doctor --strict --json`,
521
+ 不接觸供應商也能抓出 runtime 接線不完整。
522
+
486
523
  ## v0.13.0 新功能
487
524
 
488
525
  - **`long-context` 改用 AA-LCR 排序**——那是 Artificial Analysis 的長脈絡推理基準,
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.15.0
@@ -0,0 +1,4 @@
1
+ # id<TAB>preferred_lane<TAB>weight<TAB>Python regex<TAB>prompt
2
+ exact_alpha consult 1 ^ALPHA\s*$ Return exactly ALPHA and nothing else.
3
+ arithmetic consult 2 ^42\s*$ Return only the integer result of 17 + 25.
4
+ json_contract hard-judgment 2 ^\{"status":"pass","count":3\}\s*$ Return exactly this compact JSON object with the stated key order: {"status":"pass","count":3}
package/bin/omnilane CHANGED
@@ -52,6 +52,13 @@ case "$cmd" in
52
52
  ;;
53
53
  release-audit) exec bash "$REPO/scripts/release-audit.sh" "$@" ;;
54
54
  doctor) exec bash "$REPO/scripts/doctor.sh" "$@" ;;
55
+ benchmark)
56
+ command -v python3 >/dev/null 2>&1 || {
57
+ echo "omnilane: benchmark requires Python 3.9 or newer" >&2
58
+ exit 1
59
+ }
60
+ exec python3 "$REPO/scripts/benchmark.py" "$@"
61
+ ;;
55
62
  ui)
56
63
  command -v python3 >/dev/null 2>&1 || {
57
64
  echo "omnilane: ui requires Python 3.9 or newer" >&2
@@ -68,11 +75,12 @@ omnilane — one routing table, every harness
68
75
  omnilane list effective routing table
69
76
  omnilane route [--vendor V] [flags] LANE "TASK"
70
77
  dispatch or consult a model
71
- omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, wait, or audit
78
+ omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, recommend, wait, or audit
72
79
  omnilane mcp start the MCP stdio server
73
80
  omnilane completion bash|zsh|fish print a shell completion script
74
81
  omnilane release-audit [flags] offline, read-only release gate
75
- omnilane doctor [--json] read-only local health report
82
+ omnilane doctor [--json] [--strict] [--probe V] local health / opt-in live probe
83
+ omnilane benchmark [--json] [--run] [--vendor V] fixed quality/cost comparison
76
84
  omnilane ui start|status|url|stop
77
85
  omnilane configure interactive lane menu
78
86
  EOF
package/bin/omnilane-mcp CHANGED
@@ -10,6 +10,8 @@ const ROOT = path.resolve(__dirname, '..');
10
10
  const DISPATCH_SCRIPT = path.join(ROOT, 'scripts', 'dispatch.sh');
11
11
  const JOBS_SCRIPT = path.join(ROOT, 'scripts', 'jobs.sh');
12
12
  const DOCTOR_SCRIPT = path.join(ROOT, 'scripts', 'doctor.sh');
13
+ const PROVIDER_PROBE_SCRIPT = process.env.OMNILANE_PROVIDER_PROBE_SCRIPT ||
14
+ path.join(ROOT, 'scripts', 'provider-probe.sh');
13
15
  const LATEST_PROTOCOL_VERSION = '2025-11-25';
14
16
  const SUPPORTED_PROTOCOL_VERSIONS = new Set([
15
17
  '2025-11-25',
@@ -285,6 +287,37 @@ const tools = [
285
287
  openWorldHint: false,
286
288
  },
287
289
  },
290
+ {
291
+ name: 'provider_probe',
292
+ description: 'Explicitly invoke one configured provider with a bounded fixed prompt to verify live inference. Incurs one provider call; never returns the provider response.',
293
+ inputSchema: {
294
+ type: 'object',
295
+ required: ['vendor'],
296
+ properties: {
297
+ vendor: {
298
+ type: 'string',
299
+ enum: ['codex', 'claude', 'grok', 'gemini', 'kimi', 'qwen', 'opencode', 'openrouter', 'deepseek', 'zai', 'mistral', 'groq', 'cerebras'],
300
+ },
301
+ timeout: {
302
+ type: 'integer',
303
+ minimum: 1,
304
+ maximum: 300,
305
+ default: 30,
306
+ },
307
+ json: {
308
+ type: 'boolean',
309
+ default: true,
310
+ },
311
+ },
312
+ additionalProperties: false,
313
+ },
314
+ annotations: {
315
+ readOnlyHint: true,
316
+ destructiveHint: false,
317
+ idempotentHint: false,
318
+ openWorldHint: true,
319
+ },
320
+ },
288
321
  {
289
322
  name: 'jobs_stats',
290
323
  description: 'Aggregate local background-job outcomes and lane/vendor distribution from bounded public metadata. Never reads task or result bodies.',
@@ -311,6 +344,45 @@ const tools = [
311
344
  openWorldHint: false,
312
345
  },
313
346
  },
347
+ {
348
+ name: 'jobs_recommend',
349
+ description: 'Recommend a vendor per lane from bounded local completed-job outcomes. Read-only, evidence-gated, and never reads task or result bodies.',
350
+ inputSchema: {
351
+ type: 'object',
352
+ properties: {
353
+ last: {
354
+ type: 'integer',
355
+ minimum: 1,
356
+ maximum: 10000,
357
+ description: 'Limit analysis to the most recent N jobs.',
358
+ },
359
+ lane: {
360
+ type: 'string',
361
+ pattern: '^[a-z][a-z0-9-]*$',
362
+ description: 'Optionally analyze one lane.',
363
+ },
364
+ min_samples: {
365
+ type: 'integer',
366
+ minimum: 1,
367
+ maximum: 1000,
368
+ default: 3,
369
+ description: 'Completed samples required before a vendor is eligible for recommendation.',
370
+ },
371
+ json: {
372
+ type: 'boolean',
373
+ default: false,
374
+ description: 'Return the versioned JSON envelope instead of the human report.',
375
+ },
376
+ },
377
+ additionalProperties: false,
378
+ },
379
+ annotations: {
380
+ readOnlyHint: true,
381
+ destructiveHint: false,
382
+ idempotentHint: true,
383
+ openWorldHint: false,
384
+ },
385
+ },
314
386
  {
315
387
  name: 'jobs_audit',
316
388
  description: 'Bounded read-only integrity and privacy scan of the local job store. Reports findings without printing task or result content.',
@@ -435,6 +507,24 @@ function validateJsonOnlyArguments(args) {
435
507
  return null;
436
508
  }
437
509
 
510
+ function validateProviderProbeArguments(args) {
511
+ const error = validationError(args, ['vendor', 'timeout', 'json']);
512
+ if (error) return error;
513
+ const vendors = new Set([
514
+ 'codex', 'claude', 'grok', 'gemini', 'kimi', 'qwen', 'opencode',
515
+ 'openrouter', 'deepseek', 'zai', 'mistral', 'groq', 'cerebras',
516
+ ]);
517
+ if (typeof args.vendor !== 'string' || !vendors.has(args.vendor)) {
518
+ return 'vendor must name one probe-capable provider';
519
+ }
520
+ if (OWN(args, 'timeout') &&
521
+ (!Number.isInteger(args.timeout) || args.timeout < 1 || args.timeout > 300)) {
522
+ return 'timeout must be an integer from 1 to 300';
523
+ }
524
+ if (OWN(args, 'json') && typeof args.json !== 'boolean') return 'json must be a boolean';
525
+ return null;
526
+ }
527
+
438
528
  function validateJobsQueryArguments(args) {
439
529
  const error = validationError(args, ['last', 'json']);
440
530
  if (error) return error;
@@ -445,6 +535,23 @@ function validateJobsQueryArguments(args) {
445
535
  return null;
446
536
  }
447
537
 
538
+ function validateJobsRecommendArguments(args) {
539
+ const error = validationError(args, ['last', 'lane', 'min_samples', 'json']);
540
+ if (error) return error;
541
+ if (OWN(args, 'last') && (!Number.isInteger(args.last) || args.last < 1 || args.last > 10000)) {
542
+ return 'last must be an integer from 1 to 10000';
543
+ }
544
+ if (OWN(args, 'lane') && (typeof args.lane !== 'string' || !LANE_PATTERN.test(args.lane))) {
545
+ return 'lane must match ^[a-z][a-z0-9-]*$';
546
+ }
547
+ if (OWN(args, 'min_samples') &&
548
+ (!Number.isInteger(args.min_samples) || args.min_samples < 1 || args.min_samples > 1000)) {
549
+ return 'min_samples must be an integer from 1 to 1000';
550
+ }
551
+ if (OWN(args, 'json') && typeof args.json !== 'boolean') return 'json must be a boolean';
552
+ return null;
553
+ }
554
+
448
555
  function validateExplainArguments(args) {
449
556
  const error = validationError(args, ['lane', 'json']);
450
557
  if (error) return error;
@@ -645,6 +752,14 @@ async function callTool(name, args) {
645
752
  const argv = args.json === true ? ['--json'] : [];
646
753
  return runToolScript('doctor', DOCTOR_SCRIPT, argv);
647
754
  }
755
+ if (name === 'provider_probe') {
756
+ const error = validateProviderProbeArguments(args);
757
+ if (error) return toolError(error);
758
+ const argv = ['--vendor', args.vendor];
759
+ if (OWN(args, 'timeout')) argv.push('--timeout', String(args.timeout));
760
+ if (args.json !== false) argv.push('--json');
761
+ return runToolScript('provider_probe', PROVIDER_PROBE_SCRIPT, argv);
762
+ }
648
763
  if (name === 'jobs_stats' || name === 'jobs_audit') {
649
764
  const error = validateJobsQueryArguments(args);
650
765
  if (error) return toolError(error);
@@ -654,6 +769,17 @@ async function callTool(name, args) {
654
769
  if (OWN(args, 'last')) argv.push('--last', String(args.last));
655
770
  return runToolScript(name, JOBS_SCRIPT, argv);
656
771
  }
772
+ if (name === 'jobs_recommend') {
773
+ const error = validateJobsRecommendArguments(args);
774
+ if (error) return toolError(error);
775
+ const argv = [];
776
+ if (args.json === true) argv.push('--json');
777
+ argv.push('recommend');
778
+ if (OWN(args, 'last')) argv.push('--last', String(args.last));
779
+ if (OWN(args, 'lane')) argv.push('--lane', args.lane);
780
+ if (OWN(args, 'min_samples')) argv.push('--min-samples', String(args.min_samples));
781
+ return runToolScript(name, JOBS_SCRIPT, argv);
782
+ }
657
783
  throw new RpcError(-32602, `Unknown tool: ${name}`);
658
784
  }
659
785
 
@@ -53,7 +53,7 @@ _omnilane() {
53
53
  local command="${words[2]:-}" sub sub_index=3
54
54
  local -a lanes ids
55
55
  if (( CURRENT == 2 )); then
56
- _values 'command' version list route dispatch jobs doctor release-audit ui configure completion help
56
+ _values 'command' version list route dispatch jobs doctor benchmark release-audit ui configure completion help
57
57
  return
58
58
  fi
59
59
  case "$command" in
@@ -78,7 +78,7 @@ _omnilane() {
78
78
  sub="${words[4]:-}"
79
79
  fi
80
80
  if (( CURRENT == sub_index )); then
81
- _values 'job command' list status result tail retry stats wait audit prune help
81
+ _values 'job command' list status result tail retry stats recommend wait audit prune help
82
82
  elif (( CURRENT == sub_index + 1 )) &&
83
83
  [[ "$sub" == status || "$sub" == result || "$sub" == wait ||
84
84
  "$sub" == tail || "$sub" == retry ]]; then
@@ -92,7 +92,13 @@ _omnilane() {
92
92
  _arguments '--background[re-dispatch in the background]'
93
93
  elif [[ "$sub" == wait ]]; then
94
94
  _arguments '--timeout[maximum wait seconds]:seconds:'
95
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
95
+ elif [[ "$sub" == stats ]]; then
96
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
97
+ '--vendor[filter by vendor]:vendor:' '--json[versioned metadata output]'
98
+ elif [[ "$sub" == recommend ]]; then
99
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
100
+ '--min-samples[minimum completed samples]:count:' '--json[versioned metadata output]'
101
+ elif [[ "$sub" == audit ]]; then
96
102
  _arguments '--last[maximum jobs]:count:' '--json[versioned metadata output]'
97
103
  elif [[ "$sub" == prune ]]; then
98
104
  _arguments '--keep[completed jobs to retain]:count:' \
@@ -100,7 +106,18 @@ _omnilane() {
100
106
  '--apply[delete eligible jobs]'
101
107
  fi
102
108
  ;;
103
- doctor) _values 'option' --json ;;
109
+ doctor)
110
+ _arguments '--json[versioned JSON output]' '--strict[fail on warnings]' \
111
+ '--probe[run one opt-in live provider probe]:vendor:' \
112
+ '--probe-timeout[probe timeout in seconds]:seconds:'
113
+ ;;
114
+ benchmark)
115
+ _arguments '--json[versioned JSON output]' '--run[invoke providers in advise mode]' \
116
+ '*--vendor[vendor to compare]:vendor:(codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras)' \
117
+ '--timeout[per-workload timeout]:seconds:' \
118
+ '--workloads[TSV workload file]:file:_files' \
119
+ '*--cost-per-call[user-supplied vendor USD estimate]:vendor=USD:'
120
+ ;;
104
121
  release-audit)
105
122
  _arguments '--target[release version]:version:' '--allow-dirty[permit a dirty worktree]' \
106
123
  '--require-tag[require an annotated tag at HEAD]' '--manifest[include tracked inventory]' \
@@ -58,7 +58,7 @@ _omnilane() {
58
58
  prev="${COMP_WORDS[COMP_CWORD-1]:-}"
59
59
  command="${COMP_WORDS[1]:-}"
60
60
  if [[ "$COMP_CWORD" -eq 1 ]]; then
61
- words="version list route dispatch jobs doctor release-audit ui configure completion help"
61
+ words="version list route dispatch jobs doctor benchmark release-audit ui configure completion help"
62
62
  else
63
63
  case "$command" in
64
64
  route|dispatch)
@@ -84,7 +84,7 @@ _omnilane() {
84
84
  sub="${COMP_WORDS[3]:-}"
85
85
  fi
86
86
  if [[ "$COMP_CWORD" -eq "$sub_index" ]]; then
87
- words="list status result tail retry stats wait audit prune help"
87
+ words="list status result tail retry stats recommend wait audit prune help"
88
88
  elif [[ "$COMP_CWORD" -eq $((sub_index + 1)) &&
89
89
  ( "$sub" == status || "$sub" == result || "$sub" == wait ||
90
90
  "$sub" == tail || "$sub" == retry ) ]]; then
@@ -99,7 +99,11 @@ _omnilane() {
99
99
  words="--background"
100
100
  elif [[ "$sub" == wait ]]; then
101
101
  words="--timeout"
102
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
102
+ elif [[ "$sub" == stats ]]; then
103
+ words="--last --lane --vendor --json"
104
+ elif [[ "$sub" == recommend ]]; then
105
+ words="--last --lane --min-samples --json"
106
+ elif [[ "$sub" == audit ]]; then
103
107
  words="--last --json"
104
108
  elif [[ "$sub" == prune ]]; then
105
109
  words="--keep --older-than --apply"
@@ -107,7 +111,18 @@ _omnilane() {
107
111
  return
108
112
  fi
109
113
  ;;
110
- doctor) words="--json" ;;
114
+ doctor) words="--json --strict --probe --probe-timeout" ;;
115
+ benchmark)
116
+ case "$prev" in
117
+ --vendor) words="codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras" ;;
118
+ --workloads)
119
+ COMPREPLY=( $(compgen -f -- "$cur") )
120
+ return
121
+ ;;
122
+ --timeout|--cost-per-call) return ;;
123
+ *) words="--json --run --vendor --timeout --workloads --cost-per-call" ;;
124
+ esac
125
+ ;;
111
126
  release-audit) words="--target --allow-dirty --require-tag --manifest --json" ;;
112
127
  ui) words="start status url stop" ;;
113
128
  completion) words="bash zsh" ;;