omnilane 0.20.0 → 0.21.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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "One routing table, cross-vendor dispatch, foreman completion inbox.",
9
- "version": "0.20.0"
9
+ "version": "0.21.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "omnilane",
14
14
  "source": "./",
15
15
  "description": "Route each subtask to the model that fits, dispatch it to any vendor CLI, and have finished dispatches report back into the foreman's next prompt.",
16
- "version": "0.20.0",
16
+ "version": "0.21.0",
17
17
  "category": "development",
18
18
  "keywords": [
19
19
  "routing",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "One routing table, every harness: classify subtasks into lanes and auto-dispatch each lane to the best vendor CLI (Codex, Claude Code, Grok Build, Antigravity) with background jobs, depth guard, and serialized codex dispatch.",
5
5
  "author": {
6
6
  "name": "Seraphim0916"
package/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ semantic version tags.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.21.0] - 2026-08-30
10
+
11
+ ### Added
12
+
13
+ - Explicit session-mode selection for background dispatches: `--live` requires a resident session, while `--single-shot` forces a one-shot job. `--live` fails fast for a vendor without live support and names the live-capable vendors; the chosen `session_mode` is recorded in `meta.json`.
14
+ - Gemini joins Claude in the live mailbox through the `agy` stream protocol, with the shared protocol implementation in `scripts/lib/live-protocol.sh`.
15
+ - `--idle-timeout N` automatically closes an untended live session, records `closed by idle cap after Ns` as its close reason, and writes `idle_timeout` to `meta.json`.
16
+ - `tests/test_live_mailbox.sh` is wired into `tests/run.sh`, expanding the suite to 94 checks.
17
+
9
18
  ## [0.20.0] - 2026-08-30
10
19
 
11
20
  ### Added
@@ -660,7 +669,9 @@ work to the wrong model, and records the evidence behind the shipped defaults.
660
669
  - Initial shared routing table, cross-vendor dispatcher, runners, installer,
661
670
  and baseline lint fixes.
662
671
 
663
- [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.20.0...HEAD
672
+ [Unreleased]: https://github.com/Seraphim0916/omnilane/compare/v0.21.0...HEAD
673
+
674
+ [0.21.0]: https://github.com/Seraphim0916/omnilane/compare/v0.20.0...v0.21.0
664
675
 
665
676
  [0.20.0]: https://github.com/Seraphim0916/omnilane/compare/v0.15.0...v0.20.0
666
677
 
package/README.ja.md CHANGED
@@ -366,7 +366,7 @@ configure.sh set|get|unset|list|diff LANE [SPEC] # routing.local.yaml を非
366
366
 
367
367
  ## 📬 ライブメールボックス
368
368
 
369
- ライブメールボックスは、1 回で完結するディスパッチとは別の、Claude 専用の常駐バックグラウンド実行です。フォアマンが `--background` で開始し、実行中にも追加の指示を送れます。終わったらフォアマンが `jobs.sh close ID` で閉じます。放置しても常駐し続けるわけではなく、設定済みのジョブ全体タイムアウト(`--job-timeout`)に達すれば終了します。
369
+ ライブメールボックスは、1 回で完結するディスパッチとは別の、Claude Gemini が利用できる常駐バックグラウンド実行です。フォアマンが `--background` で開始し、実行中にも追加の指示を送れます。終わったらフォアマンが `jobs.sh close ID` で閉じます。放置しても常駐し続けるわけではなく、アイドル上限または設定済みのジョブ全体タイムアウト(`--job-timeout`)に達すれば終了します。
370
370
 
371
371
  ```bash
372
372
  scripts/dispatch.sh --background --vendor claude hard-judgment "タイムアウトしたテストを確認する"
@@ -378,9 +378,9 @@ scripts/jobs.sh close "$ID"
378
378
  scripts/jobs.sh retry "$ID" --background
379
379
  ```
380
380
 
381
- `watch` は `$JOB_DIR/events.jsonl` を追跡し、`tail` は公開出力の `out.txt` を読みます。ライブメールボックスを使えるのは現時点で Claude だけです。ほかのベンダーは通常の単発ディスパッチへフォールバックしますが、無通知ではありません。stderr と `$JOB_DIR/mode-notice.txt` に通知が残り、`jobs.sh status ID` にも表示されます。
381
+ `watch` は `$JOB_DIR/events.jsonl` を追跡し、`tail` は公開出力の `out.txt` を読みます。ライブメールボックスは Claude と Gemini に対応し、ほかのベンダーは通常の単発ディスパッチとして実行され、stderr と `$JOB_DIR/mode-notice.txt` に通知が残ります。`--live` は常駐セッションを必須にし、解決されたベンダーが非対応なら即時失敗します。`--single-shot` は Claude や Gemini でも単発実行を強制します。`--idle-timeout SECONDS` はアイドル上限を設定し、既定値は 900 秒、`0` で無効になります。
382
382
 
383
- アイドル中は API 呼び出しも料金も発生しませんが、ジョブ全体タイムアウトの時間は進みます。やり取りが終わったら `close` してください。終了済み、またはライブでないジョブへの `jobs.sh send` は明確なエラーで失敗します。送った後に追跡しない作業、ライブ対応していないベンダー、クリーンな状態からの再実行が必要な場合には使わず、新しいディスパッチ(または完了後の `retry`)を使ってください。
383
+ アイドル中は API 呼び出しも料金も発生しません。既定では、新しい受信メッセージまたは結果イベントが 900 秒間なければ worker が自動で終了し、ジョブ全体タイムアウトは外側の上限として残ります。やり取りが終わったら早めに `close` できます。終了済み、またはライブでないジョブへの `jobs.sh send` は明確なエラーで失敗します。送った後に追跡しない作業、ライブ対応していないベンダー、クリーンな状態からの再実行が必要な場合には使わず、新しいディスパッチ(または完了後の `retry`)を使ってください。
384
384
 
385
385
  ## ❓ FAQ
386
386
 
@@ -534,6 +534,12 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 解決済みプラン、
534
534
 
535
535
  ## 📜 リリース履歴
536
536
 
537
+ ## v0.21.0 の新機能
538
+
539
+ - **セッションモードを明示的に選択。** `dispatch --live` で常駐セッションを必須にするか、`--single-shot` で単発ジョブを強制できます。ライブセッション非対応ベンダーでは `--live` が即時に失敗し、対応ベンダーを表示します。
540
+ - **Gemini がライブメールボックスに参加。** Gemini は `agy` ストリームプロトコルを介し、Claude と並んで常駐ライブジョブを実行できます。
541
+ - **ライブジョブのアイドル上限。** `--idle-timeout N` は放置されたライブセッションを自動で閉じ、終了理由とタイムアウトを `meta.json` に記録します。
542
+
537
543
  ## v0.20.0 の新機能
538
544
 
539
545
  - **フォアマン完了インボックス。** バックグラウンド dispatch の完了時に非公開の
package/README.ko.md CHANGED
@@ -353,7 +353,7 @@ CLI 를 사용할 수 없음, `5` 1라운드 성공 투표자 부족, `6` 2라
353
353
 
354
354
  ## 📬 라이브 메일함
355
355
 
356
- 라이브 메일함은 일회성 dispatch와 다른 Claude 전용 상주 백그라운드 실행입니다. 포어맨이 `--background`로 열고, 실행 중에도 추가 지시를 보낼 수 있으며, 끝나면 `jobs.sh close ID`로 닫을 책임이 있습니다. 방치해도 영구히 남아 있지는 않습니다. 설정된 전체 작업 시간 제한(`--job-timeout`)에 도달하면 종료됩니다.
356
+ 라이브 메일함은 일회성 dispatch와 다른 Claude Gemini용 상주 백그라운드 실행입니다. 포어맨이 `--background`로 열고, 실행 중에도 추가 지시를 보낼 수 있으며, 끝나면 `jobs.sh close ID`로 닫을 책임이 있습니다. 방치해도 영구히 남아 있지는 않습니다. 유휴 상한 또는 설정된 전체 작업 시간 제한(`--job-timeout`)에 도달하면 종료됩니다.
357
357
 
358
358
  ```bash
359
359
  scripts/dispatch.sh --background --vendor claude hard-judgment "시간 제한 테스트 실패를 확인해 주세요"
@@ -365,9 +365,9 @@ scripts/jobs.sh close "$ID"
365
365
  scripts/jobs.sh retry "$ID" --background
366
366
  ```
367
367
 
368
- `watch`는 `$JOB_DIR/events.jsonl`을 따라가고, `tail`은 공개 `out.txt`를 읽습니다. 라이브 메일함은 현재 Claude 지원합니다. 다른 벤더는 일반 일회성 dispatch로 내려가지만 조용히 처리되지는 않습니다. stderr와 `$JOB_DIR/mode-notice.txt`에 알림이 남고, `jobs.sh status ID`에도 표시됩니다.
368
+ `watch`는 `$JOB_DIR/events.jsonl`을 따라가고, `tail`은 공개 `out.txt`를 읽습니다. 라이브 메일함은 Claude Gemini를 지원하며, 다른 벤더는 일반 일회성 dispatch로 실행되고 stderr와 `$JOB_DIR/mode-notice.txt`에 알림이 남습니다. `--live`는 상주 세션을 필수로 하며 결정된 벤더가 지원하지 않으면 즉시 실패합니다. `--single-shot`은 Claude나 Gemini에서도 일회성 실행을 강제합니다. `--idle-timeout SECONDS`는 유휴 상한을 설정하며 기본값은 900초이고 `0`이면 비활성화됩니다.
369
369
 
370
- 유휴 상태에서는 API 호출이나 비용이 발생하지 않지만 전체 작업 시간 제한은 계속 소모됩니다. 대화가 끝나면 `close`하세요. 끝났거나 라이브가 아닌 작업에 `jobs.sh send`를 실행하면 명확한 오류와 함께 실패합니다. 보낸 뒤 추적하지 않을 작업, 라이브 지원이 없는 벤더, 깨끗한 상태에서 다시 실행해야 하는 경우에는 쓰지 말고 새 dispatch 또는 완료 뒤 `retry`를 사용하세요.
370
+ 유휴 상태에서는 API 호출이나 비용이 발생하지 않습니다. 기본적으로 새 수신 메시지나 결과 이벤트가 900초 동안 없으면 worker가 자동으로 종료되며, 전체 작업 시간 제한은 바깥쪽 상한으로 유지됩니다. 대화가 끝나면 더 일찍 `close`할 수 있습니다. 끝났거나 라이브가 아닌 작업에 `jobs.sh send`를 실행하면 명확한 오류와 함께 실패합니다. 보낸 뒤 추적하지 않을 작업, 라이브 지원이 없는 벤더, 깨끗한 상태에서 다시 실행해야 하는 경우에는 쓰지 말고 새 dispatch 또는 완료 뒤 `retry`를 사용하세요.
371
371
 
372
372
  ## ❓ FAQ
373
373
 
@@ -517,6 +517,12 @@ scripts/dispatch.sh --dry-run hardest-coding "…" # 완전히 해석된 계
517
517
 
518
518
  ## 📜 릴리스 기록
519
519
 
520
+ ## v0.21.0 새 기능
521
+
522
+ - **세션 모드를 명시적으로 선택.** `dispatch --live`로 상주 세션을 요구하거나 `--single-shot`으로 단발 작업을 강제할 수 있습니다. 라이브 세션을 지원하지 않는 벤더에서는 `--live`가 즉시 실패하고 지원 벤더를 표시합니다.
523
+ - **Gemini가 라이브 메일함에 합류.** Gemini는 `agy` 스트림 프로토콜을 통해 Claude와 함께 상주 라이브 작업을 실행할 수 있습니다.
524
+ - **라이브 작업의 유휴 제한.** `--idle-timeout N`은 방치된 라이브 세션을 자동으로 닫고 종료 사유와 제한 시간을 `meta.json`에 기록합니다.
525
+
520
526
  ## v0.20.0 새 기능
521
527
 
522
528
  - **Foreman 완료 수신함.** 백그라운드 디스패치가 끝나면 비공개 완료 레코드를
package/README.md CHANGED
@@ -423,7 +423,7 @@ code passes through.
423
423
 
424
424
  ## 📬 Live mailbox
425
425
 
426
- A live mailbox is a resident Claude background dispatch, not a one-shot dispatch. The foreman opens it with `--background`, can send another instruction while it is still running, and is responsible for closing it with `jobs.sh close ID`. Leaving it unattended does not make it permanent: the configured whole-job timeout (`--job-timeout`) still ends it.
426
+ A live mailbox is a resident Claude or Gemini background dispatch, not a one-shot dispatch. The foreman opens it with `--background`, can send another instruction while it is still running, and is responsible for closing it with `jobs.sh close ID`. Leaving it unattended does not make it permanent: the idle cap and configured whole-job timeout (`--job-timeout`) can still end it.
427
427
 
428
428
  ```bash
429
429
  scripts/dispatch.sh --background --vendor claude hard-judgment "Review the timeout failure"
@@ -435,9 +435,9 @@ scripts/jobs.sh close "$ID"
435
435
  scripts/jobs.sh retry "$ID" --background
436
436
  ```
437
437
 
438
- `watch` follows `$JOB_DIR/events.jsonl`; `tail` reads the public `out.txt`. Live mailbox support is Claude-only today. Any other vendor runs as a normal one-shot dispatch, with a degradation notice sent to stderr and stored in `$JOB_DIR/mode-notice.txt`; `jobs.sh status ID` prints that notice as well.
438
+ `watch` follows `$JOB_DIR/events.jsonl`; `tail` reads the public `out.txt`. Live mailbox support covers Claude and Gemini; other vendors run as normal one-shot dispatches, with a notice sent to stderr and stored in `$JOB_DIR/mode-notice.txt`. `--live` requires a resident session and fails fast when the resolved vendor is not capable. `--single-shot` forces one-shot execution even for Claude or Gemini. `--idle-timeout SECONDS` sets the inactivity cap (default 900; `0` disables it).
439
439
 
440
- An idle mailbox makes no API calls and incurs no API spend, but it keeps consuming its job-timeout window. Close it when its exchange is finished. `jobs.sh send` to a finished job or a job that is not live fails with a clear error. Do not use this for fire-and-forget work, vendors without live support, or a clean-slate rerun; start a fresh dispatch (or retry a completed job) instead.
440
+ An idle mailbox makes no API calls and incurs no API spend. By default it closes after 900 seconds without a new inbox message or result event, while the whole-job timeout remains the outer cap. Close it sooner when its exchange is finished. `jobs.sh send` to a finished job or a job that is not live fails with a clear error. Do not use this for fire-and-forget work, vendors without live support, or a clean-slate rerun; start a fresh dispatch (or retry a completed job) instead.
441
441
 
442
442
  ## ❓ FAQ
443
443
 
@@ -596,6 +596,12 @@ working notes, including per-benchmark caveats, live in
596
596
 
597
597
  ## 📜 Release history
598
598
 
599
+ ## What's new in v0.21.0
600
+
601
+ - **Explicit session mode.** Use `dispatch --live` to require a resident session or `--single-shot` to force a one-shot job. `--live` fails immediately for incompatible vendors and lists the live-capable choices.
602
+ - **Gemini joins the live mailbox.** Gemini can now run a resident live job through the `agy` stream protocol alongside Claude.
603
+ - **Idle cap for live jobs.** `--idle-timeout N` automatically closes an untended live session and preserves the close reason and timeout in `meta.json`.
604
+
599
605
  ## What's new in v0.20.0
600
606
 
601
607
  - **Foreman completion inbox.** Finished background dispatches write a private
package/README.zh-CN.md CHANGED
@@ -335,7 +335,7 @@ configure.sh set|get|unset|list|diff LANE [SPEC] # 非交互编辑/查看 rou
335
335
 
336
336
  ## 📬 实时邮箱
337
337
 
338
- 实时邮箱是 Claude 专用的常驻后台派发,不是一次性派发。派发方以 `--background` 打开后,运行中仍可追加指令,并负责用 `jobs.sh close ID` 收尾。即使无人处理,它也不会永久存在:已配置的整个作业超时(`--job-timeout`)到期后仍会终止它。
338
+ 实时邮箱是 Claude Gemini 可用的常驻后台派发,不是一次性派发。派发方以 `--background` 打开后,运行中仍可追加指令,并负责用 `jobs.sh close ID` 收尾。即使无人处理,它也不会永久存在:空闲上限或已配置的整个作业超时(`--job-timeout`)到期后都会终止它。
339
339
 
340
340
  ```bash
341
341
  scripts/dispatch.sh --background --vendor claude hard-judgment "检查超时测试失败的原因"
@@ -347,9 +347,9 @@ scripts/jobs.sh close "$ID"
347
347
  scripts/jobs.sh retry "$ID" --background
348
348
  ```
349
349
 
350
- `watch` 会跟随 `$JOB_DIR/events.jsonl`;`tail` 读取公开的 `out.txt`。目前只有 Claude 支持实时邮箱。其他供应商都会降级为普通的一次性派发,但不是静默发生:stderr 和 `$JOB_DIR/mode-notice.txt` 都会留下提示,`jobs.sh status ID` 也会显示该提示。
350
+ `watch` 会跟随 `$JOB_DIR/events.jsonl`;`tail` 读取公开的 `out.txt`。目前 Claude 和 Gemini 支持实时邮箱;其他供应商会执行普通的一次性派发,并在 stderr 和 `$JOB_DIR/mode-notice.txt` 留下提示。`--live` 明确要求常驻会话,解析出的供应商不支持时立即失败。`--single-shot` 即使遇到 Claude 或 Gemini 也强制一次性派发。`--idle-timeout SECONDS` 设置空闲上限,默认 900 秒,设为 `0` 可禁用。
351
351
 
352
- 空闲时不会发出 API 调用,也不会产生 API 费用,但整个作业超时的时间仍在流逝。处理结束就应执行 `close`。向已结束或不是实时邮箱的作业执行 `jobs.sh send` 会明确报错并失败。即发即忘的工作、没有实时支持的供应商,或需要从干净状态重新运行的情况都不适用;请新建一次派发,或在作业完成后使用 `retry`。
352
+ 空闲时不会发出 API 调用,也不会产生 API 费用。默认若 900 秒内没有新邮箱消息或新结果事件,worker 会自动收尾;整个作业超时仍是外层上限。处理结束可提前执行 `close`。向已结束或不是实时邮箱的作业执行 `jobs.sh send` 会明确报错并失败。即发即忘的工作、没有实时支持的供应商,或需要从干净状态重新运行的情况都不适用;请新建一次派发,或在作业完成后使用 `retry`。
353
353
 
354
354
  ## ❓ 常见问题
355
355
 
@@ -491,6 +491,12 @@ vendor 一律当成 `work`,而且它只能逐次明确指定,永远不是 lane
491
491
 
492
492
  ## 📜 版本历程
493
493
 
494
+ ## v0.21.0 新功能
495
+
496
+ - **显式选择会话模式。** 可使用 `dispatch --live` 要求常驻会话,或用 `--single-shot` 强制单次派发;对不支持实时会话的供应商,`--live` 会立即失败并列出可用供应商。
497
+ - **Gemini 加入实时邮箱。** Gemini 通过 `agy` 流式协议加入常驻实时任务,与 Claude 并列支持。
498
+ - **实时任务的空闲上限。** `--idle-timeout N` 会自动关闭无人处理的实时会话,并在关闭原因与 `meta.json` 中保留超时信息。
499
+
494
500
  ## v0.20.0 新功能
495
501
 
496
502
  - **Foreman 完成收件箱。** 后台派发完成后会写入私有完成记录,内置 Claude
package/README.zh-TW.md CHANGED
@@ -362,7 +362,7 @@ codex/claude/grok/gemini 自選 1-4 個評審。開了之後,同一個問題丟
362
362
 
363
363
  ## 📬 即時信箱
364
364
 
365
- 即時信箱是 Claude 專用的常駐背景派工,不是一次性派工。派工者以 `--background` 開啟後,執行中仍能補傳指示,並負責用 `jobs.sh close ID` 收尾。即使沒人處理,也不會永久存在:設定的整體工作逾時(`--job-timeout`)一到就會結束。
365
+ 即時信箱是 Claude Gemini 可用的常駐背景派工,不是一次性派工。派工者以 `--background` 開啟後,執行中仍能補傳指示,並負責用 `jobs.sh close ID` 收尾。即使沒人處理,也不會永久存在:閒置上限或設定的整體工作逾時(`--job-timeout`)一到就會結束。
366
366
 
367
367
  ```bash
368
368
  scripts/dispatch.sh --background --vendor claude hard-judgment "檢查逾時測試失敗的原因"
@@ -374,9 +374,9 @@ scripts/jobs.sh close "$ID"
374
374
  scripts/jobs.sh retry "$ID" --background
375
375
  ```
376
376
 
377
- `watch` 追隨 `$JOB_DIR/events.jsonl`;`tail` 讀取公開的 `out.txt`。目前只有 Claude 支援即時信箱。其他供應商都會降級成一般的一次性派工,但通知不會被隱藏:stderr 與 `$JOB_DIR/mode-notice.txt` 都會留下提示,`jobs.sh status ID` 也會顯示它。
377
+ `watch` 追隨 `$JOB_DIR/events.jsonl`;`tail` 讀取公開的 `out.txt`。目前 Claude 與 Gemini 支援即時信箱;其他供應商會執行一般的一次性派工,stderr 與 `$JOB_DIR/mode-notice.txt` 都會留下提示。`--live` 會明確要求常駐工作階段,解析出的供應商不支援時立即失敗。`--single-shot` 即使遇到 Claude 或 Gemini 也會強制一次性派工。`--idle-timeout SECONDS` 設定閒置上限,預設 900 秒,設為 `0` 則停用。
378
378
 
379
- 閒置時不會發出 API 呼叫,也不會增加 API 費用,但仍會持續消耗整體工作逾時的時間。處理完成就應執行 `close`。對已結束或不是即時信箱的工作使用 `jobs.sh send`,會明確報錯並失敗。送出後不需追蹤的工作、沒有即時支援的供應商,或必須從乾淨狀態重跑的情況都不適用;請改用新的派工,或在工作完成後使用 `retry`。
379
+ 閒置時不會發出 API 呼叫,也不會增加 API 費用。預設若 900 秒內沒有新信箱訊息或新結果事件,工作程序會自動收尾;整體工作逾時仍是外層上限。處理完成可提早執行 `close`。對已結束或不是即時信箱的工作使用 `jobs.sh send`,會明確報錯並失敗。送出後不需追蹤的工作、沒有即時支援的供應商,或必須從乾淨狀態重跑的情況都不適用;請使用新的派工,或在工作完成後使用 `retry`。
380
380
 
381
381
  ## ❓ 常見問題
382
382
 
@@ -515,6 +515,12 @@ vendor 一律當成 `work`,而且它只能逐次明確指定,永遠不是 lane
515
515
 
516
516
  ## 📜 版本歷程
517
517
 
518
+ ## v0.21.0 新功能
519
+
520
+ - **明確選擇工作階段模式。** 可用 `dispatch --live` 要求常駐工作階段,或以 `--single-shot` 強制單次派工;對不支援即時工作階段的供應商,`--live` 會立即失敗並列出可用供應商。
521
+ - **Gemini 加入即時信箱。** Gemini 透過 `agy` 串流協定加入常駐即時工作,與 Claude 並列支援。
522
+ - **即時工作的閒置上限。** `--idle-timeout N` 會自動關閉無人處理的即時工作階段,並在關閉原因與 `meta.json` 留下逾時資訊。
523
+
518
524
  ## v0.20.0 新功能
519
525
 
520
526
  - **Foreman 完成收件匣。** 背景派工完成後會寫入私有完成紀錄,內建的 Claude
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.20.0
1
+ 0.21.0
package/bin/omnilane CHANGED
@@ -30,7 +30,8 @@ case "$cmd" in
30
30
  ;;
31
31
  list) exec bash "$REPO/scripts/dispatch.sh" --list ;;
32
32
  route|dispatch) exec bash "$REPO/scripts/dispatch.sh" "$@" ;;
