omnilane 0.13.0 → 0.14.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/CHANGELOG.md +34 -1
- package/README.ja.md +8 -0
- package/README.ko.md +8 -0
- package/README.md +38 -2
- package/README.zh-CN.md +8 -0
- package/README.zh-TW.md +29 -2
- package/VERSION +1 -1
- package/benchmarks/workloads.tsv +4 -0
- package/bin/omnilane +10 -2
- package/bin/omnilane-mcp +126 -0
- package/completions/_omnilane +21 -4
- package/completions/omnilane.bash +19 -4
- package/completions/omnilane.fish +15 -3
- package/package.json +2 -1
- package/scripts/benchmark.py +446 -0
- package/scripts/check.sh +1 -1
- package/scripts/doctor.sh +53 -10
- package/scripts/jobs.sh +162 -2
- package/scripts/provider-probe.sh +180 -0
- package/ui/app.js +56 -0
- package/ui/index.html +1 -0
- package/ui/styles.css +22 -0
package/scripts/jobs.sh
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
# omnilane background-job helper.
|
|
4
4
|
# Usage: jobs.sh [--json] list | status JOB_ID | result JOB_ID | stats [--last N]
|
|
5
|
+
# jobs.sh [--json] recommend [--last N] [--lane L] [--min-samples N]
|
|
5
6
|
# jobs.sh wait JOB_ID [--timeout N]
|
|
6
7
|
# jobs.sh audit [--last N] [--json] | prune [--keep N] [--apply]
|
|
7
8
|
|
|
@@ -48,7 +49,7 @@ die() {
|
|
|
48
49
|
exit "$rc"
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
USAGE_TEXT="usage: jobs.sh [--json] list [--lane L] [--vendor V] [--status running|done]|status ID|result ID|tail ID [--lines N]|retry ID [--background]|stats [--last N] [--lane L] [--vendor V]|wait ID [--timeout N]|cancel ID|rm ID|audit [--last N]|prune [--keep N] [--older-than DAYS] [--apply]|help"
|
|
52
|
+
USAGE_TEXT="usage: jobs.sh [--json] list [--lane L] [--vendor V] [--status running|done]|status ID|result ID|tail ID [--lines N]|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|audit [--last N]|prune [--keep N] [--older-than DAYS] [--apply]|help"
|
|
52
53
|
|
|
53
54
|
usage() {
|
|
54
55
|
die 2 "$USAGE_TEXT"
|
|
@@ -168,6 +169,83 @@ json_stat_counts() {
|
|
|
168
169
|
printf ']'
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
build_recommendation_rows() {
|
|
173
|
+
local minimum="$1"
|
|
174
|
+
shift
|
|
175
|
+
[[ $# -gt 0 ]] || return 0
|
|
176
|
+
printf '%s\n' "$@" | awk -F '\t' -v minimum="$minimum" '
|
|
177
|
+
{
|
|
178
|
+
key = $1 SUBSEP $2
|
|
179
|
+
samples[key]++
|
|
180
|
+
if ($3 == 0) succeeded[key]++
|
|
181
|
+
else failed[key]++
|
|
182
|
+
}
|
|
183
|
+
END {
|
|
184
|
+
for (key in samples) {
|
|
185
|
+
split(key, parts, SUBSEP)
|
|
186
|
+
rate = int((succeeded[key] * 100) / samples[key])
|
|
187
|
+
eligible = samples[key] >= minimum ? 1 : 0
|
|
188
|
+
printf "%s\t%d\t%d\t%d\t%s\t%d\t%d\n", \
|
|
189
|
+
parts[1], eligible, rate, samples[key], parts[2], \
|
|
190
|
+
succeeded[key] + 0, failed[key] + 0
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
' | LC_ALL=C sort -t $'\t' -k1,1 -k2,2nr -k3,3nr -k4,4nr -k5,5
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
recommendations_json() {
|
|
197
|
+
local rows="$1"
|
|
198
|
+
[[ -n "$rows" ]] || { printf '[]'; return 0; }
|
|
199
|
+
printf '%s\n' "$rows" | awk -F '\t' '
|
|
200
|
+
function close_lane( status, recommendation) {
|
|
201
|
+
if (current == "") return
|
|
202
|
+
status = recommended == "" ? "insufficient_data" : "ready"
|
|
203
|
+
recommendation = recommended == "" ? "null" : "\"" recommended "\""
|
|
204
|
+
printf "],\"status\":\"%s\",\"recommended_vendor\":%s}", status, recommendation
|
|
205
|
+
}
|
|
206
|
+
BEGIN { printf "["; current = ""; first_lane = 1 }
|
|
207
|
+
{
|
|
208
|
+
if ($1 != current) {
|
|
209
|
+
close_lane()
|
|
210
|
+
if (!first_lane) printf ","
|
|
211
|
+
first_lane = 0
|
|
212
|
+
current = $1
|
|
213
|
+
first_candidate = 1
|
|
214
|
+
recommended = ""
|
|
215
|
+
printf "{\"lane\":\"%s\",\"candidates\":[", current
|
|
216
|
+
}
|
|
217
|
+
if (!first_candidate) printf ","
|
|
218
|
+
first_candidate = 0
|
|
219
|
+
eligible = $2 == 1 ? "true" : "false"
|
|
220
|
+
printf "{\"vendor\":\"%s\",\"samples\":%d,\"succeeded\":%d,\"failed\":%d,\"success_rate\":%d,\"eligible\":%s}", \
|
|
221
|
+
$5, $4, $6, $7, $3, eligible
|
|
222
|
+
if (recommended == "" && $2 == 1) recommended = $5
|
|
223
|
+
}
|
|
224
|
+
END { close_lane(); printf "]" }
|
|
225
|
+
'
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
print_recommendations() {
|
|
229
|
+
local rows="$1" minimum="$2"
|
|
230
|
+
[[ -n "$rows" ]] || return 0
|
|
231
|
+
printf '%s\n' "$rows" | awk -F '\t' -v minimum="$minimum" '
|
|
232
|
+
$1 != current {
|
|
233
|
+
current = $1
|
|
234
|
+
if ($2 == 1) {
|
|
235
|
+
printf "recommend lane=%s vendor=%s success_rate=%d%% samples=%d succeeded=%d failed=%d\n", \
|
|
236
|
+
$1, $5, $3, $4, $6, $7
|
|
237
|
+
} else {
|
|
238
|
+
printf "recommend lane=%s status=insufficient_data min_samples=%d\n", $1, minimum
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
{
|
|
242
|
+
eligible = $2 == 1 ? "yes" : "no"
|
|
243
|
+
printf "candidate lane=%s vendor=%s success_rate=%d%% samples=%d succeeded=%d failed=%d eligible=%s\n", \
|
|
244
|
+
$1, $5, $3, $4, $6, $7, eligible
|
|
245
|
+
}
|
|
246
|
+
'
|
|
247
|
+
}
|
|
248
|
+
|
|
171
249
|
path_mode() {
|
|
172
250
|
if stat -f '%Lp' "$1" >/dev/null 2>&1; then
|
|
173
251
|
stat -f '%Lp' "$1"
|
|
@@ -191,7 +269,7 @@ set -- ${args[@]+"${args[@]}"}
|
|
|
191
269
|
COMMAND="${1:-unknown}"
|
|
192
270
|
if [[ "$JSON_MODE" -eq 1 ]]; then
|
|
193
271
|
case "$COMMAND" in
|
|
194
|
-
list|status|result|stats|audit) ;;
|
|
272
|
+
list|status|result|stats|recommend|audit) ;;
|
|
195
273
|
*) usage ;;
|
|
196
274
|
esac
|
|
197
275
|
fi
|
|
@@ -418,6 +496,88 @@ case "${1:-}" in
|
|
|
418
496
|
"$sampled" "$passed" "$failed" "$findings"
|
|
419
497
|
fi
|
|
420
498
|
[[ "$findings" -eq 0 ]] || exit 1 ;;
|
|
499
|
+
recommend)
|
|
500
|
+
limit=100
|
|
501
|
+
minimum_samples=3
|
|
502
|
+
filter_lane=""
|
|
503
|
+
shift
|
|
504
|
+
while [[ $# -gt 0 ]]; do
|
|
505
|
+
case "$1" in
|
|
506
|
+
--last)
|
|
507
|
+
[[ $# -ge 2 ]] || usage
|
|
508
|
+
limit="$2"; shift 2 ;;
|
|
509
|
+
--lane)
|
|
510
|
+
[[ $# -ge 2 ]] || usage
|
|
511
|
+
filter_lane="$2"; shift 2 ;;
|
|
512
|
+
--min-samples)
|
|
513
|
+
[[ $# -ge 2 ]] || usage
|
|
514
|
+
minimum_samples="$2"; shift 2 ;;
|
|
515
|
+
*) usage ;;
|
|
516
|
+
esac
|
|
517
|
+
done
|
|
518
|
+
[[ "$limit" =~ ^[1-9][0-9]{0,3}$ && "$limit" -le 10000 ]] || {
|
|
519
|
+
die 2 "invalid --last value (want 1..10000)"
|
|
520
|
+
}
|
|
521
|
+
[[ "$minimum_samples" =~ ^[1-9][0-9]{0,2}$ && "$minimum_samples" -le 1000 ]] || {
|
|
522
|
+
die 2 "invalid --min-samples value (want 1..1000)"
|
|
523
|
+
}
|
|
524
|
+
[[ -z "$filter_lane" || "$filter_lane" =~ ^[a-z][a-z0-9-]*$ ]] || die 2 "invalid --lane value"
|
|
525
|
+
sampled=0
|
|
526
|
+
completed=0
|
|
527
|
+
running=0
|
|
528
|
+
invalid_exit=0
|
|
529
|
+
invalid_metadata=0
|
|
530
|
+
ids=()
|
|
531
|
+
outcomes=()
|
|
532
|
+
if [[ -d "$JOBS" ]]; then
|
|
533
|
+
for job_dir in "$JOBS"/*; do
|
|
534
|
+
[[ -d "$job_dir" && ! -L "$job_dir" ]] || continue
|
|
535
|
+
id="${job_dir##*/}"
|
|
536
|
+
[[ "$id" =~ $JOB_ID_PATTERN ]] || continue
|
|
537
|
+
ids+=("$id")
|
|
538
|
+
done
|
|
539
|
+
fi
|
|
540
|
+
if [[ ${#ids[@]} -gt 0 ]]; then
|
|
541
|
+
while IFS= read -r id; do
|
|
542
|
+
job_dir="$JOBS/$id"
|
|
543
|
+
metadata_path="$job_dir/meta.json"
|
|
544
|
+
meta_ok=0
|
|
545
|
+
if read_public_metadata "$metadata_path" &&
|
|
546
|
+
parse_stats_metadata "$PUBLIC_METADATA"; then
|
|
547
|
+
meta_ok=1
|
|
548
|
+
fi
|
|
549
|
+
if [[ -n "$filter_lane" ]]; then
|
|
550
|
+
[[ "$meta_ok" -eq 1 && "$filter_lane" == "$STATS_LANE" ]] || continue
|
|
551
|
+
fi
|
|
552
|
+
sampled=$((sampled + 1))
|
|
553
|
+
if [[ "$meta_ok" -ne 1 ]]; then
|
|
554
|
+
invalid_metadata=$((invalid_metadata + 1))
|
|
555
|
+
continue
|
|
556
|
+
fi
|
|
557
|
+
exit_path="$job_dir/exit"
|
|
558
|
+
if [[ ! -e "$exit_path" && ! -L "$exit_path" ]]; then
|
|
559
|
+
running=$((running + 1))
|
|
560
|
+
continue
|
|
561
|
+
fi
|
|
562
|
+
if ! read_exit_code "$exit_path"; then
|
|
563
|
+
invalid_exit=$((invalid_exit + 1))
|
|
564
|
+
continue
|
|
565
|
+
fi
|
|
566
|
+
completed=$((completed + 1))
|
|
567
|
+
outcomes+=("$STATS_LANE"$'\t'"$STATS_VENDOR"$'\t'"$RECORDED_EXIT")
|
|
568
|
+
done < <(printf '%s\n' "${ids[@]}" | LC_ALL=C sort -r | sed -n "1,${limit}p")
|
|
569
|
+
fi
|
|
570
|
+
rows="$(build_recommendation_rows "$minimum_samples" ${outcomes[@]+"${outcomes[@]}"})"
|
|
571
|
+
if [[ "$JSON_MODE" -eq 1 ]]; then
|
|
572
|
+
recommendations="$(recommendations_json "$rows")"
|
|
573
|
+
printf '{"schema_version":1,"command":"recommend","ok":true,"sampled":%s,"completed":%s,"excluded":{"running":%s,"invalid_exit":%s,"invalid_metadata":%s},"minimum_samples":%s,"recommendations":%s}\n' \
|
|
574
|
+
"$sampled" "$completed" "$running" "$invalid_exit" "$invalid_metadata" \
|
|
575
|
+
"$minimum_samples" "$recommendations"
|
|
576
|
+
else
|
|
577
|
+
printf 'recommendations: sampled=%s completed=%s running=%s invalid_exit=%s invalid_metadata=%s min_samples=%s\n' \
|
|
578
|
+
"$sampled" "$completed" "$running" "$invalid_exit" "$invalid_metadata" "$minimum_samples"
|
|
579
|
+
print_recommendations "$rows" "$minimum_samples"
|
|
580
|
+
fi ;;
|
|
421
581
|
stats)
|
|
422
582
|
limit=100
|
|
423
583
|
filter_lane=""; filter_vendor=""
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Explicit, bounded live-inference probe for exactly one configured provider.
|
|
5
|
+
# No provider is contacted unless --vendor is present. Provider output and
|
|
6
|
+
# stderr stay inside a private temporary directory and are never relayed.
|
|
7
|
+
|
|
8
|
+
SELF_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
REPO="${OMNILANE_PROBE_REPO:-$SELF_REPO}"
|
|
10
|
+
DISPATCH="$REPO/scripts/dispatch.sh"
|
|
11
|
+
JSON_MODE=0
|
|
12
|
+
VENDOR=""
|
|
13
|
+
TIMEOUT=30
|
|
14
|
+
|
|
15
|
+
usage() {
|
|
16
|
+
echo "usage: provider-probe.sh --vendor V [--timeout SEC] [--json]" >&2
|
|
17
|
+
exit 2
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
json_escape() {
|
|
21
|
+
local s="$1" out="" ch escaped code i
|
|
22
|
+
for ((i = 0; i < ${#s}; i++)); do
|
|
23
|
+
ch="${s:i:1}"
|
|
24
|
+
case "$ch" in
|
|
25
|
+
'"') out="$out\\\"" ;;
|
|
26
|
+
'\\') out="$out\\\\" ;;
|
|
27
|
+
$'\b') out="$out\\b" ;;
|
|
28
|
+
$'\f') out="$out\\f" ;;
|
|
29
|
+
$'\n') out="$out\\n" ;;
|
|
30
|
+
$'\r') out="$out\\r" ;;
|
|
31
|
+
$'\t') out="$out\\t" ;;
|
|
32
|
+
*)
|
|
33
|
+
LC_CTYPE=C printf -v code '%d' "'$ch"
|
|
34
|
+
if [[ "$code" -ge 0 && "$code" -lt 32 ]]; then
|
|
35
|
+
printf -v escaped '\\u%04x' "$code"
|
|
36
|
+
out="$out$escaped"
|
|
37
|
+
else
|
|
38
|
+
out="$out$ch"
|
|
39
|
+
fi
|
|
40
|
+
;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
printf '%s' "$out"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
emit_result() {
|
|
47
|
+
local ok="$1" status="$2" invoked="$3" reason="$4"
|
|
48
|
+
local duration="$5" response_bytes="$6"
|
|
49
|
+
if [[ "$JSON_MODE" -eq 1 ]]; then
|
|
50
|
+
printf '{"schema_version":1,"command":"provider-probe","ok":%s,"vendor":"%s","model":"%s","status":"%s","provider_invoked":%s,"timeout":%s,"duration_seconds":%s,"response_bytes":%s,"reason":"%s"}\n' \
|
|
51
|
+
"$ok" "$(json_escape "$VENDOR")" "$(json_escape "${MODEL:-}")" \
|
|
52
|
+
"$(json_escape "$status")" "$invoked" "$TIMEOUT" "$duration" \
|
|
53
|
+
"$response_bytes" "$(json_escape "$reason")"
|
|
54
|
+
else
|
|
55
|
+
printf 'provider_probe vendor=%s model=%s status=%s provider_invoked=%s timeout=%ss duration=%ss response_bytes=%s reason=%s\n' \
|
|
56
|
+
"$VENDOR" "${MODEL:-unknown}" "$status" "$invoked" "$TIMEOUT" \
|
|
57
|
+
"$duration" "$response_bytes" "$reason"
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
while [[ $# -gt 0 ]]; do
|
|
62
|
+
case "$1" in
|
|
63
|
+
--vendor)
|
|
64
|
+
[[ $# -ge 2 ]] || usage
|
|
65
|
+
VENDOR="$2"; shift 2 ;;
|
|
66
|
+
--timeout)
|
|
67
|
+
[[ $# -ge 2 ]] || usage
|
|
68
|
+
TIMEOUT="$2"; shift 2 ;;
|
|
69
|
+
--json)
|
|
70
|
+
JSON_MODE=1; shift ;;
|
|
71
|
+
*) usage ;;
|
|
72
|
+
esac
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
[[ "$VENDOR" =~ ^(codex|claude|grok|gemini|kimi|qwen|opencode|openrouter|deepseek|zai|mistral|groq|cerebras)$ ]] || {
|
|
76
|
+
echo "omnilane: invalid vendor for provider probe" >&2
|
|
77
|
+
exit 2
|
|
78
|
+
}
|
|
79
|
+
[[ "$TIMEOUT" =~ ^[1-9][0-9]{0,2}$ && "$TIMEOUT" -le 300 ]] || {
|
|
80
|
+
echo "omnilane: invalid probe timeout (want 1..300 seconds)" >&2
|
|
81
|
+
exit 2
|
|
82
|
+
}
|
|
83
|
+
[[ -x "$DISPATCH" ]] || {
|
|
84
|
+
emit_result false unavailable false "dispatch is unavailable" 0 0
|
|
85
|
+
exit 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
PLAN=""
|
|
89
|
+
while IFS= read -r lane; do
|
|
90
|
+
[[ "$lane" =~ ^[a-z][a-z0-9-]*$ ]] || continue
|
|
91
|
+
set +e
|
|
92
|
+
candidate="$(bash "$DISPATCH" --dry-run --mode advise --vendor "$VENDOR" \
|
|
93
|
+
--timeout "$TIMEOUT" "$lane" "OMNILANE_PROVIDER_PROBE" 2>/dev/null)"
|
|
94
|
+
candidate_rc=$?
|
|
95
|
+
set -e
|
|
96
|
+
if [[ "$candidate_rc" -eq 0 && "$candidate" == *"provider_invoked=no"* ]]; then
|
|
97
|
+
PLAN="$candidate"
|
|
98
|
+
break
|
|
99
|
+
fi
|
|
100
|
+
done < <(bash "$DISPATCH" --list 2>/dev/null | sed -n 's/^\([a-z][a-z0-9-]*\):.*/\1/p')
|
|
101
|
+
|
|
102
|
+
if [[ -z "$PLAN" ]]; then
|
|
103
|
+
emit_result false unavailable false "vendor is not both configured and available" 0 0
|
|
104
|
+
exit 4
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
field_value() {
|
|
108
|
+
local key="$1"
|
|
109
|
+
printf '%s\n' "$PLAN" | sed -n "s/^${key}=//p" | sed -n '1p'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
decode_plan_value() {
|
|
113
|
+
local encoded="$1" decoded
|
|
114
|
+
[[ ${#encoded} -le 4096 && "$encoded" != *$'\n'* ]] || return 1
|
|
115
|
+
# dispatch.sh emits values with Bash printf %q. read without -r reverses
|
|
116
|
+
# its backslash quoting without evaluating command substitutions or code.
|
|
117
|
+
IFS= read decoded <<< "$encoded" || return 1
|
|
118
|
+
printf '%s' "$decoded"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
RAW_MODEL="$(field_value model)"
|
|
122
|
+
RAW_EFFORT="$(field_value effort)"
|
|
123
|
+
if ! MODEL="$(decode_plan_value "$RAW_MODEL")" ||
|
|
124
|
+
! EFFORT="$(decode_plan_value "$RAW_EFFORT")"; then
|
|
125
|
+
emit_result false unavailable false "invalid dry-run field encoding" 0 0
|
|
126
|
+
exit 4
|
|
127
|
+
fi
|
|
128
|
+
RUNNER="$REPO/scripts/runners/run-$VENDOR.sh"
|
|
129
|
+
[[ -n "$MODEL" && -x "$RUNNER" ]] || {
|
|
130
|
+
emit_result false unavailable false "resolved runner or model is unavailable" 0 0
|
|
131
|
+
exit 4
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
TMP_BASE="${TMPDIR:-/tmp}"
|
|
135
|
+
TMP_ROOT="$(mktemp -d "$TMP_BASE/omnilane-provider-probe.XXXXXX")"
|
|
136
|
+
cleanup() {
|
|
137
|
+
local path
|
|
138
|
+
case "${TMP_ROOT:-}" in
|
|
139
|
+
"$TMP_BASE"/omnilane-provider-probe.*)
|
|
140
|
+
if [[ -d "$TMP_ROOT" && ! -L "$TMP_ROOT" ]]; then
|
|
141
|
+
for path in "$TMP_ROOT"/* "$TMP_ROOT"/.[!.]*; do
|
|
142
|
+
[[ -f "$path" || -L "$path" ]] && rm -- "$path"
|
|
143
|
+
done
|
|
144
|
+
rmdir "$TMP_ROOT" 2>/dev/null || true
|
|
145
|
+
fi
|
|
146
|
+
;;
|
|
147
|
+
esac
|
|
148
|
+
}
|
|
149
|
+
trap cleanup EXIT HUP INT TERM
|
|
150
|
+
PROMPT_FILE="$TMP_ROOT/prompt.txt"
|
|
151
|
+
OUTPUT_FILE="$TMP_ROOT/output.txt"
|
|
152
|
+
(umask 077; printf '%s\n' \
|
|
153
|
+
'Reply with exactly OMNILANE_PROVIDER_PROBE_OK. Do not use tools or inspect files.' \
|
|
154
|
+
> "$PROMPT_FILE")
|
|
155
|
+
|
|
156
|
+
started="$(date +%s)"
|
|
157
|
+
set +e
|
|
158
|
+
OMNILANE_TIMEOUT="$TIMEOUT" "$RUNNER" advise \
|
|
159
|
+
"${OMNILANE_PROBE_WORKDIR:-$PWD}" "$MODEL" "$EFFORT" \
|
|
160
|
+
"$PROMPT_FILE" "$OUTPUT_FILE" >/dev/null 2>&1
|
|
161
|
+
runner_rc=$?
|
|
162
|
+
set -e
|
|
163
|
+
ended="$(date +%s)"
|
|
164
|
+
duration=$((ended - started))
|
|
165
|
+
response_bytes=0
|
|
166
|
+
if [[ -f "$OUTPUT_FILE" && ! -L "$OUTPUT_FILE" ]]; then
|
|
167
|
+
response_bytes="$(wc -c < "$OUTPUT_FILE" | tr -d '[:space:]')"
|
|
168
|
+
[[ "$response_bytes" =~ ^[0-9]+$ ]] || response_bytes=0
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [[ "$runner_rc" -eq 0 && "$response_bytes" -gt 0 ]]; then
|
|
172
|
+
emit_result true usable true "bounded live inference returned output" "$duration" "$response_bytes"
|
|
173
|
+
exit 0
|
|
174
|
+
fi
|
|
175
|
+
if [[ "$runner_rc" -eq 124 || "$runner_rc" -eq 142 ]]; then
|
|
176
|
+
emit_result false timeout true "bounded live inference timed out" "$duration" "$response_bytes"
|
|
177
|
+
else
|
|
178
|
+
emit_result false failed true "runner exited with status $runner_rc" "$duration" "$response_bytes"
|
|
179
|
+
fi
|
|
180
|
+
exit 1
|
package/ui/app.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
connectionLabel: document.getElementById("connection-label"),
|
|
22
22
|
languageSelect: document.getElementById("language-select"),
|
|
23
23
|
jobCount: document.getElementById("job-count"), search: document.getElementById("job-search"),
|
|
24
|
+
exportVisible: document.getElementById("export-visible"),
|
|
24
25
|
filter: document.getElementById("status-filter"),
|
|
25
26
|
filterButtons: Array.from(document.querySelectorAll(".filter-button")),
|
|
26
27
|
jobList: document.getElementById("job-list"), listMessage: document.getElementById("list-message"),
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
|
|
297
298
|
function setControlsDisabled(disabled) {
|
|
298
299
|
elements.search.disabled = disabled;
|
|
300
|
+
elements.exportVisible.disabled = disabled;
|
|
299
301
|
elements.filterButtons.forEach(function (button) {
|
|
300
302
|
button.disabled = disabled;
|
|
301
303
|
});
|
|
@@ -471,6 +473,52 @@
|
|
|
471
473
|
});
|
|
472
474
|
}
|
|
473
475
|
|
|
476
|
+
function exportField(meta, key) {
|
|
477
|
+
return typeof meta[key] === "string" && meta[key].length > 0 ? meta[key] : null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function exportVisibleJobs() {
|
|
481
|
+
const jobs = visibleJobs();
|
|
482
|
+
if (jobs.length === 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const exportedAt = new Date();
|
|
486
|
+
const payload = {
|
|
487
|
+
schema_version: 1,
|
|
488
|
+
exported_at: exportedAt.toISOString(),
|
|
489
|
+
query: state.query,
|
|
490
|
+
filter: state.filter,
|
|
491
|
+
jobs: jobs.map(function (job) {
|
|
492
|
+
const meta = job.meta;
|
|
493
|
+
return {
|
|
494
|
+
id: job.id,
|
|
495
|
+
state: job.state,
|
|
496
|
+
exit_code: job.exitCode,
|
|
497
|
+
lane: exportField(meta, "lane"),
|
|
498
|
+
vendor: exportField(meta, "vendor"),
|
|
499
|
+
model: exportField(meta, "model"),
|
|
500
|
+
effort: exportField(meta, "effort"),
|
|
501
|
+
mode: exportField(meta, "mode"),
|
|
502
|
+
candidate: exportField(meta, "candidate"),
|
|
503
|
+
started: exportField(meta, "started"),
|
|
504
|
+
timeout: Number.isInteger(meta.timeout) ? meta.timeout : null,
|
|
505
|
+
};
|
|
506
|
+
}),
|
|
507
|
+
};
|
|
508
|
+
const blob = new Blob([JSON.stringify(payload, null, 2) + "\n"], {
|
|
509
|
+
type: "application/json;charset=utf-8",
|
|
510
|
+
});
|
|
511
|
+
const url = URL.createObjectURL(blob);
|
|
512
|
+
const stamp = exportedAt.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
513
|
+
const anchor = document.createElement("a");
|
|
514
|
+
anchor.href = url;
|
|
515
|
+
anchor.download = "omnilane-jobs-" + stamp + ".json";
|
|
516
|
+
document.body.appendChild(anchor);
|
|
517
|
+
anchor.click();
|
|
518
|
+
anchor.remove();
|
|
519
|
+
window.setTimeout(function () { URL.revokeObjectURL(url); }, 0);
|
|
520
|
+
}
|
|
521
|
+
|
|
474
522
|
function routeLabel(job) {
|
|
475
523
|
const meta = job.meta;
|
|
476
524
|
return [
|
|
@@ -525,6 +573,7 @@
|
|
|
525
573
|
|
|
526
574
|
function renderQueue() {
|
|
527
575
|
const jobs = visibleJobs();
|
|
576
|
+
elements.exportVisible.disabled = state.unauthorized || jobs.length === 0;
|
|
528
577
|
const listScroll = elements.jobList.scrollTop;
|
|
529
578
|
const inspectorScroll = elements.inspector.scrollTop;
|
|
530
579
|
const focusedCard = document.activeElement && document.activeElement.closest
|
|
@@ -1270,6 +1319,8 @@
|
|
|
1270
1319
|
updateFilterSelection();
|
|
1271
1320
|
});
|
|
1272
1321
|
|
|
1322
|
+
elements.exportVisible.addEventListener("click", exportVisibleJobs);
|
|
1323
|
+
|
|
1273
1324
|
elements.mobileBack.addEventListener("click", function () {
|
|
1274
1325
|
returnToMobileList(true);
|
|
1275
1326
|
});
|
|
@@ -1327,6 +1378,7 @@
|
|
|
1327
1378
|
"filter.active": "Active",
|
|
1328
1379
|
"filter.done": "Done",
|
|
1329
1380
|
"filter.issues": "Issues",
|
|
1381
|
+
"export.visible": "Export visible",
|
|
1330
1382
|
"list.ariaLabel": "Recent tasks",
|
|
1331
1383
|
"list.waiting": "Waiting for tasks.",
|
|
1332
1384
|
"list.empty": "No tasks yet.",
|
|
@@ -1438,6 +1490,7 @@
|
|
|
1438
1490
|
"filter.active": "実行中",
|
|
1439
1491
|
"filter.done": "完了",
|
|
1440
1492
|
"filter.issues": "問題あり",
|
|
1493
|
+
"export.visible": "表示中をエクスポート",
|
|
1441
1494
|
"list.ariaLabel": "最近のタスク",
|
|
1442
1495
|
"list.waiting": "タスクを待機しています。",
|
|
1443
1496
|
"list.empty": "タスクはまだありません。",
|
|
@@ -1549,6 +1602,7 @@
|
|
|
1549
1602
|
"filter.active": "진행 중",
|
|
1550
1603
|
"filter.done": "완료",
|
|
1551
1604
|
"filter.issues": "문제",
|
|
1605
|
+
"export.visible": "표시 항목 내보내기",
|
|
1552
1606
|
"list.ariaLabel": "최근 작업",
|
|
1553
1607
|
"list.waiting": "작업을 기다리는 중입니다.",
|
|
1554
1608
|
"list.empty": "아직 작업이 없습니다.",
|
|
@@ -1660,6 +1714,7 @@
|
|
|
1660
1714
|
"filter.active": "進行中",
|
|
1661
1715
|
"filter.done": "完成",
|
|
1662
1716
|
"filter.issues": "有問題",
|
|
1717
|
+
"export.visible": "匯出目前結果",
|
|
1663
1718
|
"list.ariaLabel": "最近的任務",
|
|
1664
1719
|
"list.waiting": "等待任務中。",
|
|
1665
1720
|
"list.empty": "尚無任務。",
|
|
@@ -1771,6 +1826,7 @@
|
|
|
1771
1826
|
"filter.active": "进行中",
|
|
1772
1827
|
"filter.done": "完成",
|
|
1773
1828
|
"filter.issues": "有问题",
|
|
1829
|
+
"export.visible": "导出当前结果",
|
|
1774
1830
|
"list.ariaLabel": "最近的任务",
|
|
1775
1831
|
"list.waiting": "等待任务中。",
|
|
1776
1832
|
"list.empty": "暂无任务。",
|
package/ui/index.html
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
<p class="section-label" data-i18n="index.sectionLabel">Recent work</p>
|
|
40
40
|
<h1 id="dispatch-index-title" data-i18n="index.title">Task record</h1>
|
|
41
41
|
</div>
|
|
42
|
+
<button type="button" class="export-button" id="export-visible" data-i18n="export.visible">Export visible</button>
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
45
|
<div class="queue-controls">
|
package/ui/styles.css
CHANGED
|
@@ -210,6 +210,28 @@ summary:focus-visible,
|
|
|
210
210
|
border-bottom: 1px solid var(--line);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
.export-button {
|
|
214
|
+
min-height: 36px;
|
|
215
|
+
margin-left: auto;
|
|
216
|
+
border: 1px solid var(--line-strong);
|
|
217
|
+
border-radius: 4px;
|
|
218
|
+
padding: 0 10px;
|
|
219
|
+
background: var(--paper);
|
|
220
|
+
color: var(--muted);
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
font-weight: 650;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.export-button:hover:not(:disabled) {
|
|
226
|
+
color: var(--ink);
|
|
227
|
+
background: #edf0f2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.export-button:disabled {
|
|
231
|
+
cursor: not-allowed;
|
|
232
|
+
opacity: 0.5;
|
|
233
|
+
}
|
|
234
|
+
|
|
213
235
|
.section-label,
|
|
214
236
|
.state-code {
|
|
215
237
|
margin: 0 0 3px;
|