omnilane 0.32.0 → 0.34.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 +62 -1
- package/README.ja.md +35 -18
- package/README.ko.md +35 -18
- package/README.md +37 -20
- package/README.zh-CN.md +33 -18
- package/README.zh-TW.md +33 -18
- 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.yaml +13 -13
- 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/skills/omnilane/SKILL.md +70 -29
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)
|
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,26 +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 | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (xhigh) | Hardest implementation, deep root-cause debug, correctness-critical edits |
|
|
71
|
+
| hardest-coding | Claude Fable 5.1 (xhigh) | GPT-5.6 Sol (xhigh) → Grok 4.6 → Gemini 3.7 Flash (High) | Hardest implementation, deep root-cause debug, correctness-critical edits |
|
|
54
72
|
| bulk-mechanical | GPT-5.6 Sol (high) | Gemini 3.7 Flash (High) → Claude Sonnet 5 (high) | Refactors, migrations, tests, review sweeps — mechanical endurance |
|
|
55
73
|
| triage | GPT-5.6 Luna (high) | Gemini 3.7 Flash (Low) → Claude Haiku 4.5 | High-volume scans, first-pass filtering |
|
|
56
|
-
| hard-judgment | Claude
|
|
57
|
-
| taste-final | Claude Fable 5.1 (high) | GPT-5.6 Sol (max) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
|
|
74
|
+
| hard-judgment | Claude Opus 5 (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) → Grok 4.6 → Gemini 3.7 Flash (High) | User-facing prose, prompt/doc polish, Chinese phrasing, style arbitration |
|
|
58
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` |
|
|
59
|
-
| 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 |
|
|
77
|
+
| ui-draft | GPT-5.6 Sol (xhigh) | Claude Fable 5.1 (high) → Gemini 3.7 Flash (High) | UI drafts only WITH a design system / reference images; open-ended visual taste goes to taste-final |
|
|
60
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 |
|
|
61
|
-
| fast-agentic | Gemini 3.7 Flash (Medium) | GPT-5.6 Luna (high) | Fast multi-step agentic loops, multimodal checks |
|
|
62
|
-
| live-search | Grok 4.6 |
|
|
79
|
+
| fast-agentic | Gemini 3.7 Flash (Medium) | GPT-5.6 Luna (high) → Claude Haiku 4.5 | Fast multi-step agentic loops, multimodal checks |
|
|
80
|
+
| live-search | Grok 4.6 | Gemini 3.7 Flash (High) → Claude Sonnet 5 (high) → off | Realtime X/web search and social context; Flash/Sonnet fall back to their own web-search tools |
|
|
63
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.1 (`claude-fable-5-1`) is in the
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`hard-judgment: claude claude-
|
|
84
|
+
Claude Fable 5.1 (`claude-fable-5-1`) is in the taste and hardest-coding
|
|
85
|
+
defaults because it leads Opus 5 on every Artificial Analysis axis at the same
|
|
86
|
+
effort. It is not in bulk or triage because it prices at twice Opus 5 per token
|
|
87
|
+
and consumes the most subscription quota per turn. Opus 5 now leads
|
|
88
|
+
hard-judgment on a per-cost, lower-hallucination basis and remains selectable
|
|
89
|
+
everywhere via `~/.omnilane/routing.local.yaml`, for example, to bring Fable
|
|
90
|
+
back: `hard-judgment: claude claude-fable-5-1 xhigh`.
|
|
73
91
|
|
|
74
92
|
## Natural-language consultation
|
|
75
93
|
|
|
@@ -132,6 +150,24 @@ delete jobs, or edit configuration. Natural-language interpretation and
|
|
|
132
150
|
dispatch stay in this skill and the CLI. Manage the local board with
|
|
133
151
|
`omnilane ui start|status|url|stop`, and stop it when monitoring is finished.
|
|
134
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
|
+
|
|
135
171
|
## Rules
|
|
136
172
|
|
|
137
173
|
- **Dispatch in `advise` mode by default** (read-only worker). Use `--mode work`
|
|
@@ -157,24 +193,29 @@ dispatch stay in this skill and the CLI. Manage the local board with
|
|
|
157
193
|
|
|
158
194
|
## Per-model notes (apply the row matching YOUR main model)
|
|
159
195
|
|
|
160
|
-
- **Claude Fable 5.1 main**:
|
|
161
|
-
|
|
162
|
-
loops to Gemini 3.7
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
196
|
+
- **Claude Fable 5.1 main**: taste finalization and the hardest coding are
|
|
197
|
+
yours; hard judgment now defaults to Opus 5. Dispatch bulk work to Sol high,
|
|
198
|
+
hard judgment to Opus 5 xhigh, and long-context or fast loops to Gemini 3.7
|
|
199
|
+
Flash.
|
|
200
|
+
- **Claude Opus 5 main**: hard judgment is now yours by default. Taste-final
|
|
201
|
+
remains Fable's; use local overrides when Opus's lower hallucination rate or
|
|
202
|
+
price is preferred there.
|
|
203
|
+
- **Claude Sonnet main**: coordination/tools/mid-tier coding only, plus
|
|
204
|
+
fallback duty in bulk-mechanical and live-search; never self-assign top
|
|
205
|
+
judgment or hardest implementation.
|
|
167
206
|
- **GPT Sol main**: hardest coding + hard judgment are yours (use max for
|
|
168
207
|
judgment turns, xhigh for coding); cross to taste-final for style calls.
|
|
169
208
|
- **GPT Terra main**: long-context Codex fallback work is yours at max;
|
|
170
209
|
bulk-mechanical now defaults to Sol high, and genuinely hardest pieces
|
|
171
210
|
escalate to Sol xhigh.
|
|
172
|
-
- **Grok 4.6 main**: live-search and coding overflow are yours
|
|
211
|
+
- **Grok 4.6 main**: live-search and coding overflow are yours, plus fallback
|
|
212
|
+
duty in hardest-coding, hard-judgment, and taste-final; its measured
|
|
173
213
|
hallucination rate is the lowest among the frontier rows, but still verify
|
|
174
214
|
every API signature and cited fact before shipping.
|
|
175
215
|
- **Gemini 3.7 Flash main**: long-context and fast agentic/multimodal loops
|
|
176
|
-
are yours at the lane's configured effort; bulk and overflow use the high
|
|
177
|
-
|
|
216
|
+
are yours at the lane's configured effort; bulk and overflow use the high
|
|
217
|
+
row, plus fallback duty (High) in hardest-coding, taste-final, ui-draft, and
|
|
218
|
+
live-search. Never self-assign top judgment.
|
|
178
219
|
- **Gemini 3.1 Pro main**: it remains directly selectable, but the default
|
|
179
220
|
long-context lane now prefers Gemini 3.7 Flash on LCR, cost, and throughput;
|
|
180
221
|
route hardest coding and judgment to the stronger Codex and Claude lanes.
|