33
- jobs) exec bash "$REPO/scripts/jobs.sh" "$@" ;;
33
+ jobs) exec bash "$REPO/scripts/jobs.sh" "$@" ;;
34
+ goal) exec bash "$REPO/scripts/lib/goal-loop.sh" "$@" ;;
34
35
  mcp)
35
36
  command -v node >/dev/null 2>&1 || {
36
37
  echo "omnilane: mcp requires Node.js on PATH" >&2
@@ -75,6 +76,9 @@ omnilane — one routing table, every harness
75
76
  omnilane list effective routing table
76
77
  omnilane route [--vendor V] [flags] LANE "TASK"
77
78
  dispatch or consult a model
79
+ omnilane goal "TEXT" [--budget-jobs N] [--budget-seconds S] [--workdir DIR]
80
+ run bounded sequential goal loop
81
+ omnilane goal status GOAL_ID inspect goal budget and last action
78
82
  omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, recommend, wait, or audit
79
83
  omnilane mcp start the MCP stdio server
80
84
  omnilane completion bash|zsh|fish print a shell completion script
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.20.0",
3
+ "version": "0.21.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/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "omnilane",
4
- "version": "0.20.0",
4
+ "version": "0.21.0",
5
5
  "description": "One routing table, every harness: classify subtasks into lanes and auto-dispatch each lane to the best vendor CLI (Codex, Claude Code, Grok Build, Antigravity) with background jobs, depth guard, and serialized codex dispatch."
6
6
  }
