#!/usr/bin/env bash
# Claude Code 协作 supervisor（单 agent 版）
#
# 在容器内运行一个 Claude Code headless 进程（claude -p）完成 /blackboard/task.md 中的任务。
# agent 的工作区为 /blackboard/workspace，产物与轮次日志保留在黑板上；
# 任务完成（agent 用 blackboard MCP 工具创建 DONE）后，supervisor 把结果
# 以结构化 JSON（result.json）落盘并打印一行摘要，供外部调度方直接解析。
#
# 与 pi-coop supervisor 的关键差异（Claude Code CLI 与 pi 不同）：
#   1. 模型接入：Claude Code 用 ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN /
#      ANTHROPIC_MODEL 环境变量（不是 --provider/--model flag）。
#      第三方 Anthropic 兼容端点（如 DeepSeek）需 ANTHROPIC_API_KEY="" 触发
#      回退到 ANTHROPIC_AUTH_TOKEN，且 ANTHROPIC_BASE_URL 不带 /v1（SDK 自己追加）。
#   2. 工具注册：Claude Code 用 MCP server（--mcp-config），blackboard 工具
#      通过 blackboard-server.mjs 提供，工具命名空间为 mcp__blackboard__*。
#   3. 输出格式：claude -p --output-format json 输出单个 JSON，含 session_id
#      字段，直接用 jq 提取即可（无需像 pi 那样解析 NDJSON 首行）。
#   4. session 恢复：claude -p --resume <id>，跨轮保持上下文。
#   5. 权限：--dangerously-skip-permissions 跳过所有权限弹窗（headless 必需），
#      且必须以非 root 用户运行（node:22-alpine 自带 node 用户，uid 1000）。
#
# 环境变量（均由外部调用方传入，与 pi-coop 完全一致）：
#   MODEL       模型名，如 deepseek-v4-flash            （必填）
#   BASE_URL    Anthropic 兼容 API base-url              （必填）
#   API_KEY     API key（注入为 ANTHROPIC_AUTH_TOKEN）   （必填）
#   PROTOCOL    协议（仅 anthropic 受 Claude Code 支持） （可选）
#   TASK        任务描述                                 （必填；或挂载 $BB/task.md）
#   BLACKBOARD  黑板目录                                 （默认 /blackboard）
#   ROUND_MAX   最大轮次                                 （默认 10）
#   TIMEOUT     单轮超时秒数，0=不超时                   （默认 600）
#   FAIL_MODE   agent 失败时：stop=立即退出（默认）| continue=继续下一轮
set -u

BB="${BLACKBOARD:-/blackboard}"
PROMPTS="${PROMPTS:-/prompts}"
MCP_CONFIG="${MCP_CONFIG:-/mcp-servers.json}"
ROUND_MAX="${ROUND_MAX:-10}"
TIMEOUT="${TIMEOUT:-600}"
FAIL_MODE="${FAIL_MODE:-stop}"

log() { echo "[supervisor] $*"; }

