omnilane 0.8.2 → 0.9.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 +58 -1
- package/README.ja.md +75 -14
- package/README.ko.md +71 -13
- package/README.md +84 -15
- package/README.zh-CN.md +59 -11
- package/README.zh-TW.md +73 -11
- package/VERSION +1 -1
- package/bin/omnilane +12 -3
- package/bin/omnilane-mcp +756 -0
- package/completions/omnilane.fish +50 -0
- package/package.json +1 -1
- package/scripts/check.sh +125 -0
- package/scripts/configure.sh +149 -1
- package/scripts/dispatch.sh +4 -4
- package/scripts/doctor.sh +38 -0
- package/scripts/jobs.sh +132 -10
- package/scripts/lib/common.sh +57 -3
- package/scripts/runners/run-cerebras.sh +7 -0
- package/scripts/runners/run-deepseek.sh +7 -0
- package/scripts/runners/run-groq.sh +7 -0
- package/scripts/runners/run-mistral.sh +7 -0
- package/scripts/runners/run-openai-compat.sh +102 -0
- package/scripts/runners/run-openrouter.sh +5 -84
- package/scripts/runners/run-zai.sh +7 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Fish completion for omnilane. Static command/option completion only; it never
|
|
2
|
+
# invokes dispatch, provider CLIs, or the executable machine-local overlay.
|
|
3
|
+
# Dynamic lane-name and job-id completion remain Bash/Zsh-only for now.
|
|
4
|
+
|
|
5
|
+
# Top-level subcommands (offered only before a subcommand is chosen).
|
|
6
|
+
complete -c omnilane -f -n __fish_use_subcommand -a version -d 'installed release version'
|
|
7
|
+
complete -c omnilane -f -n __fish_use_subcommand -a list -d 'effective routing table'
|
|
8
|
+
complete -c omnilane -f -n __fish_use_subcommand -a route -d 'dispatch or consult a model'
|
|
9
|
+
complete -c omnilane -f -n __fish_use_subcommand -a dispatch -d 'dispatch or consult a model'
|
|
10
|
+
complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect background jobs'
|
|
11
|
+
complete -c omnilane -f -n __fish_use_subcommand -a mcp -d 'MCP stdio server'
|
|
12
|
+
complete -c omnilane -f -n __fish_use_subcommand -a doctor -d 'read-only health report'
|
|
13
|
+
complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
|
|
14
|
+
complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
|
|
15
|
+
complete -c omnilane -f -n __fish_use_subcommand -a configure -d 'lane routing (menu or set/get/unset/list/diff)'
|
|
16
|
+
complete -c omnilane -f -n __fish_use_subcommand -a completion -d 'print a shell completion script'
|
|
17
|
+
complete -c omnilane -f -n __fish_use_subcommand -a help -d 'usage'
|
|
18
|
+
|
|
19
|
+
# route / dispatch options.
|
|
20
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l mode -x -a 'advise work' -d 'read-only advise or write-enabled work'
|
|
21
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras exec vote' -d 'pin one vendor, no fallback'
|
|
22
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l effort -x -a 'low medium high xhigh max' -d 'reasoning effort'
|
|
23
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l workdir -r -d 'working directory'
|
|
24
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l model -x -d 'routed model override'
|
|
25
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l timeout -x -d 'per-call timeout in seconds'
|
|
26
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l job-timeout -x -d 'whole-job timeout in seconds'
|
|
27
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l background -d 'run as a background job'
|
|
28
|
+
complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l dry-run -d 'resolve the plan and stop'
|
|
29
|
+
|
|
30
|
+
# jobs subcommands (only before a jobs subcommand is chosen).
|
|
31
|
+
complete -c omnilane -f \
|
|
32
|
+
-n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats wait audit prune cancel rm help' \
|
|
33
|
+
-a 'list status result tail retry stats wait audit prune cancel rm help' -d 'jobs subcommand'
|
|
34
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs' -l json -d 'JSON output'
|
|
35
|
+
# jobs list / stats filters.
|
|
36
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l lane -x -d 'filter by lane'
|
|
37
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras exec' -d 'filter by vendor'
|
|
38
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list' -l status -x -a 'running done' -d 'filter by status'
|
|
39
|
+
|
|
40
|
+
# configure subcommands (non-interactive).
|
|
41
|
+
complete -c omnilane -f -n '__fish_seen_subcommand_from configure; and not __fish_seen_subcommand_from set get unset list diff' -a 'set get unset list diff' -d 'configure action'
|
|
42
|
+
|
|
43
|
+
# doctor / release-audit / ui / completion.
|
|
44
|
+
complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l json -d 'JSON output'
|
|
45
|
+
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l target -x -d 'target version'
|
|
46
|
+
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l allow-dirty -d 'permit a dirty tree'
|
|
47
|
+
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l require-tag -d 'require an annotated tag'
|
|
48
|
+
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l json -d 'JSON output'
|
|
49
|
+
complete -c omnilane -f -n '__fish_seen_subcommand_from ui' -a 'start status url stop' -d 'Live Board action'
|
|
50
|
+
complete -c omnilane -f -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'target shell'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnilane",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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/check.sh
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
# omnilane self-check: run the CONTRIBUTING required checks in one command.
|
|
4
|
+
# Usage: check.sh [--quick] [REPO_DIR]
|
|
5
|
+
# --quick skip the two slow suite runs (python unittest, tests/run.sh)
|
|
6
|
+
# A SKIP (tool unavailable or target absent) is not a failure; the script exits
|
|
7
|
+
# non-zero only when a check actually FAILs. No -e: checks may fail and are
|
|
8
|
+
# tallied rather than aborting the run.
|
|
9
|
+
|
|
10
|
+
QUICK=0
|
|
11
|
+
REPO=""
|
|
12
|
+
while [[ $# -gt 0 ]]; do
|
|
13
|
+
case "$1" in
|
|
14
|
+
--quick) QUICK=1; shift ;;
|
|
15
|
+
-h|--help) echo "usage: check.sh [--quick] [REPO_DIR]"; exit 0 ;;
|
|
16
|
+
--) shift; break ;;
|
|
17
|
+
-*) echo "check.sh: unknown option: $1" >&2; exit 2 ;;
|
|
18
|
+
*) if [[ -n "$REPO" ]]; then echo "check.sh: too many arguments" >&2; exit 2; fi
|
|
19
|
+
REPO="$1"; shift ;;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
if [[ $# -gt 0 ]]; then
|
|
23
|
+
if [[ -n "$REPO" ]]; then echo "check.sh: too many arguments" >&2; exit 2; fi
|
|
24
|
+
REPO="$1"
|
|
25
|
+
fi
|
|
26
|
+
[[ -n "$REPO" ]] || REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
27
|
+
|
|
28
|
+
PASS_N=0; FAIL_N=0; SKIP_N=0
|
|
29
|
+
_pass() { PASS_N=$((PASS_N + 1)); printf 'PASS %s\n' "$1"; }
|
|
30
|
+
_skip() { SKIP_N=$((SKIP_N + 1)); printf 'SKIP %s (%s)\n' "$1" "$2"; }
|
|
31
|
+
_fail() { FAIL_N=$((FAIL_N + 1)); printf 'FAIL %s\n' "$1"; [[ -z "${2:-}" ]] || printf ' %s\n' "$2"; }
|
|
32
|
+
|
|
33
|
+
# Shell files present in the repo (CONTRIBUTING's bash -n / shellcheck set).
|
|
34
|
+
shell_files=()
|
|
35
|
+
for pattern in 'bin/omnilane' 'scripts/*.sh' 'scripts/lib/*.sh' 'scripts/runners/*.sh' 'install.sh'; do
|
|
36
|
+
# shellcheck disable=SC2086 # $pattern is an intentional glob against $REPO
|
|
37
|
+
for f in $REPO/$pattern; do
|
|
38
|
+
[[ -f "$f" ]] && shell_files+=("$f")
|
|
39
|
+
done
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
# 1) bash -n on every tracked shell file
|
|
43
|
+
if [[ ${#shell_files[@]} -eq 0 ]]; then
|
|
44
|
+
_skip bash-syntax "no shell files found"
|
|
45
|
+
else
|
|
46
|
+
bad=""
|
|
47
|
+
for f in "${shell_files[@]}"; do
|
|
48
|
+
bash -n "$f" 2>/dev/null || bad="$bad ${f##*/}"
|
|
49
|
+
done
|
|
50
|
+
if [[ -z "$bad" ]]; then _pass bash-syntax; else _fail bash-syntax "invalid:$bad"; fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# 2) shellcheck -S warning (skip if unavailable)
|
|
54
|
+
if ! command -v shellcheck >/dev/null 2>&1; then
|
|
55
|
+
_skip shellcheck "not installed"
|
|
56
|
+
elif [[ ${#shell_files[@]} -eq 0 ]]; then
|
|
57
|
+
_skip shellcheck "no shell files"
|
|
58
|
+
elif shellcheck -S warning "${shell_files[@]}" >/dev/null 2>&1; then
|
|
59
|
+
_pass shellcheck
|
|
60
|
+
else
|
|
61
|
+
_fail shellcheck "shellcheck -S warning reported findings"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# 3) perl -c on the job-timeout supervisor
|
|
65
|
+
perl_file="$REPO/scripts/lib/job-timeout.pl"
|
|
66
|
+
if ! command -v perl >/dev/null 2>&1; then
|
|
67
|
+
_skip perl-syntax "perl not installed"
|
|
68
|
+
elif [[ ! -f "$perl_file" ]]; then
|
|
69
|
+
_skip perl-syntax "job-timeout.pl absent"
|
|
70
|
+
elif perl -c "$perl_file" >/dev/null 2>&1; then
|
|
71
|
+
_pass perl-syntax
|
|
72
|
+
else
|
|
73
|
+
_fail perl-syntax "perl -c failed"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# 4) python compile of the UI and test modules
|
|
77
|
+
py_files=()
|
|
78
|
+
for rel in scripts/ui.py tests/test_ui.py tests/test_ci_policy.py tests/ui_browser_harness.py; do
|
|
79
|
+
[[ -f "$REPO/$rel" ]] && py_files+=("$REPO/$rel")
|
|
80
|
+
done
|
|
81
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
82
|
+
_skip py-compile "python3 not installed"
|
|
83
|
+
elif [[ ${#py_files[@]} -eq 0 ]]; then
|
|
84
|
+
_skip py-compile "no python files"
|
|
85
|
+
elif python3 -m py_compile "${py_files[@]}" >/dev/null 2>&1; then
|
|
86
|
+
_pass py-compile
|
|
87
|
+
else
|
|
88
|
+
_fail py-compile "py_compile failed"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# 5) python unit tests (slow; --quick skips)
|
|
92
|
+
if [[ "$QUICK" -eq 1 ]]; then
|
|
93
|
+
_skip unittest "--quick"
|
|
94
|
+
elif ! command -v python3 >/dev/null 2>&1; then
|
|
95
|
+
_skip unittest "python3 not installed"
|
|
96
|
+
elif [[ ! -d "$REPO/tests" ]]; then
|
|
97
|
+
_skip unittest "no tests dir"
|
|
98
|
+
elif ( cd "$REPO" && python3 -m unittest discover -s tests -p 'test_*.py' >/dev/null 2>&1 ); then
|
|
99
|
+
_pass unittest
|
|
100
|
+
else
|
|
101
|
+
_fail unittest "python unittest suite failed"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# 6) shell test suite (slow; --quick skips)
|
|
105
|
+
if [[ "$QUICK" -eq 1 ]]; then
|
|
106
|
+
_skip shell-suite "--quick"
|
|
107
|
+
elif [[ ! -f "$REPO/tests/run.sh" ]]; then
|
|
108
|
+
_skip shell-suite "tests/run.sh absent"
|
|
109
|
+
elif bash "$REPO/tests/run.sh" >/dev/null 2>&1; then
|
|
110
|
+
_pass shell-suite
|
|
111
|
+
else
|
|
112
|
+
_fail shell-suite "bash tests/run.sh failed"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# 7) effective routing table resolves
|
|
116
|
+
if [[ ! -f "$REPO/scripts/dispatch.sh" ]]; then
|
|
117
|
+
_skip dispatch-list "dispatch.sh absent"
|
|
118
|
+
elif bash "$REPO/scripts/dispatch.sh" --list >/dev/null 2>&1; then
|
|
119
|
+
_pass dispatch-list
|
|
120
|
+
else
|
|
121
|
+
_fail dispatch-list "dispatch.sh --list failed"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
printf '\nsummary: %d passed, %d skipped, %d failed\n' "$PASS_N" "$SKIP_N" "$FAIL_N"
|
|
125
|
+
[[ "$FAIL_N" -eq 0 ]]
|
package/scripts/configure.sh
CHANGED
|
@@ -9,6 +9,142 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib/i18n.sh"
|
|
|
9
9
|
|
|
10
10
|
LOCAL_FILE="$OMNILANE_HOME/routing.local.yaml"
|
|
11
11
|
|
|
12
|
+
# --- Non-interactive subcommands -------------------------------------------
|
|
13
|
+
# `configure set|get|unset|list` script the same routing.local.yaml the menu
|
|
14
|
+
# writes, so automation never needs a tty. No subcommand => interactive menu.
|
|
15
|
+
|
|
16
|
+
cfg_usage() {
|
|
17
|
+
cat >&2 <<'EOF'
|
|
18
|
+
usage: configure interactive lane menu
|
|
19
|
+
configure set LANE SPEC set/override one lane. SPEC = "vendor model effort [| ...]" or "off".
|
|
20
|
+
Quote the whole SPEC (and the model) when a model name has spaces.
|
|
21
|
+
configure get LANE show the effective routing line for LANE
|
|
22
|
+
configure unset LANE remove LANE's local override
|
|
23
|
+
configure list show current local overrides
|
|
24
|
+
configure diff show how local overrides change the effective table vs the defaults
|
|
25
|
+
EOF
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Reject shell-dangerous bytes while allowing the routing RHS grammar
|
|
29
|
+
# (chains with |, quoted "models with spaces", slashes, effort tokens).
|
|
30
|
+
cfg_spec_is_safe() {
|
|
31
|
+
case "$1" in
|
|
32
|
+
'') return 1 ;;
|
|
33
|
+
*'$'*|*'`'*|*'\'*|*'#'*|*';'*|*'&'*|*'<'*|*'>'*|*$'\r'*|*$'\n'*) return 1 ;;
|
|
34
|
+
*) return 0 ;;
|
|
35
|
+
esac
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
cfg_lane_is_known() {
|
|
39
|
+
local want="$1" line
|
|
40
|
+
while IFS= read -r line; do
|
|
41
|
+
case "$line" in "$want":*) return 0 ;; esac
|
|
42
|
+
done < "$OMNILANE_REPO/routing.yaml"
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cfg_valid_lane() { [[ "$1" =~ ^[a-z][a-z0-9-]*$ ]]; }
|
|
47
|
+
|
|
48
|
+
cfg_set() {
|
|
49
|
+
local lane="${1:-}"; shift || true
|
|
50
|
+
local spec="$*"
|
|
51
|
+
cfg_valid_lane "$lane" || { echo "omnilane: invalid lane '$lane'" >&2; exit 2; }
|
|
52
|
+
cfg_lane_is_known "$lane" || { echo "omnilane: unknown lane '$lane' (see: omnilane list)" >&2; exit 2; }
|
|
53
|
+
[[ -n "$spec" ]] || { echo "omnilane: missing routing spec for '$lane'" >&2; cfg_usage; exit 2; }
|
|
54
|
+
cfg_spec_is_safe "$spec" || { echo 'omnilane: unsafe routing spec (not allowed: $ ` \ # ; & < > newlines)' >&2; exit 2; }
|
|
55
|
+
|
|
56
|
+
mkdir -p "$OMNILANE_HOME"
|
|
57
|
+
local had_file=0
|
|
58
|
+
if [[ -f "$LOCAL_FILE" ]]; then had_file=1; cp "$LOCAL_FILE" "$LOCAL_FILE.bak"; fi
|
|
59
|
+
local tmp="$LOCAL_FILE.tmp.$$"
|
|
60
|
+
{
|
|
61
|
+
echo "# updated by 'configure set' on $(date +%F) — first match per lane wins"
|
|
62
|
+
echo "$lane: $spec"
|
|
63
|
+
[[ "$had_file" -eq 1 ]] && grep -v '^#' "$LOCAL_FILE.bak" | grep -v "^$lane:" || true
|
|
64
|
+
} > "$tmp"
|
|
65
|
+
mv "$tmp" "$LOCAL_FILE"
|
|
66
|
+
|
|
67
|
+
# Semantic guard: reject only a structural FAIL on THIS lane. Availability
|
|
68
|
+
# WARN (validate exit 4) is fine — the vendor CLI may be legitimately absent.
|
|
69
|
+
local validate_out
|
|
70
|
+
validate_out="$(OMNILANE_HOME="$OMNILANE_HOME" bash "$OMNILANE_REPO/scripts/dispatch.sh" --validate 2>&1)" || true
|
|
71
|
+
if printf '%s\n' "$validate_out" | grep -q "^FAIL $lane "; then
|
|
72
|
+
if [[ "$had_file" -eq 1 ]]; then mv "$LOCAL_FILE.bak" "$LOCAL_FILE"; else /bin/rm -f "$LOCAL_FILE"; fi
|
|
73
|
+
echo "omnilane: rejected — $(printf '%s\n' "$validate_out" | grep "^FAIL $lane " | head -1)" >&2
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
[[ "$had_file" -eq 1 ]] && /bin/rm -f "$LOCAL_FILE.bak"
|
|
77
|
+
echo "set $lane -> $spec"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cfg_get() {
|
|
81
|
+
local lane="${1:-}"
|
|
82
|
+
cfg_valid_lane "$lane" || { echo "omnilane: invalid lane '$lane'" >&2; exit 2; }
|
|
83
|
+
local line
|
|
84
|
+
line="$(OMNILANE_HOME="$OMNILANE_HOME" bash "$OMNILANE_REPO/scripts/dispatch.sh" --list 2>/dev/null | grep "^$lane:" | head -1 || true)"
|
|
85
|
+
[[ -n "$line" ]] || { echo "omnilane: unknown lane '$lane' (see: omnilane list)" >&2; exit 2; }
|
|
86
|
+
printf '%s\n' "$line"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
cfg_unset() {
|
|
90
|
+
local lane="${1:-}"
|
|
91
|
+
cfg_valid_lane "$lane" || { echo "omnilane: invalid lane '$lane'" >&2; exit 2; }
|
|
92
|
+
if [[ ! -f "$LOCAL_FILE" ]] || ! grep -q "^$lane:" "$LOCAL_FILE"; then
|
|
93
|
+
echo "no local override for '$lane'"; return 0
|
|
94
|
+
fi
|
|
95
|
+
cp "$LOCAL_FILE" "$LOCAL_FILE.bak"
|
|
96
|
+
grep -v "^$lane:" "$LOCAL_FILE.bak" > "$LOCAL_FILE" || true
|
|
97
|
+
/bin/rm -f "$LOCAL_FILE.bak"
|
|
98
|
+
echo "unset $lane (local override removed)"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
cfg_list() {
|
|
102
|
+
if [[ -f "$LOCAL_FILE" ]] && grep -qE '^[a-z]' "$LOCAL_FILE"; then
|
|
103
|
+
cat "$LOCAL_FILE"
|
|
104
|
+
else
|
|
105
|
+
echo "no local overrides in $LOCAL_FILE"
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Diff the effective table (local wins) against a defaults-only resolution, so
|
|
110
|
+
# the user sees exactly which lanes their overrides change. Reuses dispatch.sh
|
|
111
|
+
# --list for both, so availability annotation and formatting stay consistent; a
|
|
112
|
+
# throwaway empty OMNILANE_HOME yields the defaults-only table.
|
|
113
|
+
cfg_diff() {
|
|
114
|
+
if [[ ! -f "$LOCAL_FILE" ]] || ! grep -qE '^[a-z]' "$LOCAL_FILE"; then
|
|
115
|
+
echo "no local overrides ($LOCAL_FILE); effective table equals the defaults"
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
local empty eff def changed=0 lane eff_line def_line
|
|
119
|
+
empty="$(mktemp -d "${TMPDIR:-/tmp}/omnilane-diff.XXXXXX")"
|
|
120
|
+
eff="$(OMNILANE_HOME="$OMNILANE_HOME" bash "$OMNILANE_REPO/scripts/dispatch.sh" --list 2>/dev/null || true)"
|
|
121
|
+
def="$(OMNILANE_HOME="$empty" bash "$OMNILANE_REPO/scripts/dispatch.sh" --list 2>/dev/null || true)"
|
|
122
|
+
/bin/rm -rf "$empty"
|
|
123
|
+
while IFS= read -r eff_line; do
|
|
124
|
+
[[ "$eff_line" =~ ^([a-z][a-z0-9-]*): ]] || continue
|
|
125
|
+
lane="${BASH_REMATCH[1]}"
|
|
126
|
+
def_line="$(printf '%s\n' "$def" | grep "^$lane:" | head -1 || true)"
|
|
127
|
+
if [[ "$eff_line" != "$def_line" ]]; then
|
|
128
|
+
changed=1
|
|
129
|
+
printf 'default> %s\n' "${def_line:-($lane not in defaults)}"
|
|
130
|
+
printf 'local > %s\n' "$eff_line"
|
|
131
|
+
echo
|
|
132
|
+
fi
|
|
133
|
+
done <<DIFF_EFF
|
|
134
|
+
$eff
|
|
135
|
+
DIFF_EFF
|
|
136
|
+
[[ "$changed" -eq 1 ]] || echo "local overrides present, but the effective table matches the defaults"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "${1:-}" in
|
|
140
|
+
set) shift; cfg_set "$@"; exit $? ;;
|
|
141
|
+
get) shift; cfg_get "$@"; exit $? ;;
|
|
142
|
+
unset) shift; cfg_unset "$@"; exit $? ;;
|
|
143
|
+
list) shift; cfg_list "$@"; exit $? ;;
|
|
144
|
+
diff) cfg_diff; exit $? ;;
|
|
145
|
+
-h|--help|help) cfg_usage; exit 0 ;;
|
|
146
|
+
esac
|
|
147
|
+
|
|
12
148
|
# Curated suggestions only — "c" always allows free text so new models work.
|
|
13
149
|
CODEX_MODELS=("gpt-5.6-sol" "gpt-5.6-terra" "gpt-5.6-luna")
|
|
14
150
|
CODEX_EFFORTS=("xhigh" "max" "ultra" "high" "medium" "low" "minimal" "none")
|
|
@@ -21,6 +157,13 @@ QWEN_MODELS=("qwen3-coder-plus" "qwen3-coder-flash")
|
|
|
21
157
|
# OpenCode models use provider/model form; OpenRouter models are catalog slugs.
|
|
22
158
|
OPENCODE_MODELS=("openrouter/anthropic/claude-sonnet-5" "openrouter/openai/gpt-5.6-sol" "opencode/default (leave model to opencode)")
|
|
23
159
|
OPENROUTER_MODELS=("anthropic/claude-sonnet-5" "openai/gpt-5.6-sol" "moonshotai/kimi-k3" "qwen/qwen3-coder-plus")
|
|
160
|
+
# Direct-API OpenAI-compatible vendors (curl + <VENDOR>_API_KEY); slugs are
|
|
161
|
+
# suggestions — "c" free text covers anything each provider's /models lists.
|
|
162
|
+
DEEPSEEK_MODELS=("deepseek-chat" "deepseek-reasoner" "deepseek-v4-flash")
|
|
163
|
+
ZAI_MODELS=("glm-4.6")
|
|
164
|
+
MISTRAL_MODELS=("devstral-latest" "codestral-latest" "mistral-medium-latest")
|
|
165
|
+
GROQ_MODELS=("openai/gpt-oss-120b" "qwen/qwen3.6-27b" "openai/gpt-oss-20b")
|
|
166
|
+
CEREBRAS_MODELS=("gpt-oss-120b" "qwen-3-32b" "llama-3.3-70b")
|
|
24
167
|
|
|
25
168
|
custom_value_is_safe() {
|
|
26
169
|
case "$1" in
|
|
@@ -79,7 +222,7 @@ while true; do
|
|
|
79
222
|
echo "$(msgf cfg_pick_range "${#LANES[@]}")"; continue
|
|
80
223
|
fi
|
|
81
224
|
lane="${LANES[$((n - 1))]}"
|
|
82
|
-
vendor="$(pick "$(msgf cfg_vendor_for "$lane")" codex claude grok gemini kimi qwen opencode openrouter "vote (multi-model panel)" "exec (your own script/gate)" off)"
|
|
225
|
+
vendor="$(pick "$(msgf cfg_vendor_for "$lane")" codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras "vote (multi-model panel)" "exec (your own script/gate)" off)"
|
|
83
226
|
[[ "$vendor" == exec* ]] && vendor="exec"
|
|
84
227
|
[[ "$vendor" == vote* ]] && vendor="vote"
|
|
85
228
|
if [[ "$vendor" == "off" ]]; then
|
|
@@ -96,6 +239,11 @@ while true; do
|
|
|
96
239
|
opencode) model="$(pick "$(msg cfg_model)" "${OPENCODE_MODELS[@]}")"; effort="-"
|
|
97
240
|
[[ "$model" == "opencode/default"* ]] && model="-" ;;
|
|
98
241
|
openrouter) model="$(pick "$(msg cfg_model)" "${OPENROUTER_MODELS[@]}")"; effort="-" ;;
|
|
242
|
+
deepseek) model="$(pick "$(msg cfg_model)" "${DEEPSEEK_MODELS[@]}")"; effort="-" ;;
|
|
243
|
+
zai) model="$(pick "$(msg cfg_model)" "${ZAI_MODELS[@]}")"; effort="-" ;;
|
|
244
|
+
mistral) model="$(pick "$(msg cfg_model)" "${MISTRAL_MODELS[@]}")"; effort="-" ;;
|
|
245
|
+
groq) model="$(pick "$(msg cfg_model)" "${GROQ_MODELS[@]}")"; effort="-" ;;
|
|
246
|
+
cerebras) model="$(pick "$(msg cfg_model)" "${CEREBRAS_MODELS[@]}")"; effort="-" ;;
|
|
99
247
|
vote) while true; do
|
|
100
248
|
count="$(pick "$(msg cfg_voters_count)" "1" "2" "3" "4")"
|
|
101
249
|
[[ "$count" =~ ^[1-4]$ ]] && break
|
package/scripts/dispatch.sh
CHANGED
|
@@ -52,7 +52,7 @@ flags:
|
|
|
52
52
|
before any provider call or job state
|
|
53
53
|
--mode advise|work advise (read-only, default) or work (may edit files)
|
|
54
54
|
--workdir DIR working directory handed to the vendor CLI
|
|
55
|
-
--vendor V pin one configured vendor (codex|claude|grok|gemini|kimi|qwen|opencode|openrouter)
|
|
55
|
+
--vendor V pin one configured vendor (codex|claude|grok|gemini|kimi|qwen|opencode|openrouter|deepseek|zai|mistral|groq|cerebras)
|
|
56
56
|
--model M override the routed model
|
|
57
57
|
--effort E override the routed effort
|
|
58
58
|
--timeout SECONDS cap each CLI call (default 600)
|
|
@@ -363,7 +363,7 @@ validate_routing() {
|
|
|
363
363
|
lane_invalid=1
|
|
364
364
|
break
|
|
365
365
|
fi
|
|
366
|
-
if ! [[ "$vendor" =~ ^(
|
|
366
|
+
if ! [[ "$vendor" =~ ^(${OMNILANE_VENDOR_ALT}|off)$ ]]; then
|
|
367
367
|
printf 'FAIL %s unknown-vendor=%s\n' "$lane" "$vendor"
|
|
368
368
|
lane_invalid=1
|
|
369
369
|
break
|
|
@@ -489,8 +489,8 @@ TASK="$2"
|
|
|
489
489
|
# A typo like --mode advice must not fall through to the write-enabled branch.
|
|
490
490
|
[[ "$MODE" == "advise" || "$MODE" == "work" ]] || { echo "omnilane: invalid --mode (advise|work)" >&2; exit 2; }
|
|
491
491
|
if [[ -n "$OVERRIDE_VENDOR" ]] &&
|
|
492
|
-
! [[ "$OVERRIDE_VENDOR" =~ ^(
|
|
493
|
-
echo "omnilane: invalid vendor (
|
|
492
|
+
! [[ "$OVERRIDE_VENDOR" =~ ^(${OMNILANE_OVERRIDE_VENDOR_ALT})$ ]]; then
|
|
493
|
+
echo "omnilane: invalid vendor (${OMNILANE_OVERRIDE_VENDOR_ALT})" >&2
|
|
494
494
|
exit 2
|
|
495
495
|
fi
|
|
496
496
|
|
package/scripts/doctor.sh
CHANGED
|
@@ -140,6 +140,44 @@ else
|
|
|
140
140
|
report FAIL watchdog "timeout, gtimeout, and perl are all unavailable"
|
|
141
141
|
fi
|
|
142
142
|
|
|
143
|
+
# Vendor CLI availability. Runners resolve each vendor's binary through a *_BIN
|
|
144
|
+
# override (default name otherwise) and source local.sh, so probe the same way
|
|
145
|
+
# in a subshell — set +u because a machine-local local.sh may reference its own
|
|
146
|
+
# unset vars, and the subshell keeps any side effects out of this report.
|
|
147
|
+
vendor_line="$(
|
|
148
|
+
set +u
|
|
149
|
+
[[ -f "$OMNILANE_HOME/local.sh" ]] && . "$OMNILANE_HOME/local.sh" 2>/dev/null
|
|
150
|
+
present=""; absent=""
|
|
151
|
+
for spec in "codex:${CODEX_BIN:-codex}" "claude:${CLAUDE_BIN:-claude}" \
|
|
152
|
+
"grok:${GROK_BIN:-grok}" "gemini:${AGY_BIN:-agy}" \
|
|
153
|
+
"kimi:${KIMI_BIN:-kimi}" "qwen:${QWEN_BIN:-qwen}" \
|
|
154
|
+
"opencode:${OPENCODE_BIN:-opencode}"; do
|
|
155
|
+
name="${spec%%:*}"; bin="${spec#*:}"
|
|
156
|
+
if command -v "$bin" >/dev/null 2>&1; then present="$present $name"; else absent="$absent $name"; fi
|
|
157
|
+
done
|
|
158
|
+
# Direct-API vendors (OpenAI-compatible, no CLI): reachable iff curl exists
|
|
159
|
+
# and the matching API key is set. Keep aligned with
|
|
160
|
+
# OMNILANE_DIRECT_API_VENDORS in lib/common.sh.
|
|
161
|
+
if command -v curl >/dev/null 2>&1; then
|
|
162
|
+
for spec in "openrouter:${OPENROUTER_API_KEY:-}" "deepseek:${DEEPSEEK_API_KEY:-}" \
|
|
163
|
+
"zai:${ZAI_API_KEY:-}" "mistral:${MISTRAL_API_KEY:-}" \
|
|
164
|
+
"groq:${GROQ_API_KEY:-}" "cerebras:${CEREBRAS_API_KEY:-}"; do
|
|
165
|
+
name="${spec%%:*}"; key="${spec#*:}"
|
|
166
|
+
if [[ -n "$key" ]]; then present="$present $name"; else absent="$absent $name"; fi
|
|
167
|
+
done
|
|
168
|
+
else
|
|
169
|
+
absent="$absent openrouter deepseek zai mistral groq cerebras"
|
|
170
|
+
fi
|
|
171
|
+
printf '%s|%s' "${present# }" "${absent# }"
|
|
172
|
+
)"
|
|
173
|
+
vendor_present="${vendor_line%%|*}"
|
|
174
|
+
vendor_absent="${vendor_line#*|}"
|
|
175
|
+
if [[ -n "$vendor_present" ]]; then
|
|
176
|
+
report PASS vendors "present: $vendor_present${vendor_absent:+; missing: $vendor_absent}"
|
|
177
|
+
else
|
|
178
|
+
report WARN vendors "no vendor CLI reachable${vendor_absent:+ (missing: $vendor_absent)}; every lane degrades to off"
|
|
179
|
+
fi
|
|
180
|
+
|
|
143
181
|
if command -v python3 >/dev/null 2>&1; then
|
|
144
182
|
if python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' \
|
|
145
183
|
>/dev/null 2>&1; then
|