@@ -3,9 +3,10 @@ set -euo pipefail
3
3
  # omnilane dispatch — one routing table, any harness.
4
4
  #
5
5
  # Usage:
6
- # dispatch.sh [--background] [--dry-run] [--mode advise|work|sysops] [--workdir DIR]
6
+ # dispatch.sh [--background] [--live|--single-shot] [--dry-run]
7
+ # [--mode advise|work|sysops] [--workdir DIR]
7
8
  # [--vendor V] [--model M] [--effort E] [--timeout SECONDS]
8
- # [--job-timeout SECONDS] LANE "TASK TEXT"
9
+ # [--job-timeout SECONDS] [--idle-timeout SECONDS] LANE "TASK TEXT"
9
10
  # dispatch.sh [--json] --list [--json]
10
11
  # dispatch.sh [--json] --explain LANE [--json]
11
12
  # dispatch.sh [--json] --validate [--json]
@@ -27,10 +28,12 @@ set -euo pipefail
27
28
  # A separate --job-timeout can cap lock wait plus all calls in this dispatch.
28
29
 
29
30
  source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
31
+ # shellcheck disable=SC1091
32
+ source "$OMNILANE_REPO/scripts/lib/live-protocol.sh"
30
33
 
31
34
  MODE="advise"; WORKDIR="$PWD"; BACKGROUND=0; DRY_RUN=0