# emit_result 把任务结果以结构化 JSON 写入 $BB/result.json 并打印一行摘要。
# status: solved | unsolved | error | timeout
emit_result() {
  local status="$1" summary="$2" code="$3"
  local flag="" artifacts_json="[]" summary_json='""' flag_json='""'

  # flag 优先从 summary 提取，其次在 workspace 产物中全量检索
  if [ -n "$summary" ]; then
    flag=$(printf '%s' "$summary" | grep -oE 'flag\{[^}]+\}' | head -1)
  fi
  if [ -z "$flag" ] && [ -d "$BB/workspace" ]; then
    flag=$(grep -rhoE 'flag\{[^}]+\}' "$BB/workspace" 2>/dev/null | head -1)
  fi

  # 产物清单：workspace 下的全部文件（最多 50 个），经 python3 转义为 JSON 数组
  if [ -d "$BB/workspace" ]; then
    artifacts_json=$(
      cd "$BB/workspace" && find . -type f 2>/dev/null | sed 's|^\./||' | head -50 |
      python3 -c 'import json,sys; print(json.dumps([l.rstrip("\n") for l in sys.stdin]))' 2>/dev/null
    )
    [ -z "$artifacts_json" ] && artifacts_json="[]"
  fi

  # summary 截断到 4000 字符并经 python3 转义，防止引号/换行破坏 JSON
  summary_json=$(printf '%s' "$summary" | head -c 4000 | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
  [ -z "$summary_json" ] && summary_json='""'
  flag_json=$(printf '%s' "$flag" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null)
  [ -z "$flag_json" ] && flag_json='""'

  cat > "$BB/result.json" <<EOF
{
  "status": "$status",
  "exit_code": $code,
  "summary": $summary_json,
  "flag": $flag_json,
  "artifacts": $artifacts_json
}
EOF
  log "===== 协作结束 ====="
  log "status=$status exit_code=$code"
  [ -n "$flag" ] && log "flag=$flag"
  log "结构化结果已写入：$BB/result.json"
}

# ---- 前置校验 ----
if [ -z "${MODEL:-}" ] || [ -z "${BASE_URL:-}" ] || [ -z "${API_KEY:-}" ]; then
  echo "错误：必须提供 MODEL / BASE_URL / API_KEY 环境变量" >&2
  exit 2
fi
if [ -z "${TASK:-}" ] && [ ! -f "$BB/task.md" ]; then
  echo "错误：请通过 TASK 环境变量或挂载 $BB/task.md 提供任务" >&2
  exit 2
fi
if [ -f "$BB/DONE" ]; then
  log "黑板已有完成标记，如需重新开始请删除 $BB/DONE"
  exit 0
fi

# ---- 初始化黑板 ----
mkdir -p "$BB/workspace" "$BB/logs" "$BB/sessions"
if [ -n "${TASK:-}" ]; then
  printf '%s\n' "$TASK" > "$BB/task.md"
fi
# 协议注入：agent 的 cwd 是 $BB/workspace，AGENTS.md 注入链从 cwd 起，
# Claude Code 会自动加载 cwd 下的 CLAUDE.md（AGENTS.md 兼容），因此把协议
# 副本放进工作区（勿修改，它是每轮系统提示的一部分）
cp "$PROMPTS/AGENTS.md" "$BB/workspace/CLAUDE.md"
cp "$PROMPTS/AGENTS.md" "$BB/workspace/AGENTS.md"
log "黑板初始化完成：$BB"
log "任务：$(head -c 200 "$BB/task.md")"

# ---- 导出 Claude Code 模型配置环境变量 ----
# 关键：ANTHROPIC_API_KEY 必须设为空字符串（而非 unset），Claude Code 才会
# 回退到 ANTHROPIC_AUTH_TOKEN。BASE_URL 不带 /v1（SDK 自动追加 /v1/messages）。
export ANTHROPIC_BASE_URL="$BASE_URL"
export ANTHROPIC_AUTH_TOKEN="$API_KEY"
export ANTHROPIC_API_KEY=""
export ANTHROPIC_MODEL="$MODEL"
# 把 opus/sonnet/haiku 三个别名都映射到目标模型，避免别名解析失败。
export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$MODEL"
# 禁用非必要流量（遥测/登录检测），第三方端点必需。
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1

# ---- 单轮运行：claude -p --output-format json，从 JSON 提取 session_id ----
run_agent() {
  local round="$1"
  local session_file="$BB/sessions/agent.session"
  local task_prompt="轮次 $round 开始。先读取 /blackboard/task.md 中的任务，检查工作区已有产物与 DONE 状态，然后继续推进任务。全部工作完成、交付物完整时，用 blackboard 工具 done 创建完成标记（summary 写最终交付总结，含关键结果/flag/提交响应/产物清单）。"
  local args=(-p "$task_prompt"
    --output-format json
    --mcp-config "$MCP_CONFIG"
    --allowedTools "mcp__blackboard__*,Read,Write,Edit,Bash"
    --append-system-prompt-file "$PROMPTS/agent.md"
    --dangerously-skip-permissions)
  if [ -s "$session_file" ]; then
    args+=(--resume "$(cat "$session_file")")
  fi

  export ROUND="$round" NAME="agent" BB="$BB"
  local logfile="$BB/logs/round-$round.log"
  log "第 $round 轮开始运行 agent（日志：$logfile）"
  # Claude Code 用 --cwd / 进程 cwd 作工作区，先进入 $BB/workspace
  if [ "$TIMEOUT" -gt 0 ] 2>/dev/null; then
    (cd "$BB/workspace" && timeout "$TIMEOUT" claude "${args[@]}") > "$logfile" 2>&1
  else
    (cd "$BB/workspace" && claude "${args[@]}") > "$logfile" 2>&1
  fi
  local rc=$?

  # 从 --output-format json 的结果提取 session_id 供下一轮 --resume
  # JSON 形如 {"type":"result","session_id":"<uuid>",...}
  local sid
  sid=$(python3 -c '
import json, sys
try:
    d = json.loads(sys.stdin.read())
    if d.get("type") == "result":
        print(d.get("session_id", ""))
except Exception:
    pass
' < "$logfile" 2>/dev/null)
  if [ -n "$sid" ]; then
    printf '%s' "$sid" > "$session_file"
  fi
  return $rc
}

# ---- 主循环：逐轮运行 agent，直到 DONE 或达到轮次上限 ----
for round in $(seq 1 "$ROUND_MAX"); do
  [ -f "$BB/DONE" ] && break
  log "===== 第 $round 轮开始 ====="

  run_agent "$round"
  rc=$?

  if [ "$rc" -eq 0 ]; then
    log "第 $round 轮完成"
  else
    log "第 $round 轮失败（exit=$rc），日志见 $BB/logs/round-$round.log"
    if [ "$FAIL_MODE" = "stop" ]; then
      # 兜底：agent 虽超时/失败，但黑板已有提交成功证据（workspace 内 *.md 含 correct:true）
      # → 视为完成正常退出，避免"flag 已提交却因未写 DONE 被强杀（exit 143）"。
      _done=0
      for _f in "$BB"/workspace/*.md "$BB"/messages/*.md; do
        [ -f "$_f" ] && grep -q '"correct":true' "$_f" && _done=1 && break
      done
      if [ "$_done" = "1" ]; then
        log "检测到提交成功证据，自动标记完成"
        { echo "Task finished (auto-detected submit success)"; } > "$BB/DONE"
        emit_result solved "$(cat "$BB/DONE")" 0
        exit 0
      fi
      if [ "$rc" -eq 124 ]; then
        emit_result timeout "" "$rc"
      else
        emit_result error "agent 退出码 $rc，日志见 logs/round-$round.log" "$rc"
      fi
      exit "$rc"
    fi
  fi
done

if [ -f "$BB/DONE" ]; then
  log "检测到完成标记，任务结束"
  emit_result solved "$(cat "$BB/DONE")" 0
  exit 0
fi

log "达到最大轮次 $ROUND_MAX 仍未完成，请检查黑板产物与日志"
emit_result unsolved "" 1
exit 1
