@zachjxyz/moxie 0.2.5 → 0.3.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/README.md +280 -0
- package/bin/moxie +4 -2
- package/lib/agents.sh +378 -46
- package/lib/gateway-agent.mjs +549 -0
- package/lib/gateway-cost.mjs +78 -0
- package/lib/gateway-keys.sh +118 -0
- package/lib/phases.sh +569 -75
- package/lib/platform.sh +78 -0
- package/lib/tokens.sh +75 -0
- package/package.json +9 -3
package/lib/agents.sh
CHANGED
|
@@ -1,12 +1,146 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# moxie/lib/agents.sh — Agent dispatch and
|
|
2
|
+
# moxie/lib/agents.sh — Agent registry, dispatch, rotation, and health checks
|
|
3
3
|
# Compatible with Bash 3.2 (macOS default) — no associative arrays.
|
|
4
4
|
|
|
5
|
+
# ---- Known agent registry ----
|
|
6
|
+
# To add a new agent: append one entry to each array at the same index.
|
|
7
|
+
# The command template is what gets written to config.toml. The prompt is
|
|
8
|
+
# appended as the final argument by dispatch_agent().
|
|
9
|
+
|
|
10
|
+
KNOWN_AGENT_NAMES=(claude codex qwen aider goose amp cline roo)
|
|
11
|
+
KNOWN_AGENT_LABELS=("Claude Code" "Codex" "Qwen Code" "Aider" "Goose" "Amp" "Cline" "Roo Code")
|
|
12
|
+
KNOWN_AGENT_BINARIES=(claude codex qwen aider goose amp cline roo)
|
|
13
|
+
KNOWN_AGENT_CMDS=(
|
|
14
|
+
'claude --dangerously-skip-permissions --effort max --output-format json -p'
|
|
15
|
+
'codex exec -c model_reasoning_effort="xhigh" --dangerously-bypass-approvals-and-sandbox -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true'
|
|
16
|
+
'qwen --yolo -o json'
|
|
17
|
+
'aider --yes-always -m'
|
|
18
|
+
'goose run --output-format json -t'
|
|
19
|
+
'amp --dangerously-allow-all --stream-json -x'
|
|
20
|
+
'cline -y --json'
|
|
21
|
+
'roo --output-format json'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# ---- Known gateway models (Vercel AI Gateway) ----
|
|
25
|
+
# To add a new gateway model: append one entry to each array at the same index.
|
|
26
|
+
|
|
27
|
+
KNOWN_GATEWAY_NAMES=(claude-gw gpt-gw qwen-gw glm-gw kimi-gw)
|
|
28
|
+
KNOWN_GATEWAY_MODELS=(
|
|
29
|
+
"anthropic/claude-opus-4-6"
|
|
30
|
+
"openai/gpt-5.4"
|
|
31
|
+
"qwen/qwen3.6-plus"
|
|
32
|
+
"zai/glm-5.1"
|
|
33
|
+
"moonshotai/kimi-k2.5"
|
|
34
|
+
)
|
|
35
|
+
KNOWN_GATEWAY_LABELS=(
|
|
36
|
+
"Claude Opus 4.6"
|
|
37
|
+
"GPT-5.4"
|
|
38
|
+
"Qwen 3.6 Plus"
|
|
39
|
+
"GLM 5.1"
|
|
40
|
+
"Kimi 2.5"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# ---- Gateway agent helpers ----
|
|
44
|
+
|
|
45
|
+
# Returns 0 if the agent is a gateway agent (command starts with "gateway:")
|
|
46
|
+
_is_gateway_agent() {
|
|
47
|
+
local name="$1"
|
|
48
|
+
local cmd
|
|
49
|
+
cmd=$(_agent_cmd "$name")
|
|
50
|
+
case "$cmd" in
|
|
51
|
+
gateway:*) return 0 ;;
|
|
52
|
+
*) return 1 ;;
|
|
53
|
+
esac
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Extracts the model string from a gateway agent's stored command
|
|
57
|
+
_agent_model() {
|
|
58
|
+
local name="$1"
|
|
59
|
+
local cmd
|
|
60
|
+
cmd=$(_agent_cmd "$name")
|
|
61
|
+
echo "${cmd#gateway:}"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ---- Detect available agents on PATH ----
|
|
65
|
+
|
|
66
|
+
detect_available_agents() {
|
|
67
|
+
AVAILABLE_AGENT_INDICES=()
|
|
68
|
+
AVAILABLE_AGENT_COUNT=0
|
|
69
|
+
|
|
70
|
+
for i in "${!KNOWN_AGENT_BINARIES[@]}"; do
|
|
71
|
+
if command -v "${KNOWN_AGENT_BINARIES[$i]}" &>/dev/null; then
|
|
72
|
+
AVAILABLE_AGENT_INDICES+=("$i")
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
AVAILABLE_AGENT_COUNT=${#AVAILABLE_AGENT_INDICES[@]}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ---- Check minimum agent count (from config) ----
|
|
79
|
+
|
|
80
|
+
check_minimum_agents() {
|
|
81
|
+
if [ "${AGENT_COUNT:-0}" -eq 0 ]; then
|
|
82
|
+
load_agents
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
local healthy=0
|
|
86
|
+
for i in "${!AGENT_NAMES[@]}"; do
|
|
87
|
+
local name="${AGENT_NAMES[$i]}"
|
|
88
|
+
if _is_gateway_agent "$name"; then
|
|
89
|
+
# Gateway agent: needs node + stored key
|
|
90
|
+
if command -v node &>/dev/null && gateway_has_key "vercel-ai-gateway"; then
|
|
91
|
+
healthy=$((healthy + 1))
|
|
92
|
+
fi
|
|
93
|
+
else
|
|
94
|
+
local cmd
|
|
95
|
+
cmd=$(_agent_cmd "$name")
|
|
96
|
+
local binary
|
|
97
|
+
binary=$(echo "$cmd" | awk '{print $1}')
|
|
98
|
+
if command -v "$binary" &>/dev/null; then
|
|
99
|
+
healthy=$((healthy + 1))
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
if [ "$healthy" -lt 2 ]; then
|
|
105
|
+
echo "" >&2
|
|
106
|
+
echo "ERROR: moxie requires at least 2 healthy agents for cross-model verification." >&2
|
|
107
|
+
echo "Single-agent mode is not supported." >&2
|
|
108
|
+
echo "" >&2
|
|
109
|
+
echo "Configured agents:" >&2
|
|
110
|
+
for i in "${!AGENT_NAMES[@]}"; do
|
|
111
|
+
local name="${AGENT_NAMES[$i]}"
|
|
112
|
+
if _is_gateway_agent "$name"; then
|
|
113
|
+
local model
|
|
114
|
+
model=$(_agent_model "$name")
|
|
115
|
+
if command -v node &>/dev/null && gateway_has_key "vercel-ai-gateway"; then
|
|
116
|
+
echo " [OK] $name — $model (AI Gateway)" >&2
|
|
117
|
+
else
|
|
118
|
+
echo " [!!] $name — $model (gateway key missing or node not found)" >&2
|
|
119
|
+
fi
|
|
120
|
+
else
|
|
121
|
+
local cmd
|
|
122
|
+
cmd=$(_agent_cmd "$name")
|
|
123
|
+
local binary
|
|
124
|
+
binary=$(echo "$cmd" | awk '{print $1}')
|
|
125
|
+
if command -v "$binary" &>/dev/null; then
|
|
126
|
+
echo " [OK] $name" >&2
|
|
127
|
+
else
|
|
128
|
+
echo " [!!] $name — '$binary' not found on PATH" >&2
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
echo "" >&2
|
|
133
|
+
echo "Run 'moxie doctor' for details, or 'moxie init' to reconfigure." >&2
|
|
134
|
+
return 1
|
|
135
|
+
fi
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
5
139
|
# Global arrays populated by load_agents():
|
|
6
140
|
# AGENT_NAMES=("codex" "claude" "qwen")
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
141
|
+
# AGENT_CMD_0="codex exec ..." (indexed by position in AGENT_NAMES)
|
|
142
|
+
# AGENT_CMD_1="claude ..."
|
|
143
|
+
# AGENT_CMD_2="qwen ..."
|
|
10
144
|
|
|
11
145
|
# ---- Load agents from config ----
|
|
12
146
|
|
|
@@ -20,30 +154,40 @@ load_agents() {
|
|
|
20
154
|
local current_agent=""
|
|
21
155
|
local current_cmd=""
|
|
22
156
|
local current_order="99"
|
|
157
|
+
local current_type=""
|
|
158
|
+
local current_model=""
|
|
159
|
+
|
|
160
|
+
_commit_agent() {
|
|
161
|
+
if [ -z "$current_agent" ]; then return; fi
|
|
162
|
+
# CLI agent: has command. Gateway agent: type=gateway with model.
|
|
163
|
+
if [ -n "$current_cmd" ]; then
|
|
164
|
+
AGENT_NAMES+=("$current_agent")
|
|
165
|
+
agent_cmds+=("$current_cmd")
|
|
166
|
+
agent_orders+=("$current_order")
|
|
167
|
+
elif [ "$current_type" = "gateway" ] && [ -n "$current_model" ]; then
|
|
168
|
+
AGENT_NAMES+=("$current_agent")
|
|
169
|
+
agent_cmds+=("gateway:$current_model")
|
|
170
|
+
agent_orders+=("$current_order")
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
23
173
|
|
|
24
174
|
while IFS= read -r line; do
|
|
25
175
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
26
176
|
[[ -z "${line// /}" ]] && continue
|
|
27
177
|
|
|
28
178
|
if [[ "$line" =~ ^\[agents\.([a-zA-Z0-9_-]+)\] ]]; then
|
|
29
|
-
|
|
30
|
-
AGENT_NAMES+=("$current_agent")
|
|
31
|
-
agent_cmds+=("$current_cmd")
|
|
32
|
-
agent_orders+=("$current_order")
|
|
33
|
-
fi
|
|
179
|
+
_commit_agent
|
|
34
180
|
current_agent="${BASH_REMATCH[1]}"
|
|
35
181
|
current_cmd=""
|
|
36
182
|
current_order="99"
|
|
183
|
+
current_type=""
|
|
184
|
+
current_model=""
|
|
37
185
|
continue
|
|
38
186
|
fi
|
|
39
187
|
|
|
40
188
|
# New non-agent section
|
|
41
189
|
if [[ "$line" =~ ^\[ ]] && ! [[ "$line" =~ ^\[agents\. ]]; then
|
|
42
|
-
|
|
43
|
-
AGENT_NAMES+=("$current_agent")
|
|
44
|
-
agent_cmds+=("$current_cmd")
|
|
45
|
-
agent_orders+=("$current_order")
|
|
46
|
-
fi
|
|
190
|
+
_commit_agent
|
|
47
191
|
current_agent=""
|
|
48
192
|
continue
|
|
49
193
|
fi
|
|
@@ -59,15 +203,21 @@ load_agents() {
|
|
|
59
203
|
if [[ "$line" =~ ^[[:space:]]*order[[:space:]]*=[[:space:]]*([0-9]+) ]]; then
|
|
60
204
|
current_order="${BASH_REMATCH[1]}"
|
|
61
205
|
fi
|
|
206
|
+
if [[ "$line" =~ ^[[:space:]]*type[[:space:]]*=[[:space:]]*(.*) ]]; then
|
|
207
|
+
current_type="${BASH_REMATCH[1]}"
|
|
208
|
+
current_type="${current_type#\"}"
|
|
209
|
+
current_type="${current_type%\"}"
|
|
210
|
+
fi
|
|
211
|
+
if [[ "$line" =~ ^[[:space:]]*model[[:space:]]*=[[:space:]]*(.*) ]]; then
|
|
212
|
+
current_model="${BASH_REMATCH[1]}"
|
|
213
|
+
current_model="${current_model#\"}"
|
|
214
|
+
current_model="${current_model%\"}"
|
|
215
|
+
fi
|
|
62
216
|
fi
|
|
63
217
|
done < "$MOXIE_CONFIG"
|
|
64
218
|
|
|
65
219
|
# Save last agent
|
|
66
|
-
|
|
67
|
-
AGENT_NAMES+=("$current_agent")
|
|
68
|
-
agent_cmds+=("$current_cmd")
|
|
69
|
-
agent_orders+=("$current_order")
|
|
70
|
-
fi
|
|
220
|
+
_commit_agent
|
|
71
221
|
|
|
72
222
|
# Sort by order using a temp file
|
|
73
223
|
local sorted
|
|
@@ -110,6 +260,7 @@ dispatch_agent() {
|
|
|
110
260
|
local agent="$1"
|
|
111
261
|
local prompt_file="$2"
|
|
112
262
|
local timeout_secs="$3"
|
|
263
|
+
local phase="${4:-unknown}"
|
|
113
264
|
local cmd
|
|
114
265
|
cmd=$(_agent_cmd "$agent")
|
|
115
266
|
|
|
@@ -118,7 +269,34 @@ dispatch_agent() {
|
|
|
118
269
|
return 1
|
|
119
270
|
fi
|
|
120
271
|
|
|
121
|
-
#
|
|
272
|
+
# Gateway agent: dispatch via Node.js script
|
|
273
|
+
if _is_gateway_agent "$agent"; then
|
|
274
|
+
local model endpoint api_key
|
|
275
|
+
model=$(_agent_model "$agent")
|
|
276
|
+
endpoint=$(toml_get "$MOXIE_CONFIG" "gateway.endpoint" "https://ai-gateway.vercel.sh")
|
|
277
|
+
api_key=$(gateway_get_key "vercel-ai-gateway") || {
|
|
278
|
+
echo "ERROR: Gateway API key not found. Run 'moxie init' to configure." >&2
|
|
279
|
+
return 1
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
env -u CLAUDE_PLUGIN_ROOT -u CODEX_PLUGIN_ROOT -u CURSOR_PLUGIN_ROOT \
|
|
283
|
+
GATEWAY_API_KEY="$api_key" \
|
|
284
|
+
GATEWAY_ENDPOINT="$endpoint" \
|
|
285
|
+
GATEWAY_MODEL="$model" \
|
|
286
|
+
GATEWAY_PHASE="$phase" \
|
|
287
|
+
GATEWAY_AGENT="$agent" \
|
|
288
|
+
timeout "$timeout_secs" \
|
|
289
|
+
node "$MOXIE_ROOT/lib/gateway-agent.mjs" "$(cat "$prompt_file")" || {
|
|
290
|
+
local rc=$?
|
|
291
|
+
if [ $rc -eq 124 ]; then
|
|
292
|
+
echo "[TIMEOUT] $agent exceeded ${timeout_secs}s — skipping"
|
|
293
|
+
fi
|
|
294
|
+
return $rc
|
|
295
|
+
}
|
|
296
|
+
return 0
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# CLI agent: dispatch via shell command
|
|
122
300
|
env -u CLAUDE_PLUGIN_ROOT \
|
|
123
301
|
-u CODEX_PLUGIN_ROOT \
|
|
124
302
|
-u CURSOR_PLUGIN_ROOT \
|
|
@@ -145,6 +323,109 @@ next_agent() {
|
|
|
145
323
|
echo "${AGENT_NAMES[0]}"
|
|
146
324
|
}
|
|
147
325
|
|
|
326
|
+
# ---- Agent health tracking ----
|
|
327
|
+
# Tracks consecutive failures per agent. An agent is degraded after
|
|
328
|
+
# AGENT_DEGRADE_THRESHOLD consecutive non-productive turns (timeout,
|
|
329
|
+
# empty output, or no useful work done).
|
|
330
|
+
|
|
331
|
+
AGENT_DEGRADE_THRESHOLD=2
|
|
332
|
+
|
|
333
|
+
# Parallel arrays indexed by AGENT_NAMES position.
|
|
334
|
+
# Initialized by _init_agent_health() after load_agents.
|
|
335
|
+
AGENT_FAIL_STREAK=()
|
|
336
|
+
AGENT_DEGRADED=()
|
|
337
|
+
|
|
338
|
+
_init_agent_health() {
|
|
339
|
+
AGENT_FAIL_STREAK=()
|
|
340
|
+
AGENT_DEGRADED=()
|
|
341
|
+
for (( i = 0; i < AGENT_COUNT; i++ )); do
|
|
342
|
+
AGENT_FAIL_STREAK+=(0)
|
|
343
|
+
AGENT_DEGRADED+=(0)
|
|
344
|
+
done
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# Returns 0 if the agent is degraded, 1 otherwise.
|
|
348
|
+
is_agent_degraded() {
|
|
349
|
+
local name="$1"
|
|
350
|
+
for i in "${!AGENT_NAMES[@]}"; do
|
|
351
|
+
if [ "${AGENT_NAMES[$i]}" = "$name" ]; then
|
|
352
|
+
[ "${AGENT_DEGRADED[$i]}" = "1" ]
|
|
353
|
+
return
|
|
354
|
+
fi
|
|
355
|
+
done
|
|
356
|
+
return 1
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# Record a productive or non-productive turn for an agent.
|
|
360
|
+
# Usage: _record_turn_health <agent_name> <logfile>
|
|
361
|
+
_record_turn_health() {
|
|
362
|
+
local name="$1"
|
|
363
|
+
local logfile="$2"
|
|
364
|
+
|
|
365
|
+
local idx=-1
|
|
366
|
+
for i in "${!AGENT_NAMES[@]}"; do
|
|
367
|
+
[ "${AGENT_NAMES[$i]}" = "$name" ] && idx=$i && break
|
|
368
|
+
done
|
|
369
|
+
[ "$idx" = "-1" ] && return
|
|
370
|
+
|
|
371
|
+
# Already degraded — don't re-evaluate
|
|
372
|
+
[ "${AGENT_DEGRADED[$idx]}" = "1" ] && return
|
|
373
|
+
|
|
374
|
+
# A productive turn has a log file > 1KB with actual agent output
|
|
375
|
+
# (not just the prompt echo or timeout message)
|
|
376
|
+
local log_size=0
|
|
377
|
+
if [ -f "$logfile" ]; then
|
|
378
|
+
log_size=$(wc -c < "$logfile" | tr -d ' ')
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Check for known failure signatures in the log
|
|
382
|
+
local is_failure=0
|
|
383
|
+
if [ "$log_size" -lt 1024 ]; then
|
|
384
|
+
is_failure=1
|
|
385
|
+
elif grep -qE '^\[TIMEOUT\]' "$logfile" 2>/dev/null; then
|
|
386
|
+
is_failure=1
|
|
387
|
+
fi
|
|
388
|
+
|
|
389
|
+
if [ "$is_failure" = "1" ]; then
|
|
390
|
+
AGENT_FAIL_STREAK[$idx]=$(( ${AGENT_FAIL_STREAK[$idx]} + 1 ))
|
|
391
|
+
local streak=${AGENT_FAIL_STREAK[$idx]}
|
|
392
|
+
|
|
393
|
+
if [ "$streak" -ge "$AGENT_DEGRADE_THRESHOLD" ]; then
|
|
394
|
+
AGENT_DEGRADED[$idx]=1
|
|
395
|
+
echo " [DEGRADED] $name failed $streak consecutive turns — removing from rotation"
|
|
396
|
+
echo " (possible quota exhaustion, auth failure, or CLI crash)"
|
|
397
|
+
|
|
398
|
+
# Count remaining healthy agents
|
|
399
|
+
local healthy=0
|
|
400
|
+
for j in "${!AGENT_DEGRADED[@]}"; do
|
|
401
|
+
[ "${AGENT_DEGRADED[$j]}" = "0" ] && healthy=$((healthy + 1))
|
|
402
|
+
done
|
|
403
|
+
|
|
404
|
+
if [ "$healthy" -lt 2 ]; then
|
|
405
|
+
echo ""
|
|
406
|
+
echo " FATAL: Only $healthy healthy agent(s) remaining."
|
|
407
|
+
echo " moxie requires at least 2 agents for cross-model verification."
|
|
408
|
+
echo " Pipeline cannot continue. Stopping."
|
|
409
|
+
return 2
|
|
410
|
+
fi
|
|
411
|
+
else
|
|
412
|
+
echo " [WARN] $name produced no useful output (turn $streak/$AGENT_DEGRADE_THRESHOLD before degraded)"
|
|
413
|
+
fi
|
|
414
|
+
else
|
|
415
|
+
# Productive turn — reset streak
|
|
416
|
+
AGENT_FAIL_STREAK[$idx]=0
|
|
417
|
+
fi
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Count healthy (non-degraded) agents.
|
|
421
|
+
count_healthy_agents() {
|
|
422
|
+
local healthy=0
|
|
423
|
+
for i in "${!AGENT_DEGRADED[@]}"; do
|
|
424
|
+
[ "${AGENT_DEGRADED[$i]}" = "0" ] && healthy=$((healthy + 1))
|
|
425
|
+
done
|
|
426
|
+
echo "$healthy"
|
|
427
|
+
}
|
|
428
|
+
|
|
148
429
|
# ---- Logging ----
|
|
149
430
|
|
|
150
431
|
dispatch_logged() {
|
|
@@ -168,12 +449,15 @@ dispatch_logged() {
|
|
|
168
449
|
return 0
|
|
169
450
|
fi
|
|
170
451
|
|
|
171
|
-
dispatch_agent "$agent" "$prompt_file" "$timeout_secs" 2>&1 | tee "$logfile" || true
|
|
452
|
+
dispatch_agent "$agent" "$prompt_file" "$timeout_secs" "$phase" 2>&1 | tee "$logfile" || true
|
|
172
453
|
|
|
173
454
|
local tokens
|
|
174
455
|
tokens=$(extract_tokens "$logfile" "$agent")
|
|
175
456
|
record_tokens "$csv_file" "$turn" "$agent" "$ts" "$tokens" "$phase"
|
|
176
457
|
echo " Tokens: $tokens"
|
|
458
|
+
|
|
459
|
+
# Track agent health based on output
|
|
460
|
+
_record_turn_health "$agent" "$logfile" || return $?
|
|
177
461
|
}
|
|
178
462
|
|
|
179
463
|
# ---- List agents ----
|
|
@@ -210,13 +494,18 @@ cmd_doctor() {
|
|
|
210
494
|
# ---- Dependencies ----
|
|
211
495
|
echo "Dependencies:"
|
|
212
496
|
_check_dep "python3" "Required for ledger parsing and token tracking" || all_ok=0
|
|
213
|
-
|
|
497
|
+
# Sleep inhibitor: platform-specific, non-fatal
|
|
498
|
+
if [ "$MOXIE_PLATFORM" = "darwin" ]; then
|
|
499
|
+
_check_dep "caffeinate" "Keeps machine awake during runs (macOS)" || true
|
|
500
|
+
elif [ "$MOXIE_PLATFORM" = "linux" ]; then
|
|
501
|
+
_check_dep "systemd-inhibit" "Keeps machine awake during runs (Linux)" || true
|
|
502
|
+
fi
|
|
214
503
|
if command -v timeout &>/dev/null; then
|
|
215
504
|
echo " [OK] timeout"
|
|
216
505
|
elif command -v gtimeout &>/dev/null; then
|
|
217
506
|
echo " [OK] coreutils (gtimeout aliased as timeout)"
|
|
218
507
|
else
|
|
219
|
-
echo " [!!] coreutils — not found. Install:
|
|
508
|
+
echo " [!!] coreutils — not found. Install: $(coreutils_install_hint)"
|
|
220
509
|
echo " Provides timeout protection for agent turns (gtimeout)."
|
|
221
510
|
all_ok=0
|
|
222
511
|
fi
|
|
@@ -225,37 +514,78 @@ cmd_doctor() {
|
|
|
225
514
|
# ---- Agents ----
|
|
226
515
|
if [ "$has_project" = "1" ]; then
|
|
227
516
|
echo "Agents (from .moxie/config.toml):"
|
|
517
|
+
local healthy_count=0
|
|
228
518
|
for i in "${!AGENT_NAMES[@]}"; do
|
|
229
519
|
local name="${AGENT_NAMES[$i]}"
|
|
230
|
-
local cmd
|
|
231
|
-
cmd=$(_agent_cmd "$name")
|
|
232
520
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
521
|
+
if _is_gateway_agent "$name"; then
|
|
522
|
+
# Gateway agent
|
|
523
|
+
local model
|
|
524
|
+
model=$(_agent_model "$name")
|
|
525
|
+
local gw_ok=1
|
|
526
|
+
|
|
527
|
+
if ! command -v node &>/dev/null; then
|
|
528
|
+
echo " [!!] $name — $model (node not found on PATH)"
|
|
529
|
+
all_ok=0
|
|
530
|
+
gw_ok=0
|
|
531
|
+
fi
|
|
532
|
+
|
|
533
|
+
if [ "$gw_ok" = "1" ] && ! gateway_has_key "vercel-ai-gateway"; then
|
|
534
|
+
echo " [!!] $name — $model (AI Gateway key not configured)"
|
|
535
|
+
echo " Run 'moxie init' to set up your gateway key."
|
|
536
|
+
all_ok=0
|
|
537
|
+
gw_ok=0
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
if [ "$gw_ok" = "1" ]; then
|
|
541
|
+
echo " [OK] $name — $model (AI Gateway, key stored)"
|
|
542
|
+
healthy_count=$((healthy_count + 1))
|
|
543
|
+
fi
|
|
248
544
|
else
|
|
249
|
-
|
|
250
|
-
|
|
545
|
+
# CLI agent
|
|
546
|
+
local cmd
|
|
547
|
+
cmd=$(_agent_cmd "$name")
|
|
548
|
+
local binary
|
|
549
|
+
binary=$(echo "$cmd" | awk '{print $1}')
|
|
550
|
+
|
|
551
|
+
if ! command -v "$binary" &>/dev/null; then
|
|
552
|
+
echo " [!!] $name — '$binary' not found on PATH"
|
|
553
|
+
all_ok=0
|
|
554
|
+
continue
|
|
555
|
+
fi
|
|
556
|
+
|
|
557
|
+
healthy_count=$((healthy_count + 1))
|
|
558
|
+
|
|
559
|
+
local version
|
|
560
|
+
version=$("$binary" --version 2>&1 | head -1)
|
|
561
|
+
if [ $? -eq 0 ] && [ -n "$version" ]; then
|
|
562
|
+
echo " [OK] $name — $version"
|
|
563
|
+
else
|
|
564
|
+
echo " [??] $name — '$binary' found but --version failed"
|
|
565
|
+
echo " May not be authenticated. Try running: $binary --version"
|
|
566
|
+
fi
|
|
251
567
|
fi
|
|
252
568
|
done
|
|
569
|
+
|
|
570
|
+
if [ "$healthy_count" -lt 2 ]; then
|
|
571
|
+
echo ""
|
|
572
|
+
echo " [FAIL] Only $healthy_count healthy agent(s). moxie requires at least 2"
|
|
573
|
+
echo " for cross-model verification. Single-agent mode is not supported."
|
|
574
|
+
all_ok=0
|
|
575
|
+
fi
|
|
253
576
|
else
|
|
254
577
|
echo "Agents:"
|
|
255
|
-
echo " (no .moxie/ project —
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
578
|
+
echo " (no .moxie/ project — scanning for known agents)"
|
|
579
|
+
detect_available_agents
|
|
580
|
+
for i in "${!KNOWN_AGENT_NAMES[@]}"; do
|
|
581
|
+
_check_agent_binary "${KNOWN_AGENT_BINARIES[$i]}" || true
|
|
582
|
+
done
|
|
583
|
+
if [ "$AVAILABLE_AGENT_COUNT" -lt 2 ]; then
|
|
584
|
+
echo ""
|
|
585
|
+
echo " [FAIL] Only $AVAILABLE_AGENT_COUNT agent(s) found. moxie requires at least 2"
|
|
586
|
+
echo " for cross-model verification. Single-agent mode is not supported."
|
|
587
|
+
all_ok=0
|
|
588
|
+
fi
|
|
259
589
|
fi
|
|
260
590
|
echo ""
|
|
261
591
|
|
|
@@ -295,8 +625,10 @@ cmd_doctor() {
|
|
|
295
625
|
echo ""
|
|
296
626
|
if [ "$all_ok" = "1" ]; then
|
|
297
627
|
echo "All checks passed. Ready to run."
|
|
628
|
+
return 0
|
|
298
629
|
else
|
|
299
630
|
echo "Some checks failed. Fix the issues above before running."
|
|
631
|
+
return 1
|
|
300
632
|
fi
|
|
301
633
|
}
|
|
302
634
|
|