32
35
  OVERRIDE_VENDOR=""; OVERRIDE_MODEL=""; OVERRIDE_EFFORT=""; OVERRIDE_TIMEOUT=""
33
- OVERRIDE_JOB_TIMEOUT=""
36
+ OVERRIDE_JOB_TIMEOUT=""; OVERRIDE_IDLE_TIMEOUT=""; SESSION_REQUEST="auto"
34
37
 
35
38
  usage_error() {
36
39
  echo 'usage: dispatch.sh [--background] [--dry-run] [flags] LANE "TASK" | [--json] --list|--validate [--json] | [--json] --explain LANE [--json] | --help' >&2
@@ -47,6 +50,9 @@ Dispatch one task to the first available vendor CLI in LANE's fallback chain.
47
50
  A TASK of "-" reads the task text from stdin.
48
51
 
49
52
  flags:
53
+ --live require a resident session (background only)
54
+ --single-shot force one-shot dispatch (background only)
55
+ --idle-timeout SECONDS close an idle live mailbox (default 900; 0 disables)
50
56
  --background run in the background and print the JOB_ID
51
57
  --dry-run print the fully resolved dispatch plan and stop
52
58
  before any provider call or job state
@@ -139,6 +145,8 @@ print_dry_run_plan() {
139
145
  print_dry_run_value workdir "$WORKDIR"
140
146
  printf 'timeout=%s\n' "$TIMEOUT"
141
147
  printf 'job_timeout=%s\n' "$job_timeout"
148
+ printf 'idle_timeout=%s\n' "$IDLE_TIMEOUT"
149
+ print_dry_run_value session_mode "$SESSION_MODE"
142
150
  printf 'candidate=%s/%s\n' "$RESOLVED_IDX" "$RESOLVED_TOTAL"
143
151
  printf 'background=%s\n' "$background"
144
152
  printf 'task_source=%s\n' "$task_source"
@@ -461,8 +469,18 @@ while [[ $# -gt 0 ]]; do
461
469
  case "$1" in
462
470
  --list|--explain|--validate|--json) usage_error ;;
463
471
  --background) BACKGROUND=1; shift ;;
472
+ --live)
473
+ [[ "$SESSION_REQUEST" != "single-shot" ]] || {
474
+ echo "omnilane: --live and --single-shot are mutually exclusive" >&2; exit 2
475
+ }
476
+ SESSION_REQUEST="live"; shift ;;
477
+ --single-shot)
478
+ [[ "$SESSION_REQUEST" != "live" ]] || {
479
+ echo "omnilane: --live and --single-shot are mutually exclusive" >&2; exit 2
480
+ }
481
+ SESSION_REQUEST="single-shot"; shift ;;
464
482
  --dry-run) DRY_RUN=1; shift ;;
