Commit aec9ecd5db8dce08fe488aaa4363c2b20457e4f6

Authored by yibo
1 parent 0e8f419d

fix: keepalive 单实例守护 + HERMES_TIMEOUT 180s

#1 HERMES_TIMEOUT 120s → 180s
   Moonshot kimi-k2.6 在 turns=9 长 context 下 LLM call 可达 60s+,
   旧的 2 分钟整体超时偶发不够,导致私聊 / 群聊回复直接 socket hang up。
   keepalive.sh 内联 HERMES_TIMEOUT=180000 启动 node。

#2 keepalive 直起 node,绕过 start-ar.sh
   phone 上某个 claude-run/hermes 会话有 shell snapshot hook,每次
   start-ar.sh 通过 dash 启动时触发它写 /data/local/tmp/hermes-snap-*.sh,
   Permission denied 错误被某种机制注入到 hermes-wechat/*.js 末尾,
   下一次 node 加载就 SyntaxError 起不来。
   修法:keepalive 用 `su -c 'cd X && env... nohup node auto-reply.js & echo $!'`
   直接起 node,不经 dash 包装层。

#3 is_auto_reply_running 的两个 bug
   - 第一版 pgrep -f node.*auto-reply.js: regex 在 su shell cmdline 里
     自匹配('su -c "..."' 包含 pattern),永远返 true,永远不重启
   - 第二版用 PID 文件 + grep /proc/PID/comm: 但 echo $! 写的是 sh
     wrapper PID(comm=sh),不是 node 子进程 PID,永远返 false → 暴 spawn
   - 终版:ps -ef + grep -E node.auto-reply.js + grep -v sh.-c
     精确匹配 "node auto-reply.js" 实进程(含空格),排除 sh wrapper line。
     pattern 里用 . 占位避开嵌套引号问题(双引号外/单引号内/转义都崩)

实测:keepalive 起 1 次后稳定 45s+,loop tick 不再误重启;
auto-reply.js 单实例(不再 4 个并行)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
wechat_plain/scripts/local-testkit/keepalive.sh
@@ -22,9 +22,13 @@ export HOME=&quot;/data/data/com.termux/files/home&quot; @@ -22,9 +22,13 @@ export HOME=&quot;/data/data/com.termux/files/home&quot;
22 HERMES_DIR="${HERMES_DIR:-$HOME/hermes-wechat}" 22 HERMES_DIR="${HERMES_DIR:-$HOME/hermes-wechat}"
23 HERMES_PORT="${HERMES_PORT:-8642}" 23 HERMES_PORT="${HERMES_PORT:-8642}"
24 GATEWAY_START="${GATEWAY_START:-$HOME/.hermes/scripts/start-gateway.sh}" 24 GATEWAY_START="${GATEWAY_START:-$HOME/.hermes/scripts/start-gateway.sh}"
25 -AR_START="${AR_START:-$HERMES_DIR/start-ar.sh}"  
26 A11Y_SVC="com.hermes.wechat.xposed/com.hermes.wechat.xposed.accessibility.HermesAccessibilityService" 25 A11Y_SVC="com.hermes.wechat.xposed/com.hermes.wechat.xposed.accessibility.HermesAccessibilityService"
27 CHECK_INTERVAL_SEC="${CHECK_INTERVAL_SEC:-15}" 26 CHECK_INTERVAL_SEC="${CHECK_INTERVAL_SEC:-15}"
  27 +# auto-reply 启动用的 env(避开 start-ar.sh 的 shell hook 污染)
  28 +MY_WXID="${MY_WXID:-wxid_51ap3m6p9xs722}"
  29 +MY_NICKNAME="${MY_NICKNAME:-小火子}"
  30 +HERMES_TIMEOUT_MS="${HERMES_TIMEOUT_MS:-180000}"
  31 +HISTORY_CONTEXT_SIZE="${HISTORY_CONTEXT_SIZE:-8}"
28 32
29 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } 33 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
30 34
@@ -81,23 +85,45 @@ start_gateway() { @@ -81,23 +85,45 @@ start_gateway() {
81 } 85 }
82 86
83 # ── 4. auto-reply.js ───────────────────────────────────────────── 87 # ── 4. auto-reply.js ─────────────────────────────────────────────
  88 +AR_PID_FILE="$HERMES_DIR/.auto-reply.pid"
  89 +
84 is_auto_reply_running() { 90 is_auto_reply_running() {
85 - pgrep -f "node.*auto-reply.js" >/dev/null 2>&1 91 + # 不能用 pgrep -f node.*auto-reply.js — 它会自匹配 su shell 自己的 cmdline。
  92 + # 用 ps -ef + grep 精确匹配 'node auto-reply.js'(含空格,排除 sh wrapper)。
  93 + # su 提权因为 auto-reply 跑在 root 下,u0_a327 看不到 root 进程的 ps。
  94 + # 用 . 占位(任何单字符)避开嵌套引号问题
  95 + su -c "ps -ef 2>/dev/null | grep -E node.auto-reply.js | grep -v sh.-c | grep -v grep" \
  96 + 2>/dev/null | grep -q .
86 } 97 }
87 98
88 start_auto_reply() { 99 start_auto_reply() {
89 log "Starting auto-reply..." 100 log "Starting auto-reply..."
90 - if [ -x "$AR_START" ]; then  
91 - # auto-reply 走 root 才能 input keyevent / am broadcast / 读微信 DB  
92 - su -c "nohup bash $AR_START > /dev/null 2>&1 &"  
93 - else  
94 - log "[ERROR] $AR_START not found" 101 + # 不走 start-ar.sh wrapper(其 shebang 走 dash 后会触发 phone 上某个
  102 + # claude-run/hermes 会话的 shell snapshot hook,污染 hermes-wechat/*.js)。
  103 + # 直接以 root 起 node + 内联 env,跨过那个 shell session。
  104 + # 起完后用 pgrep + grep 严格匹配 cmdline 确切包含 'node auto-reply.js'
  105 + # (不能用 pgrep -f 因为正则 .* 会自匹配 su shell 本身),写入 PID 文件
  106 + su -c "cd $HERMES_DIR && \
  107 + MY_WXID=${MY_WXID:-wxid_51ap3m6p9xs722} \
  108 + MY_NICKNAME=\"${MY_NICKNAME:-小火子}\" \
  109 + HERMES_HOST=127.0.0.1 \
  110 + HERMES_PORT=$HERMES_PORT \
  111 + HERMES_TIMEOUT=${HERMES_TIMEOUT_MS:-180000} \
  112 + NOTIFICATION_ENABLED=true \
  113 + HISTORY_CONTEXT_SIZE=${HISTORY_CONTEXT_SIZE:-8} \
  114 + nohup /data/data/com.termux/files/usr/bin/node auto-reply.js \
  115 + > $HERMES_DIR/auto-reply.log 2>&1 < /dev/null &
  116 + echo \$! > $AR_PID_FILE"
  117 + sleep 2
  118 + if [ -f "$AR_PID_FILE" ]; then
  119 + log "Auto-reply started PID=$(cat $AR_PID_FILE 2>/dev/null)"
95 fi 120 fi
96 } 121 }
97 122
98 # ── 5. 主循环 ──────────────────────────────────────────────────── 123 # ── 5. 主循环 ────────────────────────────────────────────────────
99 log "=== Keepalive started ===" 124 log "=== Keepalive started ==="
100 -log "HERMES_DIR=$HERMES_DIR GATEWAY_START=$GATEWAY_START AR_START=$AR_START" 125 +log "HERMES_DIR=$HERMES_DIR GATEWAY_START=$GATEWAY_START"
  126 +log "MY_WXID=$MY_WXID MY_NICKNAME=$MY_NICKNAME HERMES_TIMEOUT=${HERMES_TIMEOUT_MS}ms"
101 log "Check interval: ${CHECK_INTERVAL_SEC}s" 127 log "Check interval: ${CHECK_INTERVAL_SEC}s"
102 128
103 # 首次进入:把所有依赖拉到位 129 # 首次进入:把所有依赖拉到位