omnilane 0.31.0 → 0.33.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 +70 -1
- package/README.ja.md +66 -66
- package/README.ko.md +66 -65
- package/README.md +68 -68
- package/README.zh-CN.md +64 -62
- package/README.zh-TW.md +65 -63
- package/VERSION +1 -1
- package/completions/_omnilane +2 -1
- package/completions/omnilane.bash +2 -2
- package/completions/omnilane.fish +2 -0
- package/hooks/report-completions.sh +36 -5
- package/hooks/routing-instruction.md +25 -10
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/routing.local.yaml.example +4 -4
- package/routing.yaml +22 -24
- package/scripts/configure.sh +3 -3
- package/scripts/dispatch.sh +336 -48
- package/scripts/doctor.sh +1 -1
- package/scripts/jobs.sh +74 -30
- package/scripts/lib/common.sh +88 -0
- package/scripts/lib/job-worker.sh +0 -28
- package/scripts/provider-probe.sh +1 -1
- package/scripts/release-audit.sh +1 -1
- package/scripts/runners/run-claude.sh +63 -4
- package/scripts/runners/run-codex.sh +31 -4
- package/scripts/runners/run-gemini.sh +49 -1
- package/scripts/runners/run-grok.sh +18 -2
- package/scripts/runners/run-vote.sh +3 -3
- package/skills/omnilane/SKILL.md +82 -43
package/scripts/lib/common.sh
CHANGED
|
@@ -9,6 +9,34 @@ export OMNILANE_REPO
|
|
|
9
9
|
# Publishable default is plain CLIs on PATH; power users add ~/.omnilane/local.sh.
|
|
10
10
|
[[ -f "$OMNILANE_HOME/local.sh" ]] && source "$OMNILANE_HOME/local.sh"
|
|
11
11
|
|
|
12
|
+
# The unquoted backslash case pattern intentionally matches one backslash.
|
|
13
|
+
# shellcheck disable=SC1003
|
|
14
|
+
json_escape() {
|
|
15
|
+
local s="$1" out="" ch escaped code i
|
|
16
|
+
for ((i = 0; i < ${#s}; i++)); do
|
|
17
|
+
ch="${s:i:1}"
|
|
18
|
+
case "$ch" in
|
|
19
|
+
'"') out="$out\\\"" ;;
|
|
20
|
+
\\) out="$out\\\\" ;;
|
|
21
|
+
$'\b') out="$out\\b" ;;
|
|
22
|
+
$'\f') out="$out\\f" ;;
|
|
23
|
+
$'\n') out="$out\\n" ;;
|
|
24
|
+
$'\r') out="$out\\r" ;;
|
|
25
|
+
$'\t') out="$out\\t" ;;
|
|
26
|
+
*)
|
|
27
|
+
LC_CTYPE=C printf -v code '%d' "'$ch"
|
|
28
|
+
if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
|
|
29
|
+
printf -v escaped '\\u%04x' "$code"
|
|
30
|
+
out="$out$escaped"
|
|
31
|
+
else
|
|
32
|
+
out="$out$ch"
|
|
33
|
+
fi
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
printf '%s' "$out"
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
resolve_timeout_cmd() {
|
|
13
41
|
if command -v timeout &>/dev/null; then echo "timeout";
|
|
14
42
|
elif command -v gtimeout &>/dev/null; then echo "gtimeout";
|
|
@@ -284,6 +312,66 @@ prepare_private_store() { # path, diagnostic label
|
|
|
284
312
|
chmod 700 "$store_root" || return 1
|
|
285
313
|
}
|
|
286
314
|
|
|
315
|
+
OMNILANE_THREAD_NAME_PATTERN='^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$'
|
|
316
|
+
|
|
317
|
+
omnilane_valid_thread_name() {
|
|
318
|
+
[[ "${1:-}" =~ $OMNILANE_THREAD_NAME_PATTERN ]]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
prepare_threads_store() {
|
|
322
|
+
prepare_private_store "$OMNILANE_HOME/threads" "thread store"
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
read_thread_state() { # path, expected name; populates THREAD_STATE_*
|
|
326
|
+
local path="$1" expected_name="$2" fields separator=$'\034'
|
|
327
|
+
THREAD_STATE_NAME=""
|
|
328
|
+
THREAD_STATE_VENDOR=""
|
|
329
|
+
THREAD_STATE_MODEL=""
|
|
330
|
+
THREAD_STATE_EFFORT=""
|
|
331
|
+
THREAD_STATE_WORKDIR=""
|
|
332
|
+
THREAD_STATE_SESSION_ID=""
|
|
333
|
+
THREAD_STATE_TURNS=""
|
|
334
|
+
THREAD_STATE_LAST_JOB_ID=""
|
|
335
|
+
THREAD_STATE_CREATED=""
|
|
336
|
+
THREAD_STATE_UPDATED=""
|
|
337
|
+
|
|
338
|
+
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
339
|
+
fields="$(perl -MJSON::PP -e '
|
|
340
|
+
use strict;
|
|
341
|
+
use warnings;
|
|
342
|
+
my ($path, $expected) = @ARGV;
|
|
343
|
+
my $size = -s $path;
|
|
344
|
+
die "invalid size\n" unless defined($size) && $size > 0 && $size <= 16384;
|
|
345
|
+
open my $fh, "<", $path or die $!;
|
|
346
|
+
local $/;
|
|
347
|
+
my $state = decode_json(<$fh>);
|
|
348
|
+
die "invalid state\n" unless ref($state) eq "HASH";
|
|
349
|
+
my @string_keys = qw(name vendor model effort workdir session_id last_job_id created updated);
|
|
350
|
+
for my $key (@string_keys) {
|
|
351
|
+
my $value = $state->{$key};
|
|
352
|
+
die "invalid $key\n" if !defined($value) || ref($value) || $value =~ /[\x00-\x1f\x7f]/;
|
|
353
|
+
}
|
|
354
|
+
die "wrong name\n" unless $state->{name} eq $expected;
|
|
355
|
+
die "invalid name\n" unless $state->{name} =~ /\A[A-Za-z0-9][A-Za-z0-9._-]{0,63}\z/;
|
|
356
|
+
die "invalid vendor\n" unless $state->{vendor} =~ /\A[a-z][a-z0-9-]*\z/;
|
|
357
|
+
die "invalid session\n" unless $state->{session_id} =~ /\A[A-Za-z0-9._:-]{1,256}\z/;
|
|
358
|
+
die "invalid turns\n" if ref($state->{turns}) || ($state->{turns} // "") !~ /\A[1-9][0-9]{0,8}\z/;
|
|
359
|
+
die "invalid job id\n" unless $state->{last_job_id} =~ /\A[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+\z/;
|
|
360
|
+
for my $key (qw(created updated)) {
|
|
361
|
+
die "invalid timestamp\n" unless $state->{$key} =~ /\A[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\z/;
|
|
362
|
+
}
|
|
363
|
+
die "field too long\n" if length($state->{model}) > 512 || length($state->{effort}) > 128 || length($state->{workdir}) > 4096;
|
|
364
|
+
print join(chr(28), map { $state->{$_} } qw(name vendor model effort workdir session_id turns last_job_id created updated));
|
|
365
|
+
' "$path" "$expected_name" 2>/dev/null)" || return 1
|
|
366
|
+
|
|
367
|
+
# shellcheck disable=SC2034 # globals consumed by dispatch.sh and jobs.sh
|
|
368
|
+
IFS="$separator" read -r THREAD_STATE_NAME THREAD_STATE_VENDOR \
|
|
369
|
+
THREAD_STATE_MODEL THREAD_STATE_EFFORT THREAD_STATE_WORKDIR \
|
|
370
|
+
THREAD_STATE_SESSION_ID THREAD_STATE_TURNS THREAD_STATE_LAST_JOB_ID \
|
|
371
|
+
THREAD_STATE_CREATED THREAD_STATE_UPDATED <<< "$fields"
|
|
372
|
+
[[ -n "$THREAD_STATE_UPDATED" ]]
|
|
373
|
+
}
|
|
374
|
+
|
|
287
375
|
prepare_inbox_store() {
|
|
288
376
|
prepare_private_store "$OMNILANE_HOME/inbox" "inbox store"
|
|
289
377
|
}
|
|
@@ -21,34 +21,6 @@ RUNNER="$OMNILANE_REPO/scripts/runners/run-$VENDOR.sh"
|
|
|
21
21
|
# Two concurrent codex execs in one target dir corrupt its job index — serialize.
|
|
22
22
|
[[ "$VENDOR" == "codex" ]] && acquire_cwd_lock codex "$WORKDIR"
|
|
23
23
|
|
|
24
|
-
# The backslash case pattern is intentional.
|
|
25
|
-
# shellcheck disable=SC1003
|
|
26
|
-
json_escape() {
|
|
27
|
-
local s="$1" out="" ch escaped code i
|
|
28
|
-
for ((i = 0; i < ${#s}; i++)); do
|
|
29
|
-
ch="${s:i:1}"
|
|
30
|
-
case "$ch" in
|
|
31
|
-
'"') out="$out\\\"" ;;
|
|
32
|
-
'\\') out="$out\\\\" ;;
|
|
33
|
-
$'\b') out="$out\\b" ;;
|
|
34
|
-
$'\f') out="$out\\f" ;;
|
|
35
|
-
$'\n') out="$out\\n" ;;
|
|
36
|
-
$'\r') out="$out\\r" ;;
|
|
37
|
-
$'\t') out="$out\\t" ;;
|
|
38
|
-
*)
|
|
39
|
-
LC_CTYPE=C printf -v code '%d' "'$ch"
|
|
40
|
-
if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
|
|
41
|
-
printf -v escaped '\\u%04x' "$code"
|
|
42
|
-
out="$out$escaped"
|
|
43
|
-
else
|
|
44
|
-
out="$out$ch"
|
|
45
|
-
fi
|
|
46
|
-
;;
|
|
47
|
-
esac
|
|
48
|
-
done
|
|
49
|
-
printf '%s' "$out"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
24
|
emit_mode_notice() {
|
|
53
25
|
local notice="$1" notice_file="${OUTPUT_FILE%/*}/mode-notice.txt"
|
|
54
26
|
printf '%s\n' "$notice" >&2
|
package/scripts/release-audit.sh
CHANGED
|
@@ -14,6 +14,21 @@ RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
|
14
14
|
|
|
15
15
|
truncate_payload "$PROMPT_FILE" 102400
|
|
16
16
|
|
|
17
|
+
THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
|
|
18
|
+
THREAD_ID="${OMNILANE_THREAD_ID:-}"
|
|
19
|
+
THREAD_ARGS=()
|
|
20
|
+
if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
|
|
21
|
+
[[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
|
|
22
|
+
echo "omnilane: invalid Claude thread session id" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
}
|
|
25
|
+
case "$THREAD_MODE" in
|
|
26
|
+
new) THREAD_ARGS=(--session-id "$THREAD_ID") ;;
|
|
27
|
+
resume) THREAD_ARGS=(--resume "$THREAD_ID") ;;
|
|
28
|
+
*) echo "omnilane: invalid Claude thread mode" >&2; exit 2 ;;
|
|
29
|
+
esac
|
|
30
|
+
fi
|
|
31
|
+
|
|
17
32
|
LIVE_INBOX="${OMNILANE_INBOX:-}"
|
|
18
33
|
if [[ -n "$LIVE_INBOX" && -p "$LIVE_INBOX" ]]; then
|
|
19
34
|
EVENTS_FILE="${OUTPUT_FILE}.events.jsonl"
|
|
@@ -118,7 +133,7 @@ PY
|
|
|
118
133
|
exit "$RC"
|
|
119
134
|
fi
|
|
120
135
|
|
|
121
|
-
ARGS=(--disable-slash-commands --model "$MODEL"
|
|
136
|
+
ARGS=(--disable-slash-commands --model "$MODEL")
|
|
122
137
|
[[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(--effort "$EFFORT")
|
|
123
138
|
if [[ "$MODE" == "advise" ]]; then
|
|
124
139
|
# Read-only surface: the worker can inspect the repo but not change or run anything.
|
|
@@ -126,18 +141,62 @@ if [[ "$MODE" == "advise" ]]; then
|
|
|
126
141
|
else
|
|
127
142
|
ARGS+=(--permission-mode acceptEdits)
|
|
128
143
|
fi
|
|
144
|
+
if [[ -n "$THREAD_MODE" ]]; then
|
|
145
|
+
ARGS+=(--verbose --output-format stream-json)
|
|
146
|
+
ARGS+=("${THREAD_ARGS[@]}")
|
|
147
|
+
else
|
|
148
|
+
ARGS+=(--output-format text)
|
|
149
|
+
fi
|
|
129
150
|
ARGS+=(-p "$(cat "$PROMPT_FILE")")
|
|
130
151
|
|
|
131
152
|
set +e
|
|
132
153
|
(
|
|
133
154
|
cd "$WORKDIR" || exit 127
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
155
|
+
if [[ -n "$THREAD_MODE" ]]; then
|
|
156
|
+
run_with_timeout "$RUN_TIMEOUT" env \
|
|
157
|
+
OMNILANE_DEPTH=1 \
|
|
158
|
+
"$CLAUDE_BIN" "${ARGS[@]}" > "${OUTPUT_FILE}.events.jsonl" 2> "${OUTPUT_FILE}.stderr.log"
|
|
159
|
+
else
|
|
160
|
+
run_with_timeout "$RUN_TIMEOUT" env \
|
|
161
|
+
OMNILANE_DEPTH=1 \
|
|
162
|
+
"$CLAUDE_BIN" "${ARGS[@]}" > "${OUTPUT_FILE}.tmp" 2> "${OUTPUT_FILE}.stderr.log"
|
|
163
|
+
fi
|
|
137
164
|
)
|
|
138
165
|
RC=$?
|
|
139
166
|
set -e
|
|
140
167
|
|
|
168
|
+
if [[ -n "$THREAD_MODE" ]]; then
|
|
169
|
+
if [[ "$RC" -eq 0 ]]; then
|
|
170
|
+
if ! python3 - "$OUTPUT_FILE.events.jsonl" "${OUTPUT_FILE}.tmp" <<'PY'
|
|
171
|
+
import json
|
|
172
|
+
import pathlib
|
|
173
|
+
import sys
|
|
174
|
+
|
|
175
|
+
events_path = pathlib.Path(sys.argv[1])
|
|
176
|
+
output_path = pathlib.Path(sys.argv[2])
|
|
177
|
+
last_result = None
|
|
178
|
+
with events_path.open(encoding="utf-8") as events:
|
|
179
|
+
for raw_line in events:
|
|
180
|
+
try:
|
|
181
|
+
event = json.loads(raw_line)
|
|
182
|
+
except json.JSONDecodeError:
|
|
183
|
+
continue
|
|
184
|
+
if event.get("type") == "result" and isinstance(event.get("result"), str):
|
|
185
|
+
last_result = event["result"]
|
|
186
|
+
if last_result is None:
|
|
187
|
+
raise SystemExit(1)
|
|
188
|
+
output_path.write_text(last_result.rstrip("\n") + "\n", encoding="utf-8")
|
|
189
|
+
PY
|
|
190
|
+
then
|
|
191
|
+
echo "omnilane: Claude thread stream ended without readable result event" >> "${OUTPUT_FILE}.stderr.log"
|
|
192
|
+
RC=1
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
if [[ "$RC" -ne 0 && -s "${OUTPUT_FILE}.stderr.log" ]]; then
|
|
196
|
+
cat "${OUTPUT_FILE}.stderr.log" >&2
|
|
197
|
+
cat "${OUTPUT_FILE}.stderr.log" > "${OUTPUT_FILE}.tmp"
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
141
200
|
[[ -f "${OUTPUT_FILE}.tmp" ]] && mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE"
|
|
142
201
|
[[ -s "${OUTPUT_FILE}.stderr.log" ]] || rm "${OUTPUT_FILE}.stderr.log" 2>/dev/null || true
|
|
143
202
|
exit "$RC"
|
|
@@ -14,19 +14,46 @@ MODE="$1"; WORKDIR="$2"; MODEL="$3"; EFFORT="$4"; PROMPT_FILE="$5"; OUTPUT_FILE=
|
|
|
14
14
|
CODEX_BIN="${CODEX_BIN:-codex}"
|
|
15
15
|
RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
16
16
|
|
|
17
|
+
THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
|
|
18
|
+
THREAD_ID="${OMNILANE_THREAD_ID:-}"
|
|
19
|
+
if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
|
|
20
|
+
[[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
|
|
21
|
+
echo "omnilane: invalid Codex thread session id" >&2
|
|
22
|
+
exit 2
|
|
23
|
+
}
|
|
24
|
+
case "$THREAD_MODE" in
|
|
25
|
+
new) ;;
|
|
26
|
+
resume) ;;
|
|
27
|
+
*) echo "omnilane: invalid Codex thread mode" >&2; exit 2 ;;
|
|
28
|
+
esac
|
|
29
|
+
fi
|
|
30
|
+
|
|
17
31
|
# --skip-git-repo-check: the operator chose WORKDIR explicitly; codex would
|
|
18
32
|
# otherwise refuse any directory that is not a trusted git repo.
|
|
19
33
|
# --json: without it codex writes nothing until it exits, so a watchdog kill
|
|
20
34
|
# leaves an empty progress log that looks identical to a run that never started.
|
|
21
|
-
|
|
35
|
+
if [[ "$THREAD_MODE" == "resume" ]]; then
|
|
36
|
+
ARGS=(exec resume --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
|
|
37
|
+
else
|
|
38
|
+
ARGS=(exec --json -m "$MODEL" -o "${OUTPUT_FILE}.tmp" --skip-git-repo-check)
|
|
39
|
+
fi
|
|
22
40
|
[[ -n "$EFFORT" && "$EFFORT" != "-" ]] && ARGS+=(-c "model_reasoning_effort=\"$EFFORT\"")
|
|
23
41
|
if [[ "$MODE" == "advise" ]]; then
|
|
24
|
-
ARGS+=(--ephemeral
|
|
42
|
+
[[ -z "$THREAD_MODE" ]] && ARGS+=(--ephemeral)
|
|
43
|
+
SANDBOX=read-only
|
|
25
44
|
elif [[ "$MODE" == "sysops" ]]; then
|
|
26
|
-
|
|
45
|
+
SANDBOX=danger-full-access
|
|
46
|
+
else
|
|
47
|
+
SANDBOX=workspace-write
|
|
48
|
+
fi
|
|
49
|
+
# `codex exec resume` has no -s/--sandbox flag (rejects it with exit 2); the
|
|
50
|
+
# same policy is only reachable there through the sandbox_mode config override.
|
|
51
|
+
if [[ "$THREAD_MODE" == "resume" ]]; then
|
|
52
|
+
ARGS+=(-c "sandbox_mode=\"$SANDBOX\"")
|
|
27
53
|
else
|
|
28
|
-
ARGS+=(-s
|
|
54
|
+
ARGS+=(-s "$SANDBOX")
|
|
29
55
|
fi
|
|
56
|
+
[[ "$THREAD_MODE" == "resume" ]] && ARGS+=("$THREAD_ID" -)
|
|
30
57
|
|
|
31
58
|
truncate_payload "$PROMPT_FILE" 140000
|
|
32
59
|
|
|
@@ -14,6 +14,21 @@ AGY_BIN="${AGY_BIN:-agy}"
|
|
|
14
14
|
RUN_TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
15
15
|
CAPACITY_PATTERN='MODEL_CAPACITY_EXHAUSTED|No capacity available for model|rateLimitExceeded|RESOURCE_EXHAUSTED'
|
|
16
16
|
|
|
17
|
+
THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
|
|
18
|
+
THREAD_ID="${OMNILANE_THREAD_ID:-}"
|
|
19
|
+
THREAD_ARGS=()
|
|
20
|
+
if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
|
|
21
|
+
[[ "$THREAD_ID" =~ ^[A-Za-z0-9._:-]+$ && "${#THREAD_ID}" -le 256 ]] || {
|
|
22
|
+
echo "omnilane: invalid Gemini thread session id" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
}
|
|
25
|
+
case "$THREAD_MODE" in
|
|
26
|
+
new) ;;
|
|
27
|
+
resume) THREAD_ARGS=(--conversation "$THREAD_ID") ;;
|
|
28
|
+
*) echo "omnilane: invalid Gemini thread mode" >&2; exit 2 ;;
|
|
29
|
+
esac
|
|
30
|
+
fi
|
|
31
|
+
|
|
17
32
|
truncate_payload "$PROMPT_FILE" 140000
|
|
18
33
|
|
|
19
34
|
# Both modes run inside the target WORKDIR so the worker can actually see the
|
|
@@ -142,6 +157,38 @@ PY
|
|
|
142
157
|
exit "$RC"
|
|
143
158
|
fi
|
|
144
159
|
|
|
160
|
+
if [[ -n "$THREAD_MODE" ]]; then
|
|
161
|
+
set +e
|
|
162
|
+
(
|
|
163
|
+
cd "$RUN_DIR" || exit 127
|
|
164
|
+
env -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_AI_API_KEY \
|
|
165
|
+
NO_BROWSER=1 OMNILANE_DEPTH=1 \
|
|
166
|
+
"$AGY_BIN" --dangerously-skip-permissions --add-dir "$RUN_DIR" \
|
|
167
|
+
"${MODE_ARGS[@]}" ${MODEL_ARGS[@]+"${MODEL_ARGS[@]}"} \
|
|
168
|
+
--print-timeout "${RUN_TIMEOUT}s" --output-format json \
|
|
169
|
+
"${THREAD_ARGS[@]}" -p "$(cat "$PROMPT_FILE")" \
|
|
170
|
+
> "${OUTPUT_FILE}.result.json" 2> "${OUTPUT_FILE}.stderr.log"
|
|
171
|
+
)
|
|
172
|
+
RC=$?
|
|
173
|
+
set -e
|
|
174
|
+
if [[ "$RC" -eq 0 ]]; then
|
|
175
|
+
if ! python3 - "${OUTPUT_FILE}.result.json" "${OUTPUT_FILE}.tmp" <<'PY'
|
|
176
|
+
import json
|
|
177
|
+
import pathlib
|
|
178
|
+
import sys
|
|
179
|
+
|
|
180
|
+
result = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
181
|
+
response = result.get("response")
|
|
182
|
+
if result.get("status") != "SUCCESS" or not isinstance(response, str):
|
|
183
|
+
raise SystemExit(1)
|
|
184
|
+
pathlib.Path(sys.argv[2]).write_text(response.rstrip("\n") + "\n", encoding="utf-8")
|
|
185
|
+
PY
|
|
186
|
+
then
|
|
187
|
+
echo "omnilane: Gemini thread result was not readable SUCCESS JSON" >> "${OUTPUT_FILE}.stderr.log"
|
|
188
|
+
RC=1
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
else
|
|
145
192
|
set +e
|
|
146
193
|
(
|
|
147
194
|
cd "$RUN_DIR" || exit 127
|
|
@@ -158,8 +205,9 @@ set +e
|
|
|
158
205
|
)
|
|
159
206
|
RC=$?
|
|
160
207
|
set -e
|
|
208
|
+
fi
|
|
161
209
|
|
|
162
|
-
if grep -Eiq "$CAPACITY_PATTERN" "${OUTPUT_FILE}.tmp" "${OUTPUT_FILE}.stderr.log" 2>/dev/null; then
|
|
210
|
+
if grep -Eiq "$CAPACITY_PATTERN" "${OUTPUT_FILE}.tmp" "${OUTPUT_FILE}.result.json" "${OUTPUT_FILE}.stderr.log" 2>/dev/null; then
|
|
163
211
|
echo "omnilane: gemini capacity exhausted" >> "${OUTPUT_FILE}.stderr.log"
|
|
164
212
|
RC=126
|
|
165
213
|
fi
|
|
@@ -17,14 +17,30 @@ MAX_ATTEMPTS="${OMNILANE_GROK_MAX_ATTEMPTS:-5}"
|
|
|
17
17
|
exit 2
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
THREAD_MODE="${OMNILANE_THREAD_MODE:-}"
|
|
21
|
+
THREAD_ID="${OMNILANE_THREAD_ID:-}"
|
|
22
|
+
THREAD_ARGS=()
|
|
23
|
+
if [[ -n "$THREAD_MODE" || -n "$THREAD_ID" ]]; then
|
|
24
|
+
[[ "$THREAD_ID" =~ ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$ ]] || {
|
|
25
|
+
echo "omnilane: invalid Grok thread session id" >&2
|
|
26
|
+
exit 2
|
|
27
|
+
}
|
|
28
|
+
case "$THREAD_MODE" in
|
|
29
|
+
new) THREAD_ARGS=(--session-id "$THREAD_ID") ;;
|
|
30
|
+
resume) THREAD_ARGS=(--resume "$THREAD_ID") ;;
|
|
31
|
+
*) echo "omnilane: invalid Grok thread mode" >&2; exit 2 ;;
|
|
32
|
+
esac
|
|
33
|
+
fi
|
|
34
|
+
|
|
20
35
|
# Subscription OAuth path: an exhausted API key in env causes 403s.
|
|
21
36
|
unset XAI_API_KEY 2>/dev/null || true
|
|
22
37
|
|
|
23
38
|
truncate_payload "$PROMPT_FILE" 140000
|
|
24
39
|
|
|
25
40
|
ARGS=(--cwd "$WORKDIR" --model "$MODEL"
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
--no-memory --no-subagents --no-plan --no-alt-screen
|
|
42
|
+
--output-format plain --verbatim --prompt-file "$PROMPT_FILE")
|
|
43
|
+
ARGS+=("${THREAD_ARGS[@]}")
|
|
28
44
|
[[ "$MODE" == "advise" ]] && ARGS+=(--permission-mode plan)
|
|
29
45
|
# Web/X search stays ON by default — it is this vendor's signature lane.
|
|
30
46
|
[[ "${OMNILANE_GROK_NO_WEB:-0}" == "1" ]] && ARGS+=(--disable-web-search)
|
|
@@ -31,9 +31,9 @@ trap cleanup_temp_files EXIT
|
|
|
31
31
|
voter_spec() { # vendor -> "model<TAB>effort"
|
|
32
32
|
case "$1" in
|
|
33
33
|
codex) printf 'gpt-5.6-sol\thigh' ;;
|
|
34
|
-
claude) printf 'claude-
|
|
35
|
-
gemini) printf 'Gemini 3.
|
|
36
|
-
grok) printf 'grok-4.
|
|
34
|
+
claude) printf 'claude-fable-5-1\thigh' ;;
|
|
35
|
+
gemini) printf 'Gemini 3.7 Flash (High)\t-' ;;
|
|
36
|
+
grok) printf 'grok-4.6\t-' ;;
|
|
37
37
|
*) return 1 ;;
|
|
38
38
|
esac
|
|
39
39
|
}
|
package/skills/omnilane/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: omnilane
|
|
3
|
-
description: 'Universal model-routing table + cross-vendor dispatch for ANY harness (Claude Code, Codex, Grok Build, Antigravity). Use when delegating subtasks, choosing a model for work, planning multi-part tasks, or when asked about model routing, delegate, dispatch, which model, tier selection, escalate, 派工, 模型路由. One routing table;
|
|
3
|
+
description: 'Universal model-routing table + cross-vendor dispatch for ANY harness (Claude Code, Codex, Grok Build, Antigravity). Use when delegating subtasks, choosing a model for work, planning multi-part tasks, or when asked about model routing, delegate, dispatch, which model, tier selection, escalate, 派工, 模型路由. One routing table; every task dispatches by default via dispatch.sh — the main loop self-executes only reserved commander items.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# omnilane — one routing table, every harness
|
|
@@ -9,13 +9,31 @@ You (the main loop) may be Claude, GPT, Grok, or Gemini. The procedure is identi
|
|
|
9
9
|
|
|
10
10
|
1. **Identify your main model.** You know which model you are running as.
|
|
11
11
|
2. **Split the work into subtasks and classify each into a lane** (table below).
|
|
12
|
-
3. **Dispatch
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
3. **Dispatch every task by default — even when the lane's model is you:**
|
|
13
|
+
implementation, search, investigation, file reads, verification, tests,
|
|
14
|
+
builds, deploys. The commander self-executes only: planning and
|
|
15
|
+
decomposition, writing task briefs, reading worker reports and job files
|
|
16
|
+
(`out.txt`, `events.jsonl`, inbox records), acceptance judgment, replies to
|
|
17
|
+
the operator, git commit/push, and edits to governance files. Read-only
|
|
18
|
+
work goes out in advise mode: `triage` for high-volume scans, `long-context`
|
|
19
|
+
for large documents, `live-search` for web or X, `hard-judgment` for second
|
|
20
|
+
opinions. Editing work uses `--mode work --workdir <repo> --timeout 3600`
|
|
21
|
+
or more. Re-verify a worker's claim by reading its attached evidence or by
|
|
22
|
+
dispatching a second worker (change `--vendor`); the commander runs no
|
|
23
|
+
commands itself. Invalid reasons to skip dispatch: "this lane is mine",
|
|
24
|
+
"I am not dispatching so the rule does not apply", "it is only a file
|
|
25
|
+
read", "dispatch is slower", "it is one line". Dispatch:
|
|
17
26
|
`<repo>/scripts/dispatch.sh [--vendor V] [--mode work] [--workdir DIR] <lane> "<task>"`
|
|
18
27
|
Add `--background` for long tasks; poll with `scripts/jobs.sh status|result <id>`.
|
|
28
|
+
Use `--thread NAME` when later claude, codex, grok or gemini dispatches
|
|
29
|
+
must retain earlier context. Threads in 0.33.0 pin vendor, model, effort and
|
|
30
|
+
physical workdir; inspect or remove local state with `scripts/jobs.sh threads`,
|
|
31
|
+
`threads show NAME`, and `threads rm NAME` (removal leaves the vendor session).
|
|
32
|
+
Implementation dispatches (code edits, new files, tests, builds, deploys)
|
|
33
|
+
must carry `--mode work --workdir <repo>` and a `--timeout` of at least
|
|
34
|
+
3600 seconds. The advise default is a read-only worker under a 600 s
|
|
35
|
+
per-call watchdog, and on an implementation task it yields zero output.
|
|
36
|
+
Advise stays the default for reviews, questions, and second opinions.
|
|
19
37
|
Before changing lane order from anecdotal outcomes, run
|
|
20
38
|
`scripts/jobs.sh recommend [--last N] [--lane L] [--min-samples N]` and report
|
|
21
39
|
its evidence threshold. The command is read-only and never changes routing.
|
|
@@ -50,28 +68,26 @@ what dispatch picks when the first-choice vendor CLI is not installed.
|
|
|
50
68
|
|
|
51
69
|
| Lane | First choice | Backup | When |
|
|
52
70
|
|---|---|---|---|
|
|
53
|
-
| hardest-coding |
|
|
54
|
-
| bulk-mechanical | GPT-5.6
|
|
55
|
-
| triage | GPT-5.6 Luna (
|
|
56
|
-
| hard-judgment | Claude
|
|
57
|
-
| taste-final | Claude
|
|
58
|
-
| consult |
|
|
59
|
-
| ui-draft | GPT-5.6 Sol (xhigh) | Claude
|
|
60
|
-
| long-context | Gemini 3.
|
|
61
|
-
| fast-agentic |
|
|
62
|
-
| live-search | Grok 4.
|
|
63
|
-
| coding-overflow | Grok 4.
|
|
71
|
+
| hardest-coding | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (xhigh) | Hardest implementation, deep root-cause debug, correctness-critical edits |
|
|
72
|
+
| bulk-mechanical | GPT-5.6 Sol (high) | Gemini 3.7 Flash (High) → Claude Sonnet 5 (high) | Refactors, migrations, tests, review sweeps — mechanical endurance |
|
|
73
|
+
| triage | GPT-5.6 Luna (high) | Gemini 3.7 Flash (Low) → Claude Haiku 4.5 | High-volume scans, first-pass filtering |
|
|
74
|
+
| hard-judgment | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (max) → Grok 4.6 | Architecture arbitration, deep reasoning, second opinions |
|
|
75
|
+
| taste-final | Claude Fable 5.1 (high) | GPT-5.6 Sol (max) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
|
|
76
|
+
| consult | GPT-5.6 Sol (max) | Claude Fable 5.1 (high) → Grok 4.6 → Gemini 3.7 Flash (High) | Direct named-model consultation; always keep `--vendor` |
|
|
77
|
+
| ui-draft | GPT-5.6 Sol (xhigh) | Claude Fable 5.1 (high) | UI drafts only WITH a design system / reference images; open-ended visual taste goes to taste-final |
|
|
78
|
+
| long-context | Gemini 3.7 Flash (Medium) | GPT-5.6 Terra (max) → Claude Opus 5 (medium) | Long-context synthesis ordered on AA-LCR, then cost and throughput |
|
|
79
|
+
| fast-agentic | Gemini 3.7 Flash (Medium) | GPT-5.6 Luna (high) | Fast multi-step agentic loops, multimodal checks |
|
|
80
|
+
| live-search | Grok 4.6 | — (off) | Realtime X/web search and social context |
|
|
81
|
+
| coding-overflow | Grok 4.6 | Gemini 3.7 Flash (High) → Kimi K3 → Qwen3 Coder Plus → OpenCode | Codex-quota relief valve for mid-tier coding; verify factual claims |
|
|
64
82
|
| arbitrate | off (opt-in vote panel) | — | Disabled by default. Enable with `arbitrate: vote codex,claude,grok -` in routing.local.yaml or via the configurator (any 1-4 voters). One quota hit PER VOTER PER ROUND; you chair: read the opinions and own the decision. Effort field 2 = debate round (voters rebut each other) |
|
|
65
83
|
|
|
66
|
-
Claude Fable 5 (`claude-fable-5`) is
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
it anyway, select it in the configurator or override a lane in
|
|
74
|
-
`~/.omnilane/routing.local.yaml` (e.g. `taste-final: claude claude-fable-5 high`).
|
|
84
|
+
Claude Fable 5.1 (`claude-fable-5-1`) is in the judgment, taste, and
|
|
85
|
+
hardest-coding defaults because it leads Opus 5 on every Artificial Analysis
|
|
86
|
+
axis at the same effort. It is not in bulk or triage because it prices at twice
|
|
87
|
+
Opus 5 per token and consumes the most subscription quota per turn. Opus 5
|
|
88
|
+
remains the lower-hallucination, lower-price Claude choice and can return to any
|
|
89
|
+
lane via `~/.omnilane/routing.local.yaml`, for example:
|
|
90
|
+
`hard-judgment: claude claude-opus-5 xhigh`.
|
|
75
91
|
|
|
76
92
|
## Natural-language consultation
|
|
77
93
|
|
|
@@ -91,15 +107,15 @@ Users may speak normally; they do not need lane names.
|
|
|
91
107
|
| Alias | Vendor | Model | Effort |
|
|
92
108
|
|---|---|---|---|
|
|
93
109
|
| Opus | claude | claude-opus-5 | high |
|
|
94
|
-
| Fable | claude | claude-fable-5 | high |
|
|
110
|
+
| Fable 5.1 | claude | claude-fable-5-1 | high |
|
|
95
111
|
| Sonnet | claude | claude-sonnet-5 | high |
|
|
96
112
|
| Haiku | claude | claude-haiku-4-5 | - |
|
|
97
113
|
| Sol | codex | gpt-5.6-sol | max |
|
|
98
114
|
| Terra | codex | gpt-5.6-terra | max |
|
|
99
|
-
| Luna | codex | gpt-5.6-luna |
|
|
100
|
-
| Grok 4.
|
|
101
|
-
| Gemini Pro | gemini | Gemini 3.1 Pro (High) | - |
|
|
102
|
-
| Gemini Flash | gemini | Gemini 3.
|
|
115
|
+
| Luna | codex | gpt-5.6-luna | high |
|
|
116
|
+
| Grok 4.6 | grok | grok-4.6 | - |
|
|
117
|
+
| Gemini 3.1 Pro | gemini | Gemini 3.1 Pro (High) | - |
|
|
118
|
+
| Gemini 3.7 Flash | gemini | Gemini 3.7 Flash (High) | - |
|
|
103
119
|
| Kimi | kimi | kimi-k3 | - |
|
|
104
120
|
| Qwen | qwen | qwen3-coder-plus | - |
|
|
105
121
|
| OpenCode | opencode | provider/model form, or `-` for its own default | - |
|
|
@@ -134,6 +150,24 @@ delete jobs, or edit configuration. Natural-language interpretation and
|
|
|
134
150
|
dispatch stay in this skill and the CLI. Manage the local board with
|
|
135
151
|
`omnilane ui start|status|url|stop`, and stop it when monitoring is finished.
|
|
136
152
|
|
|
153
|
+
## Job lifecycle defaults
|
|
154
|
+
|
|
155
|
+
- **Completion inbox**: with the Claude Code plugin's hooks installed, a
|
|
156
|
+
finished `--background` job is delivered into the foreman's next prompt by
|
|
157
|
+
the bundled `UserPromptSubmit` hook, so do not poll for it. Outside Claude
|
|
158
|
+
Code, block on `scripts/jobs.sh wait <id> [--timeout N]` instead.
|
|
159
|
+
- **Live mailbox**: a `--background` dispatch to Claude or Gemini is a
|
|
160
|
+
resident worker. Send follow-up instructions with `scripts/jobs.sh send <id>
|
|
161
|
+
"<text>"` and end it with `scripts/jobs.sh close <id>`; other vendors (and
|
|
162
|
+
`--single-shot`) run one-shot. Do not use a mailbox for fire-and-forget work.
|
|
163
|
+
- **Goal orchestration**: when the next step depends on the previous result,
|
|
164
|
+
wrap the dispatches in `omnilane goal open "<objective>" --workdir DIR`, then
|
|
165
|
+
`goal dispatch <goal-id> ...`, `goal note`, `goal status`, `goal close --summary`.
|
|
166
|
+
Budgets are unlimited unless `--budget-jobs` / `--budget-seconds` is passed.
|
|
167
|
+
A single obvious task is dispatched directly, never through a goal.
|
|
168
|
+
- **Job hygiene**: `scripts/jobs.sh cancel <id>` stops a runaway job.
|
|
169
|
+
`stats`, `recommend`, and `audit` are read-only and never change routing.
|
|
170
|
+
|
|
137
171
|
## Rules
|
|
138
172
|
|
|
139
173
|
- **Dispatch in `advise` mode by default** (read-only worker). Use `--mode work`
|
|
@@ -159,19 +193,24 @@ dispatch stay in this skill and the CLI. Manage the local board with
|
|
|
159
193
|
|
|
160
194
|
## Per-model notes (apply the row matching YOUR main model)
|
|
161
195
|
|
|
162
|
-
- **Claude
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
- **Claude Fable 5.1 main**: hard judgment, taste finalization, and the hardest
|
|
197
|
+
coding are yours. Dispatch bulk work to Sol high and long-context or fast
|
|
198
|
+
loops to Gemini 3.7 Flash.
|
|
199
|
+
- **Claude Opus 5 main**: judgment and taste remain its strongest lanes, but the commander still dispatches them;
|
|
200
|
+
use local overrides when its lower hallucination rate or price is preferred.
|
|
165
201
|
- **Claude Sonnet main**: coordination/tools/mid-tier coding only; never
|
|
166
202
|
self-assign top judgment or hardest implementation.
|
|
167
203
|
- **GPT Sol main**: hardest coding + hard judgment are yours (use max for
|
|
168
204
|
judgment turns, xhigh for coding); cross to taste-final for style calls.
|
|
169
|
-
- **GPT Terra main**:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- **Gemini 3.
|
|
176
|
-
are yours
|
|
177
|
-
|
|
205
|
+
- **GPT Terra main**: long-context Codex fallback work is yours at max;
|
|
206
|
+
bulk-mechanical now defaults to Sol high, and genuinely hardest pieces
|
|
207
|
+
escalate to Sol xhigh.
|
|
208
|
+
- **Grok 4.6 main**: live-search and coding overflow are yours; its measured
|
|
209
|
+
hallucination rate is the lowest among the frontier rows, but still verify
|
|
210
|
+
every API signature and cited fact before shipping.
|
|
211
|
+
- **Gemini 3.7 Flash main**: long-context and fast agentic/multimodal loops
|
|
212
|
+
are yours at the lane's configured effort; bulk and overflow use the high row.
|
|
213
|
+
Never self-assign top judgment.
|
|
214
|
+
- **Gemini 3.1 Pro main**: it remains directly selectable, but the default
|
|
215
|
+
long-context lane now prefers Gemini 3.7 Flash on LCR, cost, and throughput;
|
|
216
|
+
route hardest coding and judgment to the stronger Codex and Claude lanes.
|