#!/usr/bin/env bash
# Evidence driver for PR #23609 (issue #16818). Usage: driver.sh <build> <tree> <port>
# Starts an isolated headless server, creates rows R2/R3/R5, then samples state once a minute.
set -u
stop_thread() { # Stop like the UI Stop button: orchestration.dispatchCommand thread.stop over /ws
  node -e '
const [port, token, threadId] = process.argv.slice(1);
const ws = new WebSocket(`ws://127.0.0.1:${port}/ws?orchestrationProtocol=2`, { headers: { authorization: `Bearer ${token}` } });
ws.onopen = () => ws.send(JSON.stringify({ _tag: "Request", id: "1", tag: "orchestration.dispatchCommand", headers: [],
  payload: { type: "thread.stop", commandId: `ev-stop-${Date.now()}`, threadId } }));
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m._tag === "Exit" || (Array.isArray(m) && m.some((x) => x._tag === "Exit"))) { console.log(JSON.stringify(m)); ws.close(); process.exit(0); } };
ws.onerror = (e) => { console.log("ws error", e.message); process.exit(1); };
setTimeout(() => { console.log("timeout"); process.exit(1); }, 15000);' "$PORT" "$TOKEN" "$R2"
}
if [ "${1:-}" = stop ]; then # driver.sh stop <build> <tree> <port> <threadId>: UI-equivalent Stop over /ws
  shift; B=$1; cd "$2"; PORT=$3; R2=$4
  TOKEN=$(node apps/server/src/bin.ts auth session issue --base-dir /tmp/ev-23609/home-$B --token-only --label ev-stop 2>/dev/null | grep "^eyJ" | tail -1)
  echo "R2 manual stop $(date -u +%T) thread=$R2: $(stop_thread)" | tee -a /tmp/ev-23609/out/transcript-$B.txt; exit 0
fi
if [ "${1:-}" = sample ]; then # driver.sh sample <build> <tree> <port>: more once-a-minute samples against a running server
  shift; EXTRA_ONLY=1; B=$1; TREE=$2; PORT=$3; SHA=$(git -C "$TREE" rev-parse HEAD)
  DB=/tmp/ev-23609/home-$B/userdata/statev2.sqlite; LOG=/tmp/ev-23609/out/server-$B.log; TX=/tmp/ev-23609/out/transcript-$B.txt
fi
if [ "${1:-}" = r5 ]; then # driver.sh r5 <build> <tree> <port>: create the fresh-thread-per-run task late (token race fallback)
  shift; B=$1; TREE=$2; PORT=$3; H=/tmp/ev-23609/home-$B; cd "$TREE"
  for v in $(env | grep -o '^T3CODE_[A-Z0-9_]*'); do unset "$v"; done
  T=$(node apps/server/src/bin.ts auth session issue --base-dir "$H" --token-only --label ev-r5 --subject mcp-client 2>/dev/null | grep "^eyJ" | tail -1)
  P=$(sqlite3 -readonly "$H/userdata/statev2.sqlite" "select project_id from projection_projects limit 1")
  # MCP clients cannot set a project default model, so an agent creates the unbound task and its
  # Claude selection becomes the task's model.
  node apps/server/src/bin.ts thread start --base-dir "$H" --provider claudeAgent --model claude-haiku-5-5 --title R5-creator --json /tmp/ev-23609/proj-$B \
    'Call the t3-code MCP tool schedule_task exactly once with title "R5", prompt "Reply with the single word ok.", schedule {"type":"interval","everyMs":60000} and bindToCurrentThread false. Then reply done. Do not use any other tool.' | tee -a /tmp/ev-23609/out/transcript-$B.txt
  echo; exit 0
fi
export PATH="$HOME/.bun/bin:$HOME/.local/bin:$HOME/.local/share/vite-plus/bin:$PATH"
for v in $(env | grep -o '^T3CODE_[A-Z0-9_]*'); do unset "$v"; done
B=$1; TREE=$2; PORT=$3
ROOT=/tmp/ev-23609; HOME_DIR=$ROOT/home-$B; PROJ=$ROOT/proj-$B; OUT=$ROOT/out
DB=$HOME_DIR/userdata/statev2.sqlite; LOG=$OUT/server-$B.log; TX=$OUT/transcript-$B.txt
SAMPLES=${SAMPLES:-7}
mkdir -p "$HOME_DIR" "$PROJ" "$OUT"
[ -d "$PROJ/.git" ] || (cd "$PROJ" && git init -q && echo x > README && git add . && git -c user.email=e@x -c user.name=e commit -qm init)
SHA=$(git -C "$TREE" rev-parse HEAD)
log() { echo "$*" | tee -a "$TX"; }
if [ "${EXTRA_ONLY:-0}" != 1 ]; then
log "# build=$B sha=$SHA port=$PORT started=$(date -u +%FT%TZ)"

