omnilane 0.41.1 → 0.42.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 +4 -4
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +25 -1
- package/README.ja.md +21 -2
- package/README.ko.md +21 -2
- package/README.md +83 -16
- package/README.zh-CN.md +21 -2
- package/README.zh-TW.md +63 -11
- package/VERSION +1 -1
- package/config/aa-model-policy.json +3046 -0
- package/docs/completion-wakeup.md +126 -0
- package/docs/native-executor.md +264 -0
- package/docs/release-notes-0.42.1.md +32 -0
- package/hooks/routing-instruction.md +101 -40
- package/package.json +6 -2
- package/plugin.json +2 -2
- package/scripts/completion-wakeup.py +390 -0
- package/scripts/dispatch.sh +240 -18
- package/scripts/jobs.sh +58 -15
- package/scripts/lib/aa_policy.py +473 -0
- package/scripts/lib/aa_retry.py +77 -0
- package/scripts/lib/common.sh +59 -0
- package/scripts/lib/job-worker.sh +2 -0
- package/scripts/lib/native.py +507 -0
- package/scripts/runners/run-grok.sh +4 -0
- package/scripts/runners/run-vote.sh +3 -0
- package/skills/omnilane/SKILL.md +120 -31
package/scripts/dispatch.sh
CHANGED
|
@@ -35,6 +35,13 @@ MODE="advise"; WORKDIR="$PWD"; BACKGROUND=0; DRY_RUN=0
|
|
|
35
35
|
OVERRIDE_VENDOR=""; OVERRIDE_MODEL=""; OVERRIDE_EFFORT=""; OVERRIDE_TIMEOUT=""
|
|
36
36
|
OVERRIDE_JOB_TIMEOUT=""; OVERRIDE_IDLE_TIMEOUT=""; SESSION_REQUEST="auto"
|
|
37
37
|
THREAD_NAME=""; THREAD_MODE=""; THREAD_ID=""; THREAD_TURN=""; THREAD_CREATED=""
|
|
38
|
+
EXECUTOR="auto"; NATIVE_CONTEXT=""; RESOLVE_WITH_CONTEXT=0
|
|
39
|
+
SELECTED_EXECUTOR="cli"; EXECUTOR_REASON="no-native-context"
|
|
40
|
+
AA_POLICY_FILE="${OMNILANE_AA_POLICY_FILE:-$OMNILANE_REPO/config/aa-model-policy.json}"
|
|
41
|
+
AA_CALLER_CONTEXT="${OMNILANE_AA_CALLER_CONTEXT:-}"
|
|
42
|
+
AA_OPERATOR_ASSERTED_HUMAN="${OMNILANE_AA_OPERATOR_ASSERTED_HUMAN:-0}"
|
|
43
|
+
AA_TARGET_CONFIG=""; AA_POLICY_ACTIVE=0; AA_EXPLICIT_TARGET=0
|
|
44
|
+
AA_LAST_DECISION=""; AA_SELECTED_DECISION=""; AA_VOTE_PREFLIGHT=0
|
|
38
45
|
|
|
39
46
|
usage_error() {
|
|
40
47
|
echo 'usage: dispatch.sh [--background] [--dry-run] [--thread NAME] [flags] LANE "TASK" | [--json] --list|--validate [--json] | [--json] --explain LANE [--json] | --help' >&2
|
|
@@ -58,6 +65,13 @@ flags:
|
|
|
58
65
|
--background run in the background and print the JOB_ID
|
|
59
66
|
--dry-run print the fully resolved dispatch plan and stop
|
|
60
67
|
before any provider call or job state
|
|
68
|
+
--executor auto|native|cli caller-owned native handoff or legacy CLI
|
|
69
|
+
--native-context FILE explicit JSON capabilities; native is not a binary
|
|
70
|
+
--caller-context FILE exact model caller identity plus inherited ceiling
|
|
71
|
+
--operator-asserted-human explicit AA model-ceiling exemption; assertion only
|
|
72
|
+
--aa-policy FILE frozen AA policy registry (default: repo config)
|
|
73
|
+
--transport-overlay FILE host-local hashed request-selector contracts
|
|
74
|
+
--target-config ID pin one runtime-verified registry configuration
|
|
61
75
|
--mode advise|work|sysops
|
|
62
76
|
advise (read-only, default), work (may edit files),
|
|
63
77
|
or sysops (vendor sandbox disabled, for service
|
|
@@ -282,6 +296,9 @@ print_dry_run_plan() {
|
|
|
282
296
|
print_dry_run_value vendor "$VENDOR"
|
|
283
297
|
print_dry_run_value model "$MODEL"
|
|
284
298
|
print_dry_run_value effort "$EFFORT"
|
|
299
|
+
print_dry_run_value executor "$SELECTED_EXECUTOR"
|
|
300
|
+
print_dry_run_value executor_reason "$EXECUTOR_REASON"
|
|
301
|
+
printf 'aa_policy=%s\n' "$AA_SELECTED_DECISION"
|
|
285
302
|
print_dry_run_value mode "$MODE"
|
|
286
303
|
print_dry_run_value workdir "$WORKDIR"
|
|
287
304
|
printf 'timeout=%s\n' "$TIMEOUT"
|
|
@@ -351,10 +368,59 @@ routing_candidate_available() {
|
|
|
351
368
|
fi
|
|
352
369
|
}
|
|
353
370
|
|
|
371
|
+
aa_policy_decide() {
|
|
372
|
+
# vendor model effort [target-config] -> structured JSON in AA_LAST_DECISION
|
|
373
|
+
local vendor="$1" model="$2" effort="$3" target_config="${4:-}" rc=0
|
|
374
|
+
local args=(--registry "$AA_POLICY_FILE" --vendor "$vendor" --model "$model" --effort "$effort")
|
|
375
|
+
[[ -z "$target_config" ]] || args+=(--target-config "$target_config")
|
|
376
|
+
if [[ "$AA_OPERATOR_ASSERTED_HUMAN" == "1" ]]; then
|
|
377
|
+
args+=(--operator-asserted-human)
|
|
378
|
+
elif [[ -n "$AA_CALLER_CONTEXT" ]]; then
|
|
379
|
+
args+=(--caller-context "$AA_CALLER_CONTEXT")
|
|
380
|
+
fi
|
|
381
|
+
AA_LAST_DECISION="$(python3 "$OMNILANE_REPO/scripts/lib/aa_policy.py" "${args[@]}")" || rc=$?
|
|
382
|
+
return "$rc"
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
aa_vote_child_spec() {
|
|
386
|
+
case "$1" in
|
|
387
|
+
codex) printf 'gpt-6-astra\txhigh\n' ;;
|
|
388
|
+
claude) printf 'claude-fable-5-1\txhigh\n' ;;
|
|
389
|
+
gemini) printf 'gemini-3.8-flash-medium\t-\n' ;;
|
|
390
|
+
grok) printf 'grok-4.6\t-\n' ;;
|
|
391
|
+
*) return 1 ;;
|
|
392
|
+
esac
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
aa_policy_candidate_allowed() {
|
|
396
|
+
local vendor="$1" model="$2" effort="$3" voter spec child_model child_effort
|
|
397
|
+
local voters=()
|
|
398
|
+
AA_VOTE_PREFLIGHT=0
|
|
399
|
+
if [[ "$vendor" != "vote" ]]; then
|
|
400
|
+
aa_policy_decide "$vendor" "$model" "$effort" "$AA_TARGET_CONFIG"
|
|
401
|
+
return $?
|
|
402
|
+
fi
|
|
403
|
+
[[ -z "$AA_TARGET_CONFIG" ]] || {
|
|
404
|
+
AA_LAST_DECISION='{"schema_version":1,"allowed":false,"code":"vote-target-config-invalid","message":"--target-config cannot identify an entire vote panel"}'
|
|
405
|
+
return 3
|
|
406
|
+
}
|
|
407
|
+
IFS=',' read -ra voters <<< "$model"
|
|
408
|
+
for voter in ${voters[@]+"${voters[@]}"}; do
|
|
409
|
+
spec="$(aa_vote_child_spec "$voter")" || {
|
|
410
|
+
AA_LAST_DECISION='{"schema_version":1,"allowed":false,"code":"unknown-vote-child","message":"vote panel contains an unknown child"}'
|
|
411
|
+
return 3
|
|
412
|
+
}
|
|
413
|
+
child_model="${spec%%$'\t'*}"; child_effort="${spec##*$'\t'}"
|
|
414
|
+
aa_policy_decide "$voter" "$child_model" "$child_effort" || return $?
|
|
415
|
+
done
|
|
416
|
+
AA_VOTE_PREFLIGHT=1
|
|
417
|
+
return 0
|
|
418
|
+
}
|
|
419
|
+
|
|
354
420
|
# Pick the first candidate whose vendor CLI is present ("off" always matches).
|
|
355
421
|
# Sets RESOLVED_SPEC / RESOLVED_FIELDS / RESOLVED_IDX / RESOLVED_TOTAL.
|
|
356
422
|
resolve_chain() {
|
|
357
|
-
local chain="$1" requested_vendor="${2:-}" seg i=0 vendor
|
|
423
|
+
local chain="$1" requested_vendor="${2:-}" seg i=0 vendor model effort considered=0
|
|
358
424
|
RESOLVED_SPEC=""; RESOLVED_IDX=0; RESOLVED_TOTAL=0; RESOLVED_FIELDS=()
|
|
359
425
|
local SEGS=() F=()
|
|
360
426
|
IFS='|' read -ra SEGS <<< "$chain"
|
|
@@ -371,16 +437,35 @@ resolve_chain() {
|
|
|
371
437
|
}
|
|
372
438
|
F=("${PARSED_FIELDS[@]}")
|
|
373
439
|
vendor="${F[0]:-}"
|
|
440
|
+
model="${OVERRIDE_MODEL:-${F[1]:-}}"
|
|
441
|
+
effort="${OVERRIDE_EFFORT:-${F[2]:-}}"
|
|
374
442
|
if [[ -n "$requested_vendor" ]]; then
|
|
375
443
|
[[ "$vendor" == "$requested_vendor" ]] || continue
|
|
444
|
+
considered=1
|
|
445
|
+
if [[ "$AA_POLICY_ACTIVE" -eq 1 ]] &&
|
|
446
|
+
! aa_policy_candidate_allowed "$vendor" "$model" "$effort"; then
|
|
447
|
+
return 6
|
|
448
|
+
fi
|
|
376
449
|
RESOLVED_SPEC="$seg"; RESOLVED_FIELDS=("${F[@]}"); RESOLVED_IDX="$i"
|
|
377
|
-
|
|
450
|
+
AA_SELECTED_DECISION="$AA_LAST_DECISION"
|
|
451
|
+
if [[ "$RESOLVE_WITH_CONTEXT" -eq 1 ]] || routing_candidate_available "$vendor" "$model"; then return 0; fi
|
|
378
452
|
return 4
|
|
379
453
|
fi
|
|
380
|
-
if [[ "$
|
|
454
|
+
if [[ "$AA_EXPLICIT_TARGET" -eq 1 && "$considered" -eq 1 ]]; then
|
|
455
|
+
return 6
|
|
456
|
+
fi
|
|
457
|
+
considered=1
|
|
458
|
+
if [[ "$AA_POLICY_ACTIVE" -eq 1 ]] &&
|
|
459
|
+
! aa_policy_candidate_allowed "$vendor" "$model" "$effort"; then
|
|
460
|
+
[[ "$AA_EXPLICIT_TARGET" -eq 1 ]] && return 6
|
|
461
|
+
continue
|
|
462
|
+
fi
|
|
463
|
+
if [[ "$RESOLVE_WITH_CONTEXT" -eq 1 || "$vendor" == "off" ]] || routing_candidate_available "$vendor" "$model"; then
|
|
381
464
|
RESOLVED_SPEC="$seg"; RESOLVED_FIELDS=("${F[@]}")
|
|
465
|
+
AA_SELECTED_DECISION="$AA_LAST_DECISION"
|
|
382
466
|
RESOLVED_IDX="$i"; return 0
|
|
383
467
|
fi
|
|
468
|
+
[[ "$AA_EXPLICIT_TARGET" -eq 1 ]] && return 4
|
|
384
469
|
done
|
|
385
470
|
[[ -n "$requested_vendor" ]] && return 5
|
|
386
471
|
return 1
|
|
@@ -627,7 +712,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
627
712
|
}
|
|
628
713
|
SESSION_REQUEST="single-shot"; shift ;;
|
|
629
714
|
--dry-run) DRY_RUN=1; shift ;;
|
|
630
|
-
--
|
|
715
|
+
--operator-asserted-human)
|
|
716
|
+
AA_OPERATOR_ASSERTED_HUMAN=1; shift ;;
|
|
717
|
+
--mode|--workdir|--vendor|--model|--effort|--timeout|--job-timeout|--idle-timeout|--thread|--executor|--native-context|--caller-context|--aa-policy|--target-config|--transport-overlay)
|
|
631
718
|
# Value-taking flags: a missing value must be a clean usage error (exit 2),
|
|
632
719
|
# not a `set -u` "unbound variable" crash on $2.
|
|
633
720
|
[[ $# -ge 2 ]] || { echo "omnilane: $1 needs a value" >&2; exit 2; }
|
|
@@ -641,6 +728,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
641
728
|
--job-timeout) OVERRIDE_JOB_TIMEOUT="$2" ;;
|
|
642
729
|
--idle-timeout) OVERRIDE_IDLE_TIMEOUT="$2" ;;
|
|
643
730
|
--thread) THREAD_NAME="$2" ;;
|
|
731
|
+
--executor) EXECUTOR="$2" ;;
|
|
732
|
+
--native-context)
|
|
733
|
+
[[ -n "$2" ]] || { echo "omnilane: native context path is empty" >&2; exit 2; }
|
|
734
|
+
NATIVE_CONTEXT="$2" ;;
|
|
735
|
+
--caller-context) AA_CALLER_CONTEXT="$2" ;;
|
|
736
|
+
--aa-policy) AA_POLICY_FILE="$2" ;;
|
|
737
|
+
--target-config) AA_TARGET_CONFIG="$2" ;;
|
|
738
|
+
--transport-overlay) export OMNILANE_AA_TRANSPORT_OVERLAY="$2" ;;
|
|
644
739
|
esac
|
|
645
740
|
shift 2 ;;
|
|
646
741
|
-*) echo "omnilane: unknown flag" >&2; exit 2 ;;
|
|
@@ -686,6 +781,35 @@ if [[ -n "$OVERRIDE_VENDOR" ]] &&
|
|
|
686
781
|
fi
|
|
687
782
|
|
|
688
783
|
depth_guard
|
|
784
|
+
command -v python3 >/dev/null 2>&1 || {
|
|
785
|
+
echo "omnilane: exact-AA policy requires Python 3" >&2; exit 2
|
|
786
|
+
}
|
|
787
|
+
[[ "$AA_OPERATOR_ASSERTED_HUMAN" == "0" || "$AA_OPERATOR_ASSERTED_HUMAN" == "1" ]] || {
|
|
788
|
+
echo "omnilane: OMNILANE_AA_OPERATOR_ASSERTED_HUMAN must be 0 or 1" >&2; exit 2
|
|
789
|
+
}
|
|
790
|
+
if [[ "$AA_OPERATOR_ASSERTED_HUMAN" == "1" && -n "$AA_CALLER_CONTEXT" ]]; then
|
|
791
|
+
echo "omnilane: caller context and operator assertion are mutually exclusive" >&2
|
|
792
|
+
exit 2
|
|
793
|
+
fi
|
|
794
|
+
[[ -n "$AA_POLICY_FILE" ]] || { echo "omnilane: AA policy path is empty" >&2; exit 2; }
|
|
795
|
+
if [[ -n "${OMNILANE_AA_TRANSPORT_OVERLAY:-}" ]]; then
|
|
796
|
+
OMNILANE_AA_OVERLAY_SHA256="$(file_sha256 "$OMNILANE_AA_TRANSPORT_OVERLAY")"
|
|
797
|
+
export OMNILANE_AA_OVERLAY_SHA256
|
|
798
|
+
fi
|
|
799
|
+
AA_POLICY_ACTIVE=1
|
|
800
|
+
if [[ -n "$OVERRIDE_VENDOR" || -n "$OVERRIDE_MODEL" || -n "$OVERRIDE_EFFORT" || -n "$AA_TARGET_CONFIG" ]]; then
|
|
801
|
+
AA_EXPLICIT_TARGET=1
|
|
802
|
+
fi
|
|
803
|
+
case "$EXECUTOR" in
|
|
804
|
+
cli) EXECUTOR_REASON="forced-cli" ;;
|
|
805
|
+
auto|native)
|
|
806
|
+
if [[ "$EXECUTOR" == "native" || -n "$NATIVE_CONTEXT" ]]; then
|
|
807
|
+
# Resolve independently of installed executables. Native rejection may
|
|
808
|
+
# fall back only to this exact CLI target, not to the next routing row.
|
|
809
|
+
RESOLVE_WITH_CONTEXT=1
|
|
810
|
+
fi ;;
|
|
811
|
+
*) echo "omnilane: invalid executor (auto|native|cli)" >&2; exit 2 ;;
|
|
812
|
+
esac
|
|
689
813
|
|
|
690
814
|
CHAIN="$(raw_lane_line "$LANE")" || { echo "omnilane: unknown lane '$LANE' (try --list)" >&2; exit 2; }
|
|
691
815
|
if [[ -n "$OVERRIDE_VENDOR" ]]; then
|
|
@@ -699,7 +823,11 @@ if [[ -n "$OVERRIDE_VENDOR" ]]; then
|
|
|
699
823
|
echo "omnilane: requested vendor '$OVERRIDE_VENDOR' is configured for lane '$LANE' but its CLI is unavailable" >&2
|
|
700
824
|
exit 4
|
|
701
825
|
;;
|
|
702
|
-
|
|
826
|
+
6)
|
|
827
|
+
printf '%s\n' "$AA_LAST_DECISION" >&2
|
|
828
|
+
exit 3
|
|
829
|
+
;;
|
|
830
|
+
5)
|
|
703
831
|
echo "omnilane: requested vendor '$OVERRIDE_VENDOR' is not configured for lane '$LANE'" >&2
|
|
704
832
|
exit 2
|
|
705
833
|
;;
|
|
@@ -710,11 +838,16 @@ if [[ -n "$OVERRIDE_VENDOR" ]]; then
|
|
|
710
838
|
esac
|
|
711
839
|
fi
|
|
712
840
|
else
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
841
|
+
resolve_rc=0
|
|
842
|
+
resolve_chain "$CHAIN" || resolve_rc=$?
|
|
843
|
+
if [[ "$resolve_rc" -eq 6 ]]; then
|
|
844
|
+
printf '%s\n' "$AA_LAST_DECISION" >&2
|
|
845
|
+
exit 3
|
|
846
|
+
elif [[ "$resolve_rc" -ne 0 ]]; then
|
|
847
|
+
echo "omnilane: no eligible available target for lane '$LANE' (chain:$CHAIN)." >&2
|
|
848
|
+
[[ -z "$AA_LAST_DECISION" ]] || printf '%s\n' "$AA_LAST_DECISION" >&2
|
|
716
849
|
exit 4
|
|
717
|
-
|
|
850
|
+
fi
|
|
718
851
|
fi
|
|
719
852
|
|
|
720
853
|
FIELDS=("${RESOLVED_FIELDS[@]}")
|
|
@@ -722,6 +855,61 @@ VENDOR="${FIELDS[0]}"; MODEL="${FIELDS[1]:-}"; EFFORT="${FIELDS[2]:-}"
|
|
|
722
855
|
[[ -n "$OVERRIDE_MODEL" ]] && MODEL="$OVERRIDE_MODEL"
|
|
723
856
|
[[ -n "$OVERRIDE_EFFORT" ]] && EFFORT="$OVERRIDE_EFFORT"
|
|
724
857
|
|
|
858
|
+
AA_REGISTRY_SHA256="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["registry_sha256"])' "$AA_SELECTED_DECISION")"
|
|
859
|
+
AA_CALLER_SHA256="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("caller_context_sha256", ""))' "$AA_SELECTED_DECISION")"
|
|
860
|
+
export OMNILANE_AA_POLICY_FILE="$AA_POLICY_FILE"
|
|
861
|
+
export OMNILANE_AA_REGISTRY_SHA256="$AA_REGISTRY_SHA256"
|
|
862
|
+
export OMNILANE_AA_CALLER_CONTEXT="$AA_CALLER_CONTEXT"
|
|
863
|
+
export OMNILANE_AA_CALLER_SHA256="$AA_CALLER_SHA256"
|
|
864
|
+
export OMNILANE_AA_OPERATOR_ASSERTED_HUMAN="$AA_OPERATOR_ASSERTED_HUMAN"
|
|
865
|
+
export OMNILANE_AA_VOTE_PREFLIGHT="$AA_VOTE_PREFLIGHT"
|
|
866
|
+
|
|
867
|
+
# Shared routing fields are fixed before executor selection. Native never
|
|
868
|
+
# sources runners or launches a shell agent.
|
|
869
|
+
TIMEOUT="$OVERRIDE_TIMEOUT"
|
|
870
|
+
if [[ -z "$TIMEOUT" ]]; then
|
|
871
|
+
LANE_TIMEOUT_VAR="OMNILANE_TIMEOUT_$(printf '%s' "${LANE//-/_}" | tr '[:lower:]' '[:upper:]')"
|
|
872
|
+
TIMEOUT="${!LANE_TIMEOUT_VAR:-}"
|
|
873
|
+
fi
|
|
874
|
+
[[ -n "$TIMEOUT" ]] || TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
875
|
+
[[ "$TIMEOUT" =~ ^[1-9][0-9]*$ ]] || {
|
|
876
|
+
echo "omnilane: invalid timeout (want a positive integer of seconds)" >&2; exit 2
|
|
877
|
+
}
|
|
878
|
+
if [[ "$RESOLVE_WITH_CONTEXT" -eq 1 ]]; then
|
|
879
|
+
command -v python3 >/dev/null 2>&1 || { echo "omnilane: native protocol requires Python 3" >&2; exit 2; }
|
|
880
|
+
NATIVE_JOB_VAR="OMNILANE_JOB_TIMEOUT_$(printf '%s' "${LANE//-/_}" | tr '[:lower:]' '[:upper:]')"
|
|
881
|
+
NATIVE_ARGS=(route --home "$OMNILANE_HOME" --executor "$EXECUTOR" --lane "$LANE"
|
|
882
|
+
--vendor "$VENDOR" --model="$MODEL" --effort="$EFFORT" --workdir "$WORKDIR"
|
|
883
|
+
--mode "$MODE" --task="$TASK" --session "$SESSION_REQUEST" --thread "$THREAD_NAME"
|
|
884
|
+
--policy "$AA_POLICY_FILE" --expected-registry-sha256 "$AA_REGISTRY_SHA256"
|
|
885
|
+
--timeout "$TIMEOUT" --job-timeout "${OVERRIDE_JOB_TIMEOUT:-${!NATIVE_JOB_VAR:-${OMNILANE_JOB_TIMEOUT:-}}}"
|
|
886
|
+
--idle-timeout "${OVERRIDE_IDLE_TIMEOUT:-${OMNILANE_IDLE_TIMEOUT:-}}")
|
|
887
|
+
[[ -z "$NATIVE_CONTEXT" ]] || NATIVE_ARGS+=(--context "$NATIVE_CONTEXT")
|
|
888
|
+
[[ -z "$AA_TARGET_CONFIG" ]] || NATIVE_ARGS+=(--target-config "$AA_TARGET_CONFIG")
|
|
889
|
+
if [[ "$AA_OPERATOR_ASSERTED_HUMAN" == "1" ]]; then
|
|
890
|
+
NATIVE_ARGS+=(--operator-asserted-human)
|
|
891
|
+
elif [[ -n "$AA_CALLER_CONTEXT" ]]; then
|
|
892
|
+
NATIVE_ARGS+=(--caller-context "$AA_CALLER_CONTEXT")
|
|
893
|
+
[[ -z "$AA_CALLER_SHA256" ]] || NATIVE_ARGS+=(--expected-caller-sha256 "$AA_CALLER_SHA256")
|
|
894
|
+
fi
|
|
895
|
+
[[ "$BACKGROUND" -eq 0 ]] || NATIVE_ARGS+=(--background)
|
|
896
|
+
[[ "$DRY_RUN" -eq 0 ]] || NATIVE_ARGS+=(--dry-run)
|
|
897
|
+
NATIVE_RC=0
|
|
898
|
+
NATIVE_OUTPUT="$(python3 "$OMNILANE_REPO/scripts/lib/native.py" "${NATIVE_ARGS[@]}")" || NATIVE_RC=$?
|
|
899
|
+
if [[ "$NATIVE_RC" -eq 0 ]]; then
|
|
900
|
+
printf '%s\n' "$NATIVE_OUTPUT"
|
|
901
|
+
exit 0
|
|
902
|
+
elif [[ "$NATIVE_RC" -ne 10 ]]; then
|
|
903
|
+
exit "$NATIVE_RC"
|
|
904
|
+
fi
|
|
905
|
+
EXECUTOR_REASON="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["reason"])' "$NATIVE_OUTPUT")"
|
|
906
|
+
MODEL="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["model"])' "$NATIVE_OUTPUT")"
|
|
907
|
+
[[ "$VENDOR" == "off" ]] || routing_candidate_available "$VENDOR" "$MODEL" || {
|
|
908
|
+
echo "omnilane: native rejected ($EXECUTOR_REASON); exact CLI target '$VENDOR' unavailable; no vendor/model substitution" >&2
|
|
909
|
+
exit 4
|
|
910
|
+
}
|
|
911
|
+
fi
|
|
912
|
+
|
|
725
913
|
if [[ -n "$THREAD_NAME" ]]; then
|
|
726
914
|
case "$VENDOR" in
|
|
727
915
|
claude|codex|grok|gemini) ;;
|
|
@@ -823,15 +1011,6 @@ fi
|
|
|
823
1011
|
# Resolve here and export so every runner (and vote's sub-runners) inherits the
|
|
824
1012
|
# same value without a per-runner code change; they already read OMNILANE_TIMEOUT.
|
|
825
1013
|
# This bounds each runner CLI call, not the whole dispatch (see header note).
|
|
826
|
-
TIMEOUT="$OVERRIDE_TIMEOUT"
|
|
827
|
-
if [[ -z "$TIMEOUT" ]]; then
|
|
828
|
-
LANE_TIMEOUT_VAR="OMNILANE_TIMEOUT_$(printf '%s' "${LANE//-/_}" | tr '[:lower:]' '[:upper:]')"
|
|
829
|
-
TIMEOUT="${!LANE_TIMEOUT_VAR:-}"
|
|
830
|
-
fi
|
|
831
|
-
[[ -n "$TIMEOUT" ]] || TIMEOUT="${OMNILANE_TIMEOUT:-600}"
|
|
832
|
-
[[ "$TIMEOUT" =~ ^[1-9][0-9]*$ ]] || {
|
|
833
|
-
echo "omnilane: invalid timeout (want a positive integer of seconds)" >&2; exit 2
|
|
834
|
-
}
|
|
835
1014
|
export OMNILANE_TIMEOUT="$TIMEOUT"
|
|
836
1015
|
|
|
837
1016
|
IDLE_TIMEOUT="$OVERRIDE_IDLE_TIMEOUT"
|
|
@@ -919,6 +1098,7 @@ if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
|
919
1098
|
exit 0
|
|
920
1099
|
fi
|
|
921
1100
|
|
|
1101
|
+
printf 'omnilane: executor=cli reason=%s\n' "$EXECUTOR_REASON" >&2
|
|
922
1102
|
mkdir -p "$OMNILANE_HOME"
|
|
923
1103
|
if [[ ! -d "$JOBS_ROOT" ]]; then
|
|
924
1104
|
mkdir -m 700 "$JOBS_ROOT"
|
|
@@ -934,6 +1114,47 @@ chmod 700 "$JOBS_ROOT"
|
|
|
934
1114
|
JOB_ID="$(date +%Y%m%d-%H%M%S)-$$-$RANDOM"
|
|
935
1115
|
JOB_DIR="$JOBS_ROOT/$JOB_ID"
|
|
936
1116
|
mkdir -m 700 "$JOB_DIR"
|
|
1117
|
+
# Snapshot authorizer separately from the worker identity. Never hand a worker
|
|
1118
|
+
# its parent's identity as its own caller context.
|
|
1119
|
+
python3 - "$OMNILANE_REPO" "$JOB_DIR" "$AA_POLICY_FILE" "$AA_REGISTRY_SHA256" "$AA_CALLER_CONTEXT" "$AA_CALLER_SHA256" "$AA_SELECTED_DECISION" "$VENDOR" <<'AA_PUBLISH'
|
|
1120
|
+
import json, sys
|
|
1121
|
+
from pathlib import Path
|
|
1122
|
+
sys.path.insert(0, str(Path(sys.argv[1]) / "scripts/lib"))
|
|
1123
|
+
import aa_policy
|
|
1124
|
+
_, repo, directory, policy, policy_sha, context, context_sha, raw, vendor = sys.argv
|
|
1125
|
+
registry, _ = aa_policy.load_registry(policy, policy_sha)
|
|
1126
|
+
caller = aa_policy.load_caller(context, registry, context_sha)[0] if context else None
|
|
1127
|
+
decision = json.loads(raw)
|
|
1128
|
+
if vendor == "vote":
|
|
1129
|
+
decision["target_config_id"] = None
|
|
1130
|
+
decision["child_context"] = None
|
|
1131
|
+
decision["target_request"] = {"vendor": "vote", "model": None, "effort": None}
|
|
1132
|
+
aa_policy.publish_context(directory, registry, caller, decision, policy)
|
|
1133
|
+
AA_PUBLISH
|
|
1134
|
+
AA_POLICY_FILE="$JOB_DIR/aa-registry.json"
|
|
1135
|
+
AA_REGISTRY_SHA256="$(file_sha256 "$AA_POLICY_FILE")"
|
|
1136
|
+
AA_CALLER_CONTEXT=""
|
|
1137
|
+
AA_CALLER_SHA256=""
|
|
1138
|
+
if [[ -f "$JOB_DIR/aa-authorizer.json" ]]; then
|
|
1139
|
+
AA_CALLER_CONTEXT="$JOB_DIR/aa-authorizer.json"
|
|
1140
|
+
AA_CALLER_SHA256="$(file_sha256 "$AA_CALLER_CONTEXT")"
|
|
1141
|
+
fi
|
|
1142
|
+
export OMNILANE_AA_POLICY_FILE="$AA_POLICY_FILE"
|
|
1143
|
+
export OMNILANE_AA_REGISTRY_SHA256="$AA_REGISTRY_SHA256"
|
|
1144
|
+
export OMNILANE_AA_AUTHORIZER_CONTEXT="$AA_CALLER_CONTEXT"
|
|
1145
|
+
export OMNILANE_AA_AUTHORIZER_SHA256="$AA_CALLER_SHA256"
|
|
1146
|
+
export OMNILANE_AA_AUTHORIZER_HUMAN="$AA_OPERATOR_ASSERTED_HUMAN"
|
|
1147
|
+
export OMNILANE_AA_CONTEXT_DIR="$JOB_DIR"
|
|
1148
|
+
export OMNILANE_AA_TARGET_CONFIG="$AA_TARGET_CONFIG"
|
|
1149
|
+
# Human exemptions belong only to this operator launch, never to a model child.
|
|
1150
|
+
unset OMNILANE_AA_OPERATOR_ASSERTED_HUMAN
|
|
1151
|
+
export OMNILANE_AA_CALLER_CONTEXT=""
|
|
1152
|
+
export OMNILANE_AA_CALLER_SHA256=""
|
|
1153
|
+
if [[ -f "$JOB_DIR/aa-child-context.json" ]]; then
|
|
1154
|
+
export OMNILANE_AA_CALLER_CONTEXT="$JOB_DIR/aa-child-context.json"
|
|
1155
|
+
OMNILANE_AA_CALLER_SHA256="$(file_sha256 "$JOB_DIR/aa-child-context.json")"
|
|
1156
|
+
export OMNILANE_AA_CALLER_SHA256
|
|
1157
|
+
fi
|
|
937
1158
|
JOB_WORKER="$JOB_DIR/job-worker.sh"
|
|
938
1159
|
JOB_WORKER_TMP="$JOB_DIR/.job-worker.tmp.$$-$RANDOM"
|
|
939
1160
|
(umask 077; cat "$JOB_WORKER_SOURCE" > "$JOB_WORKER_TMP")
|
|
@@ -990,6 +1211,7 @@ if [[ "$META_BASE" != *'}' ]]; then
|
|
|
990
1211
|
exit 1
|
|
991
1212
|
fi
|
|
992
1213
|
META_BASE="${META_BASE%\}}"
|
|
1214
|
+
META_BASE="$META_BASE,\"executor\":\"cli\",\"executor_reason\":\"$(json_escape "$EXECUTOR_REASON")\""
|
|
993
1215
|
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
1216
|
"$META_BASE" "$(json_escape "$JOB_WORKER_BASH")" \
|
|
995
1217
|
"$(json_escape "$JOB_WORKER_BASH_VERSION")" "$(json_escape "$JOB_WORKER_SOURCE")" \
|
package/scripts/jobs.sh
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
# omnilane background-job helper.
|
|
4
|
+
# Native: jobs.sh [--json] complete-native JOB_ID COMPLETION.json
|
|
5
|
+
# Native pending cancellation records state only; the caller owns its agent.
|
|
4
6
|
# Usage: jobs.sh [--json] list | status JOB_ID | result JOB_ID | stats [--last N]
|
|
5
7
|
# jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N]
|
|
6
8
|
# jobs.sh wait JOB_ID [--timeout N]
|
|
@@ -29,7 +31,7 @@ die() {
|
|
|
29
31
|
exit "$rc"
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
USAGE_TEXT="usage: jobs.sh [--json] list [--lane L] [--vendor V] [--status running|done]|status ID|result ID|tail ID [--lines N]|send ID TEXT|watch ID|close ID|retry ID [--background]|stats [--last N] [--lane L] [--vendor V]|recommend [--last N] [--lane L] [--min-samples N]|wait ID [--timeout N]|cancel ID|rm ID|threads [list] [--json]|threads show NAME [--json]|threads rm NAME|audit [--last N]|prune [--keep N] [--older-than DAYS] [--apply]|help"
|
|
34
|
+
USAGE_TEXT="usage: jobs.sh [--json] list [--lane L] [--vendor V] [--status running|done|pending|cancelled]|status ID|result ID|complete-native ID FILE|tail ID [--lines N]|send ID TEXT|watch ID|close ID|retry ID [--background]|stats [--last N] [--lane L] [--vendor V]|recommend [--last N] [--lane L] [--min-samples N]|wait ID [--timeout N]|cancel ID|rm ID|threads [list] [--json]|threads show NAME [--json]|threads rm NAME|audit [--last N]|prune [--keep N] [--older-than DAYS] [--apply]|help"
|
|
33
35
|
|
|
34
36
|
usage() {
|
|
35
37
|
die 2 "$USAGE_TEXT"
|
|
@@ -322,6 +324,23 @@ done
|
|
|
322
324
|
# set -u on Bash 3.2; a bare `jobs.sh` must reach usage, not crash.
|
|
323
325
|
set -- ${args[@]+"${args[@]}"}
|
|
324
326
|
COMMAND="${1:-unknown}"
|
|
327
|
+
|
|
328
|
+
# Native jobs have no shell worker PID. Intercept before any CLI lifecycle
|
|
329
|
+
# operation, including cancellation, retry, wait and rm. Python validates the
|
|
330
|
+
# store, job ID, lock and record before accepting or publishing a transition.
|
|
331
|
+
if [[ "$COMMAND" == "complete-native" ]] ||
|
|
332
|
+
{ [[ "${2:-}" =~ $JOB_ID_PATTERN ]] &&
|
|
333
|
+
{ [[ -e "$JOBS/${2}/native.json" || -L "$JOBS/${2}/native.json" ||
|
|
334
|
+
-e "$JOBS/${2}/native.lock" || -L "$JOBS/${2}/native.lock" ]]; }; }; then
|
|
335
|
+
case "$COMMAND" in
|
|
336
|
+
complete-native) [[ $# -eq 3 ]] || usage ;;
|
|
337
|
+
status|result|cancel) [[ $# -eq 2 ]] || usage ;;
|
|
338
|
+
*) die 2 "native job supports status, result, cancel and complete-native; caller owns agent lifecycle" ;;
|
|
339
|
+
esac
|
|
340
|
+
native_args=(job --home "$OMNILANE_HOME")
|
|
341
|
+
[[ "$JSON_MODE" -eq 0 ]] || native_args+=(--json)
|
|
342
|
+
exec python3 "$OMNILANE_REPO/scripts/lib/native.py" "${native_args[@]}" "$@"
|
|
343
|
+
fi
|
|
325
344
|
if [[ "$JSON_MODE" -eq 1 ]]; then
|
|
326
345
|
case "$COMMAND" in
|
|
327
346
|
list|status|result|stats|recommend|audit|threads) ;;
|
|
@@ -511,6 +530,15 @@ case "${1:-}" in
|
|
|
511
530
|
while [[ $# -gt 0 ]]; do
|
|
512
531
|
case "$1" in
|
|
513
532
|
--background) retry_background=1; shift ;;
|
|
533
|
+
--caller-context)
|
|
534
|
+
[[ "$#" -ge 2 ]] || die 2 "retry --caller-context requires a file"
|
|
535
|
+
export OMNILANE_AA_CALLER_CONTEXT="$2"
|
|
536
|
+
unset OMNILANE_AA_OPERATOR_ASSERTED_HUMAN
|
|
537
|
+
shift 2 ;;
|
|
538
|
+
--operator-asserted-human)
|
|
539
|
+
export OMNILANE_AA_OPERATOR_ASSERTED_HUMAN=1
|
|
540
|
+
unset OMNILANE_AA_CALLER_CONTEXT
|
|
541
|
+
shift ;;
|
|
514
542
|
*) usage ;;
|
|
515
543
|
esac
|
|
516
544
|
done
|
|
@@ -521,18 +549,14 @@ case "${1:-}" in
|
|
|
521
549
|
read_public_metadata "$JOB_DIR/meta.json" || {
|
|
522
550
|
die 1 "cannot retry: unreadable job metadata"
|
|
523
551
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
retry_lane="${
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
retry_timeout="${BASH_REMATCH[5]}"
|
|
533
|
-
retry_job_timeout="${BASH_REMATCH[6]}"
|
|
534
|
-
retry_mode="${BASH_REMATCH[7]}"
|
|
535
|
-
retry_workdir="${BASH_REMATCH[8]}"
|
|
552
|
+
retry_fields_text="$(python3 "$OMNILANE_REPO/scripts/lib/aa_retry.py" --metadata "$JOB_DIR")" || exit $?
|
|
553
|
+
retry_fields=()
|
|
554
|
+
while IFS= read -r retry_field; do retry_fields+=("$retry_field"); done <<< "$retry_fields_text"
|
|
555
|
+
[[ "${#retry_fields[@]}" -eq 8 ]] || die 1 "invalid retry metadata fields"
|
|
556
|
+
retry_lane="${retry_fields[0]}"; retry_vendor="${retry_fields[1]}"
|
|
557
|
+
retry_model="${retry_fields[2]}"; retry_effort="${retry_fields[3]}"
|
|
558
|
+
retry_timeout="${retry_fields[4]}"; retry_job_timeout="${retry_fields[5]}"
|
|
559
|
+
retry_mode="${retry_fields[6]}"; retry_workdir="${retry_fields[7]}"
|
|
536
560
|
task_path="$JOB_DIR/task.txt"
|
|
537
561
|
[[ -f "$task_path" && ! -L "$task_path" ]] || {
|
|
538
562
|
die 1 "cannot retry: original task text is missing"
|
|
@@ -541,6 +565,13 @@ case "${1:-}" in
|
|
|
541
565
|
die 1 "cannot retry: original workdir no longer exists: $retry_workdir"
|
|
542
566
|
}
|
|
543
567
|
retry_args=(--mode "$retry_mode" --workdir "$retry_workdir" --timeout "$retry_timeout")
|
|
568
|
+
policy_retry_args="$(python3 "$OMNILANE_REPO/scripts/lib/aa_retry.py" "$JOB_DIR")" || exit $?
|
|
569
|
+
while IFS= read -r policy_arg; do
|
|
570
|
+
retry_args+=("$policy_arg")
|
|
571
|
+
done <<< "$policy_retry_args"
|
|
572
|
+
# Retry reauthorizes under the original job, not the current shell identity.
|
|
573
|
+
unset OMNILANE_AA_CALLER_CONTEXT OMNILANE_AA_OPERATOR_ASSERTED_HUMAN OMNILANE_AA_TRANSPORT_OVERLAY OMNILANE_AA_OVERLAY_SHA256
|
|
574
|
+
|
|
544
575
|
[[ "$retry_job_timeout" == "null" ]] || retry_args+=(--job-timeout "$retry_job_timeout")
|
|
545
576
|
# dispatch --vendor only accepts real CLI vendors; an exec gate is re-run
|
|
546
577
|
# through normal lane resolution plus the recorded --model script path.
|
|
@@ -892,8 +923,8 @@ case "${1:-}" in
|
|
|
892
923
|
[[ -z "$filter_lane" || "$filter_lane" =~ ^[a-z][a-z0-9-]*$ ]] || die 2 "invalid --lane value"
|
|
893
924
|
[[ -z "$filter_vendor" ]] || omnilane_known_vendor "$filter_vendor" || die 2 "invalid --vendor value"
|
|
894
925
|
case "$filter_status" in
|
|
895
|
-
""|running|done) ;;
|
|
896
|
-
*) die 2 "invalid --status value (want running or
|
|
926
|
+
""|running|done|pending|cancelled) ;;
|
|
927
|
+
*) die 2 "invalid --status value (want running, done, pending or cancelled)" ;;
|
|
897
928
|
esac
|
|
898
929
|
if [[ ! -d "$JOBS" ]]; then
|
|
899
930
|
[[ "$JSON_MODE" -eq 0 ]] || printf '{"schema_version":1,"command":"list","ok":true,"jobs":[]}\n'
|
|
@@ -929,6 +960,16 @@ case "${1:-}" in
|
|
|
929
960
|
else
|
|
930
961
|
json_state="running"
|
|
931
962
|
fi
|
|
963
|
+
if [[ -e "$JOBS/$id/native.json" || -L "$JOBS/$id/native.json" || -e "$JOBS/$id/native.lock" ]]; then
|
|
964
|
+
json_state="$(python3 "$OMNILANE_REPO/scripts/lib/native.py" job --home "$OMNILANE_HOME" list-state "$id")" || die 1 "invalid native state"
|
|
965
|
+
state="$json_state"
|
|
966
|
+
# Native completion lives in native.json, not a PID/exit pair.
|
|
967
|
+
exit_json="null"
|
|
968
|
+
if [[ "$json_state" != "pending" ]]; then
|
|
969
|
+
native_status="$(python3 "$OMNILANE_REPO/scripts/lib/native.py" job --home "$OMNILANE_HOME" --json status "$id")" || die 1 "invalid native state"
|
|
970
|
+
exit_json="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["job"]["exit_code"])' "$native_status")"
|
|
971
|
+
fi
|
|
972
|
+
fi
|
|
932
973
|
metadata=""
|
|
933
974
|
metadata_json="null"
|
|
934
975
|
metadata_status="missing"
|
|
@@ -953,6 +994,7 @@ case "${1:-}" in
|
|
|
953
994
|
case "$json_state" in
|
|
954
995
|
done) [[ "$filter_status" == "done" ]] || continue ;;
|
|
955
996
|
running) [[ "$filter_status" == "running" ]] || continue ;;
|
|
997
|
+
pending|cancelled) [[ "$filter_status" == "$json_state" ]] || continue ;;
|
|
956
998
|
*) continue ;;
|
|
957
999
|
esac
|
|
958
1000
|
fi
|
|
@@ -1229,6 +1271,7 @@ case "${1:-}" in
|
|
|
1229
1271
|
[[ -d "$job_dir" && ! -L "$job_dir" ]] || continue
|
|
1230
1272
|
id="${job_dir##*/}"
|
|
1231
1273
|
[[ "$id" =~ $JOB_ID_PATTERN ]] || continue
|
|
1274
|
+
[[ ! -e "$job_dir/native.lock" && ! -e "$job_dir/native.json" ]] || continue
|
|
1232
1275
|
read_exit_code "$job_dir/exit" || continue
|
|
1233
1276
|
completed+=("$id")
|
|
1234
1277
|
done
|