🍉 Sync 2026-07-10 00:19:11

This commit is contained in:
github-actions[bot] committed 2026-07-10 00:19:11 +08:00
1 parent 2af7926aeb
commit 7a1197697c
14 files changed
+196 -24

No files matched your search

+110 -8
View File
@@ -130,6 +130,83 @@ done
[ $patched -eq 1 ] && logger -t openclaw "iframe headers patched successfully"
}
patch_webchat_session_conflict() {
# OpenClaw 2026.6.11 的 Control UI / WebChat 在第二轮消息时可能触发:
# reply session initialization conflicted for agent:main:dashboard:...
# 根因是 WebChat 追加用户消息会先更新 session registry revision,
# 而 reply 初始化仍按旧 snapshot 做 guarded write;同时 dashboard 会话会被
# terminal transcript mtime 误判为需要 rollover。这里做最小运行时兼容补丁。
[ -x "$NODE_BIN" ] || return 0
local oc_entry oc_dir dist_dir
oc_entry=$(get_oc_entry)
[ -n "$oc_entry" ] || return 0
oc_dir=$(dirname "$oc_entry")
dist_dir="${oc_dir}/dist"
[ -d "$dist_dir" ] || return 0
local patch_output
patch_output=$(OC_DIST_DIR="$dist_dir" "$NODE_BIN" <<'NODE' 2>/tmp/openclaw-webchat-patch.err
const fs = require('fs');
const path = require('path');
const dist = process.env.OC_DIST_DIR;
let patched = 0;
function patchFile(file, transforms) {
let source;
try {
source = fs.readFileSync(file, 'utf8');
} catch {
return;
}
let next = source;
for (const [needle, replacement] of transforms) {
if (next.includes(replacement)) continue;
if (next.includes(needle)) next = next.replace(needle, replacement);
}
if (next !== source) {
fs.writeFileSync(file, next);
patched += 1;
}
}
for (const name of fs.readdirSync(dist)) {
const file = path.join(dist, name);
if (!name.startsWith('get-reply-') || !name.endsWith('.js')) continue;
patchFile(file, [[
'const terminalMainTranscriptNewerThanRegistry = !isSystemEvent && await hasTerminalMainSessionTranscriptNewerThanRegistry({',
'const terminalMainTranscriptNewerThanRegistry = !isSystemEvent && !(ctx.Provider === "webchat" || ctx.Surface === "webchat" || ctx.OriginatingChannel === "webchat") && await hasTerminalMainSessionTranscriptNewerThanRegistry({'
]]);
}
for (const name of fs.readdirSync(dist)) {
const file = path.join(dist, name);
if (!name.startsWith('session-accessor-') || !name.endsWith('.js')) continue;
patchFile(file, [[
`\t\tif (revision !== params.expectedRevision) return {\n\t\t\tok: false,\n\t\t\t...currentEntry ? { currentEntry } : {},\n\t\t\treason: "stale-snapshot",\n\t\t\trevision\n\t\t};`,
`\t\tlet sessionEntryInput = params.sessionEntry;\n\t\tif (revision !== params.expectedRevision) {\n\t\t\tconst currentSessionId = typeof currentEntry?.sessionId === "string" ? currentEntry.sessionId.trim() : "";\n\t\t\tconst nextSessionId = typeof params.sessionEntry?.sessionId === "string" ? params.sessionEntry.sessionId.trim() : "";\n\t\t\tif (currentSessionId && nextSessionId && currentSessionId === nextSessionId) sessionEntryInput = {\n\t\t\t\t...currentEntry,\n\t\t\t\t...params.sessionEntry\n\t\t\t};\n\t\t\telse return {\n\t\t\t\tok: false,\n\t\t\t\t...currentEntry ? { currentEntry } : {},\n\t\t\t\treason: "stale-snapshot",\n\t\t\t\trevision\n\t\t\t};\n\t\t}`
], [
`sessionEntry: params.sessionEntry\n\t\t}) : params.sessionEntry;`,
`sessionEntry: sessionEntryInput\n\t\t}) : sessionEntryInput;`
]]);
}
process.stdout.write(String(patched));
NODE
)
local rc=$?
local patched_count=""
[ -f /tmp/openclaw-webchat-patch.err ] && patched_count=$(cat /tmp/openclaw-webchat-patch.err 2>/dev/null)
if [ $rc -eq 0 ]; then
local out_count
out_count=$(OC_DIST_DIR="$dist_dir" "$NODE_BIN" -e "const fs=require('fs'),path=require('path');let ok=0;for(const n of fs.readdirSync(process.env.OC_DIST_DIR)){const f=path.join(process.env.OC_DIST_DIR,n);if(!n.endsWith('.js'))continue;const s=fs.readFileSync(f,'utf8');if(s.includes('ctx.Provider === \"webchat\"')||s.includes('let sessionEntryInput = params.sessionEntry'))ok++;}process.stdout.write(String(ok));" 2>/dev/null || echo "")
logger -t openclaw "WebChat 会话冲突兼容补丁已检查 (${out_count:-0} 个命中, 本次修改 ${patch_output:-0} 个文件)"
else
logger -t openclaw "WebChat 会话冲突兼容补丁失败: ${patched_count:-unknown error}"
fi
rm -f /tmp/openclaw-webchat-patch.err 2>/dev/null || true
return 0
}
sync_uci_to_json() {
# 将 UCI 配置同步到 openclaw.json,同时确保 token 双向同步
local port bind token json_token
@@ -588,8 +665,11 @@ fs.writeFileSync(f, JSON.stringify(d, null, 2));
# v2026.4.10: final permission fix before Gateway start
fix_openclaw_state_permissions 2>/dev/null || true
# Patch iframe 安全头,允许 LuCI 嵌入
patch_iframe_headers
# Patch iframe 安全头,允许 LuCI 嵌入
patch_iframe_headers
# Patch WebChat 二次发送会话初始化冲突
patch_webchat_session_conflict
# 将 UCI bind 映射到 openclaw gateway --bind 参数
local gw_bind="loopback"
@@ -681,7 +761,10 @@ chmod 700 "$node_compile_cache" 2>/dev/null || true
PATH="${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
procd_set_param user openclaw
procd_set_param term_timeout 2
procd_set_param respawn 3600 1 5
# Gateway 的人工重载很常见,不能把 1 小时内的重启都计入 crash throttling。
# threshold=5 让正常运行超过 5 秒后的重载不累计失败;timeout=1 保持快速拉起;
# retry=-1 避免多次保存配置/扫码后 procd 停止 respawn。
procd_set_param respawn 5 1 -1
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/openclaw.pid
@@ -810,8 +893,15 @@ restart_gateway() {
# 仅重启 Gateway 进程,通过 kill 触发 procd respawn
# 绝对不能调用 start_service,否则会重启 PTY 实例
local lock_dir="/tmp/openclaw-restart-gateway.lock"
if ! mkdir "$lock_dir" 2>/dev/null; then
logger -t openclaw "restart_gateway 已在执行,跳过重复请求"
return 0
fi
local port
port=$(uci -q get openclaw.main.port || echo "18789")
logger -t openclaw "restart_gateway: 正在重启 Gateway"
# v2026.3.14 优化: 快速终止并重启
# 1) 同时获取端口 PID 和 procd 管理的 PID
@@ -824,16 +914,27 @@ fi
gw_pid=$(ubus call service list '{"name":"openclaw"}' 2>/dev/null | \
jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null) || true
# 2) 同时发送 SIGTERM 给所有相关进程
# 2) OpenClaw 支持 SIGUSR1 触发 in-process restart。
# 这比 SIGTERM/procd respawn 快很多,并能避免 stop 路径默认 25 秒 shutdown timeout。
# 注意要发给实际监听端口的 gateway 子进程,而不是 procd 记录的 node 父进程。
if [ -n "$port_pid" ] && kill -0 "$port_pid" 2>/dev/null; then
kill -USR1 "$port_pid" 2>/dev/null && {
logger -t openclaw "restart_gateway: 已通过 SIGUSR1 请求 Gateway 快速重载"
rmdir "$lock_dir" 2>/dev/null || true
return 0
}
fi
# 3) SIGUSR1 不可用时,回退到旧的 TERM + procd respawn 路径
[ -n "$port_pid" ] && kill "$port_pid" 2>/dev/null
[ -n "$gw_pid" ] && kill -0 "$gw_pid" 2>/dev/null && kill "$gw_pid" 2>/dev/null
# 3) 兜底: kill 所有残留进程
# 4) 兜底: kill 所有残留进程
for pid in $(pgrep -f "openclaw-gateway" 2>/dev/null) $(pgrep -f "openclaw.*gateway.*run" 2>/dev/null); do
kill "$pid" 2>/dev/null
done
# 4) 快速轮询等待端口释放 (200ms 间隔,最多 2 秒)
# 5) 快速轮询等待端口释放 (200ms 间隔,最多 2 秒)
local wait_count=0
while [ $wait_count -lt 10 ]; do
if command -v ss >/dev/null 2>&1; then
@@ -845,17 +946,18 @@ while [ $wait_count -lt 10 ]; do
wait_count=$((wait_count + 1))
done
# 5) 如果端口仍被占用,强制 SIGKILL
# 6) 如果端口仍被占用,强制 SIGKILL
if [ $wait_count -ge 10 ]; then
[ -n "$port_pid" ] && kill -9 "$port_pid" 2>/dev/null
usleep 300000 2>/dev/null || sleep 1
fi
# 6) 如果 procd 中没有 gateway 服务注册,调用 start
# 7) 如果 procd 中没有 gateway 服务注册,调用 start
if [ -z "$gw_pid" ]; then
/etc/init.d/openclaw start >/dev/null 2>&1
fi
# 如果 gw_pid 存在,procd respawn 会自动重启 gateway
rmdir "$lock_dir" 2>/dev/null || true
}
status_service() {