#!/usr/bin/env bash
# fleet-worker - run a cheap headless Claude Code worker on a non-Anthropic model.
#
# Thin launcher: points Claude Code (`claude -p`) at any Anthropic-compatible
# endpoint (default: z.ai / GLM) via env, inside an ISOLATED config dir, then
# execs. The result is a headless agent with Claude Code's full tool harness
# (Read/Write/Edit/Bash/Glob/Grep/Task) but a cheaper "grunt" brain - fanned
# out and verified by an Opus orchestrator. See ../SKILL.md.
#
# Usage:   fleet-worker [--help|--capabilities] [claude-flags...] "PROMPT"
#          fleet-worker [claude-flags...] < prompt.txt
# Input:   prompt as the final positional arg, or piped on stdin
# Output:  whatever `claude -p` emits (text, or --output-format json/stream-json)
# Stderr:  claude's own diagnostics; this launcher is silent on success
# Exit:    0 ok; 1 worker/API error; 2 usage; 5 missing dep / no key resolved
#
# Config (env, all optional - defaults target the z.ai GLM Coding Plan):
#   FLEET_WORKER_CONFIG_DIR   isolated CLAUDE_CONFIG_DIR (default ~/.fleet-worker/cfg)
#   FLEET_WORKER_BASE_URL     Anthropic-compatible endpoint (default z.ai)
#   FLEET_WORKER_MODEL        main model      (default glm-5.3)
#   FLEET_WORKER_SMALL_MODEL  background model (default glm-4.5-air)
#   FLEET_WORKER_EFFORT       seeded effortLevel (default high)
#   FLEET_WORKER_CLAUDE_BIN   claude binary to exec (default: claude); lets a
#                             caller that validated an override run that same
#                             binary here (fleetflow doctor/spawn parity)
#   FLEET_WORKER_PERMISSION_MODE  worker --permission-mode (default bypassPermissions).
#                             Use dontAsk + an allowlist to spawn FROM an auto-mode
#                             orchestrator - a bypassPermissions launch is hard-denied
#                             there as "Create Unsafe Agents". See ../SKILL.md.
# Key resolution order (the key is never printed; owned by fleet-lib.sh):
#   1. ANTHROPIC_AUTH_TOKEN (already exported)            -> used as-is
#   2. FLEET_WORKER_KEYRING_SERVICE + FLEET_WORKER_KEYRING_KEY -> `keyring get svc key`
#      (an empty keyring entry falls through to 3)
#   3. ZHIPU_API_KEY / GLM_API_KEY                         -> used as-is
#
# Examples:
#   fleet-worker "List the TODOs under src/ and summarize them"
#   fleet-worker --output-format json "Refactor utils.py" | fleet-collect.sh
#   FLEET_WORKER_CONFIG_DIR=~/.fleet-worker/cfg-a fleet-worker --output-format json "task a"
set -uo pipefail

EXIT_OK=0; EXIT_USAGE=2; EXIT_MISSING_DEP=5

case "${1:-}" in
  -h|--help) awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "$0"; exit "$EXIT_OK" ;;
  --capabilities)
    # Machine-readable handshake for callers (fleetflow ff-doctor/ff-spawn):
    # one capability token per line, exit 0. APPEND-ONLY - callers match
    # exact tokens, so never rename one. Handled BEFORE key resolution so a
    # capability probe needs no auth and can never reach the exec. This
    # replaces textual grep, which a comment could spoof (codex review,
    # 2026-08-25).
    printf 'claude-bin-override\n'; exit "$EXIT_OK" ;;
esac

# --- Auth isolation (LOAD-BEARING; see references/fleet-worker-spec.md sec 4) ------
# A dedicated config dir means the worker inherits NO host Claude.ai OAuth
# account or forceLoginMethod, so our token is the only credential and actually
# reaches the endpoint - otherwise a host subscription token wins and the
# endpoint rejects it with 401.
GLM_CFG="${FLEET_WORKER_CONFIG_DIR:-$HOME/.fleet-worker/cfg}"
if ! mkdir -p "$GLM_CFG" 2>/dev/null; then
  echo "fleet-worker: cannot create config dir: $GLM_CFG" >&2
  exit "$EXIT_MISSING_DEP"
fi
if [ ! -f "$GLM_CFG/settings.json" ]; then
  printf '{ "hooks": {}, "effortLevel": "%s" }\n' "${FLEET_WORKER_EFFORT:-high}" > "$GLM_CFG/settings.json"