465
- --mode|--workdir|--vendor|--model|--effort|--timeout|--job-timeout)
483
+ --mode|--workdir|--vendor|--model|--effort|--timeout|--job-timeout|--idle-timeout)
466
484
  # Value-taking flags: a missing value must be a clean usage error (exit 2),
467
485
  # not a `set -u` "unbound variable" crash on $2.
468
486
  [[ $# -ge 2 ]] || { echo "omnilane: $1 needs a value" >&2; exit 2; }
@@ -474,6 +492,7 @@ while [[ $# -gt 0 ]]; do
474
492
  --effort) OVERRIDE_EFFORT="$2" ;;
475
493
  --timeout) OVERRIDE_TIMEOUT="$2" ;;
476
494
  --job-timeout) OVERRIDE_JOB_TIMEOUT="$2" ;;
495
+ --idle-timeout) OVERRIDE_IDLE_TIMEOUT="$2" ;;
477
496
  esac
478
497
  shift 2 ;;
479
498
  -*) echo "omnilane: unknown flag" >&2; exit 2 ;;
@@ -487,6 +506,11 @@ done
487
506
  echo 'omnilane: unexpected extra arguments; quote a multiword task' >&2
488
507
  exit 2
489
508
  }
509
+ if [[ "$SESSION_REQUEST" != "auto" && "$BACKGROUND" -ne 1 ]]; then
510
+ echo "omnilane: --${SESSION_REQUEST} requires --background" >&2
511
+ exit 2
512
+ fi
513
+
490
514
  LANE="$1"
