feat: 完成 F2R 重构主线,完善 TLUSTY 初始模型与 LTE 初始化,并补齐 SYNSPEC 不透明度/旋转卷积链路
This commit is contained in:
+133
-52
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
set -u
|
||||
|
||||
# --- 配置变量 ---
|
||||
WORK_DIR="/home/dckj/SpectraRust"
|
||||
@@ -8,92 +9,172 @@ CMD_PROMPT="使用 codegraph-guide skill 继续执行重构任务。"
|
||||
# 状态文件
|
||||
PHASE_FILE="${WORK_DIR}/.f2r_phase"
|
||||
COMPLETE_FILE="${WORK_DIR}/.f2r_complete"
|
||||
RATE_LIMIT_FILE="${WORK_DIR}/.f2r_rate_limit"
|
||||
RATE_LIMIT_FILE="${WORK_DIR}/.f2r_rate_limit" # 内容:退避到期 epoch 秒
|
||||
FAIL_COUNT_FILE="${WORK_DIR}/.f2r_fail_count" # 内容:连续失败次数
|
||||
TASKS_FILE="${WORK_DIR}/.f2r_tasks"
|
||||
LOCK_FILE="${WORK_DIR}/.f2r.lock"
|
||||
|
||||
# 日志文件路径
|
||||
# 退避参数(秒)
|
||||
BACKOFF_529=900 # 529 模型过载(临时性):15 分钟短退避
|
||||
BACKOFF_429_FALLBACK=3600 # 429 无法解析重置时间时:默认 1 小时
|
||||
BACKOFF_MODEL_ERR=1800 # 模型不存在:30 分钟
|
||||
BACKOFF_CIRCUIT=7200 # 连续失败触发熔断:2 小时
|
||||
MAX_CONSEC_FAIL=6 # 连续失败熔断阈值
|
||||
|
||||
# 日志(export TZ 确保子命令 / date 一致用 UTC+8)
|
||||
export TZ="Asia/Shanghai"
|
||||
LOG_FILE="${WORK_DIR}/logs/claude_$(date +%Y%m%d_%H%M%S).log"
|
||||
CRON_LOG="${WORK_DIR}/logs/cron.log"
|
||||
CRON_LOG_MAX=5242880 # cron.log 归档阈值:5MB
|
||||
|
||||
log() { echo "[$(date '+%F %T')] $*"; }
|
||||
|
||||
# --- 1. 环境检查 ---
|
||||
if [ ! -d "$WORK_DIR" ]; then
|
||||
echo "❌ 错误: 工作目录不存在: $WORK_DIR"
|
||||
log "❌ 错误: 工作目录不存在: $WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$CMD_PATH" ]; then
|
||||
echo "❌ 错误: 命令不存在或不可执行: $CMD_PATH"
|
||||
log "❌ 错误: 命令不存在或不可执行: $CMD_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 2. 完成检测 ---
|
||||
if [ -f "$COMPLETE_FILE" ]; then
|
||||
echo "✅ 重构已标记为完成 ($(cat "$COMPLETE_FILE")),跳过。"
|
||||
echo "如需重新启动,请删除 ${COMPLETE_FILE}"
|
||||
log "✅ 重构已标记为完成 ($(cat "$COMPLETE_FILE" 2>/dev/null)),跳过。如需重启请删除 ${COMPLETE_FILE}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 3. 429 限流退避 ---
|
||||
# --- 3. 并发锁(flock,无竞态,替代 pgrep 检测)---
|
||||
exec 200>"$LOCK_FILE"
|
||||
if ! flock -n 200; then
|
||||
log "⚠️ 已有实例在运行,跳过。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 4. 峰时段(UTC+8 14:00~18:00)禁用执行 ---
|
||||
CURRENT_HOUR=$(date +%H)
|
||||
if [ "$CURRENT_HOUR" -ge 14 ] && [ "$CURRENT_HOUR" -lt 18 ]; then
|
||||
log "⏰ 高峰期 14:00~18:00 (UTC+8),跳过。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 5. 限流退避(epoch 秒)---
|
||||
if [ -f "$RATE_LIMIT_FILE" ]; then
|
||||
LIMIT_UNTIL=$(cat "$RATE_LIMIT_FILE" 2>/dev/null)
|
||||
if [ -n "$LIMIT_UNTIL" ]; then
|
||||
# 将 "2026-06-08 09:10:18" 格式转换为 epoch
|
||||
RESET_EPOCH=$(date -d "$LIMIT_UNTIL" +%s 2>/dev/null)
|
||||
NOW_EPOCH=$(date +%s)
|
||||
if [ -n "$RESET_EPOCH" ] && [ "$NOW_EPOCH" -lt "$RESET_EPOCH" ]; then
|
||||
REMAINING=$(( (RESET_EPOCH - NOW_EPOCH) / 60 ))
|
||||
echo "⏳ API 限流中,还需等待 ${REMAINING} 分钟(重置于 ${LIMIT_UNTIL}),跳过。"
|
||||
exit 0
|
||||
else
|
||||
# 已过重置时间,清除标记
|
||||
rm -f "$RATE_LIMIT_FILE"
|
||||
echo "🔓 限流已重置,继续执行。"
|
||||
fi
|
||||
NOW_EPOCH=$(date +%s)
|
||||
if [[ "$LIMIT_UNTIL" =~ ^[0-9]+$ ]] && [ "$NOW_EPOCH" -lt "$LIMIT_UNTIL" ]; then
|
||||
REMAINING=$(( (LIMIT_UNTIL - NOW_EPOCH) / 60 ))
|
||||
log "⏳ 退避中,还需 ${REMAINING} 分钟(至 $(date -d "@$LIMIT_UNTIL" '+%F %T')),跳过。"
|
||||
exit 0
|
||||
else
|
||||
rm -f "$RATE_LIMIT_FILE"
|
||||
log "🔓 退避已到期,继续执行。"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 4. 检查并发进程 ---
|
||||
RUNNING_PIDS=$(pgrep -f "claude.*--print" 2>/dev/null)
|
||||
if [ -n "$RUNNING_PIDS" ]; then
|
||||
echo "⚠️ 检测到已有调度任务在运行 (PID: $RUNNING_PIDS),退出脚本。"
|
||||
exit 1
|
||||
# --- 6. cron.log 轮转(超过阈值则归档,不删)---
|
||||
if [ -f "$CRON_LOG" ]; then
|
||||
CRON_SIZE=$(wc -c < "$CRON_LOG" 2>/dev/null || echo 0)
|
||||
if [ "${CRON_SIZE:-0}" -gt "$CRON_LOG_MAX" ]; then
|
||||
mv "$CRON_LOG" "${CRON_LOG}.$(date +%Y%m%d_%H%M%S).bak"
|
||||
log "📦 cron.log 超过 ${CRON_LOG_MAX}B,已归档。"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 5. 启动进程 ---
|
||||
cd "$WORK_DIR" || exit 1
|
||||
# --- 7. 启动 claude ---
|
||||
cd "$WORK_DIR" || { log "❌ 无法进入 ${WORK_DIR}"; exit 1; }
|
||||
|
||||
nohup "$CMD_PATH" --permission-mode bypassPermissions --print "$CMD_PROMPT" \
|
||||
< /dev/null > "$LOG_FILE" 2>&1 &
|
||||
CURRENT_PID=$!
|
||||
|
||||
# --- 6. 等待完成并分析结果 ---
|
||||
# --print 模式是同步的,wait 等它结束
|
||||
# --print 同步,等待结束
|
||||
wait "$CURRENT_PID" 2>/dev/null
|
||||
EXIT_CODE=$?
|
||||
LOG_SIZE=$(wc -c < "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
|
||||
# --- 7. 后处理:检测 429 和完成标记 ---
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
# 检测 429 限流
|
||||
if grep -q "429" "$LOG_FILE" 2>/dev/null; then
|
||||
# 提取重置时间(格式:已达到 5 小时的使用上限。您的限额将在 2026-06-08 09:10:18 重置)
|
||||
RESET_TIME=$(grep -oP '限额将在 \K[\d-]+ [\d:]+' "$LOG_FILE" 2>/dev/null | head -1)
|
||||
if [ -n "$RESET_TIME" ]; then
|
||||
echo "$RESET_TIME" > "$RATE_LIMIT_FILE"
|
||||
echo "🔴 检测到 429 限流,重置时间: ${RESET_TIME},已记录到 ${RATE_LIMIT_FILE}"
|
||||
fi
|
||||
# --- 8. 错误判定 + 退避 ---
|
||||
# 写入退避到期 epoch
|
||||
set_backoff() { # $1=秒 $2=原因
|
||||
local secs="$1" reason="$2"
|
||||
local until_epoch
|
||||
until_epoch=$(( $(date +%s) + secs ))
|
||||
echo "$until_epoch" > "$RATE_LIMIT_FILE"
|
||||
log "🔒 ${reason},退避 ${secs}s(至 $(date -d "@$until_epoch" '+%F %T'))。"
|
||||
}
|
||||
|
||||
# 连续失败计数 +1,超阈值熔断
|
||||
bump_fail() { # $1=原因
|
||||
local reason="$1" n
|
||||
n=$(cat "$FAIL_COUNT_FILE" 2>/dev/null || echo 0)
|
||||
n=$(( n + 1 ))
|
||||
echo "$n" > "$FAIL_COUNT_FILE"
|
||||
log "❌ 失败 #${n}:${reason} | 退出码 ${EXIT_CODE} | 日志 ${LOG_SIZE}B"
|
||||
log " 日志路径: ${LOG_FILE}"
|
||||
if [ "$n" -ge "$MAX_CONSEC_FAIL" ]; then
|
||||
set_backoff "$BACKOFF_CIRCUIT" "连续失败 ${n} 次触发熔断"
|
||||
echo 0 > "$FAIL_COUNT_FILE" # 熔断后清零,避免反复触发
|
||||
fi
|
||||
}
|
||||
|
||||
# 检测模型不存在错误
|
||||
if grep -q "模型不存在" "$LOG_FILE" 2>/dev/null; then
|
||||
echo "❌ 模型不存在错误,暂停 30 分钟。"
|
||||
echo "$(date -d '+30 minutes' '+%Y-%m-%d %H:%M:%S')" > "$RATE_LIMIT_FILE"
|
||||
fi
|
||||
|
||||
# 统计日志大小用于诊断
|
||||
LOG_SIZE=$(wc -c < "$LOG_FILE")
|
||||
echo "✅ 会话完成 | PID: $CURRENT_PID | 退出码: $EXIT_CODE | 日志: ${LOG_SIZE} 字节"
|
||||
echo " 日志路径: $LOG_FILE"
|
||||
else
|
||||
echo "❌ 无日志文件生成"
|
||||
# 异常小/缺失日志:claude 未正常产出,直接计失败(避免被误判为成功)
|
||||
if [ "${LOG_SIZE:-0}" -le 50 ]; then
|
||||
bump_fail "日志异常小或缺失(${LOG_SIZE}B)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 识别错误类型(优先按日志特征,再按退出码)
|
||||
ERR_TYPE=""
|
||||
if grep -qE "限额将在|使用上限|429[^0-9]" "$LOG_FILE" 2>/dev/null; then
|
||||
ERR_TYPE="429"
|
||||
elif grep -q "529 \[" "$LOG_FILE" 2>/dev/null; then
|
||||
ERR_TYPE="529"
|
||||
elif grep -q "模型不存在" "$LOG_FILE" 2>/dev/null; then
|
||||
ERR_TYPE="model_err"
|
||||
elif [ "$EXIT_CODE" -ne 0 ]; then
|
||||
ERR_TYPE="exit_nonzero"
|
||||
fi
|
||||
|
||||
case "$ERR_TYPE" in
|
||||
429)
|
||||
# 用量上限:尽量解析重置时间,否则用默认长退避
|
||||
RESET_TIME=$(grep -oP '限额将在 \K[\d-]+ [\d:]+' "$LOG_FILE" 2>/dev/null | head -1)
|
||||
if [ -n "$RESET_TIME" ]; then
|
||||
RESET_EPOCH=$(date -d "$RESET_TIME" +%s 2>/dev/null)
|
||||
if [ -n "$RESET_EPOCH" ]; then
|
||||
echo "$RESET_EPOCH" > "$RATE_LIMIT_FILE"
|
||||
log "🔴 429 用量上限,退避至 $(date -d "@$RESET_EPOCH" '+%F %T')(重置于 ${RESET_TIME})。"
|
||||
else
|
||||
set_backoff "$BACKOFF_429_FALLBACK" "429 重置时间解析失败"
|
||||
fi
|
||||
else
|
||||
set_backoff "$BACKOFF_429_FALLBACK" "429 无重置时间"
|
||||
fi
|
||||
bump_fail "429 用量上限"
|
||||
;;
|
||||
529)
|
||||
# 模型过载:临时性,短退避(区别于 429 的长退避)
|
||||
set_backoff "$BACKOFF_529" "529 模型过载"
|
||||
bump_fail "529 模型过载"
|
||||
;;
|
||||
model_err)
|
||||
set_backoff "$BACKOFF_MODEL_ERR" "模型不存在"
|
||||
bump_fail "模型不存在"
|
||||
;;
|
||||
exit_nonzero)
|
||||
bump_fail "claude 非零退出"
|
||||
;;
|
||||
*)
|
||||
# 真成功:清零失败计数
|
||||
echo 0 > "$FAIL_COUNT_FILE"
|
||||
log "✅ 会话完成 | PID ${CURRENT_PID} | 退出码 ${EXIT_CODE} | 日志 ${LOG_SIZE}B"
|
||||
log " 日志路径: ${LOG_FILE}"
|
||||
# 若本次创建了完成标记,提示一下
|
||||
if [ -f "$COMPLETE_FILE" ]; then
|
||||
log "🎯 检测到 ${COMPLETE_FILE},重构已完成。"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user