fi
export CLAUDE_CONFIG_DIR="$GLM_CFG"

# --- Shared defaults + key chain (fleet-lib.sh, also sourced by the doctor) --
__fw_lib="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/fleet-lib.sh"
if [ ! -f "$__fw_lib" ]; then
  echo "fleet-worker: missing $__fw_lib (re-run install)" >&2; exit "$EXIT_MISSING_DEP"
fi
. "$__fw_lib"

# --- Resolve the API key (never echoed) --------------------------------------
if ! __key="$(fw_resolve_key)"; then
  cat >&2 <<'MSG'
fleet-worker: no API key resolved. Provide one of:
  - export ANTHROPIC_AUTH_TOKEN=<key>
  - export FLEET_WORKER_KEYRING_SERVICE=<svc> FLEET_WORKER_KEYRING_KEY=<name>   (uses `keyring get`)
  - export ZHIPU_API_KEY=<key>    (or GLM_API_KEY)
MSG
  exit "$EXIT_MISSING_DEP"
fi

# FLEET_WORKER_CLAUDE_BIN: the claude binary to exec (default: claude).
# fleetflow's ff-spawn forwards its FLEETFLOW_CLAUDE_BIN here so the binary the
# doctor validated is the one the lane runs - a hardcoded `claude` let a valid
# override pass preflight and then fail spawn (codex review, 2026-08-25).
CLAUDE_BIN="${FLEET_WORKER_CLAUDE_BIN:-claude}"
command -v "$CLAUDE_BIN" >/dev/null 2>&1 || {
  echo "fleet-worker: '$CLAUDE_BIN' (Claude Code) not found on PATH" >&2; exit "$EXIT_MISSING_DEP"; }

# --- Endpoint + model mapping ------------------------------------------------
export ANTHROPIC_BASE_URL="${FLEET_WORKER_BASE_URL:-$FW_DEFAULT_BASE_URL}"
export ANTHROPIC_AUTH_TOKEN="$__key"
export ANTHROPIC_DEFAULT_OPUS_MODEL="${FLEET_WORKER_MODEL:-$FW_DEFAULT_MODEL}"
export ANTHROPIC_DEFAULT_SONNET_MODEL="${FLEET_WORKER_MODEL:-$FW_DEFAULT_MODEL}"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="${FLEET_WORKER_SMALL_MODEL:-$FW_DEFAULT_SMALL_MODEL}"

# --- Permission mode ---------------------------------------------------------
# Default bypassPermissions keeps back-compat; safety = the cage (isolated worktree
# + config + the orchestrator's merge gate), not the prompt. But when this worker is
# spawned FROM a parent session in auto mode, a bypassPermissions launch is hard-denied
# by the auto-mode classifier as "Create Unsafe Agents" - no allow-rule saves it. The
# fix is a gated but still-non-interactive mode: dontAsk + an allowlist. Full model in
# ../SKILL.md "Permission posture" and docs/AUTO-MODE-CLASSIFIER.md.
PERM_MODE="${FLEET_WORKER_PERMISSION_MODE:-bypassPermissions}"
case "$PERM_MODE" in
  default|acceptEdits|plan|auto|dontAsk|bypassPermissions) ;;
  *) echo "fleet-worker: invalid FLEET_WORKER_PERMISSION_MODE: $PERM_MODE" >&2
     echo "  (expected: default|acceptEdits|plan|auto|dontAsk|bypassPermissions)" >&2
     exit "$EXIT_USAGE" ;;
esac
# dontAsk auto-denies anything not allow-listed; a worker with no allowlist does nothing.
if [ "$PERM_MODE" = "dontAsk" ]; then
  case " $* " in
    *" --allowedTools "*|*" --allowed-tools "*) : ;;
    *) grep -q '"allow"' "$GLM_CFG/settings.json" 2>/dev/null || \
       echo "fleet-worker: permission-mode dontAsk with no allowlist - worker will auto-deny most tools; pass --allowedTools \"...\" or set permissions.allow in $GLM_CFG/settings.json" >&2 ;;
  esac
fi

# `--model sonnet` resolves to $FLEET_WORKER_MODEL via the mapping above.
# `</dev/null` avoids the ~3s "no stdin data received" wait when the prompt is
# passed as an argument.
exec "$CLAUDE_BIN" -p --model sonnet --permission-mode "$PERM_MODE" "$@" </dev/null
