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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -1
- package/README.ja.md +9 -3
- package/README.ko.md +9 -3
- package/README.md +9 -3
- package/README.zh-CN.md +9 -3
- package/README.zh-TW.md +9 -3
- package/VERSION +1 -1
- package/bin/omnilane +5 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/dispatch.sh +55 -7
- package/scripts/jobs.sh +24 -12
- package/scripts/lib/goal-loop.sh +776 -0
- package/scripts/lib/job-worker.sh +98 -37
- package/scripts/lib/live-protocol.sh +70 -0
- package/scripts/runners/run-gemini.sh +109 -0
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "One routing table, cross-vendor dispatch, foreman completion inbox.",
|
|
9
|
-
"version": "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.
|
|
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.
|
|
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.
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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`를 읽습니다. 라이브 메일함은
|
|
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 호출이나 비용이 발생하지
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|
package/scripts/dispatch.sh
CHANGED
|
@@ -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] [--
|
|
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 "$
|
|
654
|
-
"$(json_escape "$
|
|
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
|
-
|
|
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"
|
|
222
|
+
local value="$1" current_re legacy_re
|
|
208
223
|
local json_string='([^"\\]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*'
|
|
209
|
-
|
|
210
|
-
[[
|
|
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="$(
|
|
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=
|
|
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
|
-
|
|
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
|