491
515
  TASK="$2"
492
516
  [[ "$LANE" =~ ^[a-z][a-z0-9-]*$ ]] || { echo "omnilane: invalid lane name" >&2; exit 2; }
@@ -541,6 +565,21 @@ fi
541
565
  RUNNER="$OMNILANE_REPO/scripts/runners/run-$VENDOR.sh"
542
566
  [[ -x "$RUNNER" ]] || { echo "omnilane: no runner for vendor '$VENDOR'" >&2; exit 2; }
543
567
 
568
+ SESSION_MODE="single-shot"
569
+ if [[ "$SESSION_REQUEST" != "single-shot" ]] && live_vendor_capable "$VENDOR"; then
570
+ SESSION_MODE="live"
571
+ fi
572
+ if [[ "$SESSION_REQUEST" == "live" ]] && ! live_vendor_capable "$VENDOR"; then
573
+ echo "omnilane: vendor '$VENDOR' cannot run live; live-capable vendors: $(live_capable_vendors)" >&2
574
+ exit 2
575
+ fi
576
+ export OMNILANE_SESSION_MODE="$SESSION_MODE"
577
+ if [[ "$SESSION_REQUEST" == "live" ]]; then
578
+ export OMNILANE_LIVE_REQUIRED=1
579
+ else
580
+ export OMNILANE_LIVE_REQUIRED=0
581
+ fi
582
+
544
583
  # Watchdog seconds: --timeout > per-lane OMNILANE_TIMEOUT_<LANE> > OMNILANE_TIMEOUT > 600.
