omnilane 0.34.0 → 0.41.1
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 +47 -1
- package/README.ja.md +44 -33
- package/README.ko.md +44 -32
- package/README.md +71 -77
- package/README.zh-CN.md +42 -30
- package/README.zh-TW.md +65 -68
- package/VERSION +1 -1
- package/docs/aa-model-coverage-2026-09-05.json +29204 -0
- package/docs/model-capabilities-2026-09.md +380 -0
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/routing.local.yaml.example +8 -3
- package/routing.yaml +16 -16
- package/scripts/configure.sh +4 -4
- package/scripts/dispatch.sh +83 -14
- package/scripts/doctor.sh +55 -1
- package/scripts/jobs.sh +6 -2
- package/scripts/lib/common.sh +47 -1
- package/scripts/lib/job-worker.sh +312 -20
- package/scripts/lib/live-protocol.sh +147 -2
- package/scripts/lib/normalize-claude-stream.py +72 -0
- package/scripts/lib/prepare-agy-mode.py +374 -0
- package/scripts/release-audit.sh +103 -0
- package/scripts/runners/run-claude.sh +81 -47
- package/scripts/runners/run-codex-live.py +462 -0
- package/scripts/runners/run-codex.sh +62 -3
- package/scripts/runners/run-gemini.sh +85 -10
- package/scripts/runners/run-grok-live.py +426 -0
- package/scripts/runners/run-grok.sh +113 -6
- package/scripts/runners/run-vote.sh +3 -3
- package/skills/omnilane/SKILL.md +106 -59
package/scripts/dispatch.sh
CHANGED
|
@@ -60,9 +60,9 @@ flags:
|
|
|
60
60
|
before any provider call or job state
|
|
61
61
|
--mode advise|work|sysops
|
|
62
62
|
advise (read-only, default), work (may edit files),
|
|
63
|
-
or sysops (
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
or sysops (vendor sandbox disabled, for service
|
|
64
|
+
operations like launchctl; supported by
|
|
65
|
+
codex, claude, grok, and gemini)
|
|
66
66
|
--workdir DIR working directory handed to the vendor CLI
|
|
67
67
|
--vendor V pin one configured vendor (codex|claude|grok|gemini|kimi|qwen|opencode|openrouter|deepseek|zai|mistral|groq|cerebras)
|
|
68
68
|
--model M override the routed model
|
|
@@ -785,13 +785,32 @@ fi
|
|
|
785
785
|
RUNNER="$OMNILANE_REPO/scripts/runners/run-$VENDOR.sh"
|
|
786
786
|
[[ -x "$RUNNER" ]] || { echo "omnilane: no runner for vendor '$VENDOR'" >&2; exit 2; }
|
|
787
787
|
|
|
788
|
+
# Fail closed before creating job state or starting a provider when the selected
|
|
789
|
+
# runtime cannot enforce the requested mode. Dry-run reports the same gap.
|
|
790
|
+
if [[ "$VENDOR" == "grok" && "$MODE" == "work" && "$(uname -s)" == "Darwin" ]]; then
|
|
791
|
+
echo "omnilane: Grok work requires disabled agent-tool network; xAI CLI child-network restrictions are not enforced on macOS" >&2
|
|
792
|
+
exit 2
|
|
793
|
+
fi
|
|
794
|
+
|
|
788
795
|
SESSION_MODE="single-shot"
|
|
789
|
-
if [[ -z "$THREAD_NAME" && "$SESSION_REQUEST" != "single-shot" ]] && live_vendor_capable "$VENDOR"
|
|
790
|
-
|
|
796
|
+
if [[ -z "$THREAD_NAME" && "$SESSION_REQUEST" != "single-shot" ]] && live_vendor_capable "$VENDOR" &&
|
|
797
|
+
[[ "$SESSION_REQUEST" == "live" || "$VENDOR" == "claude" || "$VENDOR" == "gemini" ]]; then
|
|
798
|
+
# Preserve established auto behavior: Codex/Grok require an explicit --live.
|
|
799
|
+
# Claude/Gemini retain their existing auto-selected resident sessions.
|
|
800
|
+
# Grok ACP currently exposes no enforceable restricted policy.
|
|
801
|
+
# Only explicit sysops dispatch may use its resident ACP session.
|
|
802
|
+
if [[ "$VENDOR" != "grok" || "$MODE" == "sysops" ]]; then
|
|
803
|
+
SESSION_MODE="live"
|
|
804
|
+
fi
|
|
791
805
|
fi
|
|
792
|
-
if [[ "$SESSION_REQUEST" == "live" ]]
|
|
793
|
-
|
|
794
|
-
|
|
806
|
+
if [[ "$SESSION_REQUEST" == "live" ]]; then
|
|
807
|
+
if [[ "$VENDOR" == "grok" && "$MODE" != "sysops" ]]; then
|
|
808
|
+
echo "omnilane: Grok --live supports only explicit --mode sysops; restricted ACP policy is unavailable" >&2
|
|
809
|
+
exit 2
|
|
810
|
+
elif ! live_vendor_capable "$VENDOR"; then
|
|
811
|
+
echo "omnilane: vendor '$VENDOR' cannot run live; live-capable vendors: $(live_capable_vendors)" >&2
|
|
812
|
+
exit 2
|
|
813
|
+
fi
|
|
795
814
|
fi
|
|
796
815
|
export OMNILANE_SESSION_MODE="$SESSION_MODE"
|
|
797
816
|
if [[ "$SESSION_REQUEST" == "live" ]]; then
|
|
@@ -822,9 +841,20 @@ IDLE_TIMEOUT="$OVERRIDE_IDLE_TIMEOUT"
|
|
|
822
841
|
exit 2
|
|
823
842
|
}
|
|
824
843
|
export OMNILANE_IDLE_TIMEOUT="$IDLE_TIMEOUT"
|
|
844
|
+
# The idle cap only exists inside a live mailbox, so a single-shot dispatch
|
|
845
|
+
# would be told about a limit that never applies to it.
|
|
846
|
+
if [[ "$SESSION_MODE" == "live" && "$IDLE_TIMEOUT" -gt 0 && "$TIMEOUT" -gt "$IDLE_TIMEOUT" ]]; then
|
|
847
|
+
echo "omnilane: --timeout is ${TIMEOUT}s, but the independent live mailbox idle cap remains ${IDLE_TIMEOUT}s; adjust it with --idle-timeout SECONDS" >&2
|
|
848
|
+
fi
|
|
825
849
|
|
|
826
850
|
JOB_SUPERVISOR="$OMNILANE_REPO/scripts/lib/job-timeout.pl"
|
|
827
|
-
|
|
851
|
+
JOB_WORKER_SOURCE="$OMNILANE_REPO/scripts/lib/job-worker.sh"
|
|
852
|
+
# Pinned deliberately: /bin/bash is 3.2 on macOS while a PATH bash is typically
|
|
853
|
+
# 5.x, so the worker and every library it sources must stay bash-3.2 compatible.
|
|
854
|
+
# No declare -A, mapfile, ${v,,}, [[ -v ]], ;;&, EPOCHSECONDS or coproc.
|
|
855
|
+
JOB_WORKER_BASH="/bin/bash"
|
|
856
|
+
[[ -x "$JOB_WORKER_BASH" ]] || { echo "omnilane: fixed worker interpreter is unavailable: $JOB_WORKER_BASH" >&2; exit 2; }
|
|
857
|
+
JOB_WORKER_BASH_VERSION="$($JOB_WORKER_BASH -c 'printf %s "$BASH_VERSION"')"
|
|
828
858
|
|
|
829
859
|
# Optional whole-job seconds: flag > per-lane env > global env > automatic
|
|
830
860
|
# non-Git Codex work guard > disabled.
|
|
@@ -870,7 +900,7 @@ if [[ -n "$JOB_TIMEOUT" && ! "$JOB_TIMEOUT" =~ ^[1-9][0-9]{0,8}$ ]]; then
|
|
|
870
900
|
fi
|
|
871
901
|
JOB_TIMEOUT_JSON="${JOB_TIMEOUT:-null}"
|
|
872
902
|
unset OMNILANE_JOB_SUPERVISED
|
|
873
|
-
[[ -x "$
|
|
903
|
+
[[ -x "$JOB_WORKER_SOURCE" ]] || { echo "omnilane: internal job worker is unavailable" >&2; exit 2; }
|
|
874
904
|
if [[ -n "$JOB_TIMEOUT" ]]; then
|
|
875
905
|
[[ -f "$JOB_SUPERVISOR" ]] || { echo "omnilane: whole-job timeout supervisor is unavailable" >&2; exit 2; }
|
|
876
906
|
command -v perl &>/dev/null || { echo "omnilane: --job-timeout requires perl" >&2; exit 2; }
|
|
@@ -904,6 +934,17 @@ chmod 700 "$JOBS_ROOT"
|
|
|
904
934
|
JOB_ID="$(date +%Y%m%d-%H%M%S)-$$-$RANDOM"
|
|
905
935
|
JOB_DIR="$JOBS_ROOT/$JOB_ID"
|
|
906
936
|
mkdir -m 700 "$JOB_DIR"
|
|
937
|
+
JOB_WORKER="$JOB_DIR/job-worker.sh"
|
|
938
|
+
JOB_WORKER_TMP="$JOB_DIR/.job-worker.tmp.$$-$RANDOM"
|
|
939
|
+
(umask 077; cat "$JOB_WORKER_SOURCE" > "$JOB_WORKER_TMP")
|
|
940
|
+
chmod 400 "$JOB_WORKER_TMP"
|
|
941
|
+
mv "$JOB_WORKER_TMP" "$JOB_WORKER"
|
|
942
|
+
JOB_WORKER_SOURCE_SHA256="$(file_sha256 "$JOB_WORKER_SOURCE")" || exit 2
|
|
943
|
+
JOB_WORKER_SHA256="$(file_sha256 "$JOB_WORKER")" || exit 2
|
|
944
|
+
if [[ "$JOB_WORKER_SHA256" != "$JOB_WORKER_SOURCE_SHA256" ]]; then
|
|
945
|
+
echo "omnilane: job worker source changed while creating the per-job snapshot" >&2
|
|
946
|
+
exit 1
|
|
947
|
+
fi
|
|
907
948
|
FOREMAN_SESSION=""
|
|
908
949
|
if [[ -n "${CLAUDE_CODE_SESSION_ID+x}" ]]; then
|
|
909
950
|
if [[ "$CLAUDE_CODE_SESSION_ID" =~ ^[A-Za-z0-9._:-]+$ &&
|
|
@@ -941,6 +982,24 @@ else
|
|
|
941
982
|
"$(date -u +%FT%TZ)" > "$JOB_DIR/meta.json")
|
|
942
983
|
fi
|
|
943
984
|
|
|
985
|
+
META_TMP="$JOB_DIR/.meta.tmp.$$-$RANDOM"
|
|
986
|
+
META_BASE="$(cat "$JOB_DIR/meta.json")"
|
|
987
|
+
if [[ "$META_BASE" != *'}' ]]; then
|
|
988
|
+
rm "$META_TMP" 2>/dev/null || true
|
|
989
|
+
echo "omnilane: cannot add worker provenance to malformed meta.json" >&2
|
|
990
|
+
exit 1
|
|
991
|
+
fi
|
|
992
|
+
META_BASE="${META_BASE%\}}"
|
|
993
|
+
printf '%s,"worker_interpreter_path":"%s","worker_interpreter_version":"%s","job_worker_source_path":"%s","job_worker_source_sha256":"%s","job_worker_path":"%s","job_worker_sha256":"%s"}\n' \
|
|
994
|
+
"$META_BASE" "$(json_escape "$JOB_WORKER_BASH")" \
|
|
995
|
+
"$(json_escape "$JOB_WORKER_BASH_VERSION")" "$(json_escape "$JOB_WORKER_SOURCE")" \
|
|
996
|
+
"$(json_escape "$JOB_WORKER_SOURCE_SHA256")" "$(json_escape "$JOB_WORKER")" \
|
|
997
|
+
"$(json_escape "$JOB_WORKER_SHA256")" \
|
|
998
|
+
> "$META_TMP"
|
|
999
|
+
json_file_round_trip_valid "$META_TMP" || exit 1
|
|
1000
|
+
chmod 600 "$META_TMP"
|
|
1001
|
+
mv "$META_TMP" "$JOB_DIR/meta.json"
|
|
1002
|
+
|
|
944
1003
|
if [[ -n "$THREAD_NAME" ]]; then
|
|
945
1004
|
printf 'omnilane: thread %s turn %s (%s session %s, %s)\n' \
|
|
946
1005
|
"$THREAD_NAME" "$THREAD_TURN" "$VENDOR" "$THREAD_ID" "$THREAD_MODE"
|
|
@@ -948,6 +1007,9 @@ fi
|
|
|
948
1007
|
|
|
949
1008
|
secure_job_files() {
|
|
950
1009
|
find "$JOB_DIR" -type f -exec chmod 600 {} +
|
|
1010
|
+
if [[ -f "$JOB_WORKER" && ! -L "$JOB_WORKER" ]]; then
|
|
1011
|
+
chmod 400 "$JOB_WORKER"
|
|
1012
|
+
fi
|
|
951
1013
|
}
|
|
952
1014
|
|
|
953
1015
|
sanitize_utf8() {
|
|
@@ -1023,6 +1085,9 @@ write_completion_record() {
|
|
|
1023
1085
|
"$rc" "$(json_escape "$finished")" "$(json_escape "$tail_value")" \
|
|
1024
1086
|
> "$tmp" || write_rc=$?
|
|
1025
1087
|
fi
|
|
1088
|
+
if [[ "$write_rc" -eq 0 ]] && ! json_file_round_trip_valid "$tmp"; then
|
|
1089
|
+
write_rc=1
|
|
1090
|
+
fi
|
|
1026
1091
|
if [[ "$write_rc" -eq 0 ]]; then
|
|
1027
1092
|
chmod 600 "$tmp" || write_rc=$?
|
|
1028
1093
|
fi
|
|
@@ -1047,7 +1112,7 @@ finish_job() {
|
|
|
1047
1112
|
secure_job_files
|
|
1048
1113
|
(umask 077; printf '%s\n' "$rc" > "$JOB_DIR/exit")
|
|
1049
1114
|
if [[ "${OMNILANE_INBOX:-1}" != "0" ]]; then
|
|
1050
|
-
write_completion_record "$rc" >/dev/null
|
|
1115
|
+
write_completion_record "$rc" >/dev/null || true
|
|
1051
1116
|
fi
|
|
1052
1117
|
FINISHED_RC="$rc"
|
|
1053
1118
|
}
|
|
@@ -1057,11 +1122,15 @@ run_job() {
|
|
|
1057
1122
|
write_current_pid_file "$JOB_DIR/pid"
|
|
1058
1123
|
set +e
|
|
1059
1124
|
if [[ -n "$JOB_TIMEOUT" ]]; then
|
|
1060
|
-
|
|
1061
|
-
"$
|
|
1125
|
+
OMNILANE_JOB_WORKER_REPO="$OMNILANE_REPO" \
|
|
1126
|
+
OMNILANE_JOB_WORKER_EXPECTED_SHA256="$JOB_WORKER_SHA256" \
|
|
1127
|
+
OMNILANE_JOB_SUPERVISED=1 perl "$JOB_SUPERVISOR" "$JOB_TIMEOUT" \
|
|
1128
|
+
"$JOB_WORKER_BASH" "$JOB_WORKER" "$VENDOR" "$MODE" "$WORKDIR" "$MODEL" "$EFFORT" \
|
|
1062
1129
|
"$JOB_DIR/task.txt" "$JOB_DIR/out.txt"
|
|
1063
1130
|
else
|
|
1064
|
-
"$
|
|
1131
|
+
OMNILANE_JOB_WORKER_REPO="$OMNILANE_REPO" \
|
|
1132
|
+
OMNILANE_JOB_WORKER_EXPECTED_SHA256="$JOB_WORKER_SHA256" \
|
|
1133
|
+
"$JOB_WORKER_BASH" "$JOB_WORKER" "$VENDOR" "$MODE" "$WORKDIR" "$MODEL" "$EFFORT" \
|
|
1065
1134
|
"$JOB_DIR/task.txt" "$JOB_DIR/out.txt"
|
|
1066
1135
|
fi
|
|
1067
1136
|
rc=$?
|
package/scripts/doctor.sh
CHANGED
|
@@ -28,10 +28,13 @@ done
|
|
|
28
28
|
echo "omnilane: --probe-timeout requires --probe V" >&2
|
|
29
29
|
exit 2
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
SCRIPT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
32
|
+
REPO="${OMNILANE_DOCTOR_REPO:-$SCRIPT_ROOT}"
|
|
32
33
|
OMNILANE_HOME="${OMNILANE_HOME:-$HOME/.omnilane}"
|
|
33
34
|
PROBE_SCRIPT="${OMNILANE_PROVIDER_PROBE_SCRIPT:-$REPO/scripts/provider-probe.sh}"
|
|
34
35
|
GOAL_LOOP="${OMNILANE_DOCTOR_GOAL_LOOP:-$REPO/scripts/lib/goal-loop.sh}"
|
|
36
|
+
# shellcheck disable=SC1091
|
|
37
|
+
source "$SCRIPT_ROOT/scripts/lib/live-protocol.sh"
|
|
35
38
|
PASS_COUNT=0
|
|
36
39
|
WARN_COUNT=0
|
|
37
40
|
FAIL_COUNT=0
|
|
@@ -355,6 +358,57 @@ fi
|
|
|
355
358
|
|
|
356
359
|
# A real inference call is explicit and single-vendor only. Default doctor
|
|
357
360
|
# remains local/offline. Never relay provider output or runner stderr.
|
|
361
|
+
live_capable_present=""
|
|
362
|
+
live_unavailable_present=""
|
|
363
|
+
resolved_codex_bin="$(
|
|
364
|
+
set +u
|
|
365
|
+
[[ -f "$OMNILANE_HOME/local.sh" ]] && . "$OMNILANE_HOME/local.sh" 2>/dev/null
|
|
366
|
+
printf '%s' "${CODEX_BIN:-codex}"
|
|
367
|
+
)"
|
|
368
|
+
resolved_grok_bin="$(
|
|
369
|
+
set +u
|
|
370
|
+
[[ -f "$OMNILANE_HOME/local.sh" ]] && . "$OMNILANE_HOME/local.sh" 2>/dev/null
|
|
371
|
+
printf '%s' "${GROK_BIN:-grok}"
|
|
372
|
+
)"
|
|
373
|
+
for name in $vendor_present; do
|
|
374
|
+
case "$name" in
|
|
375
|
+
claude|gemini)
|
|
376
|
+
live_capable_present="$live_capable_present $name"
|
|
377
|
+
;;
|
|
378
|
+
codex)
|
|
379
|
+
if codex_live_surface_available "$resolved_codex_bin"; then
|
|
380
|
+
live_capable_present="$live_capable_present codex"
|
|
381
|
+
report PASS codex-live "app-server initialize handshake succeeded"
|
|
382
|
+
else
|
|
383
|
+
live_unavailable_present="$live_unavailable_present codex"
|
|
384
|
+
report PASS codex-live "unavailable: app-server initialize handshake failed; upgrade codex"
|
|
385
|
+
fi
|
|
386
|
+
;;
|
|
387
|
+
grok)
|
|
388
|
+
if grok_live_surface_available "$resolved_grok_bin"; then
|
|
389
|
+
live_capable_present="$live_capable_present grok"
|
|
390
|
+
report PASS grok-live "agent stdio initialize handshake succeeded"
|
|
391
|
+
else
|
|
392
|
+
live_unavailable_present="$live_unavailable_present grok"
|
|
393
|
+
report PASS grok-live "unavailable: agent stdio initialize handshake failed"
|
|
394
|
+
fi
|
|
395
|
+
;;
|
|
396
|
+
*)
|
|
397
|
+
live_unavailable_present="$live_unavailable_present $name"
|
|
398
|
+
;;
|
|
399
|
+
esac
|
|
400
|
+
done
|
|
401
|
+
if [[ -n "$live_capable_present" ]]; then
|
|
402
|
+
report PASS live-capable "${live_capable_present# }"
|
|
403
|
+
else
|
|
404
|
+
report WARN live-capable "none"
|
|
405
|
+
fi
|
|
406
|
+
if [[ -n "$live_unavailable_present" ]]; then
|
|
407
|
+
report PASS live-unavailable "${live_unavailable_present# }"
|
|
408
|
+
else
|
|
409
|
+
report PASS live-unavailable "none"
|
|
410
|
+
fi
|
|
411
|
+
|
|
358
412
|
if [[ -n "$PROBE_VENDOR" ]]; then
|
|
359
413
|
if [[ ! -x "$PROBE_SCRIPT" ]]; then
|
|
360
414
|
report FAIL provider-probe "probe runner is unavailable"
|
package/scripts/jobs.sh
CHANGED
|
@@ -137,11 +137,15 @@ load_job_vendor() {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
require_live_job() {
|
|
140
|
+
local action="${1:-}"
|
|
140
141
|
load_job_vendor
|
|
141
142
|
live_vendor_capable "$JOB_VENDOR" ||
|
|
142
143
|
die 1 "job vendor '$JOB_VENDOR' is not live-capable (live vendors: $(live_capable_vendors)); it runs in single-shot mode"
|
|
143
144
|
[[ "$JOB_SESSION_MODE" == "live" ]] ||
|
|
144
145
|
die 1 "job vendor '$JOB_VENDOR' was dispatched in single-shot mode"
|
|
146
|
+
if [[ "$action" == "send" && "$JOB_VENDOR" == "grok" ]]; then
|
|
147
|
+
echo "note: Grok send cancels an active turn before starting the new prompt; conversation context survives, but in-progress work is lost"
|
|
148
|
+
fi
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
wait_for_live_ready() {
|
|
@@ -194,7 +198,7 @@ parse_stats_metadata() {
|
|
|
194
198
|
parse_audit_metadata() {
|
|
195
199
|
local value="$1" current_re legacy_re
|
|
196
200
|
local json_string='([^"\\]|\\["\\/bfnrt]|\\u[0-9a-fA-F]{4})*'
|
|
197
|
-
current_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"session_mode\":\"(live|single-shot)\",\"idle_timeout\":(0|[1-9][0-9]*),\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work|sysops)\",\"workdir\":\"${json_string}\",\"foreman_session\":\"${json_string}\",\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"
|
|
201
|
+
current_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"session_mode\":\"(live|single-shot)\",\"idle_timeout\":(0|[1-9][0-9]*),\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work|sysops)\",\"workdir\":\"${json_string}\",\"foreman_session\":\"${json_string}\",(\"thread\":\"${json_string}\",\"thread_turn\":[1-9][0-9]*,)?\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"(,\"worker_interpreter_path\":\"${json_string}\",\"worker_interpreter_version\":\"${json_string}\",\"job_worker_sha256\":\"[0-9a-f]{64}\")?\\}$"
|
|
198
202
|
legacy_re="^\\{\"lane\":\"[a-z][a-z0-9-]*\",\"vendor\":\"(${OMNILANE_VENDOR_ALT})\",\"model\":\"${json_string}\",\"effort\":\"${json_string}\",\"timeout\":(0|[1-9][0-9]*),\"job_timeout\":(null|0|[1-9][0-9]*),\"mode\":\"(advise|work|sysops)\",\"workdir\":\"${json_string}\",(\"foreman_session\":\"${json_string}\",)?\"candidate\":\"[1-9][0-9]*/[1-9][0-9]*\",\"started\":\"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\"\\}$"
|
|
199
203
|
[[ "$value" =~ $current_re || "$value" =~ $legacy_re ]]
|
|
200
204
|
}
|
|
@@ -405,7 +409,7 @@ case "${1:-}" in
|
|
|
405
409
|
send)
|
|
406
410
|
[[ "$JSON_MODE" -eq 0 && $# -eq 3 ]] || usage
|
|
407
411
|
select_job "$2"
|
|
408
|
-
require_live_job
|
|
412
|
+
require_live_job send
|
|
409
413
|
wait_for_live_ready
|
|
410
414
|
if [[ "${FOREMAN_SESSION+x}" == "x" ]]; then
|
|
411
415
|
foreman_value="$FOREMAN_SESSION"
|
package/scripts/lib/common.sh
CHANGED
|
@@ -37,6 +37,51 @@ json_escape() {
|
|
|
37
37
|
printf '%s' "$out"
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
json_file_round_trip_valid() {
|
|
41
|
+
local path="$1"
|
|
42
|
+
local valid=1
|
|
43
|
+
if [[ ! -f "$path" || -L "$path" ]]; then
|
|
44
|
+
valid=0
|
|
45
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
46
|
+
python3 - "$path" <<'PY' >/dev/null 2>&1 || valid=0
|
|
47
|
+
import json
|
|
48
|
+
import pathlib
|
|
49
|
+
import sys
|
|
50
|
+
|
|
51
|
+
value = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
52
|
+
json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
|
53
|
+
PY
|
|
54
|
+
elif command -v perl >/dev/null 2>&1; then
|
|
55
|
+
perl -MJSON::PP -e '
|
|
56
|
+
use strict;
|
|
57
|
+
use warnings;
|
|
58
|
+
my ($path) = @ARGV;
|
|
59
|
+
open my $fh, "<", $path or die $!;
|
|
60
|
+
local $/;
|
|
61
|
+
my $value = decode_json(<$fh>);
|
|
62
|
+
JSON::PP->new->canonical->encode($value);
|
|
63
|
+
' "$path" >/dev/null 2>&1 || valid=0
|
|
64
|
+
else
|
|
65
|
+
valid=0
|
|
66
|
+
fi
|
|
67
|
+
if [[ "$valid" -ne 1 ]]; then
|
|
68
|
+
printf 'omnilane: JSON round-trip validation failed for %s\n' "$path" >&2
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
file_sha256() {
|
|
74
|
+
local path="$1"
|
|
75
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
76
|
+
sha256sum "$path" | awk '{print $1}'
|
|
77
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
78
|
+
shasum -a 256 "$path" | awk '{print $1}'
|
|
79
|
+
else
|
|
80
|
+
printf 'omnilane: no sha256sum/shasum available for worker provenance\n' >&2
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
83
|
+
}
|
|
84
|
+
|
|
40
85
|
resolve_timeout_cmd() {
|
|
41
86
|
if command -v timeout &>/dev/null; then echo "timeout";
|
|
42
87
|
elif command -v gtimeout &>/dev/null; then echo "gtimeout";
|
|
@@ -456,7 +501,8 @@ acquire_cwd_lock() { # vendor, workdir — the lock keys on the TARGET dir, not
|
|
|
456
501
|
empty_since=-1
|
|
457
502
|
fi
|
|
458
503
|
if [[ "$waited" -ge "$lock_timeout" ]]; then
|
|
459
|
-
echo "omnilane: lock timeout for $vendor in $dir" >&2
|
|
504
|
+
echo "omnilane: lock timeout for $vendor in $dir; advise/read-only tasks may use a different --workdir to avoid this lock" >&2
|
|
505
|
+
exit 87
|
|
460
506
|
fi
|
|
461
507
|
sleep 2; waited=$((waited + 2))
|
|
462
508
|
done
|