Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

196 lines
8.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Quick smoke test for the mock CLIs.
# Runs each agent's wrapper against a known recording and asserts that:
# 1. The mock binary exits 0
# 2. Stdout produces a sensible number of lines (>= 5 for JSON formats,
# >= 1 for plain)
# 3. The first JSON line for each JSON agent has the expected shape
#
# Usage:
# bash mocks/scripts/smoke-test.sh
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
MOCKS="$(cd "$HERE/.." && pwd -P)"
TRACE_ID="${OD_MOCKS_SMOKE_TRACE:-04097377}" # the 17-tool claude session
# Ensure recordings are on disk — the corpus is hosted on R2 and fetched
# on demand. If nothing's been pulled yet (or only a few are), run the
# fetcher so smoke covers the full agent matrix.
if ! ls "$MOCKS/recordings"/*.jsonl >/dev/null 2>&1; then
echo "no recordings found — fetching from R2 first..."
bash "$HERE/fetch-recordings.sh" >/dev/null
echo
fi
export PATH="$MOCKS/bin:$PATH"
export OD_MOCKS_TRACE="$TRACE_ID"
export OD_MOCKS_NO_DELAY=1
failed=0
pass() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
fail() { printf ' \033[31m✗\033[0m %s\n' "$1"; failed=$((failed + 1)); }
check_json_first_event() {
local agent="$1" expected_type="$2"
local first
first=$(echo "smoke" | "$agent" run 2>/dev/null | head -1 || true)
if [ -z "$first" ]; then fail "$agent: empty stdout"; return; fi
local got
got=$(printf '%s' "$first" | node -e 'let buf="";process.stdin.on("data",d=>buf+=d);process.stdin.on("end",()=>{try{console.log(JSON.parse(buf).type||"")}catch{console.log("INVALID")}})')
if [ "$got" = "$expected_type" ]; then
pass "$agent first event = $expected_type"
else
fail "$agent first event = $got (wanted $expected_type)"
fi
}
echo "Smoke testing mock CLIs against trace $TRACE_ID"
echo
# opencode / opencode-cli (primary OD-facing bin) → step_start
check_json_first_event opencode step_start
check_json_first_event opencode-cli step_start
# codex → thread.started
check_json_first_event codex thread.started
# claude → system / init
# (codex/claude have a different entry verb; using a uniform "first line type" check)
first=$(echo smoke | claude -p 2>/dev/null | head -1 || true)
if printf '%s' "$first" | grep -q '"type":"system"'; then
pass "claude first event = system"
else
fail "claude first event missing system shape: ${first:0:80}"
fi
# gemini → init
check_json_first_event gemini init
# cursor-agent → system + subtype:init
first=$(echo smoke | cursor-agent 2>/dev/null | head -1 || true)
if printf '%s' "$first" | grep -q '"type":"system"' && printf '%s' "$first" | grep -q '"subtype":"init"'; then
pass "cursor-agent first event = system+init"
else
fail "cursor-agent first event missing system/init shape: ${first:0:80}"
fi
# Plain agents — first non-empty line should be from the report content.
for agent in deepseek qwen grok; do
out=$(echo smoke | "$agent" 2>/dev/null | head -1 || true)
if [ -n "$out" ]; then
pass "$agent emitted plain text (${#out} chars on first line)"
else
fail "$agent emitted nothing"
fi
done
# vela subcommands — `vela models` (prints catalog) and `vela login` (writes
# ~/.amr/config.json). These exit immediately, no recording involved.
vela_models_out=$(vela models 2>/dev/null | wc -l | tr -d ' ')
if [ "$vela_models_out" -ge 10 ]; then
pass "vela models printed $vela_models_out catalog lines"
else
fail "vela models printed only $vela_models_out lines (expected ≥10)"
fi
# Sandbox vela login into a temp HOME so we never touch the caller's real
# ~/.amr config (which holds the production vela login state for anyone
# using the real CLI). vela's login subcommand resolves ~/.amr from $HOME,
# so override just for this one invocation.
amr_sandbox="$(mktemp -d -t od-mocks-amr.XXXXXX)"
trap 'rm -rf "$amr_sandbox"' EXIT
if HOME="$amr_sandbox" FAKE_VELA_LOGIN_USER_EMAIL=smoke@od.local vela login >/dev/null 2>&1 \
&& [ -f "$amr_sandbox/.amr/config.json" ]; then
email=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$amr_sandbox/.amr/config.json','utf-8')).profiles.prod.user.email)" 2>/dev/null || echo "")
if [ "$email" = "smoke@od.local" ]; then
pass "vela login wrote ~/.amr/config.json with profile.prod.user.email"
else
fail "vela login config.json missing expected email (got: $email)"
fi
else
fail "vela login did not produce ~/.amr/config.json"
fi
# vela ACP roundtrip (strict set_model gate enforced).
vela_acp_out=$(cat <<EOF | vela agent run --runtime opencode 2>/dev/null
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/tmp"}}
{"jsonrpc":"2.0","id":3,"method":"session/set_model","params":{"sessionId":"fake-vela-session-1","modelId":"deepseek-v3.2"}}
{"jsonrpc":"2.0","id":4,"method":"session/prompt","params":{"sessionId":"fake-vela-session-1","prompt":[{"type":"text","text":"hi"}]}}
EOF
)
if printf '%s' "$vela_acp_out" | grep -q '"agentCapabilities"' \
&& printf '%s' "$vela_acp_out" | grep -q '"availableModels"' \
&& printf '%s' "$vela_acp_out" | grep -q '"id":3,"result":{}' \
&& printf '%s' "$vela_acp_out" | grep -q '"sessionUpdate":"agent_message_chunk"' \
&& printf '%s' "$vela_acp_out" | grep -q '"id":4,"result":{"stopReason":'; then
pass "vela agent run ACP roundtrip (initialize+models, set_model accepted, prompt streamed)"
else
fail "vela agent run ACP roundtrip incomplete"
fi
# vela strict set_model gate — skipping set_model must reject prompt.
vela_gate_out=$(cat <<EOF | vela agent run --runtime opencode 2>/dev/null
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/tmp"}}
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"fake-vela-session-1","prompt":[{"type":"text","text":"hi"}]}}
EOF
)
if printf '%s' "$vela_gate_out" | grep -q 'session/set_model must be called before session/prompt'; then
pass "vela strict set_model gate rejects session/prompt without prior set_model"
else
fail "vela strict set_model gate did not reject (negative-path regression)"
fi
# Kimi — prompt-mode stream-json. Newer Kimi CLIs reject an `acp`
# positional arg, so keep this smoke check on the real prompt-mode
# launch shape OD uses in Settings and chat.
kimi_out=$(kimi -p "Reply with only: ok" --output-format stream-json 2>/dev/null || true)
if printf '%s' "$kimi_out" | grep -q '"role":"assistant"'; then
pass "kimi prompt-mode stream-json smoke passed"
else
fail "kimi prompt-mode stream-json smoke failed"
fi
kimi_legacy_err="$amr_sandbox/kimi-legacy.err"
if kimi acp -p "Reply with only: ok" --output-format stream-json \
> /dev/null 2> "$kimi_legacy_err"; then
fail "kimi legacy positional launch unexpectedly succeeded"
elif grep -q 'too many arguments' "$kimi_legacy_err"; then
pass "kimi legacy positional launch fails with too many arguments"
else
fail "kimi legacy positional launch failed without too many arguments"
fi
# ACP agents — JSON-RPC server. Send initialize+session/new+prompt and
# verify the protocol responses come back in order.
# kiro-cli and vibe-acp are the primary OD-facing bin names; test them
# alongside the fallback names (kiro, vibe).
for agent in hermes kilo kiro kiro-cli vibe vibe-acp devin; do
out=$(cat <<EOF | "$agent" 2>/dev/null
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/tmp"}}
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"X","prompt":[{"type":"text","text":"hi"}]}}
EOF
)
# Expect: id=1 initialize result, id=2 session/new result, ≥1 session/update, id=3 prompt result
if printf '%s' "$out" | grep -q '"id":1,"result":{"protocolVersion":1' \
&& printf '%s' "$out" | grep -q '"id":2,"result":{"sessionId":' \
&& printf '%s' "$out" | grep -q '"sessionUpdate":"agent_message_chunk"' \
&& printf '%s' "$out" | grep -q '"id":3,"result":{"stopReason":'; then
pass "$agent ACP roundtrip complete (init → session/new → update → prompt result)"
else
fail "$agent ACP roundtrip incomplete"
fi
done
echo
if [ "$failed" -eq 0 ]; then
echo "All mock CLIs working. ✅"
else
echo "$failed check(s) failed. ❌"
exit 1
fi