545
584
  # Resolve here and export so every runner (and vote's sub-runners) inherits the
546
585
  # same value without a per-runner code change; they already read OMNILANE_TIMEOUT.
@@ -556,6 +595,14 @@ fi
556
595
  }
557
596
  export OMNILANE_TIMEOUT="$TIMEOUT"
558
597
 
598
+ IDLE_TIMEOUT="$OVERRIDE_IDLE_TIMEOUT"
599
+ [[ -n "$IDLE_TIMEOUT" ]] || IDLE_TIMEOUT="${OMNILANE_IDLE_TIMEOUT:-900}"
600
+ [[ "$IDLE_TIMEOUT" =~ ^(0|[1-9][0-9]*)$ ]] || {
601
+ echo "omnilane: invalid idle timeout (want a non-negative integer of seconds)" >&2
602
+ exit 2
603
+ }
604
+ export OMNILANE_IDLE_TIMEOUT="$IDLE_TIMEOUT"
605
+
559
606
  JOB_SUPERVISOR="$OMNILANE_REPO/scripts/lib/job-timeout.pl"
560
607
  JOB_WORKER="$OMNILANE_REPO/scripts/lib/job-worker.sh"
561
608
 
@@ -649,9 +696,10 @@ else
649
696
  fi
650
697
 
651
698
  # meta "timeout" is the resolved per-CLI-call watchdog cap, not a whole-job total.
