omnilane 0.32.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 +46 -1
- package/README.ja.md +15 -1
- package/README.ko.md +15 -1
- package/README.md +16 -2
- package/README.zh-CN.md +14 -1
- package/README.zh-TW.md +14 -1
- 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/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 +43 -7
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.
|
|
@@ -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`
|
|
@@ -160,7 +196,7 @@ dispatch stay in this skill and the CLI. Manage the local board with
|
|
|
160
196
|
- **Claude Fable 5.1 main**: hard judgment, taste finalization, and the hardest
|
|
161
197
|
coding are yours. Dispatch bulk work to Sol high and long-context or fast
|
|
162
198
|
loops to Gemini 3.7 Flash.
|
|
163
|
-
- **Claude Opus 5 main**: judgment and taste remain
|
|
199
|
+
- **Claude Opus 5 main**: judgment and taste remain its strongest lanes, but the commander still dispatches them;
|
|
164
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.
|