#!/usr/bin/env bash # Dispatch to a standard live Herdr agent, with conservative failover. set -euo pipefail usage() { cat <<'EOF' 用法: scripts/herdr-agent-dispatch.sh --explain scripts/herdr-agent-dispatch.sh [--timeout-ms N] lane: governance | critical-review | implementation | ui | test | fast-check | long-context EOF } route_for() { case "$1" in governance) printf '%s\n' orchestrator_codex context_kimi implementer_cursor reserve_claude ;; critical-review) printf '%s\n' orchestrator_codex reserve_claude context_kimi ;; implementation) printf '%s\n' implementer_cursor implementer_cline verifier_antigravity orchestrator_codex ;; ui) printf '%s\n' implementer_cursor verifier_antigravity implementer_cline ;; test) printf '%s\n' implementer_cursor implementer_cline verifier_antigravity ;; fast-check) printf '%s\n' verifier_antigravity implementer_cline implementer_cursor ;; long-context) printf '%s\n' context_pi context_kimi orchestrator_codex ;; *) return 1 ;; esac } if [[ "${1:-}" == "--explain" ]]; then [[ $# -eq 2 ]] || { usage >&2; exit 2; } if ! route_for "$2"; then printf '[错误] 未知 lane: %s\n' "$2" >&2 exit 2 fi exit 0 fi timeout_ms=120000 if [[ "${1:-}" == "--timeout-ms" ]]; then [[ $# -ge 4 ]] || { usage >&2; exit 2; } timeout_ms="$2" shift 2 fi if [[ $# -ne 2 ]]; then usage >&2 exit 2 fi lane="$1" prompt="$2" if ! routes="$(route_for "${lane}")"; then printf '[错误] 未知 lane: %s\n' "${lane}" >&2 exit 2 fi if [[ ! "${timeout_ms}" =~ ^[1-9][0-9]*$ ]]; then printf '[错误] timeout 必须是正整数毫秒。\n' >&2 exit 2 fi if [[ "${HERDR_ENV:-}" != "1" ]]; then printf '[错误] 当前进程不在 Herdr 管理的 pane 中。\n' >&2 exit 1 fi command -v jq >/dev/null 2>&1 || { printf '[错误] 缺少 jq。\n' >&2; exit 1; } repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" if [[ -z "${repo_root}" || ! -f "${repo_root}/AGENTS.md" || ! -f "${repo_root}/PROJECT.md" ]]; then printf '[错误] 必须从 Agent-System 任务 worktree 派发。\n' >&2 exit 1 fi branch="$(git branch --show-current)" if [[ "${branch}" != feature/* && "${branch}" != fix/* ]]; then printf '[错误] 只允许从 feature/* 或 fix/* 分支派发,当前为 %s。\n' "${branch}" >&2 exit 1 fi current_cwd="$(realpath -m "${repo_root}")" agent_json="$(herdr agent list)" dispatch_id="${BASHPID:-$$}-${SECONDS}" retry_prefix="HERDR_RETRY:${dispatch_id}" protocol="$(printf '\n\n[Herdr failover protocol]\n只有在尚未写文件、未运行有副作用命令,且仅因额度、认证或客户端故障无法开始时,回复独占一行 %s:quota、%s:auth 或 %s:client。已经开始工作时不得返回该标记;请写 handoff 并说明当前状态。' "${retry_prefix}" "${retry_prefix}" "${retry_prefix}")" while IFS= read -r target; do row="$(jq -c --arg name "${target}" '.result.agents[] | select(.name == $name)' <<<"${agent_json}" | head -n 1)" if [[ -z "${row}" ]]; then printf '[跳过] %s 未在线。\n' "${target}" >&2 continue fi status="$(jq -r '.agent_status' <<<"${row}")" agent_cwd="$(jq -r '.cwd' <<<"${row}")" if [[ "${agent_cwd}" != "${current_cwd}" ]]; then printf '[跳过] %s cwd=%s,不是当前任务 worktree。\n' "${target}" "${agent_cwd}" >&2 continue fi if [[ "${status}" != "idle" && "${status}" != "done" ]]; then printf '[跳过] %s 状态为 %s。\n' "${target}" "${status}" >&2 continue fi printf '[派发] lane=%s target=%s\n' "${lane}" "${target}" >&2 set +e result="$(herdr agent prompt "${target}" "${prompt}${protocol}" --wait --timeout "${timeout_ms}" 2>&1)" rc=$? set -e if [[ ${rc} -ne 0 ]]; then if rg -q 'agent_blocked|agent_not_found|agent_not_ready' <<<"${result}"; then printf '[降级] %s 在投递前不可用。\n' "${target}" >&2 continue fi printf '%s\n' "${result}" >&2 printf '[停止] 结果可能已投递;为避免重复执行,不自动切换备援。\n' >&2 exit 75 fi transcript="$(herdr agent read "${target}" --source recent-unwrapped --lines 120 2>/dev/null || true)" if rg -q "^${retry_prefix}:(quota|auth|client)$" <<<"${transcript}"; then printf '[降级] %s 明确报告写入前失败。\n' "${target}" >&2 agent_json="$(herdr agent list)" continue fi printf '%s\n' "${result}" exit 0 done <<<"${routes}" printf '[错误] lane=%s 无可用实例;请用 herdr-agent-start.sh 启动标准实例。\n' "${lane}" >&2 exit 69