652
- (umask 077; printf '{"lane":"%s","vendor":"%s","model":"%s","effort":"%s","timeout":%s,"job_timeout":%s,"mode":"%s","workdir":"%s","foreman_session":"%s","candidate":"%s/%s","started":"%s"}\n' \
653
- "$(json_escape "$LANE")" "$(json_escape "$VENDOR")" "$(json_escape "$MODEL")" \
654
- "$(json_escape "$EFFORT")" "$TIMEOUT" "$JOB_TIMEOUT_JSON" "$(json_escape "$MODE")" \
699
+ (umask 077; printf '{"lane":"%s","vendor":"%s","session_mode":"%s","idle_timeout":%s,"model":"%s","effort":"%s","timeout":%s,"job_timeout":%s,"mode":"%s","workdir":"%s","foreman_session":"%s","candidate":"%s/%s","started":"%s"}\n' \
700
+ "$(json_escape "$LANE")" "$(json_escape "$VENDOR")" "$(json_escape "$SESSION_MODE")" \
701
+ "$IDLE_TIMEOUT" "$(json_escape "$MODEL")" "$(json_escape "$EFFORT")" \
702
+ "$TIMEOUT" "$JOB_TIMEOUT_JSON" "$(json_escape "$MODE")" \
655
703
  "$(json_escape "$WORKDIR")" "$(json_escape "$FOREMAN_SESSION")" \
656
704
  "$RESOLVED_IDX" "$RESOLVED_TOTAL" \
657
705
  "$(date -u +%FT%TZ)" > "$JOB_DIR/meta.json")
package/scripts/jobs.sh CHANGED
@@ -10,6 +10,8 @@ set -euo pipefail
10
10
  # Runtime-relative shared library.
11
11
  # shellcheck disable=SC1091
12
12
  source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
13
+ # shellcheck disable=SC1091
14
+ source "$OMNILANE_REPO/scripts/lib/live-protocol.sh"
13
15
  JOBS="$OMNILANE_HOME/jobs"
14
16
  JOB_ID_PATTERN='^[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+$'
15
17
  JSON_MODE=0
@@ -145,15 +147,28 @@ read_job_pid() {
145
147
  }
146
148
 
147
149
  load_job_vendor() {
150
+ local session_mode_re='"session_mode":"(live|single-shot)"'
148
151
  if ! read_public_metadata "$JOB_DIR/meta.json" || ! parse_stats_metadata "$PUBLIC_METADATA"; then
149
152
  die 1 "job metadata is not safely readable"
150
153
  fi
151
154
  JOB_VENDOR="$STATS_VENDOR"
155
+ JOB_SESSION_MODE=""
156
+ if [[ "$PUBLIC_METADATA" =~ $session_mode_re ]]; then
157
+ JOB_SESSION_MODE="${BASH_REMATCH[1]}"
158
+ elif [[ "$JOB_VENDOR" == "claude" ]]; then
159
+ # Compatibility with live jobs created before session_mode metadata.
160
+ JOB_SESSION_MODE="live"
161
+ else
162
+ JOB_SESSION_MODE="single-shot"
163
+ fi
152
164
  }
153
165
 
154
166
  require_live_job() {
155
167
  load_job_vendor
156
- [[ "$JOB_VENDOR" == "claude" ]] || die 1 "job vendor '$JOB_VENDOR' is not live-capable; it runs in single-shot mode"
168
+ live_vendor_capable "$JOB_VENDOR" ||
169
+ die 1 "job vendor '$JOB_VENDOR' is not live-capable (live vendors: $(live_capable_vendors)); it runs in single-shot mode"
170
+ [[ "$JOB_SESSION_MODE" == "live" ]] ||
171
+ die 1 "job vendor '$JOB_VENDOR' was dispatched in single-shot mode"
157
172
  }
158
173
 
159
174
  wait_for_live_ready() {
@@ -204,10 +219,11 @@ parse_stats_metadata() {
204
219
  }
205
220
 
206
221
  parse_audit_metadata() {
207
- local value="$1" metadata_re
222
+ local value="$1" current_re legacy_re
208
223
  local json_string='([^"\\]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*'
209
- metadata_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work)\",\"workdir\":\"${json_string}\",\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"\\}$"
210
- [[ "$value" =~ $metadata_re ]]
224
+ current_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"session_mode\":\"(live|single-shot)\",\"idle_timeout\":(0|[1-9][0-9]*),\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work|sysops)\",\"workdir\":\"${json_string}\",\"foreman_session\":\"${json_string}\",\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"\\}$"
225
+ legacy_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work|sysops)\",\"workdir\":\"${json_string}\",(\"foreman_session\":\"${json_string}\",)?\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"\\}$"
226
+ [[ "$value" =~ $current_re || "$value" =~ $legacy_re ]]
211
227
  }
212
228
 
213
229
  print_stat_counts() {
@@ -352,8 +368,7 @@ case "${1:-}" in
352
368
  else
353
369
  foreman_value="${foreman_session-}"
354
370
  fi
355
- payload="$(printf '{"type":"user","omnilane_job_id":"%s","foreman_session":"%s","message":{"role":"user","content":[{"type":"text","text":"%s"}]}}' \
356
- "$(json_escape "$2")" "$(json_escape "$foreman_value")" "$(json_escape "$3")")"
371
+ payload="$(live_encode_message "$JOB_VENDOR" "$2" "$foreman_value" "$3")"
357
372
  set +e
358
373
  write_fifo_with_timeout "$JOB_DIR/inbox.fifo" "$payload"
359
374
  send_rc=$?
@@ -404,11 +419,7 @@ case "${1:-}" in
404
419
  done
405
420
  read_exit_code "$JOB_DIR/exit" || die 1 "invalid recorded exit status"
406
421
  events_path="$JOB_DIR/events.jsonl"
407
- result_count=0
408
- if [[ -f "$events_path" && ! -L "$events_path" ]]; then
409
- result_count="$(grep -Ec '"type"[[:space:]]*:[[:space:]]*"result"' "$events_path" || true)"
410
- [[ "$result_count" =~ ^[0-9]+$ ]] || result_count=0
411
- fi
422
+ result_count="$(live_count_result_events "$JOB_VENDOR" "$events_path")"
412
423
  echo "closed $2 (result_events=$result_count exit=$RECORDED_EXIT)"
413
424
  exit "$RECORDED_EXIT"
414
425
  ;;
@@ -565,7 +576,8 @@ case "${1:-}" in
565
576
  elif [[ -d "$artifact" ]]; then
566
577
  audit_emit "$id" nested-directory
567
578
  findings=$((findings + 1)); job_failed=1
568
- elif [[ -p "$artifact" && "${artifact##*/}" == "inbox.fifo" ]]; then
579
+ elif [[ -p "$artifact" && ( "${artifact##*/}" == "inbox.fifo" ||
580
+ "${artifact##*/}" == "runner-inbox.fifo" ) ]]; then
569
581
  mode="$(path_mode "$artifact" 2>/dev/null || true)"
570
582
  if [[ "$mode" != "600" ]]; then
571
583
  audit_emit "$id" unsafe-fifo-mode