#!/usr/bin/env bash # 用法:start.sh # 在目前 clone 旁建 worktree、開 Herdr tab、啟動 claude、送提示詞並確認 agent 有反應、掛 in-progress 標籤。 # 送出提示詞後 15 秒內沒觀察到 working 或 blocked 就視為失敗並回滾(關 tab、移除 worktree、刪本次新建的分支)。 # 最後一行印出 JSON:{"tab":..,"pane":..,"path":..,"resumed":..,"state":"working"|"blocked"} source "$(dirname "$0")/lib.sh" REPO="${1:?}"; N="${2:?用法:start.sh }" LINK="https://gitea.jsc.idv.tw/$REPO/issues/$N" test "${HERDR_ENV:-}" = 1 || die "不在 Herdr 裡(HERDR_ENV≠1)" CLONE="$(clone_root_for "$REPO")" BRANCH="issue-$N" WT="$(worktree_path "$N" "$CLONE")" if [ "$(agent_status "$N")" != missing ]; then die "issue-$N 的 agent 還活著(tab $(tab_for_issue "$N")),不重複啟動" fi [ "$(issue_state "$REPO" "$N")" = open ] || die "#$N 不是 open" BLOCKERS="$(open_blockers "$REPO" "$N")" || exit 1 [ -z "$BLOCKERS" ] || die "#$N 被還沒關閉的議題擋住:$(echo "$BLOCKERS" | tr '\n' ' ')(Gitea 依賴設定)。等它們關閉,或到 Gitea 拿掉依賴再啟動" git -C "$CLONE" fetch --quiet origin RESUMED=false if git -C "$CLONE" show-ref --verify --quiet "refs/heads/$BRANCH"; then RESUMED=true if ! git -C "$CLONE" worktree list --porcelain | grep -qx "worktree $WT"; then git -C "$CLONE" worktree add --quiet "$WT" "$BRANCH" fi else git -C "$CLONE" worktree add --quiet -b "$BRANCH" "$WT" origin/main fi TAB="" # 回滾到跟沒跑過一樣:關 tab、移除 worktree;分支只在本次新建時刪除。 rollback() { [ -n "$TAB" ] && herdr tab close "$TAB" >/dev/null 2>&1 || true if git -C "$CLONE" worktree list --porcelain | grep -qx "worktree $WT"; then git -C "$CLONE" worktree remove --force "$WT" >/dev/null 2>&1 || true fi $RESUMED || git -C "$CLONE" branch -D "$BRANCH" >/dev/null 2>&1 || true } TAB_JSON="$(herdr tab create --workspace "${HERDR_WORKSPACE_ID:?}" --cwd "$WT" --label "#$N" --no-focus)" TAB="$(jq_py 'print(find(d,"tab")["tab_id"])' <<<"$TAB_JSON")" PANE="$(jq_py 'print(find(d,"root_pane")["pane_id"])' <<<"$TAB_JSON")" START_ERR="$(mktemp)" for _ in 1 2 3 4 5 6; do if herdr agent start "issue-$N" --kind claude --pane "$PANE" --timeout 90000 -- --model "$MODEL" >/dev/null 2>"$START_ERR"; then rm -f "$START_ERR"; break fi sleep 2 done if [ -f "$START_ERR" ]; then cat "$START_ERR" >&2; rm -f "$START_ERR" rollback die "agent 啟動失敗,已回滾" fi # git 身分:與 tea 預設使用者一致(只設在這個 worktree,不動 clone 的設定) GIT_USER="$(command tea login list --output json | jq_py "print(next((l['user'] for l in d if l['name']=='$HERDR_ISSUE_LOGIN'),''))")" GIT_EMAIL="$(tea api "/user" | jq_py 'print(d.get("email") or "")' 2>/dev/null || true)" git -C "$CLONE" config extensions.worktreeConfig true # agent 在 worktree 裡自己跑的 git(push、fetch)也要有帳密:把 helper 設到 clone 的本地設定(worktree 共用),只對這個主機生效 GIT_HOST_URL="$(command tea login list --output json | jq_py "print(next((l['url'] for l in d if l['name']=='$HERDR_ISSUE_LOGIN'),''))")" if [ -n "$GIT_HOST_URL" ]; then git -C "$CLONE" config --local --replace-all "credential.$GIT_HOST_URL.helper" "$SCRIPTS/git-credential.sh" fi git -C "$WT" config --worktree http.sslVerify false git -C "$WT" config --worktree user.name "$GIT_USER" [ -z "$GIT_EMAIL" ] || git -C "$WT" config --worktree user.email "$GIT_EMAIL" if $RESUMED; then RESUME_LINE="分支已有先前進度,先看 git log 和議題核取方塊再接續。"; else RESUME_LINE=""; fi PROMPT="$(sed -e "s|{LINK}|$LINK|g" -e "s|{REPO}|$REPO|g" -e "s|{N}|$N|g" -e "s|{S}|$SCRIPTS|g" -e "s|{LOGIN}|$HERDR_ISSUE_LOGIN|g" -e "s|{RESUME}|$RESUME_LINE|" "$SCRIPTS/prompt.txt")" # 送出後必須觀察到 agent 進入 working 或 blocked;否則 Herdr 回非 0(agent_prompt_stalled、timeout、agent_blocked 等)。 PROMPT_ERR="$(mktemp)" if PROMPT_OUT="$(herdr agent prompt "issue-$N" "$PROMPT" --wait --until working --until blocked --timeout 15000 2>"$PROMPT_ERR")"; then rm -f "$PROMPT_ERR" else echo "gitea-issue: 送出提示詞後 agent 沒有反應,已回滾" >&2 cat "$PROMPT_ERR" >&2; rm -f "$PROMPT_ERR" rollback exit 1 fi STATE="$(jq_py 'print(find(d,"agent_status") or "")' <<<"$PROMPT_OUT" 2>/dev/null || true)" case "$STATE" in working|blocked) ;; *) STATE="$(agent_status "$N")" ;; esac ensure_label "$REPO" tea issues edit "$N" --repo "$REPO" --remove-labels "$LABEL" >/dev/null 2>&1 || true tea issues edit "$N" --repo "$REPO" --add-labels "$LABEL" >/dev/null printf '{"tab":"%s","pane":"%s","path":"%s","resumed":%s,"state":"%s"}\n' "$TAB" "$PANE" "$WT" "$RESUMED" "$STATE"