cd "$TREE"
node apps/server/src/bin.ts serve --base-dir "$HOME_DIR" --port "$PORT" --host 127.0.0.1 "$PROJ" >"$LOG" 2>&1 &
SERVER_PID=$!; echo "$SERVER_PID" > "$OUT/server-$B.pid"; log "server pid=$SERVER_PID"
for _ in $(seq 60); do curl -s -o /dev/null "http://127.0.0.1:$PORT/" && break; sleep 1; done
# TOKEN: ordinary bearer session for /ws; MCP_TOKEN: subject mcp-client, the only kind /mcp accepts.
TOKEN=$(node apps/server/src/bin.ts auth session issue --base-dir "$HOME_DIR" --token-only --label ev 2>/dev/null | grep "^eyJ" | tail -1)
MCP_TOKEN=$(node apps/server/src/bin.ts auth session issue --base-dir "$HOME_DIR" --token-only --label ev-mcp --subject mcp-client 2>/dev/null | tail -1)
export T3_ACP_MCP_ENDPOINT="http://127.0.0.1:$PORT/mcp" T3_ACP_MCP_AUTHORIZATION="Bearer $MCP_TOKEN"
mcp() { node apps/server/src/bin.ts acp-mcp-call "$1" "$2"; }

node apps/server/src/bin.ts project add --base-dir "$HOME_DIR" --title "ev-$B" "$PROJ" 2>&1 | tee -a "$TX"
PROJECT_ID=$(sqlite3 -readonly "$DB" "select project_id from projection_projects limit 1" 2>/dev/null)
log "project=$PROJECT_ID"
SCHED='{"type":"interval","everyMs":60000}'
BUSY='Then run exactly `sleep 270` in bash, then reply done. Do not edit files or use any other tool.'
launch() { # title message
  node apps/server/src/bin.ts thread start --base-dir "$HOME_DIR" --provider claudeAgent --model claude-haiku-5-5 --title "$1" --json "$PROJ" "$2" | tee -a "$TX" | grep -o '"threadId":"[^"]*"' | head -1 | cut -d'"' -f4
}
R2=$(launch R2-busy "First call the t3-code MCP tool schedule_task exactly once with title \"R2\", prompt \"Reply with the single word ok.\", schedule $SCHED and bindToCurrentThread true. $BUSY")
R3=$(launch R3-busy "First call the t3-code MCP tool schedule_task twice: once with title \"R3-A\", prompt \"Task A: reply with the single word ok.\", schedule $SCHED, bindToCurrentThread true; and once with title \"R3-B\", prompt \"Task B: reply with the single word ok.\", the same schedule and bindToCurrentThread true. $BUSY")
log "R2 thread=$R2  R3 thread=$R3"
mcp schedule_task "{\"title\":\"R5\",\"prompt\":\"Reply with the single word ok.\",\"projectId\":\"$PROJECT_ID\",\"bindToCurrentThread\":false,\"schedule\":$SCHED}" | tee -a "$TX"; echo >>"$TX"


fi
q() { sqlite3 -readonly "$DB" "$@"; }
sample() {
  log "== $(date -u +%T) [$B $SHA]"
  log "-- scheduled_tasks: title|thread_id|next_run_at|last_run_status|run_count"
  q "select title, coalesce(thread_id,'NULL'), next_run_at, last_run_status, run_count from scheduled_tasks order by title" | tee -a "$TX"
  log "-- runs: thread|ordinal|status|held|scheduledTaskId"
  q "select t.title, r.ordinal, r.status, coalesce(json_extract(r.payload_json,'\$.queueHeld'),''), coalesce(json_extract(m.payload_json,'\$.scheduledTaskId'),'') from orchestration_v2_projection_runs r left join orchestration_v2_projection_threads t on t.thread_id=r.thread_id left join orchestration_v2_projection_messages m on m.message_id=json_extract(r.payload_json,'\$.userMessageId') order by t.title, r.ordinal" | tee -a "$TX"
  log "-- queued per (thread, task):"
  q "select t.title, coalesce(json_extract(m.payload_json,'\$.scheduledTaskId'),''), count(*) from orchestration_v2_projection_runs r left join orchestration_v2_projection_threads t on t.thread_id=r.thread_id left join orchestration_v2_projection_messages m on m.message_id=json_extract(r.payload_json,'\$.userMessageId') where r.status='queued' group by 1,2" | tee -a "$TX"
  log "-- skip lines so far: $(grep -c 'Skipping' "$LOG")"
}

if [ "${EXTRA_ONLY:-0}" = 1 ]; then for _ in $(seq "$SAMPLES"); do sleep 60; sample; done; exit 0; fi
STOPPED=0; START=$(date +%s); N=0
while [ "$N" -lt "$SAMPLES" ]; do
  if [ "$STOPPED" = 0 ] && [ -n "$R2" ]; then
    QUEUED=$(q "select count(*) from orchestration_v2_projection_runs where thread_id='$R2' and status='queued'" 2>/dev/null || echo 0)
    if [ "${QUEUED:-0}" -ge 1 ]; then log "R2: $(date -u +%T) queued=$QUEUED -> thread.stop: $(stop_thread)"; STOPPED=1; fi
  fi
  if [ $(( $(date +%s) - START )) -ge $(( (N + 1) * 60 )) ]; then N=$((N + 1)); sample; fi
  sleep 5
done
log "## server log skip lines ($B)"
grep -E 'Skipping|schedule' "$LOG" | grep -i skip | tee -a "$TX"
grep -n 'scheduledTaskId\|taskId' "$LOG" | grep -i skip | head -40 | tee -a "$TX"
log "# done $(date -u +%FT%TZ)"
