#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════════ # setup-offload.sh — Offload 功能一键开启/关闭 # ═══════════════════════════════════════════════════════════════════ # # 用法: # bash setup-offload.sh --enable --user-id --backend-url [--backend-api-key ] # bash setup-offload.sh --disable # bash setup-offload.sh --status # # 开启流程: # 1. 前置检查(openclaw.json 存在、openclaw 已安装) # 2. Patch 校验 & 执行(after_tool_call messages 注入)— 失败则终止 # 3. 设置 plugins.slots.contextEngine # 4. 设置 offload.enabled + backendUrl + userId [+ backendApiKey] # 5. 设置 compaction.mode = safeguard # # 关闭流程: # 1. 设置 offload.enabled = false # 2. 删除 plugins.slots.contextEngine(清理 slot 占用) # ═══════════════════════════════════════════════════════════════════ set -euo pipefail # ── 颜色 ── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${CYAN}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } fail() { echo -e "${RED}[FAIL]${NC} $*" >&2; exit 1; } # ── 常量 ── OPENCLAW_JSON="${HOME}/.openclaw/openclaw.json" PLUGIN_ID="memory-tencentdb" CONTEXT_ENGINE_ID="memory-tencentdb" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PATCH_SCRIPT="${SCRIPT_DIR}/openclaw-after-tool-call-messages.patch.sh" # ── 参数解析 ── MODE="" USER_ID="" BACKEND_URL="" BACKEND_API_KEY="" while [[ $# -gt 0 ]]; do case "$1" in --enable) MODE="enable"; shift ;; --disable) MODE="disable"; shift ;; --status) MODE="status"; shift ;; --user-id) USER_ID="$2"; shift 2 ;; --backend-url) BACKEND_URL="$2"; shift 2 ;; --backend-api-key) BACKEND_API_KEY="$2"; shift 2 ;; -h|--help) echo "用法:" echo " bash setup-offload.sh --enable --user-id --backend-url [--backend-api-key ]" echo " bash setup-offload.sh --disable" echo " bash setup-offload.sh --status" echo "" echo "参数:" echo " --user-id (必填) 用户 ID" echo " --backend-url (必填) offload 后端地址,如 http://1.2.3.4:8080" echo " --backend-api-key (可选) 后端 API 认证 token" exit 0 ;; *) fail "未知参数: $1" ;; esac done [[ -z "$MODE" ]] && fail "请指定模式: --enable / --disable / --status" # ═══════════════════════════════════════════════════════════════════ # 公共函数 # ═══════════════════════════════════════════════════════════════════ check_openclaw_json() { if [[ ! -f "$OPENCLAW_JSON" ]]; then fail "openclaw.json 不存在: $OPENCLAW_JSON" fi # 验证 JSON 格式 python3 -c "import json; json.load(open('$OPENCLAW_JSON'))" 2>/dev/null \ || fail "openclaw.json 格式错误" } backup_config() { local bak="${OPENCLAW_JSON}.bak.$(date +%Y%m%d_%H%M%S)" cp "$OPENCLAW_JSON" "$bak" info "配置已备份: $bak" } # ═══════════════════════════════════════════════════════════════════ # --status: 显示当前配置状态 # ═══════════════════════════════════════════════════════════════════ show_status() { check_openclaw_json python3 -c " import json with open('$OPENCLAW_JSON') as f: cfg = json.load(f) # Context Engine Slot slot = cfg.get('plugins', {}).get('slots', {}).get('contextEngine', '(未设置)') print(f' Context Engine Slot: {slot}') # Offload config offload = cfg.get('plugins', {}).get('entries', {}).get('$PLUGIN_ID', {}).get('config', {}).get('offload', {}) enabled = offload.get('enabled', False) backend = offload.get('backendUrl', '(未设置)') user_id = offload.get('userId', '(未设置)') api_key = offload.get('backendApiKey', '') timeout = offload.get('backendTimeoutMs', '(默认)') mild = offload.get('mildOffloadRatio', '(默认 0.5)') agg = offload.get('aggressiveCompressRatio', '(默认 0.85)') status_icon = '✅ 已开启' if enabled else '❌ 已关闭' api_key_display = f'{api_key[:8]}...' if api_key and len(api_key) > 8 else (api_key or '(未设置)') print(f' Offload 状态: {status_icon}') print(f' Backend URL: {backend}') print(f' Backend Key: {api_key_display}') print(f' User ID: {user_id}') print(f' Timeout: {timeout}ms') print(f' Mild Ratio: {mild}') print(f' Aggressive: {agg}') # Compaction mode compaction = cfg.get('agents', {}).get('defaults', {}).get('compaction', {}).get('mode', '(未设置)') print(f' Compaction: {compaction}') " } # ═══════════════════════════════════════════════════════════════════ # --enable: 开启 offload # ═══════════════════════════════════════════════════════════════════ enable_offload() { # 参数校验 [[ -z "$USER_ID" ]] && fail "缺少 --user-id 参数" [[ -z "$BACKEND_URL" ]] && fail "缺少 --backend-url 参数" # URL 格式基本校验 if [[ ! "$BACKEND_URL" =~ ^https?:// ]]; then fail "backendUrl 格式错误,应以 http:// 或 https:// 开头: $BACKEND_URL" fi check_openclaw_json backup_config echo "" info "${BOLD}[1/4] Patch 校验${NC}" # ── Step 1: 调用 patch 脚本(幂等,已 patch 过会跳过) ── # patch 脚本自带精确的幂等检测,通过退出码判断结果: # 0 = 成功(新 patch 或已跳过) # 1 = 失败(无法 patch) if [[ -f "$PATCH_SCRIPT" ]]; then info "执行 patch 脚本..." local patch_exit=0 local patch_output patch_output=$(bash "$PATCH_SCRIPT" 2>&1) || patch_exit=$? # 显示 patch 脚本输出(缩进) while IFS= read -r line; do echo " $line" done <<< "$patch_output" if [[ $patch_exit -eq 0 ]]; then ok "Patch 校验通过" else echo "" echo -e "${RED}═══════════════════════════════════════════════════${NC}" echo -e "${RED} ❌ Patch 失败(退出码: $patch_exit)${NC}" echo -e "${RED}═══════════════════════════════════════════════════${NC}" echo "" echo -e " ${RED}after_tool_call hook 无法获取 session messages,${NC}" echo -e " ${RED}offload L1/L3 压缩将无法正常工作。${NC}" echo "" echo -e " ${CYAN}排查步骤:${NC}" echo -e " 1. DEBUG=1 bash $PATCH_SCRIPT" echo -e " 2. 检查 openclaw 版本是否兼容" echo "" exit 2 fi else echo -e "${RED}[FAIL]${NC} Patch 脚本不存在: $PATCH_SCRIPT" >&2 echo -e " ${RED}offload 功能依赖此 patch,终止开启流程。${NC}" >&2 exit 2 fi # ── Step 2: 设置 context engine slot ── echo "" info "${BOLD}[2/4] 设置 Context Engine Slot${NC}" python3 -c " import json with open('$OPENCLAW_JSON') as f: cfg = json.load(f) # 确保 plugins.slots 存在 cfg.setdefault('plugins', {}).setdefault('slots', {}) cfg['plugins']['slots']['contextEngine'] = '$CONTEXT_ENGINE_ID' print(' slots.contextEngine = $CONTEXT_ENGINE_ID') with open('$OPENCLAW_JSON', 'w') as f: json.dump(cfg, f, indent=2, ensure_ascii=False) f.write('\n') " ok "Context Engine Slot 已设置" # ── Step 3: 设置 offload 配置 ── echo "" info "${BOLD}[3/4] 设置 Offload 配置${NC}" python3 -c " import json with open('$OPENCLAW_JSON') as f: cfg = json.load(f) # 确保路径存在 entry = cfg.setdefault('plugins', {}).setdefault('entries', {}).setdefault('$PLUGIN_ID', {}) config = entry.setdefault('config', {}) offload = config.setdefault('offload', {}) # 设置必要配置 offload['enabled'] = True offload['backendUrl'] = '$BACKEND_URL' offload['userId'] = '$USER_ID' offload.setdefault('backendTimeoutMs', 120000) api_key = '$BACKEND_API_KEY' if api_key: offload['backendApiKey'] = api_key print(f' offload.backendApiKey = {api_key[:8]}...' if len(api_key) > 8 else f' offload.backendApiKey = {api_key}') elif 'backendApiKey' in offload: del offload['backendApiKey'] print(' offload.enabled = true') print(' offload.backendUrl = $BACKEND_URL') print(' offload.userId = $USER_ID') print(f' offload.backendTimeoutMs = {offload[\"backendTimeoutMs\"]}') with open('$OPENCLAW_JSON', 'w') as f: json.dump(cfg, f, indent=2, ensure_ascii=False) f.write('\n') " ok "Offload 配置已设置" # ── Step 4: 设置 compaction mode ── echo "" info "${BOLD}[4/4] 设置 Compaction Mode${NC}" python3 -c " import json with open('$OPENCLAW_JSON') as f: cfg = json.load(f) defaults = cfg.setdefault('agents', {}).setdefault('defaults', {}) compaction = defaults.setdefault('compaction', {}) old_mode = compaction.get('mode', '(未设置)') compaction['mode'] = 'safeguard' print(f' compaction.mode: {old_mode} → safeguard') with open('$OPENCLAW_JSON', 'w') as f: json.dump(cfg, f, indent=2, ensure_ascii=False) f.write('\n') " ok "Compaction mode 已设置为 safeguard" # ── 完成 ── echo "" echo -e "${GREEN}═══════════════════════════════════════════════════${NC}" echo -e "${GREEN} ✅ Offload 已开启${NC}" echo -e "${GREEN}═══════════════════════════════════════════════════${NC}" echo "" show_status echo "" echo -e " ${CYAN}提示: 需要重启 gateway 才能生效${NC}" echo -e " ${CYAN} bash install-plugin.sh --restart${NC}" } # ═══════════════════════════════════════════════════════════════════ # --disable: 关闭 offload # ═══════════════════════════════════════════════════════════════════ disable_offload() { check_openclaw_json backup_config python3 -c " import json with open('$OPENCLAW_JSON') as f: cfg = json.load(f) # 关闭 offload.enabled(用 setdefault 确保路径存在,修改能回写到 cfg) entry = cfg.setdefault('plugins', {}).setdefault('entries', {}).setdefault('$PLUGIN_ID', {}) config = entry.setdefault('config', {}) offload = config.setdefault('offload', {}) offload['enabled'] = False print(' offload.enabled = false') # 移除 contextEngine slot plugins = cfg.get('plugins', {}) slots = plugins.get('slots', {}) if 'contextEngine' in slots: del slots['contextEngine'] print(' slots.contextEngine → 已删除') # 如果 slots 变空了,也一并移除 slots 键 if not slots and 'slots' in plugins: del plugins['slots'] print(' plugins.slots → 已清理(空对象)') else: print(' slots.contextEngine → 无需删除(不存在)') with open('$OPENCLAW_JSON', 'w') as f: json.dump(cfg, f, indent=2, ensure_ascii=False) f.write('\n') " echo "" echo -e "${YELLOW}═══════════════════════════════════════════════════${NC}" echo -e "${YELLOW} ❌ Offload 已关闭${NC}" echo -e "${YELLOW}═══════════════════════════════════════════════════${NC}" echo "" echo -e " ${CYAN}提示: 需要重启 gateway 才能生效${NC}" echo -e " ${CYAN} bash install-plugin.sh --restart${NC}" } # ═══════════════════════════════════════════════════════════════════ # 主入口 # ═══════════════════════════════════════════════════════════════════ case "$MODE" in enable) enable_offload ;; disable) disable_offload ;; status) echo "" info "${BOLD}Offload 配置状态${NC}" show_status ;; esac