{"repo": "10000ge10000/luci-app-openclaw", "files": {"root/usr/share/openclaw/oc-config.sh": {"26646fa63dc245216dfffa1bf2f9e58036944749": {"t": 1785674143.3817928, "n": 104079, "h": "e2978c5f76a6e185", "c": "#!/bin/sh\n# ============================================================================\n# OpenClaw 配置管理工具 — OpenWrt 适配版\n# 基于原始 oc-config.sh 移植,适配 ash/busybox 环境\n# ============================================================================\n\n# ── 颜色 ──\nRED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[1;33m'\nCYAN='\\033[0;36m'; BOLD='\\033[1m'; DIM='\\033[2m'; NC='\\033[0m'\n\n# ── 交互式菜单引擎路径 ──\nOC_MENU_ENGINE=\"/usr/share/openclaw/oc-menu-engine.js\"\nOC_INTERACTIVE=\"/usr/share/openclaw/oc-config-interactive.js\"\n\n# ── 端口检查兼容函数 (ss 或 netstat) ──\n# check_port_listening — 检查端口是否在监听,返回 0/1\ncheck_port_listening() {\n\tlocal p=\"$1\"\n\tif command -v ss >/dev/null 2>&1; then\n\t\tss -ltn 2>/dev/null | grep -q \":${p} \"\n\telse\n\t\tnetstat -tln 2>/dev/null | grep -q \":${p} \"\n\tfi\n}\n# get_pid_by_port — 获取监听指定端口的进程 PID\nget_pid_by_port() {\n\tlocal p=\"$1\"\n\tif command -v ss >/dev/null 2>&1; then\n\t\tss -tlnp 2>/dev/null | grep \":${p} \" | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p' | head -1\n\telse\n\t\tnetstat -tlnp 2>/dev/null | grep \":${p} \" | sed -n 's|.* \\([0-9]*\\)/.*|\\1|p' | head -1\n\tfi\n}\n\n# ── 路径 (OpenWrt 适配,支持自定义安装路径) ──\n# 从 UCI 配置读取自定义路径,环境变量可覆盖。\n# 注意 OC_INSTALL_PATH 历史上被当作“基础路径”传入,统一解析后再展开到 openclaw 根目录。\n[ -r /usr/libexec/openclaw-paths.sh ] && . /usr/libexec/openclaw-paths.sh\nOC_CONFIGURED_PATH=\"${OC_INSTALL_PATH:-$(uci -q get openclaw.main.install_path 2>/dev/null || echo '/opt')}\"\nif command -v oc_load_paths >/dev/null 2>&1 && oc_load_paths \"$OC_CONFIGURED_PATH\"; then\n\tOC_INSTALL_PATH=\"$OC_ROOT\"\nelse\n\tOC_BASE_PATH=\"${OC_CONFIGURED_PATH%/}\"\n\tOC_INSTALL_PATH=\"${OC_BASE_PATH}/openclaw\"\n\tNODE_BASE=\"${NODE_BASE:-${OC_INSTALL_PATH}/node}\"\n\tOC_GLOBAL=\"${OC_GLOBAL:-${OC_INSTALL_PATH}/global}\"\n\tOC_DATA=\"${OC_DATA:-${OC_INSTALL_PATH}/data}\"\n\tCONFIG_FILE=\"${OC_DATA}/.openclaw/openclaw.json\"\nfi\nNODE_BASE=\"${NODE_BASE:-${OC_INSTALL_PATH}/node}\"\nOC_GLOBAL=\"${OC_GLOBAL:-${OC_INSTALL_PATH}/global}\"\nOC_DATA=\"${OC_DATA:-${OC_INSTALL_PATH}/data}\"\nNODE_BIN=\"${NODE_BASE}/bin/node\"\nOC_STATE_DIR=\"${OC_DATA}/.openclaw\"\nCONFIG_FILE=\"${OC_STATE_DIR}/openclaw.json\"\n\nexport HOME=\"$OC_DATA\"\nexport OPENCLAW_HOME=\"$OC_DATA\"\nexport OPENCLAW_STATE_DIR=\"$OC_STATE_DIR\"\nexport OPENCLAW_CONFIG_PATH=\"$CONFIG_FILE\"\nexport NODE_ICU_DATA=\"${NODE_BASE}/share/icu\"\nexport PATH=\"${NODE_BASE}/bin:${OC_GLOBAL}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n\nfix_openclaw_state_permissions() {\n\t[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh fix-state \"$OC_STATE_DIR\"\n}\n\nprepare_openclaw_workdirs() {\n\t[ -x /usr/libexec/openclaw-permissions.sh ] && /usr/libexec/openclaw-permissions.sh prepare-workdirs \"$OC_DATA\"\n}\n\n# ── 查找 openclaw 入口 ──\nOC_PKG_DIR=\"\"\nfor d in \"${OC_GLOBAL}/lib/node_modules/openclaw\" \"${OC_GLOBAL}/node_modules/openclaw\" \"${NODE_BASE}/lib/node_modules/openclaw\"; do\n\tif [ -d \"$d\" ]; then\n\t\tOC_PKG_DIR=\"$d\"\n\t\tbreak\n\tfi\ndone\n\nOC_ENTRY=\"\"\nif [ -n \"$OC_PKG_DIR\" ]; then\n\tif [ -f \"${OC_PKG_DIR}/openclaw.mjs\" ]; then\n\t\tOC_ENTRY=\"${OC_PKG_DIR}/openclaw.mjs\"\n\telif [ -f \"${OC_PKG_DIR}/dist/cli.js\" ]; then\n\t\tOC_ENTRY=\"${OC_PKG_DIR}/dist/cli.js\"\n\tfi\nfi\n\noc_cmd() {\n\tif [ -n \"$OC_ENTRY\" ] && [ -x \"$NODE_BIN\" ]; then\n\t\t\"$NODE_BIN\" \"$OC_ENTRY\" \"$@\" 2>&1\n\t\tlocal rc=$?\n\t\t# 修复权限: oc_cmd 以 root 运行但配置文件应属于 openclaw 用户\n\t\tfix_openclaw_state_permissions 2>/dev/null || true\n\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\tchown openclaw:openclaw \"${CONFIG_FILE}.bak\" 2>/dev/null || true\n\t\treturn $rc\n\telse\n\t\techo \"ERROR: OpenClaw 未安装或 Node.js 不可用\"\n\t\treturn 1\n\tfi\n}\n\n# ── JSON 读写 (使用 Node.js) ──\njson_get() {\n\tif [ ! -f \"$CONFIG_FILE\" ]; then echo \"\"; return; fi\n\t_JS_KEY=\"$1\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\ttry{\n\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\tconst ks=process.env._JS_KEY.split('.');let v=d;\n\t\t\tfor(const k of ks){v=v[k];if(v===undefined){console.log('');process.exit(0);}}\n\t\t\tif(typeof v==='object')console.log(JSON.stringify(v));else console.log(v);\n\t\t}catch(e){console.log('');}\n\t\" 2>/dev/null\n}\n\njson_set() {\n\tlocal key=\"$1\" value=\"$2\"\n\tlocal _js_err=\"\"\n\t\n\t# 步骤1: 确保配置文件存在\n\tif [ ! -f \"$CONFIG_FILE\" ]; then\n\t\t# 检查父目录是否可创建\n\t\tlocal parent_dir=\"$(dirname \"$CONFIG_FILE\")\"\n\t\tif ! mkdir -p \"$parent_dir\" 2>/dev/null; then\n\t\t\techo \"ERROR: 无法创建配置目录 $parent_dir\" >&2\n\t\t\techo \"HINT: 请检查 $OC_DATA 是否存在且有写权限\" >&2\n\t\t\treturn 1\n\t\tfi\n\t\t\n\t\t# 检查目录权限\n\t\tif [ ! -w \"$parent_dir\" ]; then\n\t\t\techo \"ERROR: 配置目录 $parent_dir 不可写\" >&2\n\t\t\techo \"HINT: 请运行: chmod 755 $parent_dir\" >&2\n\t\t\treturn 1\n\t\tfi\n\t\t\n\t\t# 尝试修复所有权\n\t\tfix_openclaw_state_permissions 2>/dev/null || true\n\t\t\n\t\t# 创建空配置文件\n\t\tif ! echo '{}' > \"$CONFIG_FILE\" 2>/dev/null; then\n\t\t\techo \"ERROR: 无法创建配置文件 $CONFIG_FILE\" >&2\n\t\t\techo \"HINT: 请检查磁盘空间和文件系统状态\" >&2\n\t\t\treturn 1\n\t\tfi\n\t\t\n\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\tfi\n\t\n\t# 步骤2: 检查配置文件是否可写\n\tif [ ! -w \"$CONFIG_FILE\" ]; then\n\t\techo \"ERROR: 配置文件 $CONFIG_FILE 不可写\" >&2\n\t\techo \"HINT: 请运行: chmod 644 $CONFIG_FILE\" >&2\n\t\treturn 1\n\tfi\n\t\n\t# 步骤3: 检查 Node.js 是否可用\n\tif [ ! -x \"$NODE_BIN\" ]; then\n\t\techo \"ERROR: Node.js 不可用: $NODE_BIN\" >&2\n\t\techo \"HINT: 请先运行 openclaw-env setup 安装 Node.js\" >&2\n\t\treturn 1\n\tfi\n\t\n\t# 步骤4: 使用临时文件传递值,避免环境变量转义问题\n\tlocal tmp_val_file=\"/tmp/.oc_json_val_$$\"\n\tif ! printf '%s' \"$value\" > \"$tmp_val_file\" 2>/dev/null; then\n\t\techo \"ERROR: 无法创建临时文件 $tmp_val_file\" >&2\n\t\treturn 1\n\tfi\n\t\n\t# 步骤5: 执行 JSON 写入\n\t_JS_KEY=\"$key\" _JS_DEBUG=\"${OC_CONFIG_DEBUG:-0}\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');let d={};\n\t\tconst debug=process.env._JS_DEBUG==='1';\n\t\ttry{\n\t\t\tconst content=fs.readFileSync('${CONFIG_FILE}','utf8');\n\t\t\td=JSON.parse(content);\n\t\t}catch(e){\n\t\t\tif(debug)console.error('JSON parse warning:',e.message);\n\t\t}\n\t\tconst ks=process.env._JS_KEY.split('.');let o=d;\n\t\tfor(let i=0;i&1\n\tlocal _js_rc=$?\n\t\n\t# 清理临时文件\n\trm -f \"$tmp_val_file\" 2>/dev/null\n\t\n\t# 步骤6: 检查执行结果\n\tif [ $_js_rc -ne 0 ]; then\n\t\techo \"ERROR: JSON 写入失败 (exit code: $_js_rc)\" >&2\n\t\treturn 1\n\tfi\n\t\n\t# 步骤7: 修复文件所有权\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\n\treturn 0\n}\n\n# ── 启用 auth 插件 ──\nenable_auth_plugins() {\n\t[ ! -f \"$CONFIG_FILE\" ] && return\n\t\"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\ttry{\n\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\tif(!d.plugins)d.plugins={};if(!d.plugins.entries)d.plugins.entries={};\n\t\t\tconst e=d.plugins.entries;\n\t\t\t// 启用有效的认证插件\n\t\t\t['copilot-proxy'].forEach(p=>{\n\t\t\t\tif(!e[p])e[p]={};e[p].enabled=true;\n\t\t\t});\n\t\t\t// 清理已废弃或未安装的插件配置 (v2026.4.9: 这些插件已移除或更名)\n\t\t\tdelete e['qwen-portal-auth'];\n\t\t\tdelete e['google-gemini-cli-auth'];\n\t\t\tdelete e['minimax-portal-auth'];\n\t\t\tdelete e['google-antigravity-auth'];\n\t\t\tdelete e['openclaw-weixin']; // 微信插件有独立安装流程,不应在 entries 中\n\t\t\t// 清理过时的 installs 配置\n\t\t\tif(d.plugins && d.plugins.installs){\n\t\t\t\tdelete d.plugins.installs['google-gemini-cli-auth'];\n\t\t\t\tdelete d.plugins.installs['minimax-portal-auth'];\n\t\t\t}\n\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t}catch(e){}\n\t\" 2>/dev/null\n}\n\n# ── 修复插件配置中的插件名称不匹配问题 ──\n# v2026.3.13: OpenClaw 加强了配置验证,plugins.allow 中的名称必须与实际插件名完全匹配\n# 问题: 旧版本写入的是 \"openclaw-qqbot\",但实际插件名是 \"@tencent-connect/openclaw-qqbot\"\n# 此函数会自动检测并修正不匹配的插件名称\nfix_plugin_config() {\n\t[ ! -f \"$CONFIG_FILE\" ] && return\n\t[ ! -x \"$NODE_BIN\" ] && return\n\t\n\t# 检查是否存在 qqbot 插件目录\n\tlocal qqbot_ext_dir=\"${OC_STATE_DIR}/extensions/openclaw-qqbot\"\n\t[ ! -d \"$qqbot_ext_dir\" ] && return\n\t\n\t# 读取并修复 plugins.allow 中的插件名称\n\tlocal fixed=0\n\t\"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\ttry{\n\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\tif(!d.plugins)d.plugins={};\n\t\t\t\n\t\t\t// 修复 plugins.allow 数组中的插件名称\n\t\t\tif(Array.isArray(d.plugins.allow)){\n\t\t\t\tconst oldName='openclaw-qqbot';\n\t\t\t\tconst newName='@tencent-connect/openclaw-qqbot';\n\t\t\t\tconst idx=d.plugins.allow.indexOf(oldName);\n\t\t\t\tif(idx!==-1){\n\t\t\t\t\t// 检查是否已有正确的名称\n\t\t\t\t\tif(!d.plugins.allow.includes(newName)){\n\t\t\t\t\t\td.plugins.allow[idx]=newName;\n\t\t\t\t\t\tconsole.log('FIXED');\n\t\t\t\t\t}else{\n\t\t\t\t\t\t// 已有正确名称,删除错误的\n\t\t\t\t\t\td.plugins.allow.splice(idx,1);\n\t\t\t\t\t\tconsole.log('REMOVED_DUPLICATE');\n\t\t\t\t\t}\n\t\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// 同时修复 plugins.entries 中的键名\n\t\t\tif(d.plugins.entries && d.plugins.entries['openclaw-qqbot']){\n\t\t\t\tif(!d.plugins.entries['@tencent-connect/openclaw-qqbot']){\n\t\t\t\t\td.plugins.entries['@tencent-connect/openclaw-qqbot']=d.plugins.entries['openclaw-qqbot'];\n\t\t\t\t}\n\t\t\t\tdelete d.plugins.entries['openclaw-qqbot'];\n\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\tconsole.log('FIXED_ENTRIES');\n\t\t\t}\n\t\t}catch(e){}\n\t\" 2>/dev/null | while read line; do\n\t\tcase \"$line\" in\n\t\t\tFIXED|REMOVED_DUPLICATE|FIXED_ENTRIES)\n\t\t\t\techo -e \" ${GREEN}✅ 已修复插件配置名称: openclaw-qqbot → @tencent-connect/openclaw-qqbot${NC}\"\n\t\t\t\tfixed=1\n\t\t\t\t;;\n\t\tesac\n\tdone\n\t\n\t# 确保配置文件权限正确\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\n\treturn $fixed\n}\n\n# ── 确保 qqbot 插件在 plugins.allow 中 ──\nensure_qqbot_plugin_allowed() {\n\t[ ! -f \"$CONFIG_FILE\" ] && return\n\t[ ! -x \"$NODE_BIN\" ] && return\n\t\n\t\"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\ttry{\n\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\tif(!d.plugins)d.plugins={};\n\t\t\tif(!Array.isArray(d.plugins.allow))d.plugins.allow=[];\n\t\t\t\n\t\t\tconst correctName='@tencent-connect/openclaw-qqbot';\n\t\t\tconst oldName='openclaw-qqbot';\n\t\t\t\n\t\t\t// 移除旧的不正确名称\n\t\t\td.plugins.allow=d.plugins.allow.filter(n=>n!==oldName);\n\t\t\t\n\t\t\t// 添加正确的名称(如果不存在)\n\t\t\tif(!d.plugins.allow.includes(correctName)){\n\t\t\t\td.plugins.allow.push(correctName);\n\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\tconsole.log('ADDED');\n\t\t\t}\n\t\t}catch(e){}\n\t\" 2>/dev/null\n\t\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n}\n\n# ── 模型认证: 将 API Key 写入 auth-profiles.json (而非 openclaw.json) ──\n# 用法: auth_set_apikey [profile_id]\n# 例: auth_set_apikey openai sk-xxx\n# auth_set_apikey anthropic sk-ant-xxx\n# auth_set_apikey openai-compatible sk-xxx custom:manual\nauth_set_apikey() {\n\tlocal provider=\"$1\" api_key=\"$2\" profile_id=\"${3:-${1}:manual}\"\n\tlocal auth_dir=\"${OC_STATE_DIR}/agents/main/agent\"\n\tlocal auth_file=\"${auth_dir}/auth-profiles.json\"\n\tmkdir -p \"$auth_dir\"\n\tchown -R openclaw:openclaw \"${OC_STATE_DIR}/agents\" 2>/dev/null || true\n\t_AP_PROVIDER=\"$provider\" _AP_KEY=\"$api_key\" _AP_PROFILE=\"$profile_id\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs'),f=process.env._AP_FILE||'${auth_file}';\n\t\tlet d={version:1,profiles:{},usageStats:{}};\n\t\ttry{d=JSON.parse(fs.readFileSync(f,'utf8'));}catch(e){}\n\t\tif(!d.profiles)d.profiles={};\n\t\td.profiles[process.env._AP_PROFILE]={\n\t\t\ttype:'api_key',\n\t\t\tprovider:process.env._AP_PROVIDER,\n\t\t\tkey:process.env._AP_KEY\n\t\t};\n\t\tfs.writeFileSync(f,JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$auth_file\" 2>/dev/null || true\n}\n\n# ── GitHub Copilot Token 写入 auth-profiles.json (type:token) ──\n# GitHub Copilot 使用 token 类型而非 api_key,OpenClaw 会自动兑换 Copilot session token\n# 用法: auth_set_copilot_token \nauth_set_copilot_token() {\n\tlocal github_token=\"$1\"\n\tlocal auth_dir=\"${OC_STATE_DIR}/agents/main/agent\"\n\tlocal auth_file=\"${auth_dir}/auth-profiles.json\"\n\tmkdir -p \"$auth_dir\"\n\tchown -R openclaw:openclaw \"${OC_STATE_DIR}/agents\" 2>/dev/null || true\n\t_AP_TOKEN=\"$github_token\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs'),f='${auth_file}';\n\t\tlet d={version:1,profiles:{},usageStats:{}};\n\t\ttry{d=JSON.parse(fs.readFileSync(f,'utf8'));}catch(e){}\n\t\tif(!d.profiles)d.profiles={};\n\t\td.profiles['github-copilot:github']={\n\t\t\ttype:'token',\n\t\t\tprovider:'github-copilot',\n\t\t\ttoken:process.env._AP_TOKEN\n\t\t};\n\t\tfs.writeFileSync(f,JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$auth_file\" 2>/dev/null || true\n}\n\n# ── 注册模型到 agents.defaults.models 并设为默认 ──\n# 用法: register_and_set_model \n# 例: register_and_set_model openai/gpt-5.2\n# register_and_set_model anthropic/claude-sonnet-4\n# 注意: 模型 ID 可能含 \".\" (如 gpt-5.2),不能用 json_set (以 . 分割路径)\nregister_and_set_model() {\n\tlocal model_id=\"$1\"\n\t_RSM_MID=\"$model_id\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\tlet d={};\n\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\tif(!d.agents)d.agents={};\n\t\tif(!d.agents.defaults)d.agents.defaults={};\n\t\tif(!d.agents.defaults.models)d.agents.defaults.models={};\n\t\tif(!d.agents.defaults.model)d.agents.defaults.model={};\n\t\tconst mid=process.env._RSM_MID;\n\t\td.agents.defaults.models[mid]={};\n\t\td.agents.defaults.model.primary=mid;\n\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n}\n\n# ── 注册自定义提供商 (需要 baseUrl 的 OpenAI 兼容提供商) ──\n# 用法: register_custom_provider [model_display_name] [context_window] [max_tokens]\n# 例: register_custom_provider dashscope https://dashscope.aliyuncs.com/compatible-mode/v1 sk-xxx qwen-max \"Qwen Max\"\n# 例: register_custom_provider bailian https://coding.dashscope.aliyuncs.com/v1 sk-sp-xxx qwen3.5-plus \"qwen3.5-plus\" 1000000 65536\nregister_custom_provider() {\n\tlocal provider_name=\"$1\" base_url=\"$2\" api_key=\"$3\" model_id=\"$4\" model_display=\"${5:-$4}\"\n\tlocal ctx_window=\"${6:-128000}\" max_tok=\"${7:-32000}\"\n\t_RCP_PROV=\"$provider_name\" _RCP_URL=\"$base_url\" _RCP_KEY=\"$api_key\" _RCP_MID=\"$model_id\" _RCP_MNAME=\"$model_display\" _RCP_CTX=\"$ctx_window\" _RCP_MAXTOK=\"$max_tok\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\tlet d={};\n\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\tif(!d.models)d.models={};\n\t\tif(!d.models.providers)d.models.providers={};\n\t\td.models.mode='merge';\n\t\tconst prov=process.env._RCP_PROV;\n\t\td.models.providers[prov]={\n\t\t\tbaseUrl:process.env._RCP_URL,\n\t\t\tapiKey:process.env._RCP_KEY,\n\t\t\tapi:'openai-completions',\n\t\t\tmodels:[{\n\t\t\t\tid:process.env._RCP_MID,\n\t\t\t\tname:process.env._RCP_MNAME,\n\t\t\t\treasoning:false,\n\t\t\t\tinput:['text','image'],\n\t\t\t\tcost:{input:0,output:0,cacheRead:0,cacheWrite:0},\n\t\t\t\tcontextWindow:parseInt(process.env._RCP_CTX)||128000,\n\t\t\t\tmaxTokens:parseInt(process.env._RCP_MAXTOK)||32000\n\t\t\t}]\n\t\t};\n\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n}\n\n# ── 注册 Coding Plan 提供商 (多模型批量注册) ──\n# 用法: register_codingplan_provider \n# 按阿里云官方文档注册 bailian 提供商,包含所有 Coding Plan 套餐支持的模型\nregister_codingplan_provider() {\n\tlocal api_key=\"$1\"\n\t_RCP_KEY=\"$api_key\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\tlet d={};\n\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\tif(!d.models)d.models={};\n\t\tif(!d.models.providers)d.models.providers={};\n\t\td.models.mode='merge';\n\t\td.models.providers['bailian']={\n\t\t\tbaseUrl:'https://coding.dashscope.aliyuncs.com/v1',\n\t\t\tapiKey:process.env._RCP_KEY,\n\t\t\tapi:'openai-completions',\n\t\t\tmodels:[\n\t\t\t\t{id:'qwen3.5-plus',name:'qwen3.5-plus',reasoning:false,input:['text','image'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:1000000,maxTokens:65536},\n\t\t\t\t{id:'qwen3-coder-plus',name:'qwen3-coder-plus',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:1000000,maxTokens:65536},\n\t\t\t\t{id:'qwen3-coder-next',name:'qwen3-coder-next',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:262144,maxTokens:65536},\n\t\t\t\t{id:'qwen3-max-2026-01-23',name:'qwen3-max-2026-01-23',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:262144,maxTokens:65536},\n\t\t\t\t{id:'MiniMax-M2.5',name:'MiniMax-M2.5',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:204800,maxTokens:131072},\n\t\t\t\t{id:'glm-5',name:'glm-5',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:202752,maxTokens:16384},\n\t\t\t\t{id:'glm-4.7',name:'glm-4.7',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:202752,maxTokens:16384},\n\t\t\t\t{id:'kimi-k2.5',name:'kimi-k2.5',reasoning:false,input:['text','image'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:262144,maxTokens:32768}\n\t\t\t]\n\t\t};\n\t\tif(!d.agents)d.agents={};\n\t\tif(!d.agents.defaults)d.agents.defaults={};\n\t\tif(!d.agents.defaults.models)d.agents.defaults.models={};\n\t\t['qwen3.5-plus','qwen3-coder-plus','qwen3-coder-next','qwen3-max-2026-01-23','MiniMax-M2.5','glm-5','glm-4.7','kimi-k2.5'].forEach(m=>{\n\t\t\td.agents.defaults.models['bailian/'+m]={};\n\t\t});\n\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n}\n\n# ── 注册腾讯云 Coding Plan 提供商 (多模型批量注册) ──\n# 用法: register_lkeap_codingplan_provider \n# 按腾讯云官方文档 (https://cloud.tencent.com/document/product/1772/128949) 注册\n# provider name: lkeap, Base URL: https://api.lkeap.cloud.tencent.com/coding/v3\nregister_lkeap_codingplan_provider() {\n\tlocal api_key=\"$1\"\n\t_RCP_KEY=\"$api_key\" \"$NODE_BIN\" -e \"\n\t\tconst fs=require('fs');\n\t\tlet d={};\n\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\tif(!d.models)d.models={};\n\t\tif(!d.models.providers)d.models.providers={};\n\t\td.models.mode='merge';\n\t\td.models.providers['lkeap']={\n\t\t\tbaseUrl:'https://api.lkeap.cloud.tencent.com/coding/v3',\n\t\t\tapiKey:process.env._RCP_KEY,\n\t\t\tapi:'openai-completions',\n\t\t\tmodels:[\n\t\t\t\t{id:'tc-code-latest',name:'Auto (智能匹配最优模型)',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:128000,maxTokens:8192},\n\t\t\t\t{id:'hunyuan-2.0-instruct',name:'Tencent HY 2.0 Instruct',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:128000,maxTokens:16000},\n\t\t\t\t{id:'hunyuan-2.0-thinking',name:'Tencent HY 2.0 Think',reasoning:true,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:128000,maxTokens:64000},\n\t\t\t\t{id:'hunyuan-t1',name:'Hunyuan-T1',reasoning:true,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:32000,maxTokens:64000},\n\t\t\t\t{id:'hunyuan-turbos',name:'Hunyuan-TurboS',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:32000,maxTokens:16000},\n\t\t\t\t{id:'minimax-m2.5',name:'MiniMax-M2.5',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:204800,maxTokens:131072},\n\t\t\t\t{id:'kimi-k2.5',name:'Kimi-K2.5',reasoning:false,input:['text','image'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:262144,maxTokens:32768},\n\t\t\t\t{id:'glm-5',name:'GLM-5',reasoning:false,input:['text'],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:202752,maxTokens:8192}\n\t\t\t]\n\t\t};\n\t\tif(!d.agents)d.agents={};\n\t\tif(!d.agents.defaults)d.agents.defaults={};\n\t\tif(!d.agents.defaults.models)d.agents.defaults.models={};\n\t\t['tc-code-latest','hunyuan-2.0-instruct','hunyuan-2.0-thinking','hunyuan-t1','hunyuan-turbos','minimax-m2.5','kimi-k2.5','glm-5'].forEach(m=>{\n\t\t\td.agents.defaults.models['lkeap/'+m]={};\n\t\t});\n\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\" 2>/dev/null\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n}\n\n# ── 辅助函数 ──\n\n# 清理输入: 去除 ANSI 转义序列、不可见字符,只保留 ASCII 可打印字符\nsanitize_input() {\n\t# 1) tr -cd ' -~' : 只保留 ASCII 0x20-0x7E (去除 ESC/控制字符/Unicode 不可见字符)\n\t# 2) sed : 去除 ESC 被剥离后残余的 CSI 序列 (如 bracketed paste 的 [200~ [201~)\n\t# 3) sed : 去除首尾空白\n\tprintf '%s' \"$1\" | tr -cd ' -~' | sed 's/\\[[0-9;]*[a-zA-Z~]//g' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'\n}\n\nprompt_with_default() {\n\tlocal prompt=\"$1\" default=\"$2\" varname=\"$3\"\n\tif [ -n \"$default\" ]; then\n\t\tprintf \" ${CYAN}${prompt} [${default}]:${NC} \" >&2\n\telse\n\t\tprintf \" ${CYAN}${prompt}:${NC} \" >&2\n\tfi\n\tread input\n\tinput=$(sanitize_input \"$input\")\n\tlocal _result=\"${input:-$default}\"\n\texport \"__prompt_result__=$_result\"\n\teval \"$varname=\\\"\\$__prompt_result__\\\"\"\n\tunset __prompt_result__\n}\n\nconfirm_yes() {\n\tlocal ans=\"$1\"\n\tcase \"$ans\" in y|Y|yes|YES|Yes) return 0 ;; *) return 1 ;; esac\n}\n\nrestart_gateway() {\n\techo \"\"\n\techo -e \" ${YELLOW}正在重启 Gateway...${NC}\"\n\n\t# 修复数据目录权限 (root 用户操作可能改变了文件属主)\n\tfix_openclaw_state_permissions 2>/dev/null || true\n\n\t# 首次安装后 UCI 默认保持 disabled。用户在配置向导中确认“立即重启”\n\t# 即表示希望当前配置生效,这里同步启用服务,避免提示重启但实际仍为“已禁用”。\n\tif [ \"$(uci -q get openclaw.main.enabled 2>/dev/null || echo 0)\" != \"1\" ]; then\n\t\techo -e \" ${CYAN}检测到 Gateway 未启用,正在启用服务...${NC}\"\n\t\tuci -q set openclaw.main.enabled='1' 2>/dev/null || true\n\t\tuci -q commit openclaw 2>/dev/null || true\n\t\t/etc/init.d/openclaw enable >/dev/null 2>&1 || true\n\tfi\n\n\tlocal port\n\tport=$(json_get gateway.port)\n\tport=${port:-18789}\n\n\t# ── kill gateway 进程,让 procd respawn ──\n\t/etc/init.d/openclaw restart_gateway >/dev/null 2>&1\n\t/etc/init.d/openclaw start >/dev/null 2>&1\n\n\t# ── 等待端口恢复 (最多 30 秒,含端口释放 + Node.js 冷启动) ──\n\techo -e \" ${YELLOW}⏳ Gateway 启动中,请稍候 (约 15-30 秒)...${NC}\"\n\tlocal waited=0\n\twhile [ $waited -lt 30 ]; do\n\t\tsleep 3\n\t\twaited=$((waited + 3))\n\t\tif check_port_listening \"$port\"; then\n\t\t\techo -e \" ${GREEN}✅ Gateway 已重启成功 (${waited}秒)${NC}\"\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\t# 30 秒内没就绪,提示用户但不继续阻塞\n\techo -e \" ${YELLOW}⏳ Gateway 仍在启动中,请稍后确认${NC}\"\n\techo -e \" ${CYAN} 查看日志: logread -e openclaw${NC}\"\n\techo \"\"\n}\n\nask_restart() {\n\tprompt_with_default \"是否立即重启 Gateway? (y/n)\" \"y\" do_restart\n\tconfirm_yes \"$do_restart\" && restart_gateway\n}\n\n# ── 查看当前配置 ──\nshow_current_config() {\n\techo \"\"\n\techo -e \"${GREEN}┌──────────────────────────────────────────────────────────┐${NC}\"\n\techo -e \"${GREEN}│${NC} 📋 ${BOLD}当前配置概览${NC}\"\n\techo -e \"${GREEN}├──────────────────────────────────────────────────────────┤${NC}\"\n\n\tlocal port=$(json_get gateway.port)\n\tlocal bind=$(json_get gateway.bind)\n\tlocal mode=$(json_get gateway.mode)\n\techo -e \"${GREEN}│${NC} 网关端口 ............ ${CYAN}${port:-18789}${NC}\"\n\techo -e \"${GREEN}│${NC} 绑定模式 ............ ${CYAN}${bind:-lan}${NC}\"\n\techo -e \"${GREEN}│${NC} 运行模式 ............ ${CYAN}${mode:-local}${NC}\"\n\n\tlocal model=$(json_get agents.defaults.model.primary)\n\tif [ -n \"$model\" ]; then\n\t\techo -e \"${GREEN}│${NC} 活跃模型 ............ ${CYAN}${model}${NC}\"\n\telse\n\t\techo -e \"${GREEN}│${NC} 活跃模型 ............ ${YELLOW}未配置${NC}\"\n\tfi\n\n\techo -e \"${GREEN}├──────────────────────────────────────────────────────────┤${NC}\"\n\techo -e \"${GREEN}│${NC} ${BOLD}渠道配置状态${NC}\"\n\n\t# 检测微信渠道\n\tlocal wechat_ext_dir=\"${OC_STATE_DIR}/extensions/openclaw-weixin\"\n\tlocal wechat_npm_plugin=\"\"\n\tif [ -d \"${OC_STATE_DIR}/npm/projects\" ]; then\n\t\twechat_npm_plugin=$(find \"${OC_STATE_DIR}/npm/projects\" -path \"*/node_modules/@tencent-weixin/openclaw-weixin/openclaw.plugin.json\" -type f 2>/dev/null | head -n 1)\n\tfi\n\tif { [ -d \"$wechat_ext_dir\" ] && [ -f \"${wechat_ext_dir}/openclaw.plugin.json\" ]; } || [ -n \"$wechat_npm_plugin\" ]; then\n\t\techo -e \"${GREEN}│${NC} 微信 .............. ${GREEN}✅ 已配置${NC}\"\n\telse\n\t\techo -e \"${GREEN}│${NC} 微信 .............. ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\n\tlocal tg_token=$(json_get channels.telegram.botToken)\n\tlocal dc_token=$(json_get channels.discord.botToken)\n\tlocal fs_appid=$(json_get channels.feishu.appId)\n\tlocal sk_token=$(json_get channels.slack.botToken)\n\tlocal qq_appid=$(json_get channels.qqbot.appId)\n\n\tif [ -n \"$qq_appid\" ]; then\n\t\tlocal qq_short=$(echo \"$qq_appid\" | cut -c1-8)\n\t\techo -e \"${GREEN}│${NC} QQ (qqbot) ......... ${GREEN}✅ 已配置${NC} (AppID: ${qq_short}...)\"\n\telse\n\t\techo -e \"${GREEN}│${NC} QQ (qqbot) ......... ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\tif [ -n \"$tg_token\" ]; then\n\t\tlocal tg_short=$(echo \"$tg_token\" | cut -c1-12)\n\t\techo -e \"${GREEN}│${NC} Telegram ............ ${GREEN}✅ 已配置${NC} (${tg_short}...)\"\n\telse\n\t\techo -e \"${GREEN}│${NC} Telegram ............ ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\tif [ -n \"$dc_token\" ]; then\n\t\techo -e \"${GREEN}│${NC} Discord ............. ${GREEN}✅ 已配置${NC}\"\n\telse\n\t\techo -e \"${GREEN}│${NC} Discord ............. ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\tif [ -n \"$fs_appid\" ]; then\n\t\tlocal fs_short=$(echo \"$fs_appid\" | cut -c1-6)\n\t\techo -e \"${GREEN}│${NC} 飞书 ................ ${GREEN}✅ 已配置${NC} (AppID: ${fs_short}...)\"\n\telse\n\t\techo -e \"${GREEN}│${NC} 飞书 ................ ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\tif [ -n \"$sk_token\" ]; then\n\t\techo -e \"${GREEN}│${NC} Slack ............... ${GREEN}✅ 已配置${NC}\"\n\telse\n\t\techo -e \"${GREEN}│${NC} Slack ............... ${YELLOW}❌ 未配置${NC}\"\n\tfi\n\n\techo -e \"${GREEN}└──────────────────────────────────────────────────────────┘${NC}\"\n\n\techo \"\"\n\techo -e \" ${BOLD}系统信息:${NC}\"\n\techo -e \" Node.js: $(\"$NODE_BIN\" -v 2>/dev/null || echo '未安装')\"\n\tif [ -n \"$OC_ENTRY\" ]; then\n\t\techo -e \" OpenClaw: $(oc_cmd --version 2>/dev/null || echo '未知')\"\n\telse\n\t\techo -e \" OpenClaw: 未安装\"\n\tfi\n\techo -e \" 架构: $(uname -m)\"\n\tlocal mem_total=$(awk '/MemTotal/{printf \"%.0f\", $2/1024}' /proc/meminfo 2>/dev/null || echo \"?\")\n\techo -e \" 内存: ${mem_total} MB\"\n}\n\n# ══════════════════════════════════════════════════════════════\n# 配置 AI 模型\n# ══════════════════════════════════════════════════════════════\nconfigure_model() {\n\techo \"\"\n\techo -e \" ${BOLD}🤖 配置 AI 模型和提供商${NC}\"\n\techo \"\"\n\techo -e \" ${GREEN}${BOLD}🌟 ── 推荐 ──${NC}\"\n\techo -e \" ${CYAN}w)${NC} 🌟 官方完整模型配置向导 ${GREEN}(推荐,支持所有提供商)${NC}\"\n\techo \"\"\n\techo -e \" ${BOLD}🌍 ── 国外模型提供商 ──${NC}\"\n\techo -e \" ${CYAN}a)${NC} OpenAI (GPT-5.2, GPT-5 mini, GPT-4.1)\"\n\techo -e \" ${CYAN}b)${NC} Anthropic (Claude Sonnet 4, Opus 4, Haiku)\"\n\techo -e \" ${CYAN}c)${NC} Google Gemini (Gemini 2.5 Pro/Flash, Gemini 3)\"\n\techo -e \" ${CYAN}d)${NC} OpenRouter (聚合多家模型)\"\n\techo -e \" ${CYAN}e)${NC} GitHub Copilot (需要 Copilot 订阅)\"\n\techo -e \" ${CYAN}f)${NC} xAI Grok (Grok-4/3)\"\n\techo \"\"\n\techo -e \" ${BOLD}🇨🇳 ── 国内模型提供商 ──${NC}\"\n\techo -e \" ${CYAN}g)${NC} 阿里云通义千问 Qwen (Portal/API/Coding Plan)\"\n\techo -e \" ${CYAN}h)${NC} 硅基流动 SiliconFlow\"\n\techo -e \" ${CYAN}i)${NC} 腾讯云 Coding Plan (HY T1/TurboS/GLM-5/Kimi)\"\n\techo -e \" ${CYAN}j)${NC} 百度千帆 (ERNIE-4.0, ERNIE-3.5)\"\n\techo -e \" ${CYAN}k)${NC} 智谱 GLM / Z.AI (GLM-5.1, GLM-5)\"\n\techo \"\"\n\techo -e \" ${BOLD}🏠 ── 本地模型 / 自定义 API ──${NC}\"\n\techo -e \" ${CYAN}l)${NC} Ollama (本地模型,无需 API Key)\"\n\techo -e \" ${CYAN}m)${NC} 自定义 OpenAI 兼容 API\"\n\techo -e \" ${CYAN}n)${NC} 自定义 Anthropic 兼容 API\"\n\techo -e \" ${CYAN}o)${NC} 一万AI分享 粉丝专享 API\"\n\techo \"\"\n\techo -e \" ${CYAN}q)${NC} 返回\"\n\techo \"\"\n\tprompt_with_default \"请选择\" \"w\" choice\n\n\tcase \"$choice\" in\n\t\tw)\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}启动官方完整模型配置向导...${NC}\"\n\t\t\techo -e \" ${YELLOW}提示: ↑↓ 移动, Tab/空格 选中, 回车 确认${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}清理过时插件配置...${NC}\"\n\t\t\tenable_auth_plugins\n\t\t\techo \"\"\n\t\t\t(oc_cmd configure --section model) || echo -e \" ${YELLOW}配置向导已退出${NC}\"\n\t\t\techo \"\"\n\t\t\task_restart\n\t\t\t;;\n\t\ta)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}OpenAI 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://platform.openai.com/api-keys${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 OpenAI API Key (sk-...)\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey openai \"$api_key\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} gpt-5.2 — 最强编程与代理旗舰 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} gpt-5-mini — 高性价比推理\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} gpt-5-nano — 极速低成本\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} gpt-4.1 — 最强非推理模型\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} o3 — 推理模型\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} o4-mini — 推理轻量\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"gpt-5.2\" ;;\n\t\t\t\t\tb) model_name=\"gpt-5-mini\" ;;\n\t\t\t\t\tc) model_name=\"gpt-5-nano\" ;;\n\t\t\t\t\td) model_name=\"gpt-4.1\" ;;\n\t\t\t\t\te) model_name=\"o3\" ;;\n\t\t\t\t\tf) model_name=\"o4-mini\" ;;\n\t\t\t\t\tg) prompt_with_default \"请输入模型名称\" \"gpt-5.2\" model_name ;;\n\t\t\t\t\t*) model_name=\"gpt-5.2\" ;;\n\t\t\t\tesac\n\t\t\t\tregister_and_set_model \"openai/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ OpenAI 已配置,活跃模型: openai/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tb)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}Anthropic 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://console.anthropic.com/settings/keys${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 Anthropic API Key (sk-ant-...)\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey anthropic \"$api_key\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} claude-sonnet-4-20250514 — Claude Sonnet 4 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} claude-opus-4-20250514 — Claude Opus 4 顶级推理\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} claude-haiku-4-5 — Claude Haiku 4.5 轻量快速\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} claude-sonnet-4.5 — Claude Sonnet 4.5\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} claude-sonnet-4.6 — Claude Sonnet 4.6\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"claude-sonnet-4-20250514\" ;;\n\t\t\t\t\tb) model_name=\"claude-opus-4-20250514\" ;;\n\t\t\t\t\tc) model_name=\"claude-haiku-4-5\" ;;\n\t\t\t\t\td) model_name=\"claude-sonnet-4-5\" ;;\n\t\t\t\t\te) model_name=\"claude-sonnet-4-6\" ;;\n\t\t\t\t\tf) prompt_with_default \"请输入模型名称\" \"claude-sonnet-4-20250514\" model_name ;;\n\t\t\t\t\t*) model_name=\"claude-sonnet-4-20250514\" ;;\n\t\t\t\tesac\n\t\t\t\tregister_and_set_model \"anthropic/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ Anthropic 已配置,活跃模型: anthropic/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tc)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}Google Gemini 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://aistudio.google.com/apikey${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 Google AI API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey google \"$api_key\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} gemini-2.5-pro — 旗舰推理 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} gemini-2.5-flash — 快速均衡\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} gemini-2.5-flash-lite — 极速低成本\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} gemini-3-flash-preview — Gemini 3 Flash 预览\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} gemini-3-pro-preview — Gemini 3 Pro 预览\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"gemini-2.5-pro\" ;;\n\t\t\t\t\tb) model_name=\"gemini-2.5-flash\" ;;\n\t\t\t\t\tc) model_name=\"gemini-2.5-flash-lite\" ;;\n\t\t\t\t\td) model_name=\"gemini-3-flash-preview\" ;;\n\t\t\t\t\te) model_name=\"gemini-3-pro-preview\" ;;\n\t\t\t\t\tf) prompt_with_default \"请输入模型名称\" \"gemini-2.5-pro\" model_name ;;\n\t\t\t\t\t*) model_name=\"gemini-2.5-pro\" ;;\n\t\t\t\tesac\n\t\t\t\tregister_and_set_model \"google/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ Google Gemini 已配置,活跃模型: google/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\td)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}OpenRouter 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://openrouter.ai/keys${NC}\"\n\t\t\techo -e \" ${YELLOW}聚合多家模型,一个 Key 可调用所有主流模型${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 OpenRouter API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey openrouter \"$api_key\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}常用模型 (格式: provider/model):${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} anthropic/claude-sonnet-4 — Claude Sonnet 4 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} anthropic/claude-opus-4 — Claude Opus 4\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} openai/gpt-5.2 — GPT-5.2\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} google/gemini-2.5-pro — Gemini 2.5 Pro\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} deepseek/deepseek-r1 — DeepSeek R1\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} meta-llama/llama-4-maverick — Meta Llama 4\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"anthropic/claude-sonnet-4\" ;;\n\t\t\t\t\tb) model_name=\"anthropic/claude-opus-4\" ;;\n\t\t\t\t\tc) model_name=\"openai/gpt-5.2\" ;;\n\t\t\t\t\td) model_name=\"google/gemini-2.5-pro\" ;;\n\t\t\t\t\te) model_name=\"deepseek/deepseek-r1\" ;;\n\t\t\t\t\tf) model_name=\"meta-llama/llama-4-maverick\" ;;\n\t\t\t\t\tg) prompt_with_default \"请输入模型名称\" \"anthropic/claude-sonnet-4\" model_name ;;\n\t\t\t\t\t*) model_name=\"anthropic/claude-sonnet-4\" ;;\n\t\t\t\tesac\n\t\t\t\tregister_and_set_model \"openrouter/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ OpenRouter 已配置,活跃模型: openrouter/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\te)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}GitHub Copilot 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}需要有效的 GitHub Copilot 订阅 (Free/Pro/Business 均可)${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}启动 GitHub Copilot OAuth 登录 (Device Flow)...${NC}\"\n\t\t\techo -e \" ${DIM}请在浏览器中打开显示的 URL,输入授权码完成登录${NC}\"\n\t\t\techo \"\"\n\t\t\tif oc_cmd models auth login-github-copilot --yes; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${GREEN}✅ GitHub Copilot OAuth 认证成功${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}选择默认模型:${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}── GPT 系列 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} github-copilot/gpt-4.1 — GPT-4.1 ${GREEN}(推荐)${NC}\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} github-copilot/gpt-4o — GPT-4o\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} github-copilot/gpt-5 — GPT-5\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} github-copilot/gpt-5-mini — GPT-5 mini\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} github-copilot/gpt-5.1 — GPT-5.1\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} github-copilot/gpt-5.2 — GPT-5.2\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} github-copilot/gpt-5.2-codex — GPT-5.2 Codex\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}── Claude 系列 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}h)${NC} github-copilot/claude-sonnet-4 — Claude Sonnet 4\"\n\t\t\t\techo -e \" ${CYAN}i)${NC} github-copilot/claude-sonnet-4.5 — Claude Sonnet 4.5\"\n\t\t\t\techo -e \" ${CYAN}j)${NC} github-copilot/claude-sonnet-4.6 — Claude Sonnet 4.6\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}── Gemini 系列 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}k)${NC} github-copilot/gemini-2.5-pro — Gemini 2.5 Pro\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}m)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"github-copilot/gpt-4.1\" ;;\n\t\t\t\t\tb) model_name=\"github-copilot/gpt-4o\" ;;\n\t\t\t\t\tc) model_name=\"github-copilot/gpt-5\" ;;\n\t\t\t\t\td) model_name=\"github-copilot/gpt-5-mini\" ;;\n\t\t\t\t\te) model_name=\"github-copilot/gpt-5.1\" ;;\n\t\t\t\t\tf) model_name=\"github-copilot/gpt-5.2\" ;;\n\t\t\t\t\tg) model_name=\"github-copilot/gpt-5.2-codex\" ;;\n\t\t\t\t\th) model_name=\"github-copilot/claude-sonnet-4\" ;;\n\t\t\t\t\ti) model_name=\"github-copilot/claude-sonnet-4.5\" ;;\n\t\t\t\t\tj) model_name=\"github-copilot/claude-sonnet-4.6\" ;;\n\t\t\t\t\tk) model_name=\"github-copilot/gemini-2.5-pro\" ;;\n\t\t\t\t\tm) prompt_with_default \"请输入模型名称\" \"github-copilot/gpt-4.1\" model_name ;;\n\t\t\t\t\t*) model_name=\"github-copilot/gpt-4.1\" ;;\n\t\t\t\tesac\n\t\t\t\tregister_and_set_model \"$model_name\"\n\t\t\t\techo -e \" ${GREEN}✅ 活跃模型已设置: ${model_name}${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}OAuth 授权已退出或失败${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tg)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}阿里云通义千问 Qwen 配置${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}配置方式:${NC}\"\n\t\t\techo -e \" ${CYAN}a)${NC} 通过官方向导配置 (Qwen Portal OAuth)\"\n\t\t\techo -e \" ${CYAN}b)${NC} 百炼按量付费 API Key (sk-xxx, 按 token 计费)\"\n\t\t\techo -e \" ${CYAN}c)${NC} ${GREEN}Coding Plan 套餐${NC} (sk-sp-xxx, 按套餐抵扣额度) ${GREEN}★ 推荐${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${DIM}提示: Coding Plan 套餐和百炼按量付费的 API Key / Base URL 不互通,请勿混用${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请选择\" \"c\" qwen_mode\n\t\t\tcase \"$qwen_mode\" in\n\t\t\t\ta)\n\t\t\t\t\techo \"\"\n\t\t\t\t\techo -e \" ${CYAN}启用 Qwen Portal Auth 插件...${NC}\"\n\t\t\t\t\tenable_auth_plugins\n\t\t\t\t\techo -e \" ${CYAN}启动 Qwen OAuth 授权...${NC}\"\n\t\t\t\t\toc_cmd models auth login --provider qwen-portal --set-default || echo -e \" ${YELLOW}OAuth 授权已退出${NC}\"\n\t\t\t\t\techo \"\"\n\t\t\t\t\task_restart\n\t\t\t\t\treturn\n\t\t\t\t\t;;\n\t\t\t\tb)\n\t\t\t\t\techo \"\"\n\t\t\t\t\techo -e \" ${BOLD}百炼按量付费配置${NC}\"\n\t\t\t\t\techo -e \" ${YELLOW}获取 API Key: https://dashscope.console.aliyun.com/apiKey${NC}\"\n\t\t\t\t\techo -e \" ${DIM}Base URL: https://dashscope.aliyuncs.com/compatible-mode/v1${NC}\"\n\t\t\t\t\techo \"\"\n\t\t\t\t\tprompt_with_default \"请输入百炼 API Key (sk-...)\" \"\" api_key\n\t\t\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\techo -e \" ${CYAN}── 千问商业版 ──${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}a)${NC} qwen-max — 千问Max 旗舰模型 (推荐)\"\n\t\t\t\t\t\techo -e \" ${CYAN}b)${NC} qwen-plus — 千问Plus 均衡之选 (已升级Qwen3.5)\"\n\t\t\t\t\t\techo -e \" ${CYAN}c)${NC} qwen-flash — 千问Flash 速度最快 (已升级Qwen3.5)\"\n\t\t\t\t\t\techo -e \" ${CYAN}d)${NC} qwen-turbo — 千问Turbo 经济实惠\"\n\t\t\t\t\t\techo -e \" ${CYAN}e)${NC} qwen-long — 千问Long 超长上下文 (1000万Token)\"\n\t\t\t\t\t\techo -e \" ${CYAN}── 千问Coder ──${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}f)${NC} qwen3-coder-plus — 代码专用旗舰 (100万上下文)\"\n\t\t\t\t\t\techo -e \" ${CYAN}g)${NC} qwen3-coder-flash — 代码专用极速\"\n\t\t\t\t\t\techo -e \" ${CYAN}── 推理模型 ──${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}h)${NC} qwq-plus — QwQ推理模型 (数学/代码强化)\"\n\t\t\t\t\t\techo -e \" ${CYAN}── 千问开源版 ──${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}i)${NC} qwen3-235b-a22b — Qwen3 235B MoE\"\n\t\t\t\t\t\techo -e \" ${CYAN}j)${NC} qwen3-32b — Qwen3 32B\"\n\t\t\t\t\t\techo -e \" ${CYAN}k)${NC} qwen3-30b-a3b — Qwen3 30B MoE\"\n\t\t\t\t\t\techo -e \" ${CYAN}── 第三方模型 ──${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}l)${NC} deepseek-r1 — DeepSeek R1 推理\"\n\t\t\t\t\t\techo -e \" ${CYAN}m)${NC} deepseek-v3 — DeepSeek V3\"\n\t\t\t\t\t\techo -e \" ${CYAN}n)${NC} kimi-k2.5 — Kimi K2.5\"\n\t\t\t\t\t\techo -e \" ${CYAN}o)${NC} glm-5 — 智谱 GLM-5\"\n\t\t\t\t\t\techo -e \" ${CYAN}p)${NC} MiniMax-M2.5 — MiniMax M2.5\"\n\t\t\t\t\t\techo -e \" ${CYAN}────────────${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}z)${NC} 手动输入模型名\"\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\t\t\ta) model_name=\"qwen-max\" ;;\n\t\t\t\t\t\t\tb) model_name=\"qwen-plus\" ;;\n\t\t\t\t\t\t\tc) model_name=\"qwen-flash\" ;;\n\t\t\t\t\t\t\td) model_name=\"qwen-turbo\" ;;\n\t\t\t\t\t\t\te) model_name=\"qwen-long\" ;;\n\t\t\t\t\t\t\tf) model_name=\"qwen3-coder-plus\" ;;\n\t\t\t\t\t\t\tg) model_name=\"qwen3-coder-flash\" ;;\n\t\t\t\t\t\t\th) model_name=\"qwq-plus\" ;;\n\t\t\t\t\t\t\ti) model_name=\"qwen3-235b-a22b\" ;;\n\t\t\t\t\t\t\tj) model_name=\"qwen3-32b\" ;;\n\t\t\t\t\t\t\tk) model_name=\"qwen3-30b-a3b\" ;;\n\t\t\t\t\t\t\tl) model_name=\"deepseek-r1\" ;;\n\t\t\t\t\t\t\tm) model_name=\"deepseek-v3\" ;;\n\t\t\t\t\t\t\tn) model_name=\"kimi-k2.5\" ;;\n\t\t\t\t\t\t\to) model_name=\"glm-5\" ;;\n\t\t\t\t\t\t\tp) model_name=\"MiniMax-M2.5\" ;;\n\t\t\t\t\t\t\tz) prompt_with_default \"请输入模型名称\" \"qwen-max\" model_name ;;\n\t\t\t\t\t\t\t*) model_name=\"qwen-max\" ;;\n\t\t\t\t\t\tesac\n\t\t\t\t\t\tauth_set_apikey dashscope \"$api_key\"\n\t\t\t\t\t\tregister_custom_provider dashscope \"https://dashscope.aliyuncs.com/compatible-mode/v1\" \"$api_key\" \"$model_name\" \"$model_name\"\n\t\t\t\t\t\tregister_and_set_model \"dashscope/${model_name}\"\n\t\t\t\t\t\techo -e \" ${GREEN}✅ 通义千问已配置 (按量付费),活跃模型: dashscope/${model_name}${NC}\"\n\t\t\t\t\tfi\n\t\t\t\t\t;;\n\t\t\t\tc|*)\n\t\t\t\t\techo \"\"\n\t\t\t\t\techo -e \" ${BOLD}Coding Plan 套餐配置${NC}\"\n\t\t\t\t\techo -e \" ${YELLOW}订阅套餐: https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan${NC}\"\n\t\t\t\t\techo -e \" ${YELLOW}获取专属 API Key: 在上方页面获取 Coding Plan 专属 Key (sk-sp-...)${NC}\"\n\t\t\t\t\techo -e \" ${DIM}Base URL: https://coding.dashscope.aliyuncs.com/v1${NC}\"\n\t\t\t\t\techo -e \" ${DIM}文档: https://help.aliyun.com/zh/model-studio/openclaw-coding-plan${NC}\"\n\t\t\t\t\techo \"\"\n\t\t\t\t\tprompt_with_default \"请输入 Coding Plan 专属 API Key (sk-sp-...)\" \"\" api_key\n\t\t\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN}a)${NC} qwen3.5-plus — Qwen3.5 Plus (推荐, 100万上下文)\"\n\t\t\t\t\t\techo -e \" ${CYAN}b)${NC} qwen3-coder-plus — Qwen3 Coder Plus (代码专用, 100万上下文)\"\n\t\t\t\t\t\techo -e \" ${CYAN}c)${NC} qwen3-coder-next — Qwen3 Coder Next\"\n\t\t\t\t\t\techo -e \" ${CYAN}d)${NC} qwen3-max-2026-01-23 — Qwen3 Max\"\n\t\t\t\t\t\techo -e \" ${CYAN}e)${NC} MiniMax-M2.5 — MiniMax M2.5\"\n\t\t\t\t\t\techo -e \" ${CYAN}f)${NC} glm-5 — 智谱 GLM-5\"\n\t\t\t\t\t\techo -e \" ${CYAN}g)${NC} kimi-k2.5 — Kimi K2.5\"\n\t\t\t\t\t\techo -e \" ${CYAN}h)${NC} 手动输入模型名\"\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\tprompt_with_default \"请选择默认模型\" \"a\" model_choice\n\t\t\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\t\t\ta) model_name=\"qwen3.5-plus\" ;;\n\t\t\t\t\t\t\tb) model_name=\"qwen3-coder-plus\" ;;\n\t\t\t\t\t\t\tc) model_name=\"qwen3-coder-next\" ;;\n\t\t\t\t\t\t\td) model_name=\"qwen3-max-2026-01-23\" ;;\n\t\t\t\t\t\t\te) model_name=\"MiniMax-M2.5\" ;;\n\t\t\t\t\t\t\tf) model_name=\"glm-5\" ;;\n\t\t\t\t\t\t\tg) model_name=\"kimi-k2.5\" ;;\n\t\t\t\t\t\t\th) prompt_with_default \"请输入模型名称\" \"qwen3.5-plus\" model_name ;;\n\t\t\t\t\t\t\t*) model_name=\"qwen3.5-plus\" ;;\n\t\t\t\t\t\tesac\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\techo -e \" ${CYAN}正在注册 Coding Plan 提供商 (含全部可用模型)...${NC}\"\n\t\t\t\t\t\tauth_set_apikey bailian \"$api_key\"\n\t\t\t\t\t\tregister_codingplan_provider \"$api_key\"\n\t\t\t\t\t\tregister_and_set_model \"bailian/${model_name}\"\n\t\t\t\t\t\techo -e \" ${GREEN}✅ Coding Plan 已配置,活跃模型: bailian/${model_name}${NC}\"\n\t\t\t\t\t\techo -e \" ${DIM}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${NC}\"\n\t\t\t\t\tfi\n\t\t\t\t\t;;\n\t\t\tesac\n\t\t\t;;\n\t\tf)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}xAI Grok 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://console.x.ai${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 xAI API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} grok-4 — Grok 4 旗舰 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} grok-4-fast — Grok 4 Fast\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} grok-3 — Grok 3\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} grok-3-fast — Grok 3 Fast\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} grok-3-mini — Grok 3 Mini\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} grok-3-mini-fast — Grok 3 Mini Fast\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"grok-4\" ;;\n\t\t\t\t\tb) model_name=\"grok-4-fast\" ;;\n\t\t\t\t\tc) model_name=\"grok-3\" ;;\n\t\t\t\t\td) model_name=\"grok-3-fast\" ;;\n\t\t\t\t\te) model_name=\"grok-3-mini\" ;;\n\t\t\t\t\tf) model_name=\"grok-3-mini-fast\" ;;\n\t\t\t\t\tg) prompt_with_default \"请输入模型名称\" \"grok-4\" model_name ;;\n\t\t\t\t\t*) model_name=\"grok-4\" ;;\n\t\t\t\tesac\n\t\t\t\tauth_set_apikey xai \"$api_key\"\n\t\t\t\tregister_and_set_model \"xai/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ xAI Grok 已配置,活跃模型: xai/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\th)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}硅基流动 SiliconFlow 配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://cloud.siliconflow.cn/account/ak${NC}\"\n\t\t\techo -e \" ${YELLOW}国内推理平台,支持多种开源模型${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 SiliconFlow API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型分类说明:${NC}\"\n\t\t\t\techo -e \" ${YELLOW}* Pro模型 (带 Pro/ 前缀): 仅支持充值余额支付,并发与速率(Rate Limits)可变。${NC}\"\n\t\t\t\techo -e \" ${YELLOW}* 非Pro模型 (无 Pro/ 前缀): 支持赠费余额(代金券)和充值余额支付,Rate Limits 固定。${NC}\"\n\t\t\t\techo -e \" ${GREEN}【建议】如果您是代金券/赠送余额用户,请务必选择【非Pro模型】。${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}── 非Pro模型 (支持代金券/免费额度) ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}1)${NC} deepseek-ai/DeepSeek-V3 — DeepSeek-V3 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}2)${NC} deepseek-ai/DeepSeek-R1 — DeepSeek-R1 (推理模型)\"\n\t\t\t\techo -e \" ${CYAN}3)${NC} Qwen/Qwen2.5-72B-Instruct — 通义千问 2.5 72B\"\n\t\t\t\techo -e \" ${CYAN}4)${NC} Qwen/Qwen2.5-7B-Instruct — 通义千问 2.5 7B\"\n\t\t\t\techo -e \" ${CYAN}5)${NC} THUDM/glm-4-9b-chat — 智谱 GLM-4 9B\"\n\t\t\t\techo -e \" ${CYAN}6)${NC} 01-ai/Yi-1.5-34B-Chat-16K — 零一万物 Yi-1.5 34B\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}── Pro模型 (仅支持充值余额) ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}7)${NC} Pro/deepseek-ai/DeepSeek-V3 — DeepSeek-V3 (Pro增强侧)\"\n\t\t\t\techo -e \" ${CYAN}8)${NC} Pro/zai-org/GLM-5 — 智谱 GLM-5\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}0)${NC} 手动输入其他任意模型名称\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型 [0-8]\" \"1\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\t1) model_name=\"deepseek-ai/DeepSeek-V3\" ;;\n\t\t\t\t\t2) model_name=\"deepseek-ai/DeepSeek-R1\" ;;\n\t\t\t\t\t3) model_name=\"Qwen/Qwen2.5-72B-Instruct\" ;;\n\t\t\t\t\t4) model_name=\"Qwen/Qwen2.5-7B-Instruct\" ;;\n\t\t\t\t\t5) model_name=\"THUDM/glm-4-9b-chat\" ;;\n\t\t\t\t\t6) model_name=\"01-ai/Yi-1.5-34B-Chat-16K\" ;;\n\t\t\t\t\t7) model_name=\"Pro/deepseek-ai/DeepSeek-V3\" ;;\n\t\t\t\t\t8) model_name=\"Pro/zai-org/GLM-5\" ;;\n\t\t\t\t\t0) prompt_with_default \"请输入模型详细名称\" \"deepseek-ai/DeepSeek-V3\" model_name ;;\n\t\t\t\t\t*) model_name=\"deepseek-ai/DeepSeek-V3\" ;;\n\t\t\t\tesac\n\t\t\t\tauth_set_apikey siliconflow \"$api_key\"\n\t\t\t\tregister_custom_provider siliconflow \"https://api.siliconflow.cn/v1\" \"$api_key\" \"$model_name\" \"$model_name\"\n\t\t\t\tregister_and_set_model \"siliconflow/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ SiliconFlow 已配置,活跃模型: siliconflow/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tl)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}🦙 Ollama 本地模型配置${NC}\"\n\t\t\techo -e \" ${YELLOW}Ollama 在本地或局域网运行大模型,无需 API Key${NC}\"\n\t\t\techo -e \" ${YELLOW}安装 Ollama: https://ollama.com${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}连接方式:${NC}\"\n\t\t\techo -e \" ${CYAN}a)${NC} 本机运行 (localhost:11434)\"\n\t\t\techo -e \" ${CYAN}b)${NC} 局域网其他设备\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请选择\" \"a\" ollama_mode\n\t\t\tlocal ollama_url=\"\"\n\t\t\tcase \"$ollama_mode\" in\n\t\t\t\tb)\n\t\t\t\t\tprompt_with_default \"Ollama 地址 (如 192.168.1.100:11434)\" \"\" ollama_host\n\t\t\t\t\tif [ -n \"$ollama_host\" ]; then\n\t\t\t\t\t\t# 补全协议前缀\n\t\t\t\t\t\tcase \"$ollama_host\" in\n\t\t\t\t\t\t\thttp://*|https://*) ollama_url=\"${ollama_host}\" ;;\n\t\t\t\t\t\t\t*) ollama_url=\"http://${ollama_host}\" ;;\n\t\t\t\t\t\tesac\n\t\t\t\t\t\t# v2026.3.2: Ollama 使用原生 API,baseUrl 不带 /v1\n\t\t\t\t\t\tollama_url=$(echo \"$ollama_url\" | sed 's|/v1/*$||;s|/*$||')\n\t\t\t\t\tfi\n\t\t\t\t\t;;\n\t\t\t\t*)\n\t\t\t\t\tollama_url=\"http://127.0.0.1:11434\"\n\t\t\t\t\t;;\n\t\t\tesac\n\t\t\tif [ -n \"$ollama_url\" ]; then\n\t\t\t\t# 检测 Ollama 是否可达\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}检测 Ollama 连通性...${NC}\"\n\t\t\t\tlocal ollama_base=$(echo \"$ollama_url\" | sed 's|/v1$||')\n\t\t\t\tlocal ollama_check=$(curl -sf --connect-timeout 3 --max-time 5 \"${ollama_base}/api/tags\" 2>/dev/null || echo \"\")\n\t\t\t\tif [ -n \"$ollama_check\" ]; then\n\t\t\t\t\techo -e \" ${GREEN}✅ Ollama 已连接${NC}\"\n\t\t\t\t\t# 列出已安装的模型\n\t\t\t\t\tlocal model_list=$(\"$NODE_BIN\" -e \"\n\t\t\t\t\t\ttry{\n\t\t\t\t\t\t\tconst d=JSON.parse(process.argv[1]);\n\t\t\t\t\t\t\t(d.models||[]).forEach((m,i)=>console.log(' '+(i+1)+') '+m.name));\n\t\t\t\t\t\t}catch(e){}\n\t\t\t\t\t\" \"$ollama_check\" 2>/dev/null)\n\t\t\t\t\tif [ -n \"$model_list\" ]; then\n\t\t\t\t\t\techo -e \" ${CYAN}已安装的模型:${NC}\"\n\t\t\t\t\t\techo \"$model_list\"\n\t\t\t\t\t\techo -e \" ${CYAN}m)${NC} 手动输入模型名\"\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\tprompt_with_default \"请选择模型\" \"1\" ollama_sel\n\t\t\t\t\t\tif [ \"$ollama_sel\" = \"m\" ]; then\n\t\t\t\t\t\t\tprompt_with_default \"请输入模型名称\" \"llama3.3\" model_name\n\t\t\t\t\t\telif echo \"$ollama_sel\" | grep -qE '^[0-9]+$'; then\n\t\t\t\t\t\t\tmodel_name=$(\"$NODE_BIN\" -e \"\n\t\t\t\t\t\t\t\ttry{\n\t\t\t\t\t\t\t\t\tconst d=JSON.parse(process.argv[1]);\n\t\t\t\t\t\t\t\t\tconst m=(d.models||[])[parseInt(process.argv[2])-1];\n\t\t\t\t\t\t\t\t\tconsole.log(m?m.name:'');\n\t\t\t\t\t\t\t\t}catch(e){console.log('');}\n\t\t\t\t\t\t\t\" \"$ollama_check\" \"$ollama_sel\" 2>/dev/null)\n\t\t\t\t\t\t\tif [ -z \"$model_name\" ]; then\n\t\t\t\t\t\t\t\techo -e \" ${YELLOW}无效选择,使用默认模型${NC}\"\n\t\t\t\t\t\t\t\tmodel_name=\"llama3.3\"\n\t\t\t\t\t\t\tfi\n\t\t\t\t\t\tfi\n\t\t\t\t\telse\n\t\t\t\t\t\techo -e \" ${YELLOW}未检测到已安装模型,请先在 Ollama 中拉取模型:${NC}\"\n\t\t\t\t\t\techo -e \" ${CYAN} ollama pull llama3.3${NC}\"\n\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\tprompt_with_default \"请输入模型名称\" \"llama3.3\" model_name\n\t\t\t\t\tfi\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${YELLOW}⚠️ 无法连接 Ollama (${ollama_base})${NC}\"\n\t\t\t\t\techo -e \" ${YELLOW} 请确认 Ollama 已启动并可访问${NC}\"\n\t\t\t\t\techo -e \" ${CYAN} 提示: 如果 Ollama 在其他设备上,需设置 OLLAMA_HOST=0.0.0.0${NC}\"\n\t\t\t\t\techo \"\"\n\t\t\t\t\tprompt_with_default \"仍要继续配置? (y/n)\" \"n\" force_continue\n\t\t\t\t\tif ! confirm_yes \"$force_continue\"; then\n\t\t\t\t\t\treturn\n\t\t\t\t\tfi\n\t\t\t\t\tprompt_with_default \"请输入模型名称\" \"llama3.3\" model_name\n\t\t\t\tfi\n\t\t\t\tif [ -n \"$model_name\" ]; then\n\t\t\t\t\t# Ollama 无需 API Key,使用占位符\n\t\t\t\t\tauth_set_apikey ollama \"ollama-local\" \"ollama:local\"\n\t\t\t\t\t# v2026.3.2: Ollama 使用原生 ollama API,不再走 OpenAI 兼容层\n\t\t\t\t\t_RCP_PROV=\"ollama\" _RCP_URL=\"$ollama_url\" _RCP_KEY=\"ollama-local\" _RCP_MID=\"$model_name\" _RCP_MNAME=\"$model_name\" \"$NODE_BIN\" -e \"\n\t\t\t\t\t\tconst fs=require('fs');\n\t\t\t\t\t\tlet d={};\n\t\t\t\t\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\t\t\t\t\tif(!d.models)d.models={};\n\t\t\t\t\t\tif(!d.models.providers)d.models.providers={};\n\t\t\t\t\t\td.models.mode='merge';\n\t\t\t\t\t\td.models.providers['ollama']={\n\t\t\t\t\t\t\tbaseUrl:process.env._RCP_URL,\n\t\t\t\t\t\t\tapiKey:process.env._RCP_KEY,\n\t\t\t\t\t\t\tapi:'ollama',\n\t\t\t\t\t\t\tmodels:[{\n\t\t\t\t\t\t\t\tid:process.env._RCP_MID,\n\t\t\t\t\t\t\t\tname:process.env._RCP_MNAME,\n\t\t\t\t\t\t\t\treasoning:false,\n\t\t\t\t\t\t\t\tinput:['text'],\n\t\t\t\t\t\t\t\tcost:{input:0,output:0,cacheRead:0,cacheWrite:0},\n\t\t\t\t\t\t\t\tcontextWindow:128000,\n\t\t\t\t\t\t\t\tmaxTokens:32000\n\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t};\n\t\t\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\t\t\" 2>/dev/null\n\t\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\t\tregister_and_set_model \"ollama/${model_name}\"\n\t\t\t\t\techo -e \" ${GREEN}✅ Ollama 已配置,活跃模型: ollama/${model_name}${NC}\"\n\t\t\t\t\techo -e \" ${CYAN} Ollama 地址: ${ollama_url}${NC}\"\n\t\t\t\tfi\n\t\t\tfi\n\t\t\t;;\n\t\ti)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}腾讯云大模型 Coding Plan 套餐配置${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${YELLOW}订阅/管理套餐: https://hunyuan.cloud.tencent.com/#/app/subscription${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: 在上方页面创建 Coding Plan 专属 Key (sk-sp-...)${NC}\"\n\t\t\techo -e \" ${DIM}文档: https://cloud.tencent.com/document/product/1772/128947${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入 Coding Plan API Key (sk-sp-...)\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型 (Coding Plan 套餐内):${NC}\"\n\t\t\t\techo -e \" ${CYAN}── 智能推荐 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} tc-code-latest — 自动路由 (由平台选择最佳模型) ${GREEN}★ 推荐${NC}\"\n\t\t\t\techo -e \" ${CYAN}── 推理模型 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} hunyuan-t1 — 混元 T1 深度推理\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} hunyuan-2.0-thinking — 混元 2.0 Thinking\"\n\t\t\t\techo -e \" ${CYAN}── 旗舰模型 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} hunyuan-turbos — 混元 TurboS 旗舰\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} hunyuan-2.0-instruct — 混元 2.0 Instruct\"\n\t\t\t\techo -e \" ${CYAN}── 第三方模型 ──${NC}\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} glm-5 — 智谱 GLM-5\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} kimi-k2.5 — Moonshot Kimi K2.5\"\n\t\t\t\techo -e \" ${CYAN}h)${NC} minimax-m2.5 — MiniMax M2.5\"\n\t\t\t\techo -e \" ${CYAN}────────────${NC}\"\n\t\t\t\techo -e \" ${CYAN}z)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择默认模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"tc-code-latest\" ;;\n\t\t\t\t\tb) model_name=\"hunyuan-t1\" ;;\n\t\t\t\t\tc) model_name=\"hunyuan-2.0-thinking\" ;;\n\t\t\t\t\td) model_name=\"hunyuan-turbos\" ;;\n\t\t\t\t\te) model_name=\"hunyuan-2.0-instruct\" ;;\n\t\t\t\t\tf) model_name=\"glm-5\" ;;\n\t\t\t\t\tg) model_name=\"kimi-k2.5\" ;;\n\t\t\t\t\th) model_name=\"minimax-m2.5\" ;;\n\t\t\t\t\tz) prompt_with_default \"请输入模型名称\" \"tc-code-latest\" model_name ;;\n\t\t\t\t\t*) model_name=\"tc-code-latest\" ;;\n\t\t\t\tesac\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}正在注册腾讯云 Coding Plan 提供商 (含全部套餐模型)...${NC}\"\n\t\t\t\tauth_set_apikey lkeap \"$api_key\"\n\t\t\t\tregister_lkeap_codingplan_provider \"$api_key\"\n\t\t\t\tregister_and_set_model \"lkeap/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ 腾讯云 Coding Plan 已配置,活跃模型: lkeap/${model_name}${NC}\"\n\t\t\t\techo -e \" ${DIM}提示: 套餐内全部模型已注册,可随时在 WebChat 中通过 /model 切换${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tj)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}百度千帆大模型配置${NC}\"\n\t\t\techo -e \" ${YELLOW}获取 API Key: https://console.bce.baidu.com/qianfan/ais/console/onlineService${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${DIM}提示: 需要 API Key (Access Token) 和可选的 Secret Key${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入百度千帆 API Key (Access Token)\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey qianfan \"$api_key\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} ernie-4.0-8k — 文心一言 4.0 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} ernie-3.5-8k — 文心一言 3.5\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} ernie-4.0-turbo-8k — 文心一言 4.0 Turbo\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} ernie-speed-8k — 文心一言 Speed 极速\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"ernie-4.0-8k\" ;;\n\t\t\t\t\tb) model_name=\"ernie-3.5-8k\" ;;\n\t\t\t\t\tc) model_name=\"ernie-4.0-turbo-8k\" ;;\n\t\t\t\t\td) model_name=\"ernie-speed-8k\" ;;\n\t\t\t\t\te) prompt_with_default \"请输入模型名称\" \"ernie-4.0-8k\" model_name ;;\n\t\t\t\t\t*) model_name=\"ernie-4.0-8k\" ;;\n\t\t\t\tesac\n\t\t\t\t# 百度千帆使用 OpenAI 兼容接口\n\t\t\t\t# 注: OpenClaw v2026.3.28+ 支持 qianfan 原生 provider\n\t\t\t\tregister_custom_provider qianfan \"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop\" \"$api_key\" \"$model_name\" \"$model_name\"\n\t\t\t\tregister_and_set_model \"qianfan/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ 百度千帆已配置,活跃模型: qianfan/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tk)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}智谱 GLM / Z.AI 配置${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${CYAN}认证方式:${NC}\"\n\t\t\techo -e \" ${CYAN}a)${NC} CN (open.bigmodel.cn) ${GREEN}★ 国内用户推荐${NC}\"\n\t\t\techo -e \" ${CYAN}b)${NC} Coding-Plan-CN (智谱 Coding Plan 套餐)\"\n\t\t\techo -e \" ${CYAN}c)${NC} Global (api.z.ai)\"\n\t\t\techo -e \" ${CYAN}d)${NC} 手动输入 API Key\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请选择认证方式\" \"a\" zai_method\n\t\t\tcase \"$zai_method\" in\n\t\t\t\ta)\n\t\t\t\t\tzai_base_url=\"https://open.bigmodel.cn/api/paas/v4\"\n\t\t\t\t\techo -e \" ${YELLOW}获取 API Key: https://open.bigmodel.cn/api-key${NC}\"\n\t\t\t\t\t;;\n\t\t\t\tb)\n\t\t\t\t\tzai_base_url=\"https://open.bigmodel.cn/api/coding/paas/v4\"\n\t\t\t\t\techo -e \" ${YELLOW}Coding Plan 套餐 API Key (sk-...)${NC}\"\n\t\t\t\t\t;;\n\t\t\t\tc)\n\t\t\t\t\tzai_base_url=\"https://api.z.ai/api/paas/v4\"\n\t\t\t\t\techo -e \" ${YELLOW}全球版 API Key${NC}\"\n\t\t\t\t\t;;\n\t\t\t\td)\n\t\t\t\t\tprompt_with_default \"请输入 Base URL\" \"https://open.bigmodel.cn/api/paas/v4\" zai_base_url\n\t\t\t\t\t;;\n\t\t\t\t*)\n\t\t\t\t\tzai_base_url=\"https://open.bigmodel.cn/api/paas/v4\"\n\t\t\t\t\t;;\n\t\t\tesac\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"请输入智谱 API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}可用模型:${NC}\"\n\t\t\t\techo -e \" ${CYAN}a)${NC} glm-5.1 — GLM-5.1 (推荐)\"\n\t\t\t\techo -e \" ${CYAN}b)${NC} glm-5 — GLM-5\"\n\t\t\t\techo -e \" ${CYAN}c)${NC} glm-4.7 — GLM-4.7\"\n\t\t\t\techo -e \" ${CYAN}d)${NC} glm-4.7-flash — GLM-4.7 Flash\"\n\t\t\t\techo -e \" ${CYAN}e)${NC} glm-4.5 — GLM-4.5\"\n\t\t\t\techo -e \" ${CYAN}f)${NC} glm-4.5-flash — GLM-4.5 Flash (免费额度)\"\n\t\t\t\techo -e \" ${CYAN}g)${NC} 手动输入模型名\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请选择模型\" \"a\" model_choice\n\t\t\t\tcase \"$model_choice\" in\n\t\t\t\t\ta) model_name=\"glm-5.1\" ;;\n\t\t\t\t\tb) model_name=\"glm-5\" ;;\n\t\t\t\t\tc) model_name=\"glm-4.7\" ;;\n\t\t\t\t\td) model_name=\"glm-4.7-flash\" ;;\n\t\t\t\t\te) model_name=\"glm-4.5\" ;;\n\t\t\t\t\tf) model_name=\"glm-4.5-flash\" ;;\n\t\t\t\t\tg) prompt_with_default \"请输入模型名称\" \"glm-5.1\" model_name ;;\n\t\t\t\t\t*) model_name=\"glm-5.1\" ;;\n\t\t\t\tesac\n\t\t\t\t# 智谱 GLM 使用原生 zai provider (OpenClaw 内置支持)\n\t\t\t\tauth_set_apikey zai \"$api_key\"\n\t\t\t\tregister_custom_provider zai \"$zai_base_url\" \"$api_key\" \"$model_name\" \"$model_name\" \"128000\" \"4096\"\n\t\t\t\tregister_and_set_model \"zai/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ 智谱 GLM 已配置,活跃模型: zai/${model_name}${NC}\"\n\t\t\t\techo -e \" ${DIM} Base URL: ${zai_base_url}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tm)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}自定义 OpenAI 兼容 API${NC}\"\n\t\t\techo -e \" ${YELLOW}支持任何兼容 OpenAI API 格式的服务商${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"API Base URL (如 https://api.example.com/v1)\" \"\" base_url\n\t\t\tprompt_with_default \"API Key\" \"\" api_key\n\t\t\tprompt_with_default \"模型名称\" \"\" model_name\n\t\t\tif [ -n \"$base_url\" ] && [ -n \"$api_key\" ] && [ -n \"$model_name\" ]; then\n\t\t\t\tauth_set_apikey openai-compatible \"$api_key\" \"openai-compatible:manual\"\n\t\t\t\tregister_custom_provider openai-compatible \"$base_url\" \"$api_key\" \"$model_name\" \"$model_name\"\n\t\t\t\tregister_and_set_model \"openai-compatible/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ 自定义模型已配置,活跃模型: openai-compatible/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tn)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}自定义 Anthropic 兼容 API${NC}\"\n\t\t\techo -e \" ${YELLOW}支持任何兼容 Anthropic Messages API 格式的服务商${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"API Base URL (如 https://api.anthropic.com)\" \"\" base_url\n\t\t\tprompt_with_default \"API Key\" \"\" api_key\n\t\t\tprompt_with_default \"模型名称\" \"claude-sonnet-4-20250514\" model_name\n\t\t\tif [ -n \"$base_url\" ] && [ -n \"$api_key\" ] && [ -n \"$model_name\" ]; then\n\t\t\t\t_ACP_URL=\"${base_url%/}\" _ACP_KEY=\"$api_key\" _ACP_MID=\"$model_name\" \"$NODE_BIN\" -e \"\n\t\t\t\t\tconst fs=require('fs');\n\t\t\t\t\tlet d={};\n\t\t\t\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\t\t\t\tif(!d.models)d.models={};\n\t\t\t\t\tif(!d.models.providers)d.models.providers={};\n\t\t\t\t\td.models.mode='merge';\n\t\t\t\t\td.models.providers['anthropic-compatible']={\n\t\t\t\t\t\tbaseUrl:process.env._ACP_URL,\n\t\t\t\t\t\tapiKey:process.env._ACP_KEY,\n\t\t\t\t\t\tapi:'anthropic-messages',\n\t\t\t\t\t\tmodels:[{\n\t\t\t\t\t\t\tid:process.env._ACP_MID,\n\t\t\t\t\t\t\tname:process.env._ACP_MID,\n\t\t\t\t\t\t\treasoning:false,\n\t\t\t\t\t\t\tinput:['text','image'],\n\t\t\t\t\t\t\tcost:{input:0,output:0,cacheRead:0,cacheWrite:0},\n\t\t\t\t\t\t\tcontextWindow:200000,\n\t\t\t\t\t\t\tmaxTokens:16000\n\t\t\t\t\t\t}]\n\t\t\t\t\t};\n\t\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\t\" 2>/dev/null\n\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\tauth_set_apikey anthropic-compatible \"$api_key\" \"anthropic-compatible:manual\"\n\t\t\t\tregister_and_set_model \"anthropic-compatible/${model_name}\"\n\t\t\t\techo -e \" ${GREEN}✅ 自定义 Anthropic API 已配置,活跃模型: anthropic-compatible/${model_name}${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\to)\n\t\t\techo \"\"\n\t\t\techo -e \" ${BOLD}一万AI分享 粉丝专享 API${NC}\"\n\t\t\techo -e \" ${YELLOW}OpenAI 兼容模式;Base URL 和模型已内置,只需要填写 API Key。${NC}\"\n\t\t\techo -e \" ${DIM}Base URL: https://api.910501.xyz/v1${NC}\"\n\t\t\techo -e \" ${DIM}Model: gpt-5.5${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"API Key\" \"\" api_key\n\t\t\tif [ -n \"$api_key\" ]; then\n\t\t\t\tauth_set_apikey yiwanai \"$api_key\" \"yiwanai:fan\"\n\t\t\t\tregister_custom_provider yiwanai \"https://api.910501.xyz/v1\" \"$api_key\" \"gpt-5.5\" \"gpt-5.5\" 1000000 32000\n\t\t\t\t_YW_PROV=\"yiwanai\" \"$NODE_BIN\" -e \"\n\t\t\t\t\tconst fs=require('fs');\n\t\t\t\t\tlet d={};\n\t\t\t\t\ttry{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}\n\t\t\t\t\tconst p=d.models&&d.models.providers&&d.models.providers[process.env._YW_PROV];\n\t\t\t\t\tif(p&&p.models&&p.models[0]){\n\t\t\t\t\t\tp.models[0].reasoning=true;\n\t\t\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\t\t}\n\t\t\t\t\" 2>/dev/null\n\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\tregister_and_set_model \"yiwanai/gpt-5.5\"\n\t\t\t\techo -e \" ${GREEN}✅ 一万AI分享粉丝专享 API 已配置,活跃模型: yiwanai/gpt-5.5${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tq) return ;;\n\tesac\n\n\tif [ \"$choice\" != \"0\" ] && [ \"$choice\" != \"1\" ]; then\n\t\techo \"\"\n\t\task_restart\n\tfi\n}\n\n# ══════════════════════════════════════════════════════════════\n# 设定当前活跃模型\n# ══════════════════════════════════════════════════════════════\nset_active_model() {\n\techo \"\"\n\techo -e \" ${BOLD}🔄 设定当前活跃模型${NC}\"\n\techo \"\"\n\n\tlocal current_model=$(json_get agents.defaults.model.primary)\n\techo -e \" 当前活跃模型: ${GREEN}${BOLD}${current_model:-未设置}${NC}\"\n\techo \"\"\n\n\tlocal models_json=\"\"\n\tmodels_json=$(oc_cmd models list --json 2>/dev/null || echo \"\")\n\tlocal model_count=0\n\tif [ -n \"$models_json\" ]; then\n\t\tmodel_count=$(\"$NODE_BIN\" -e \"\n\t\t\ttry{const d=JSON.parse(process.argv[1]);console.log((d.models||[]).length);}catch(e){console.log(0);}\n\t\t\" \"$models_json\" 2>/dev/null || echo \"0\")\n\tfi\n\n\tif [ \"$model_count\" -gt 0 ] 2>/dev/null; then\n\t\techo -e \" ${CYAN}已配置的模型:${NC}\"\n\t\techo \"\"\n\t\t\"$NODE_BIN\" -e \"\n\t\t\tconst d=JSON.parse(process.argv[1]);\n\t\t\t(d.models||[]).forEach((m,i)=>{\n\t\t\t\tconst mark=m.key===process.argv[2]?' ← 当前活跃':'';\n\t\t\t\tconst n=m.name&&m.name!==m.key?' ('+m.name+')':'';\n\t\t\t\tconsole.log(' '+(i+1)+') '+m.key+n+mark);\n\t\t\t});\n\t\t\" \"$models_json\" \"$current_model\" 2>/dev/null\n\t\techo \"\"\n\t\techo -e \" ${CYAN}m)${NC} 手动输入模型 ID\"\n\t\techo -e \" ${CYAN}0)${NC} 返回\"\n\t\techo \"\"\n\t\tprompt_with_default \"请选择\" \"0\" model_sel\n\n\t\tif [ \"$model_sel\" = \"0\" ]; then\n\t\t\treturn\n\t\telif [ \"$model_sel\" = \"m\" ]; then\n\t\t\tprompt_with_default \"请输入模型 ID (如 openai/gpt-4o)\" \"${current_model:-}\" manual_model\n\t\t\tif [ -n \"$manual_model\" ]; then\n\t\t\t\tregister_and_set_model \"$manual_model\"\n\t\t\t\techo -e \" ${GREEN}✅ 活跃模型已设为: ${manual_model}${NC}\"\n\t\t\t\task_restart\n\t\t\tfi\n\t\telif echo \"$model_sel\" | grep -qE '^[0-9]+$'; then\n\t\t\tlocal selected=$(\"$NODE_BIN\" -e \"\n\t\t\t\tconst d=JSON.parse(process.argv[1]);\n\t\t\t\tconst m=(d.models||[])[parseInt(process.argv[2])-1];\n\t\t\t\tconsole.log(m?m.key:'');\n\t\t\t\" \"$models_json\" \"$model_sel\" 2>/dev/null)\n\t\t\tif [ -n \"$selected\" ]; then\n\t\t\t\tregister_and_set_model \"$selected\"\n\t\t\t\techo -e \" ${GREEN}✅ 活跃模型已切换为: ${selected}${NC}\"\n\t\t\t\task_restart\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}无效选择${NC}\"\n\t\t\tfi\n\t\tfi\n\telse\n\t\techo -e \" ${YELLOW}尚未配置任何模型。${NC}\"\n\t\techo -e \" ${YELLOW}请先通过「配置 AI 模型提供商」(菜单 2) 添加模型。${NC}\"\n\t\techo \"\"\n\t\tprompt_with_default \"直接输入模型 ID 设置? (留空返回)\" \"\" manual_model\n\t\tif [ -n \"$manual_model\" ]; then\n\t\t\tregister_and_set_model \"$manual_model\"\n\t\t\techo -e \" ${GREEN}✅ 活跃模型已设为: ${manual_model}${NC}\"\n\t\t\task_restart\n\t\tfi\n\tfi\n}\n\n# ══════════════════════════════════════════════════════════════\n# 配置 QQ 机器人 (通过 qqbot 插件 @tencent-connect/openclaw-qqbot)\n# ══════════════════════════════════════════════════════════════\nconfigure_qq() {\n\techo \"\"\n\techo -e \" ${BOLD}🐧 QQ 机器人配置${NC}\"\n\techo \"\"\n\n\t# 检查 qqbot 插件是否已安装并正常加载\n\tlocal plugin_installed=0\n\tlocal plugin_blocked=0\n\tlocal qqbot_ext_dir=\"${OC_STATE_DIR}/extensions/openclaw-qqbot\"\n\tif [ -n \"$OC_ENTRY\" ] && [ -x \"$NODE_BIN\" ]; then\n\t\tlocal plugin_list=$(oc_cmd plugins list 2>&1)\n\t\t# 在表格输出中查找含 qqbot 的行是否也包含 loaded\n\t\tif echo \"$plugin_list\" | grep -i \"qqbot\" | grep -qi \"loaded\"; then\n\t\t\tplugin_installed=1\n\t\t\techo -e \" ${GREEN}✅ qqbot 插件已安装并加载${NC}\"\n\t\telif echo \"$plugin_list\" | grep -qi \"plugin not found.*openclaw-qqbot\\|suspicious ownership\"; then\n\t\t\t# 插件目录存在但被阻止 (权限问题或 stale config)\n\t\t\tif [ -d \"$qqbot_ext_dir\" ]; then\n\t\t\t\tplugin_blocked=1\n\t\t\t\techo -e \" ${YELLOW}⚠️ qqbot 插件已安装但未能正常加载${NC}\"\n\t\t\t\techo -e \" ${CYAN}正在修复插件目录权限...${NC}\"\n\t\t\t\tchown -R root:root \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\t\tchmod -R 755 \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\t\techo -e \" ${GREEN}✅ 权限已修复,重启 Gateway 后生效${NC}\"\n\t\t\t\tplugin_installed=1\n\t\t\tfi\n\t\telif [ -d \"$qqbot_ext_dir\" ] && [ -f \"${qqbot_ext_dir}/openclaw.plugin.json\" ]; then\n\t\t\t# 目录存在、有 plugin.json 但未出现在插件列表 — 修复权限\n\t\t\techo -e \" ${YELLOW}⚠️ qqbot 插件目录存在但未能加载${NC}\"\n\t\t\techo -e \" ${CYAN}正在修复插件目录权限...${NC}\"\n\t\t\tchown -R root:root \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\tchmod -R 755 \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\techo -e \" ${GREEN}✅ 权限已修复${NC}\"\n\t\t\tplugin_installed=1\n\t\tfi\n\tfi\n\n\tif [ \"$plugin_installed\" -eq 0 ]; then\n\t\techo -e \" ${YELLOW}⚠️ qqbot 插件尚未安装${NC}\"\n\t\techo -e \" QQ 渠道需要先安装 qqbot 插件才能使用。\"\n\t\techo \"\"\n\t\tprompt_with_default \"是否立即安装 qqbot 插件? (y/n)\" \"y\" install_qqbot\n\t\tif confirm_yes \"$install_qqbot\"; then\n\t\t\techo -e \" ${CYAN}正在安装 @tencent-connect/openclaw-qqbot ...${NC}\"\n\t\t\techo -e \" ${DIM}(首次安装可能需要几分钟)${NC}\"\n\t\t\tlocal install_out\n\t\t\tinstall_out=$(oc_cmd plugins install @tencent-connect/openclaw-qqbot@latest 2>&1)\n\t\t\tlocal install_rc=$?\n\n\t\t\t# 关键: 安装后立即修复插件目录权限为 root (OpenClaw 安全策略要求)\n\t\t\t# 同时修复权限模式为 755,确保 Gateway 可读取插件\n\t\t\tif [ -d \"$qqbot_ext_dir\" ]; then\n\t\t\t\tchown -R root:root \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\t\tchmod -R 755 \"$qqbot_ext_dir\" 2>/dev/null\n\t\t\tfi\n\n\t\t\tif [ $install_rc -eq 0 ]; then\n\t\t\t\techo -e \" ${GREEN}✅ qqbot 插件安装成功${NC}\"\n\t\t\t\tplugin_installed=1\n\t\t\telse\n\t\t\t\t# 安装命令返回非零,可能是因为 config invalid (死锁)\n\t\t\t\t# 检查插件目录是否实际已存在 (说明下载成功但校验报错)\n\t\t\t\tif [ -d \"$qqbot_ext_dir\" ] && [ -f \"${qqbot_ext_dir}/openclaw.plugin.json\" ]; then\n\t\t\t\t\techo -e \" ${YELLOW}⚠️ 插件已下载但加载校验未通过 (exit: $install_rc)${NC}\"\n\t\t\t\t\techo -e \" ${CYAN}这通常是因为配置中已有 qqbot 设置但插件未被信任。${NC}\"\n\t\t\t\t\techo -e \" ${CYAN}已自动修复权限,重启 Gateway 后应能正常加载。${NC}\"\n\t\t\t\t\tplugin_installed=1\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${RED}❌ 插件安装失败 (exit: $install_rc)${NC}\"\n\t\t\t\t\techo -e \" ${DIM}${install_out}${NC}\" | tail -5\n\t\t\t\t\techo \"\"\n\t\t\t\t\techo -e \" ${YELLOW}插件安装失败,但你仍然可以先配置 QQ 机器人参数。${NC}\"\n\t\t\t\t\techo -e \" ${YELLOW}稍后可手动安装: openclaw plugins install @tencent-connect/openclaw-qqbot@latest${NC}\"\n\t\t\t\tfi\n\t\t\t\techo \"\"\n\t\t\tfi\n\t\t\t\n\t\t\t# v2026.3.13: 确保插件名称正确写入 plugins.allow\n\t\t\t# OpenClaw 要求 plugins.allow 中的名称必须与实际插件名完全匹配\n\t\t\tif [ \"$plugin_installed\" -eq 1 ]; then\n\t\t\t\tensure_qqbot_plugin_allowed\n\t\t\tfi\n\t\telse\n\t\t\techo -e \" ${YELLOW}已跳过插件安装,继续配置 QQ 机器人参数。${NC}\"\n\t\t\techo -e \" ${CYAN}稍后安装命令: openclaw plugins install @tencent-connect/openclaw-qqbot@latest${NC}\"\n\t\t\techo \"\"\n\t\tfi\n\tfi\n\n\techo \"\"\n\techo -e \" ${YELLOW}获取 App ID 和 App Secret 步骤:${NC}\"\n\techo -e \" 1. 前往 ${CYAN}QQ 开放平台${NC}: ${CYAN}https://q.qq.com/qqbot/openclaw/login.html${NC}\"\n\techo -e \" 2. 用手机 QQ 扫码注册/登录\"\n\techo -e \" 3. 进入 QQ 机器人页面 → 点击「创建机器人」\"\n\techo -e \" 4. 创建完成后,复制页面中的 ${CYAN}App ID${NC} 和 ${CYAN}App Secret${NC}\"\n\techo \"\"\n\techo -e \" ${RED}⚠️ 注意: App Secret 不支持二次查看(会强制重置),请妥善保存!${NC}\"\n\techo \"\"\n\n\tlocal current_appid=$(json_get channels.qqbot.appId)\n\tif [ -n \"$current_appid\" ]; then\n\t\techo -e \" ${GREEN}当前已配置 App ID: ${current_appid}${NC}\"\n\tfi\n\n\tprompt_with_default \"请输入 QQ 机器人 App ID\" \"\" qq_appid\n\tprompt_with_default \"请输入 QQ 机器人 App Secret\" \"\" qq_secret\n\tqq_appid=$(sanitize_input \"$qq_appid\" | tr -d '[:space:]')\n\tqq_secret=$(sanitize_input \"$qq_secret\" | tr -d '[:space:]')\n\n\tif [ -n \"$qq_appid\" ] && [ -n \"$qq_secret\" ]; then\n\t\t# App ID 应为纯数字\n\t\tif ! printf '%s' \"$qq_appid\" | grep -qE '^[0-9]+$'; then\n\t\t\techo -e \" ${RED}❌ App ID 格式错误,应为纯数字${NC}\"\n\t\t\treturn\n\t\tfi\n\n\t\t# App Secret 基本格式检查 (非空即可,长度通常 32 位)\n\t\tif [ ${#qq_secret} -lt 10 ]; then\n\t\t\techo -e \" ${YELLOW}⚠️ App Secret 长度过短(${#qq_secret} 字符),请确认是否完整粘贴。${NC}\"\n\t\t\tprompt_with_default \"是否仍然保存? (y/n)\" \"n\" force_save\n\t\t\tif ! confirm_yes \"$force_save\"; then\n\t\t\t\techo -e \" ${YELLOW}已取消,配置未保存。${NC}\"\n\t\t\t\treturn\n\t\t\tfi\n\t\tfi\n\n\t\t# 使用 openclaw CLI 一键配置 (推荐方式)\n\t\techo -e \" ${CYAN}正在配置 qqbot 渠道...${NC}\"\n\t\tlocal add_out\n\t\tadd_out=$(oc_cmd channels add --channel qqbot --token \"${qq_appid}:${qq_secret}\" 2>&1)\n\t\tlocal add_rc=$?\n\n\t\tif [ $add_rc -ne 0 ]; then\n\t\t\techo -e \" ${YELLOW}CLI 配置未成功,尝试直接写入配置文件...${NC}\"\n\t\t\t# 回退: 直接写入 JSON 配置\n\t\t\tjson_set channels.qqbot.enabled true\n\t\t\tjson_set channels.qqbot.appId \"$qq_appid\"\n\t\t\tjson_set channels.qqbot.clientSecret \"$qq_secret\"\n\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\tfi\n\n\t\t# 保存后验证\n\t\tlocal saved_appid=$(json_get channels.qqbot.appId)\n\t\tif [ -z \"$saved_appid\" ]; then\n\t\t\techo -e \" ${RED}❌ 配置保存异常! 请检查配置文件${NC}\"\n\t\t\treturn\n\t\tfi\n\n\t\techo -e \" ${GREEN}✅ QQ 机器人配置已保存${NC}\"\n\t\techo -e \" ${CYAN} App ID: ${qq_appid}${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${YELLOW}提示:${NC}\"\n\t\techo -e \" • 配置完成后需重启 Gateway 使配置生效\"\n\t\techo -e \" • 如果机器人回复「该机器人去火星了」,请检查 App ID 和 App Secret 是否正确\"\n\t\techo -e \" • 当前不建议将 QQ 机器人添加进 QQ 群聊\"\n\t\techo -e \" • 插件升级: ${CYAN}openclaw plugins update openclaw-qqbot${NC}\"\n\t\techo \"\"\n\n\t\t# 重启 Gateway 使配置生效\n\t\task_restart\n\telse\n\t\techo -e \" ${YELLOW}信息不完整,已取消。${NC}\"\n\tfi\n}\n\n# ══════════════════════════════════════════════════════════════\n# 配置 Telegram\n# ══════════════════════════════════════════════════════════════\nconfigure_telegram() {\n\techo \"\"\n\techo -e \" ${BOLD}📱 Telegram Bot 配置${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}获取 Bot Token 步骤:${NC}\"\n\techo -e \" 1. 打开 Telegram → 搜索 ${CYAN}@BotFather${NC}\"\n\techo -e \" 2. 发送 ${CYAN}/newbot${NC} → 按提示创建\"\n\techo -e \" 3. 复制生成的 Token\"\n\techo \"\"\n\n\tlocal current_token=$(json_get channels.telegram.botToken)\n\tif [ -n \"$current_token\" ]; then\n\t\tlocal ct_short=$(echo \"$current_token\" | cut -c1-12)\n\t\techo -e \" ${GREEN}当前已配置 Token: ${ct_short}...${NC}\"\n\tfi\n\n\tprompt_with_default \"请输入 Telegram Bot Token\" \"\" tg_token\n\n\tif [ -n \"$tg_token\" ]; then\n\t\t# ── 强力清洗: 先用 sanitize_input 去除 ANSI 转义序列,再白名单过滤 ──\n\t\ttg_token=$(sanitize_input \"$tg_token\")\n\t\ttg_token=$(printf '%s' \"$tg_token\" | tr -cd 'A-Za-z0-9:_-')\n\n\t\t# ── 格式验证: 使用 grep 正则匹配 \"数字:字母数字\" ──\n\t\tif ! printf '%s' \"$tg_token\" | grep -qE '^[0-9]+:[A-Za-z0-9_-]+$'; then\n\t\t\techo -e \" ${RED}❌ Token 格式错误${NC}\"\n\t\t\techo -e \" ${YELLOW} 正确格式: 123456789:ABCdefGHIjklMNOpqr${NC}\"\n\t\t\techo -e \" ${YELLOW} 请检查粘贴是否完整,重试。${NC}\"\n\t\t\treturn\n\t\tfi\n\n\t\techo -e \" ${CYAN}验证 Token...${NC}\"\n\t\tlocal verify=\"\"\n\t\tverify=$(curl -s --connect-timeout 5 --max-time 10 \"https://api.telegram.org/bot${tg_token}/getMe\" 2>/dev/null || echo '{\"ok\":false}')\n\t\tif echo \"$verify\" | grep -q '\"ok\":true'; then\n\t\t\tlocal bot_name=$(echo \"$verify\" | grep -o '\"username\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\t\t\techo -e \" ${GREEN}✅ Token 验证成功 — @${bot_name}${NC}\"\n\t\telse\n\t\t\techo -e \" ${RED}❌ Token 验证失败${NC}\"\n\t\t\techo -e \" ${YELLOW} 可能原因: Token 不正确 或 无法连接 Telegram API${NC}\"\n\t\t\tprompt_with_default \"是否仍然保存此 Token? (y/n)\" \"n\" force_save\n\t\t\tif ! confirm_yes \"$force_save\"; then\n\t\t\t\techo -e \" ${YELLOW}已取消,Token 未保存。${NC}\"\n\t\t\t\treturn\n\t\t\tfi\n\t\tfi\n\n\t\t# ── 使用 json_set 直接写入 (避免 oc_cmd CLI 参数解析问题) ──\n\t\tjson_set channels.telegram.botToken \"$tg_token\"\n\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\tchown openclaw:openclaw \"${CONFIG_FILE}.bak\" 2>/dev/null || true\n\n\t\t# ── 保存后验证: 读回检查 Token 是否完整 ──\n\t\tlocal saved_token=$(json_get channels.telegram.botToken)\n\t\tif [ \"$saved_token\" != \"$tg_token\" ]; then\n\t\t\techo -e \" ${RED}❌ Token 保存异常! 已保存的值与输入不一致${NC}\"\n\t\t\techo -e \" ${YELLOW} 期望: ${tg_token}${NC}\"\n\t\t\techo -e \" ${YELLOW} 实际: ${saved_token}${NC}\"\n\t\t\techo -e \" ${YELLOW} 请重新配置。${NC}\"\n\t\t\treturn\n\t\tfi\n\t\techo -e \" ${GREEN}✅ Telegram Bot Token 已保存${NC}\"\n\n\t\t# 重启 Gateway 使 Token 生效 (必须重启,否则 Bot 无法连接 Telegram)\n\t\techo -e \" ${CYAN}正在重启 Gateway 使 Token 生效...${NC}\"\n\t\trestart_gateway\n\n\t\t# Token 保存且 Gateway 重启后,自动进入配对流程\n\t\techo \"\"\n\t\techo -e \" ${CYAN}接下来进行 Telegram 配对,让 Bot 关联您的账号。${NC}\"\n\t\tprompt_with_default \"是否现在进行配对? (y/n)\" \"y\" do_pair\n\t\tif confirm_yes \"$do_pair\"; then\n\t\t\ttelegram_pairing\n\t\tfi\n\telse\n\t\techo -e \" ${YELLOW}未输入 Token,已取消。${NC}\"\n\tfi\n}\n\n# ── 配置 Discord ──\nconfigure_discord() {\n\techo \"\"\n\techo -e \" ${BOLD}🎮 Discord Bot 配置${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}获取 Bot Token:${NC} ${CYAN}https://discord.com/developers/applications${NC}\"\n\techo \"\"\n\tprompt_with_default \"请输入 Discord Bot Token\" \"\" dc_token\n\tdc_token=$(sanitize_input \"$dc_token\" | tr -d '[:space:]')\n\tif [ -n \"$dc_token\" ]; then\n\t\tjson_set channels.discord.botToken \"$dc_token\"\n\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\techo -e \" ${GREEN}✅ Discord Bot Token 已保存${NC}\"\n\t\task_restart\n\tfi\n}\n\n# ── 配置飞书 ──\nconfigure_feishu() {\n\techo \"\"\n\techo -e \" ${BOLD}🐦 飞书 Bot 配置${NC}\"\n\techo \"\"\n\n\t# 检查 Node.js 是否可用\n\tif [ ! -x \"$NODE_BIN\" ]; then\n\t\techo -e \" ${RED}❌ Node.js 不可用,请先安装运行环境${NC}\"\n\t\treturn 1\n\tfi\n\n\t# 检查并安装 Python 3 (飞书插件依赖)\n\tif ! command -v python3 >/dev/null 2>&1; then\n\t\techo -e \" ${YELLOW}⚠️ 飞书插件需要 Python 3 支持${NC}\"\n\t\techo -e \" ${CYAN}正在尝试安装 python3-light...${NC}\"\n\t\topkg update >/dev/null 2>&1\n\t\t# 使用 python3-light 减少安装体积 (约 2MB vs 完整版 30MB+)\n\t\topkg install python3-light 2>&1 | tail -5 || true\n\t\tif command -v python3 >/dev/null 2>&1; then\n\t\t\techo -e \" ${GREEN}✅ Python 3 安装成功${NC}\"\n\t\telse\n\t\t\techo -e \" ${RED}❌ Python 3 安装失败${NC}\"\n\t\t\techo -e \" ${YELLOW}请手动安装: opkg install python3-light${NC}\"\n\t\t\techo \"\"\n\t\t\tprompt_with_default \"是否继续尝试安装飞书? (y/n)\" \"n\" continue_install\n\t\t\tif ! confirm_yes \"$continue_install\"; then\n\t\t\t\treturn 1\n\t\t\tfi\n\t\tfi\n\tfi\n\n\techo -e \" ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\techo -e \" ${CYAN}飞书官方安装向导${NC}\"\n\techo -e \" ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}即将执行飞书官方安装命令:${NC}\"\n\techo -e \" ${CYAN}npx -y @larksuite/openclaw-lark-tools install${NC}\"\n\techo \"\"\n\techo -e \" ${DIM}安装过程中可以:${NC}\"\n\techo -e \" ${DIM} • 新建机器人: 扫描二维码一键创建${NC}\"\n\techo -e \" ${DIM} • 关联已有机器人: 输入 App ID 和 App Secret${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}提示: 若命令行出错,可在命令前增加 sudo 重新执行${NC}\"\n\techo \"\"\n\n\tprompt_with_default \"是否开始安装? (y/n)\" \"y\" do_install\n\tif ! confirm_yes \"$do_install\"; then\n\t\techo -e \" ${YELLOW}已取消安装${NC}\"\n\t\treturn\n\tfi\n\n\techo \"\"\n\techo -e \" ${CYAN}正在启动飞书安装向导...${NC}\"\n\techo \"\"\n\n\t# 执行官方安装命令\n\tcd \"$OC_DATA\"\n\tNPX_BIN=\"${NODE_BASE}/bin/npx\"\n\tlocal install_rc=0\n\tif [ -x \"$NPX_BIN\" ]; then\n\t\t\"$NPX_BIN\" -y @larksuite/openclaw-lark-tools install || install_rc=$?\n\telse\n\t\t# 如果 npx 不存在,使用 node 运行\n\t\t\"$NODE_BIN\" \"$NODE_BASE/lib/node_modules/npm/bin/npx-cli.js\" -y @larksuite/openclaw-lark-tools install || install_rc=$?\n\tfi\n\n\techo \"\"\n\tif [ $install_rc -eq 0 ]; then\n\t\techo -e \" ${GREEN}✅ 飞书安装完成!${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\t\techo -e \" ${YELLOW}下一步:${NC}\"\n\t\techo -e \" ${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\"\n\t\techo \"\"\n\t\techo -e \" 1. 在飞书中向机器人发送任意消息,即可开始对话\"\n\t\techo -e \" 2. 发送 ${CYAN}/feishu auth${NC} 完成批量授权\"\n\t\techo -e \" 3. 发送 ${CYAN}/feishu start${NC} 验证安装是否成功\"\n\t\techo -e \" 4. 发送 ${CYAN}学习一下我安装的新飞书插件,列出有哪些能力${NC}\"\n\t\techo \"\"\n\telse\n\t\techo -e \" ${YELLOW}⚠️ 安装向导退出 (exit: $install_rc)${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${CYAN}手动安装命令:${NC}\"\n\t\techo -e \" ${CYAN}npx -y @larksuite/openclaw-lark-tools install${NC}\"\n\t\techo \"\"\n\tfi\n}\n\n# ── 配置 Slack ──\nconfigure_slack() {\n\techo \"\"\n\techo -e \" ${BOLD}💬 Slack Bot 配置${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}获取 Bot Token:${NC} ${CYAN}https://api.slack.com/apps${NC} → Create App\"\n\techo \"\"\n\tprompt_with_default \"请输入 Slack Bot Token (xoxb-...)\" \"\" sk_token\n\tsk_token=$(sanitize_input \"$sk_token\" | tr -d '[:space:]')\n\tif [ -n \"$sk_token\" ]; then\n\t\tjson_set channels.slack.botToken \"$sk_token\"\n\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\techo -e \" ${GREEN}✅ Slack Bot Token 已保存${NC}\"\n\t\task_restart\n\tfi\n}\n\n# ══════════════════════════════════════════════════════════════\n# Telegram 配对助手\n# ══════════════════════════════════════════════════════════════\ntelegram_pairing() {\n\techo \"\"\n\techo -e \" ${BOLD}🤝 Telegram 配对助手${NC}\"\n\techo \"\"\n\n\tlocal tg_token=$(json_get channels.telegram.botToken)\n\tif [ -z \"$tg_token\" ]; then\n\t\techo -e \" ${YELLOW}未检测到 Telegram Bot Token,请先配置 Telegram。${NC}\"\n\t\treturn\n\tfi\n\n\techo -e \" ${CYAN}诊断 Telegram API 连通性...${NC}\"\n\tlocal verify=\"\"\n\tverify=$(curl -s --connect-timeout 5 --max-time 10 \"https://api.telegram.org/bot${tg_token}/getMe\" 2>/dev/null || echo '{\"ok\":false}')\n\tif echo \"$verify\" | grep -q '\"ok\":true'; then\n\t\tlocal bot_name=$(echo \"$verify\" | grep -o '\"username\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\t\techo -e \" ${GREEN}✅ Telegram API 连通正常 — @${bot_name}${NC}\"\n\telse\n\t\techo -e \" ${RED}❌ Telegram API 连通检测未通过${NC}\"\n\t\techo -e \" ${YELLOW} 可能原因: Token 不正确、网络不通 或 Telegram 被屏蔽${NC}\"\n\t\techo -e \" ${CYAN} 建议: 返回重新配置 Token 或检查代理/网络设置${NC}\"\n\t\treturn\n\tfi\n\n\techo \"\"\n\techo -e \" ${GREEN}╔══════════════════════════════════════════════════╗${NC}\"\n\techo -e \" ${GREEN}║ 请在 Telegram 中向 Bot 发送 /start ║${NC}\"\n\techo -e \" ${GREEN}║ 然后回到这里按回车,脚本自动检测配对请求 ║${NC}\"\n\techo -e \" ${GREEN}╚══════════════════════════════════════════════════╝${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}发送 /start 后按回车继续 (输入 q 退出)...${NC}\"\n\tread _wait\n\tcase \"$_wait\" in q|Q) return ;; esac\n\n\tlocal paired=0\n\tlocal attempt=1\n\twhile [ $attempt -le 3 ]; do\n\t\techo -e \" ${CYAN}检测配对请求... (第 ${attempt}/3 轮)${NC}\"\n\t\tlocal pair_json=$(oc_cmd pairing list telegram --json 2>/dev/null || echo \"\")\n\t\tlocal codes=\"\"\n\t\tif [ -n \"$pair_json\" ]; then\n\t\t\tcodes=$(echo \"$pair_json\" | grep -o '\"code\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | cut -d'\"' -f4)\n\t\tfi\n\n\t\tif [ -n \"$codes\" ]; then\n\t\t\t# 逐个处理配对码 (避免管道子 shell 变量丢失问题)\n\t\t\tlocal _codes_tmp=\"/tmp/oc_pair_codes_$$\"\n\t\t\techo \"$codes\" > \"$_codes_tmp\"\n\t\t\twhile IFS= read -r code; do\n\t\t\t\t[ -z \"$code\" ] && continue\n\t\t\t\techo -e \" ${CYAN}发现配对请求: ${code}${NC}\"\n\t\t\t\tlocal approve=$(oc_cmd pairing approve telegram \"$code\" 2>&1)\n\t\t\t\tif echo \"$approve\" | grep -qi \"approved\\|success\\|ok\"; then\n\t\t\t\t\techo \"\"\n\t\t\t\t\techo -e \" ${GREEN}${BOLD}🎉 Telegram 配对成功!${NC}\"\n\t\t\t\tfi\n\t\t\tdone < \"$_codes_tmp\"\n\t\t\trm -f \"$_codes_tmp\"\n\t\t\t# 检查是否还有待配对的\n\t\t\tlocal re_check=$(oc_cmd pairing list telegram --json 2>/dev/null || echo \"\")\n\t\t\tlocal re_codes=$(echo \"$re_check\" | grep -o '\"code\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | cut -d'\"' -f4)\n\t\t\tif [ -z \"$re_codes\" ]; then\n\t\t\t\tpaired=1\n\t\t\t\tbreak\n\t\t\tfi\n\t\tfi\n\n\t\tif [ $attempt -lt 3 ] && [ $paired -eq 0 ]; then\n\t\t\techo -e \" ${YELLOW}未检测到,等待 8 秒后重试...${NC}\"\n\t\t\tsleep 8\n\t\tfi\n\t\tattempt=$((attempt + 1))\n\tdone\n\n\tif [ \"$paired\" -eq 0 ]; then\n\t\techo \"\"\n\t\techo -e \" ${YELLOW}未自动检测到配对请求。${NC}\"\n\t\techo -e \" ${CYAN}如果 Bot 已回复配对码,请手动输入 (回车跳过):${NC}\"\n\t\tprompt_with_default \"配对码\" \"\" manual_code\n\t\tif [ -n \"$manual_code\" ]; then\n\t\t\tlocal approve=$(oc_cmd pairing approve telegram \"$manual_code\" 2>&1)\n\t\t\tif echo \"$approve\" | grep -qi \"approved\\|success\\|ok\"; then\n\t\t\t\techo -e \" ${GREEN}${BOLD}🎉 Telegram 配对成功!${NC}\"\n\t\t\t\tpaired=1\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}配对失败: $approve${NC}\"\n\t\t\tfi\n\t\tfi\n\tfi\n\n\t# 配对成功后重启网关,使配对关系立即生效\n\tif [ \"$paired\" -eq 1 ]; then\n\t\techo \"\"\n\t\techo -e \" ${CYAN}正在重启 Gateway 使配对生效...${NC}\"\n\t\trestart_gateway\n\t\techo -e \" ${GREEN}✅ 现在可以在 Telegram 中与 Bot 对话了!${NC}\"\n\tfi\n}\n\n# ══════════════════════════════════════════════════════════════\n# 配置渠道子菜单\n# ══════════════════════════════════════════════════════════════\nconfigure_channels() {\n\twhile true; do\n\t\techo \"\"\n\t\techo -e \" ${BOLD}📡 配置消息渠道${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${CYAN}提示: 微信配置请使用 LuCI 界面「微信配置」菜单${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${CYAN}1)${NC} QQ 机器人 ${GREEN}(腾讯QQ)${NC}\"\n\t\techo -e \" ${CYAN}2)${NC} Telegram ${GREEN}(最常用)${NC}\"\n\t\techo -e \" ${CYAN}3)${NC} Discord\"\n\t\techo -e \" ${CYAN}4)${NC} 飞书 (Feishu)\"\n\t\techo -e \" ${CYAN}5)${NC} Slack\"\n\t\techo -e \" ${CYAN}6)${NC} WhatsApp ${YELLOW}(需通过 Web 控制台扫码)${NC}\"\n\t\techo -e \" ${CYAN}7)${NC} Telegram 配对助手\"\n\t\techo -e \" ${CYAN}8)${NC} 官方完整渠道配置向导\"\n\t\techo -e \" ${CYAN}0)${NC} 返回主菜单\"\n\t\techo \"\"\n\t\tprompt_with_default \"请选择\" \"1\" ch_choice\n\n\t\tcase \"$ch_choice\" in\n\t\t\t1) configure_qq ;;\n\t\t\t2) configure_telegram ;;\n\t\t\t3) configure_discord ;;\n\t\t\t4) configure_feishu ;;\n\t\t\t5) configure_slack ;;\n\t\t\t6)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${YELLOW}WhatsApp 需要通过 Web 控制台扫码配对:${NC}\"\n\t\t\t\tlocal gw_token=$(json_get gateway.auth.token)\n\t\t\t\tlocal gw_port=$(json_get gateway.port)\n\t\t\t\tgw_port=${gw_port:-18789}\n\t\t\t\techo -e \" ${CYAN}http://<你的路由器IP>:${gw_port}/?token=${gw_token}${NC}\"\n\t\t\t\techo -e \" 打开后进入 Channels → WhatsApp 扫码即可。\"\n\t\t\t\t;;\n\t\t\t7) telegram_pairing ;;\n\t\t\t8)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}启动官方渠道配置向导...${NC}\"\n\t\t\t\t(oc_cmd configure --section channels) || echo -e \" ${YELLOW}配置向导已退出${NC}\"\n\t\t\t\t;;\n\t\t\t0) return ;;\n\t\t\t*) echo -e \" ${YELLOW}无效选择${NC}\" ;;\n\t\tesac\n\tdone\n}\n\n# ══════════════════════════════════════════════════════════════\n# 健康检查\n# ══════════════════════════════════════════════════════════════\nhealth_check() {\n\techo \"\"\n\techo -e \" ${BOLD}🔍 健康检查${NC}\"\n\techo \"\"\n\n\tlocal gw_port=$(json_get gateway.port)\n\tgw_port=${gw_port:-18789}\n\n\t# ── v2026.3.2: 使用官方 config validate 验证配置 ──\n\tif command -v openclaw >/dev/null 2>&1 || [ -n \"$OC_ENTRY\" ]; then\n\t\techo -e \" ${CYAN}验证配置文件格式...${NC}\"\n\t\tlocal validate_out=\"\"\n\t\tvalidate_out=$(oc_cmd config validate --json 2>/dev/null) || true\n\t\tif [ -n \"$validate_out\" ]; then\n\t\t\tlocal has_errors=$(\"$NODE_BIN\" -e \"\n\t\t\t\ttry{const r=JSON.parse(process.argv[1]);\n\t\t\t\tif(r.valid===true){console.log('OK');}\n\t\t\t\telse if(r.errors&&r.errors.length>0){r.errors.forEach(e=>console.log('ERR:'+e.message));}\n\t\t\t\telse{console.log('OK');}}catch(e){console.log('SKIP');}\n\t\t\t\" \"$validate_out\" 2>/dev/null)\n\t\t\tif [ \"$has_errors\" = \"OK\" ]; then\n\t\t\t\techo -e \" ${GREEN}✅ 配置文件格式有效${NC}\"\n\t\t\telif [ \"$has_errors\" = \"SKIP\" ]; then\n\t\t\t\techo -e \" ${YELLOW}⚠️ 无法解析验证结果,跳过${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${RED}❌ 配置文件存在错误:${NC}\"\n\t\t\t\techo \"$has_errors\" | while IFS= read -r line; do\n\t\t\t\t\techo -e \" ${YELLOW}• ${line#ERR:}${NC}\"\n\t\t\t\tdone\n\t\t\tfi\n\t\telse\n\t\t\techo -e \" ${YELLOW}⚠️ config validate 不可用,跳过格式验证${NC}\"\n\t\tfi\n\t\techo \"\"\n\tfi\n\n\t# ── 自动修复: 移除旧版错误写入的顶层 models.xxx 无效键 ──\n\tif [ -f \"$CONFIG_FILE\" ]; then\n\t\tlocal has_bad_models=$(\"$NODE_BIN\" -e \"\n\t\t\tconst d=JSON.parse(require('fs').readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\tconst m=d.models;\n\t\t\tif(m&&typeof m==='object'){\n\t\t\t\tconst bad=Object.keys(m).filter(k=>['openai','anthropic','google','openrouter','deepseek','github-copilot','dashscope','xai','groq','siliconflow','custom'].includes(k));\n\t\t\t\tif(bad.length>0){console.log(bad.join(','));}\n\t\t\t}\n\t\t\" 2>/dev/null)\n\t\tif [ -n \"$has_bad_models\" ]; then\n\t\t\techo -e \" ${YELLOW}⚠️ 检测到旧版配置错误: 顶层 models 包含无效键 (${has_bad_models})${NC}\"\n\t\t\techo -e \" ${CYAN}正在自动修复...${NC}\"\n\t\t\t\"$NODE_BIN\" -e \"\n\t\t\t\tconst fs=require('fs');\n\t\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\t\tconst bad=['openai','anthropic','google','openrouter','deepseek','github-copilot','dashscope','xai','groq','siliconflow','custom'];\n\t\t\t\tif(d.models&&typeof d.models==='object'){\n\t\t\t\t\tbad.forEach(k=>delete d.models[k]);\n\t\t\t\t\tif(Object.keys(d.models).length===0||(Object.keys(d.models).length===1&&d.models.providers)){}\n\t\t\t\t\telse if(Object.keys(d.models).filter(k=>k!=='mode'&&k!=='providers').length===0){}\n\t\t\t\t\tif(!d.models.providers&&Object.keys(d.models).every(k=>bad.includes(k)||k==='mode'))delete d.models;\n\t\t\t\t}\n\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\" 2>/dev/null\n\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\techo -e \" ${GREEN}✅ 已移除无效的 models 配置键${NC}\"\n\t\t\techo \"\"\n\t\tfi\n\tfi\n\n\tif check_port_listening \"$gw_port\"; then\n\t\techo -e \" ${GREEN}✅ Gateway 端口 ${gw_port} 正在监听${NC}\"\n\telse\n\t\techo -e \" ${RED}❌ Gateway 端口 ${gw_port} 未监听${NC}\"\n\tfi\n\n\tlocal http_code=$(curl -s -o /dev/null -w \"%{http_code}\" --connect-timeout 3 \"http://127.0.0.1:${gw_port}/\" 2>/dev/null || echo \"000\")\n\tif [ \"$http_code\" = \"200\" ] || [ \"$http_code\" = \"302\" ] || [ \"$http_code\" = \"401\" ]; then\n\t\techo -e \" ${GREEN}✅ HTTP 响应正常 (${http_code})${NC}\"\n\telse\n\t\techo -e \" ${RED}❌ HTTP 响应异常 (${http_code})${NC}\"\n\tfi\n\n\t# v2026.3.2: 使用 gateway health --json 做深度健康检查 (HTTP /health 已被 SPA 接管)\n\tlocal health_resp=$(oc_cmd gateway health --json 2>/dev/null)\n\tif [ -n \"$health_resp\" ]; then\n\t\tlocal health_ok=$(\"$NODE_BIN\" -e \"try{const h=JSON.parse(process.argv[1]);console.log(h.ok?'ok':'fail');}catch(e){console.log('parse_error');}\" \"$health_resp\" 2>/dev/null)\n\t\tif [ \"$health_ok\" = \"ok\" ]; then\n\t\t\techo -e \" ${GREEN}✅ Gateway 健康检查正常${NC}\"\n\t\telif [ \"$health_ok\" = \"parse_error\" ]; then\n\t\t\techo -e \" ${YELLOW}⚠️ Gateway 健康检查响应无法解析${NC}\"\n\t\telse\n\t\t\techo -e \" ${YELLOW}⚠️ Gateway 健康检查异常${NC}\"\n\t\tfi\n\tfi\n\n\tif [ -f \"$CONFIG_FILE\" ]; then\n\t\techo -e \" ${GREEN}✅ 配置文件存在${NC}\"\n\telse\n\t\techo -e \" ${RED}❌ 配置文件不存在${NC}\"\n\tfi\n\n\techo \"\"\n\techo -e \" ${CYAN}运行官方诊断...${NC}\"\n\toc_cmd doctor 2>/dev/null || true\n\n\techo \"\"\n\techo -e \" ${CYAN}最近日志 (最后 10 行):${NC}\"\n\tlogread -e openclaw 2>/dev/null | tail -10 || echo \" (无日志)\"\n}\n\n# ══════════════════════════════════════════════════════════════\n# 恢复默认配置\n# ══════════════════════════════════════════════════════════════\nreset_to_defaults() {\n\techo \"\"\n\techo -e \" ${BOLD}⚠️ 恢复默认配置${NC}\"\n\techo \"\"\n\techo -e \" ${YELLOW}请选择恢复级别:${NC}\"\n\techo \"\"\n\techo -e \" ${CYAN}1)${NC} 🔧 仅重置网关设置 (端口/绑定/模式恢复默认,保留模型和渠道)\"\n\techo -e \" ${CYAN}2)${NC} 🤖 清除模型配置 (移除所有 AI 模型和 API Key)\"\n\techo -e \" ${CYAN}3)${NC} 📡 清除渠道配置 (移除所有消息渠道配置)\"\n\techo -e \" ${CYAN}4)${NC} 🔄 完全恢复出厂 (删除所有配置,重新初始化)\"\n\techo -e \" ${CYAN}0)${NC} 返回\"\n\techo \"\"\n\tprompt_with_default \"请选择\" \"0\" reset_choice\n\n\tcase \"$reset_choice\" in\n\t\t1)\n\t\t\techo \"\"\n\t\t\techo -e \" ${YELLOW}将重置: 网关端口→18789, 绑定→lan, 模式→local${NC}\"\n\t\t\techo -e \" ${YELLOW}保留: 认证令牌、模型配置、消息渠道${NC}\"\n\t\t\tprompt_with_default \"确认恢复网关默认设置? (yes/no)\" \"no\" confirm\n\t\t\tif [ \"$confirm\" = \"yes\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}正在重置网关设置...${NC}\"\n\t\t\t\tjson_set gateway.port 18789 2>&1\n\t\t\t\tjson_set gateway.bind lan 2>&1\n\t\t\t\tjson_set gateway.mode local 2>&1\n\t\t\t\tjson_set gateway.controlUi.allowInsecureAuth true 2>&1\n\t\t\t\tjson_set gateway.controlUi.dangerouslyDisableDeviceAuth true 2>&1\n\t\t\t\tjson_set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true 2>&1\n\t\t\t\tjson_set gateway.tailscale.mode off 2>&1\n\t\t\t\techo -e \" ${GREEN}✅ 网关设置已恢复默认${NC}\"\n\t\t\t\task_restart\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}已取消${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\t2)\n\t\t\techo \"\"\n\t\t\techo -e \" ${RED}⚠️ 将清除: 所有模型配置、API Key、活跃模型设置${NC}\"\n\t\t\tprompt_with_default \"确认清除所有模型配置? (yes/no)\" \"no\" confirm\n\t\t\tif [ \"$confirm\" = \"yes\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}正在清除模型配置...${NC}\"\n\t\t\t\toc_cmd config unset models >/dev/null 2>&1 || true\n\t\t\t\toc_cmd config unset agents.defaults.model >/dev/null 2>&1 || true\n\t\t\t\toc_cmd config unset agents.defaults.models >/dev/null 2>&1 || true\n\t\t\t\t# 同时清除 auth-profiles.json 中的认证信息\n\t\t\t\tlocal auth_file=\"${OC_STATE_DIR}/agents/main/agent/auth-profiles.json\"\n\t\t\t\tif [ -f \"$auth_file\" ]; then\n\t\t\t\t\techo '{\"version\":1,\"profiles\":{},\"usageStats\":{}}' > \"$auth_file\"\n\t\t\t\t\tchown openclaw:openclaw \"$auth_file\" 2>/dev/null || true\n\t\t\t\tfi\n\t\t\t\techo -e \" ${GREEN}✅ 模型配置已清除${NC}\"\n\t\t\t\techo -e \" ${YELLOW}请通过菜单 [2] 重新配置 AI 模型${NC}\"\n\t\t\t\task_restart\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}已取消${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\t3)\n\t\t\techo \"\"\n\t\t\techo -e \" ${RED}⚠️ 将清除: 所有消息渠道配置 (Telegram/Discord/飞书等)${NC}\"\n\t\t\tprompt_with_default \"确认清除所有渠道配置? (yes/no)\" \"no\" confirm\n\t\t\tif [ \"$confirm\" = \"yes\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}正在清除渠道配置...${NC}\"\n\t\t\t\t# 清除 openclaw.json 中的 channels 配置\n\t\t\t\toc_cmd config unset channels >/dev/null 2>&1 || true\n\t\t\t\t\n\t\t\t\t# v2026.3.14: 同时清除 plugins 中与渠道相关的配置\n\t\t\t\t# 防止重置后插件配置残留导致状态不一致\n\t\t\t\tif [ -f \"$CONFIG_FILE\" ] && [ -x \"$NODE_BIN\" ]; then\n\t\t\t\t\t\"$NODE_BIN\" -e \"\n\t\t\t\t\t\tconst fs=require('fs');\n\t\t\t\t\t\ttry{\n\t\t\t\t\t\t\tconst d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));\n\t\t\t\t\t\t\tlet modified=false;\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// 清除 plugins.entries 中与消息渠道相关的插件\n\t\t\t\t\t\t\tif(d.plugins && d.plugins.entries){\n\t\t\t\t\t\t\t\tconst channelPlugins=['openclaw-qqbot','@tencent-connect/openclaw-qqbot','openclaw-lark','@larksuite/openclaw-lark'];\n\t\t\t\t\t\t\t\tchannelPlugins.forEach(p=>{\n\t\t\t\t\t\t\t\t\tif(d.plugins.entries[p]){\n\t\t\t\t\t\t\t\t\t\tdelete d.plugins.entries[p];\n\t\t\t\t\t\t\t\t\t\tmodified=true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t// 清除 plugins.allow 中的渠道插件\n\t\t\t\t\t\t\tif(Array.isArray(d.plugins && d.plugins.allow)){\n\t\t\t\t\t\t\t\tconst beforeLen=d.plugins.allow.length;\n\t\t\t\t\t\t\t\td.plugins.allow=d.plugins.allow.filter(p=>\n\t\t\t\t\t\t\t\t\t!p.includes('qqbot') && \n\t\t\t\t\t\t\t\t\t!p.includes('lark') && \n\t\t\t\t\t\t\t\t\t!p.includes('telegram') &&\n\t\t\t\t\t\t\t\t\t!p.includes('discord') &&\n\t\t\t\t\t\t\t\t\t!p.includes('slack') &&\n\t\t\t\t\t\t\t\t\t!p.includes('whatsapp')\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tif(d.plugins.allow.length!==beforeLen)modified=true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tif(modified){\n\t\t\t\t\t\t\t\tfs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));\n\t\t\t\t\t\t\t\tconsole.log('CLEANED');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}catch(e){}\n\t\t\t\t\t\" 2>/dev/null\n\t\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\tfi\n\t\t\t\t\n\t\t\t\t# 清除飞书扩展目录中的敏感数据 (保留插件本体)\n\t\t\t\tlocal feishu_ext_dir=\"${OC_STATE_DIR}/extensions/openclaw-lark\"\n\t\t\t\tif [ -d \"$feishu_ext_dir\" ]; then\n\t\t\t\t\t# 只清除配置文件,保留插件代码\n\t\t\t\t\trm -f \"${feishu_ext_dir}/.credentials\"* 2>/dev/null\n\t\t\t\t\trm -f \"${feishu_ext_dir}/config.json\" 2>/dev/null\n\t\t\t\t\trm -rf \"${feishu_ext_dir}/.cache\" 2>/dev/null\n\t\t\t\t\techo -e \" ${CYAN}已清理飞书插件缓存数据${NC}\"\n\t\t\t\tfi\n\t\t\t\t\n\t\t\t\t# 清除 QQ 机器人扩展目录中的敏感数据\n\t\t\t\tlocal qqbot_ext_dir=\"${OC_STATE_DIR}/extensions/openclaw-qqbot\"\n\t\t\t\tif [ -d \"$qqbot_ext_dir\" ]; then\n\t\t\t\t\trm -f \"${qqbot_ext_dir}/credentials\"* 2>/dev/null\n\t\t\t\t\trm -f \"${qqbot_ext_dir}/config.json\" 2>/dev/null\n\t\t\t\tfi\n\t\t\t\t\n\t\t\t\techo -e \" ${GREEN}✅ 渠道配置已清除${NC}\"\n\t\t\t\techo -e \" ${YELLOW}请通过菜单 [4] 重新配置消息渠道${NC}\"\n\t\t\t\task_restart\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}已取消${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\tc)\n\t\t\techo \"\"\n\t\t\techo -e \" ${RED}╔══════════════════════════════════════════════════════╗${NC}\"\n\t\t\techo -e \" ${RED}║ ⚠️ 完全恢复出厂设置 ║${NC}\"\n\t\t\techo -e \" ${RED}║ 此操作将删除所有配置并重新初始化 ║${NC}\"\n\t\t\techo -e \" ${RED}╚══════════════════════════════════════════════════════╝${NC}\"\n\t\t\techo \"\"\n\t\t\techo -e \" ${RED}此操作不可撤销!${NC}\"\n\t\t\tprompt_with_default \"输入 RESET 确认恢复出厂设置\" \"\" confirm\n\t\t\tif [ \"$confirm\" = \"RESET\" ]; then\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}[1/5] 停止 Gateway...${NC}\"\n\t\t\t\t# 只停止 gateway 实例, 不能停 pty (否则会断开当前终端连接)\n\t\t\t\tlocal gw_pid=\"\"\n\t\t\t\tgw_pid=$(ubus call service list '{\"name\":\"openclaw\"}' 2>/dev/null | jsonfilter -e '$.openclaw.instances.gateway.pid' 2>/dev/null) || true\n\t\t\t\tif [ -n \"$gw_pid\" ] && kill -0 \"$gw_pid\" 2>/dev/null; then\n\t\t\t\t\tkill \"$gw_pid\" 2>/dev/null || true\n\t\t\t\t\tsleep 2\n\t\t\t\telse\n\t\t\t\t\t# 按端口查找 gateway 进程\n\t\t\t\t\tlocal gw_port_cur=$(json_get gateway.port)\n\t\t\t\t\tgw_port_cur=${gw_port_cur:-18789}\n\t\t\t\t\tlocal gw_pid2=$(get_pid_by_port \"$gw_port_cur\")\n\t\t\t\t\tif [ -n \"$gw_pid2\" ]; then\n\t\t\t\t\t\tkill \"$gw_pid2\" 2>/dev/null || true\n\t\t\t\t\t\tsleep 2\n\t\t\t\t\tfi\n\t\t\t\tfi\n\t\t\t\techo -e \" ${GREEN} Gateway 已停止${NC}\"\n\n\t\t\t\techo -e \" ${CYAN}[2/5] 备份当前配置...${NC}\"\n\t\t\t\tlocal backup_dir=\"${OC_STATE_DIR}/backups\"\n\t\t\t\tlocal backup_ts=$(date +%Y%m%d_%H%M%S)\n\t\t\t\tmkdir -p \"$backup_dir\"\n\t\t\t\tchown openclaw:openclaw \"$backup_dir\" 2>/dev/null || true\n\t\t\t\tif [ -f \"$CONFIG_FILE\" ]; then\n\t\t\t\t\tcp \"$CONFIG_FILE\" \"${backup_dir}/openclaw_${backup_ts}.json\"\n\t\t\t\t\techo -e \" ${GREEN} 备份已保存: backups/openclaw_${backup_ts}.json${NC}\"\n\t\t\t\tfi\n\n\t\t\t\techo -e \" ${CYAN}[3/5] 重置配置...${NC}\"\n\t\t\t\t# 直接删除配置文件 (避免 oc_cmd reset 可能的交互式阻塞)\n\t\t\t\trm -f \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\trm -f \"${CONFIG_FILE}.bak\" 2>/dev/null || true\n\t\t\t\techo '{}' > \"$CONFIG_FILE\"\n\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\t\techo -e \" ${GREEN} 配置已清除${NC}\"\n\n\t\t\t\techo -e \" ${CYAN}[4/5] 重新初始化...${NC}\"\n\t\t\t\t# 尝试 onboard,超时 10 秒避免阻塞\n\t\t\t\tlocal _node_bin\n\t\t\t\t_node_bin=$(which node 2>/dev/null || echo \"$NODE_BIN\")\n\t\t\t\tif command -v timeout >/dev/null 2>&1; then\n\t\t\t\t\ttimeout 10 sh -c \"\\\"$_node_bin\\\" \\\"$OC_ENTRY\\\" onboard --non-interactive --accept-risk --tools-profile coding\" >/dev/null 2>&1 || true\n\t\t\t\telse\n\t\t\t\t\t\"$_node_bin\" \"$OC_ENTRY\" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 &\n\t\t\t\t\tlocal _ob_pid=$!\n\t\t\t\t\tsleep 10\n\t\t\t\t\tkill \"$_ob_pid\" 2>/dev/null || true\n\t\t\t\t\twait \"$_ob_pid\" 2>/dev/null || true\n\t\t\t\tfi\n\t\t\t\techo -e \" ${GREEN} 初始化完成${NC}\"\n\n\t\t\t\techo -e \" ${CYAN}[5/5] 应用 OpenWrt 适配配置...${NC}\"\n\t\t\t\tlocal new_token\n\t\t\t\tnew_token=$(head -c 24 /dev/urandom | hexdump -e '24/1 \"%02x\"' 2>/dev/null || dd if=/dev/urandom bs=24 count=1 2>/dev/null | od -An -tx1 | tr -d ' \\n' | head -c 48)\n\t\t\t\tjson_set gateway.port 18789\n\t\t\t\tjson_set gateway.bind lan\n\t\t\t\tjson_set gateway.mode local\n\t\t\t\tjson_set gateway.auth.mode token\n\t\t\t\tjson_set gateway.auth.token \"$new_token\"\n\t\t\t\tjson_set gateway.controlUi.allowInsecureAuth true\n\t\t\t\tjson_set gateway.controlUi.dangerouslyDisableDeviceAuth true\n\t\t\t\tjson_set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true\n\t\t\t\tjson_set gateway.tailscale.mode off\n\t\t\t\tjson_set acp.dispatch.enabled false\n\t\t\t\tjson_set tools.profile coding\n\n\t\t\t\t# 同步 token 到 UCI\n\t\t\t\t. /lib/functions.sh 2>/dev/null || true\n\t\t\t\tuci set openclaw.main.token=\"$new_token\" 2>/dev/null\n\t\t\t\tuci commit openclaw 2>/dev/null\n\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${GREEN}✅ 出厂设置已恢复!${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}新认证令牌: ${new_token}${NC}\"\n\t\t\t\techo \"\"\n\n\t\t\t\t# 重启 gateway (通过 procd reload, 这样不会杀 pty)\n\t\t\t\t/etc/init.d/openclaw start >/dev/null 2>&1 &\n\t\t\t\techo -e \" ${YELLOW}⏳ Gateway 启动中,请稍候...${NC}\"\n\t\t\t\tlocal gw_port=18789\n\t\t\t\tlocal waited=0\n\t\t\t\twhile [ $waited -lt 15 ]; do\n\t\t\t\t\tsleep 2\n\t\t\t\t\twaited=$((waited + 2))\n\t\t\t\t\tif check_port_listening \"$gw_port\"; then\n\t\t\t\t\t\techo -e \" ${GREEN}✅ Gateway 已重新启动${NC}\"\n\t\t\t\t\t\tbreak\n\t\t\t\t\tfi\n\t\t\t\tdone\n\t\t\t\tif [ $waited -ge 15 ]; then\n\t\t\t\t\techo -e \" ${YELLOW}⏳ Gateway 可能仍在启动中,请稍后检查${NC}\"\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}已取消${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\t0|\"\") return ;;\n\t\t*) echo -e \" ${YELLOW}无效选择${NC}\" ;;\n\tesac\n}\n\n# ══════════════════════════════════════════════════════════════\n# 主菜单\n# ══════════════════════════════════════════════════════════════\n# ══════════════════════════════════════════════════════════════\n# 备份/还原配置菜单 (v2026.3.8+ openclaw backup create/verify)\n# ══════════════════════════════════════════════════════════════\nbackup_restore_menu() {\n\techo \"\"\n\techo -e \" ${BOLD}💾 备份/还原配置${NC}\"\n\techo \"\"\n\techo -e \" ${CYAN}1)${NC} 创建配置备份 (仅配置文件)\"\n\techo -e \" ${CYAN}2)${NC} 创建完整备份 (配置 + 状态数据)\"\n\techo -e \" ${CYAN}3)${NC} 验证最新备份\"\n\techo -e \" ${CYAN}4)${NC} 查看备份列表\"\n\techo -e \" ${CYAN}5)${NC} 从最新备份恢复配置\"\n\techo -e \" ${CYAN}0)${NC} 返回主菜单\"\n\techo \"\"\n\tprompt_with_default \"请选择\" \"1\" backup_choice\n\n\t# 备份目录 (openclaw backup create 输出到 CWD)\n\tlocal backup_dir=\"${OC_STATE_DIR}/backups\"\n\tmkdir -p \"$backup_dir\" 2>/dev/null\n\n\tcase \"$backup_choice\" in\n\t\t1)\n\t\t\techo -e \" ${CYAN}正在创建配置备份...${NC}\"\n\t\t\tlocal out\n\t\t\tout=$(cd \"$backup_dir\" && oc_cmd backup create --only-config --no-include-workspace 2>&1)\n\t\t\tlocal rc=$?\n\t\t\techo \"$out\"\n\t\t\tif [ $rc -eq 0 ] && echo \"$out\" | grep -q \"\\.tar\\.gz\"; then\n\t\t\t\techo -e \" ${GREEN}✅ 配置备份已创建${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}⚠️ 备份功能需要 OpenClaw v2026.3.8+${NC}\"\n\t\t\t\techo -e \" ${DIM}如果备份命令不可用,可手动备份: cp ${CONFIG_FILE} ${CONFIG_FILE}.bak${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\t2)\n\t\t\techo -e \" ${CYAN}正在创建完整备份...${NC}\"\n\t\t\techo -e \" ${DIM}(包含配置和状态数据,可能需要较长时间)${NC}\"\n\t\t\tlocal out\n\t\t\tout=$(cd \"$backup_dir\" && HOME=\"$backup_dir\" oc_cmd backup create --no-include-workspace 2>&1)\n\t\t\tlocal rc=$?\n\t\t\techo \"$out\"\n\t\t\t# 完整备份可能输出到 HOME,尝试移动到 backup_dir\n\t\t\tfor f in \"${OC_DATA}\"/*-openclaw-backup.tar.gz; do\n\t\t\t\t[ -f \"$f\" ] && mv \"$f\" \"$backup_dir/\" 2>/dev/null\n\t\t\tdone\n\t\t\tif [ $rc -eq 0 ] && echo \"$out\" | grep -q \"\\.tar\\.gz\"; then\n\t\t\t\techo -e \" ${GREEN}✅ 完整备份已创建${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}⚠️ 备份失败${NC}\"\n\t\t\t\techo -e \" ${DIM}提示: 如果配置文件有校验警告,完整备份可能受限。请使用选项 1 (仅配置文件) 备份${NC}\"\n\t\t\tfi\n\t\t\t;;\n\t\t3)\n\t\t\tlocal latest=$(ls -t \"${backup_dir}\"/*-openclaw-backup.tar.gz 2>/dev/null | head -1)\n\t\t\tif [ -z \"$latest\" ]; then\n\t\t\t\t# 也检查旧位置\n\t\t\t\tlatest=$(ls -t \"${OC_STATE_DIR}\"/*-openclaw-backup.tar.gz \"${OC_DATA}\"/*-openclaw-backup.tar.gz 2>/dev/null | head -1)\n\t\t\tfi\n\t\t\tif [ -z \"$latest\" ]; then\n\t\t\t\techo -e \" ${YELLOW}未找到备份文件,请先创建备份${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}验证备份: ${latest}${NC}\"\n\t\t\t\toc_cmd backup verify \"$latest\" 2>&1\n\t\t\tfi\n\t\t\t;;\n\t\tc)\n\t\t\techo \"\"\n\t\t\tif [ -d \"$backup_dir\" ]; then\n\t\t\t\tlocal count=$(ls \"${backup_dir}\"/*-openclaw-backup.tar.gz 2>/dev/null | wc -l)\n\t\t\t\tif [ \"$count\" -gt 0 ] 2>/dev/null; then\n\t\t\t\t\techo -e \" ${BOLD}备份文件列表:${NC}\"\n\t\t\t\t\tls -lh \"${backup_dir}\"/*-openclaw-backup.tar.gz 2>/dev/null | while read line; do\n\t\t\t\t\t\techo -e \" ${DIM}${line}${NC}\"\n\t\t\t\t\tdone\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${YELLOW}暂无备份文件${NC}\"\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\techo -e \" ${YELLOW}暂无备份文件${NC}\"\n\t\t\tfi\n\t\t\techo \"\"\n\t\t\techo -e \" ${DIM}备份目录: ${backup_dir}${NC}\"\n\t\t\t;;\n\t\td)\n\t\t\tlocal latest=$(ls -t \"${backup_dir}\"/*-openclaw-backup.tar.gz 2>/dev/null | head -1)\n\t\t\tif [ -z \"$latest\" ]; then\n\t\t\t\techo -e \" ${YELLOW}未找到备份文件,请先创建备份${NC}\"\n\t\t\telse\n\t\t\t\techo -e \" ${CYAN}将从以下备份恢复:${NC}\"\n\t\t\t\techo -e \" ${DIM}${latest}${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${YELLOW}⚠️ 这会还原备份中的所有配置和数据文件到原路径!${NC}\"\n\t\t\t\tprompt_with_default \"确认恢复? (y/N)\" \"N\" confirm_restore\n\t\t\t\tif [ \"$confirm_restore\" = \"y\" ] || [ \"$confirm_restore\" = \"Y\" ]; then\n\t\t\t\t\t# 验证备份中 openclaw.json 有效\n\t\t\t\t\tlocal tmp_json=\"/tmp/oc-restore-check.json\"\n\t\t\t\t\ttar -xzf \"$latest\" --wildcards '*/openclaw.json' -O > \"$tmp_json\" 2>/dev/null\n\t\t\t\t\tif [ ! -s \"$tmp_json\" ] || ! \"$NODE_BIN\" -e \"JSON.parse(require('fs').readFileSync('${tmp_json}','utf8'))\" 2>/dev/null; then\n\t\t\t\t\t\trm -f \"$tmp_json\"\n\t\t\t\t\t\techo -e \" ${RED}❌ 备份中的配置文件无效,恢复已取消${NC}\"\n\t\t\t\t\telse\n\t\t\t\t\t\trm -f \"$tmp_json\"\n\t\t\t\t\t\t# 备份当前配置\n\t\t\t\t\t\tcp -f \"$CONFIG_FILE\" \"${CONFIG_FILE}.pre-restore\" 2>/dev/null\n\t\t\t\t\t\t# 获取备份名前缀\n\t\t\t\t\t\tlocal backup_name=$(tar -tzf \"$latest\" 2>/dev/null | head -1 | cut -d/ -f1)\n\t\t\t\t\t\tif [ -z \"$backup_name\" ]; then\n\t\t\t\t\t\t\techo -e \" ${RED}❌ 备份文件格式无法识别${NC}\"\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\techo -e \" ${DIM}正在还原文件...${NC}\"\n\t\t\t\t\t\t\t# 停止服务\n\t\t\t\t\t\t\t/etc/init.d/openclaw stop >/dev/null 2>&1\n\t\t\t\t\t\t\tsleep 2\n\t\t\t\t\t\t\t# 提取 payload 到根目录 (还原到原始绝对路径)\n\t\t\t\t\t\t\ttar -xzf \"$latest\" --strip-components=3 -C / \"${backup_name}/payload/posix/\" 2>&1\n\t\t\t\t\t\t\t# 修复权限\n\t\t\t\t\t\t\tfix_openclaw_state_permissions 2>/dev/null || true\n\t\t\t\t\t\t\techo -e \" ${GREEN}✅ 配置和数据已完整恢复!原配置已保存为 openclaw.json.pre-restore${NC}\"\n\t\t\t\t\t\t\techo \"\"\n\t\t\t\t\t\t\tprompt_with_default \"是否重启服务使配置生效? (Y/n)\" \"Y\" do_restart\n\t\t\t\t\t\t\tif [ \"$do_restart\" != \"n\" ] && [ \"$do_restart\" != \"N\" ]; then\n\t\t\t\t\t\t\t\trestart_gateway\n\t\t\t\t\t\t\tfi\n\t\t\t\t\t\tfi\n\t\t\t\t\tfi\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${DIM}已取消${NC}\"\n\t\t\t\tfi\n\t\t\tfi\n\t\t\t;;\n\t\t0|\"\") return ;;\n\t\t*) echo -e \" ${YELLOW}无效选择${NC}\" ;;\n\tesac\n}\n\n# ══════════════════════════════════════════════════════════════════════════\n# 交互式菜单 (方向键导航)\n# ══════════════════════════════════════════════════════════════════════════\n\n# 检测是否支持交互模式 (需要 Node.js + TTY 终端)\ncan_use_interactive() {\n\t[ -x \"$NODE_BIN\" ] || return 1\n\t[ -f \"$OC_INTERACTIVE\" ] || return 1\n\t[ -f \"$OC_MENU_ENGINE\" ] || return 1\n\t# 检测是否为真实 TTY (排除某些非交互环境)\n\t[ -t 0 ] && [ -t 1 ] || return 1\n\treturn 0\n}\n\n# 启动交互式菜单 (方向键导航版本)\nlaunch_interactive_menu() {\n\tif ! can_use_interactive; then\n\t\techo -e \" ${YELLOW}⚠️ 当前环境不支持交互模式,使用传统菜单${NC}\"\n\t\treturn 1\n\tfi\n\n\t# 调用 Node.js 交互式前端\n\t\"$NODE_BIN\" \"$OC_INTERACTIVE\" 2>&1\n\tlocal rc=$?\n\n\tfix_openclaw_state_permissions 2>/dev/null || true\n\t# 返回后刷新配置权限\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\treturn $rc\n}\n\n# 启动交互式模型配置\nlaunch_interactive_model_config() {\n\tif ! can_use_interactive; then\n\t\techo -e \" ${YELLOW}⚠️ 当前环境不支持交互模式,使用传统菜单${NC}\"\n\t\tconfigure_model # 回退到传统菜单\n\t\treturn $?\n\tfi\n\n\t# 调用 Node.js 交互式前端的模型配置模块\n\t\"$NODE_BIN\" \"$OC_INTERACTIVE\" model 2>&1\n\tlocal rc=$?\n\n\tfix_openclaw_state_permissions 2>/dev/null || true\n\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\treturn $rc\n}\n\nmain_menu() {\n\t# 直接启动交互模式 (如果支持)\n\tif can_use_interactive && [ \"${OC_FORCE_TRADITIONAL:-0}\" != \"1\" ]; then\n\t\tlaunch_interactive_menu\n\t\treturn $?\n\tfi\n\n\t# 传统菜单 (回退方案)\n\twhile true; do\n\t\techo \"\"\n\t\techo -e \"${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}\"\n\t\techo -e \"${GREEN}║${NC} ${BOLD}OpenClaw AI Gateway — OpenWrt 配置管理${NC} ${GREEN}║${NC}\"\n\t\techo -e \"${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${DIM}━━━ AI 模型配置 ━━━${NC}\"\n\t\techo -e \" ${CYAN}1)${NC} 🤖 配置 AI 模型和提供商\"\n\t\techo -e \" ${CYAN}2)${NC} 🎯 设置活动模型\"\n\t\techo \"\"\n\t\techo -e \" ${DIM}━━━ 消息渠道 ━━━${NC}\"\n\t\techo -e \" ${CYAN}3)${NC} 📡 配置消息渠道 (电报/QQ/飞书)\"\n\t\techo \"\"\n\t\techo -e \" ${DIM}━━━ 系统管理 ━━━${NC}\"\n\t\techo -e \" ${CYAN}4)${NC} 🩺 健康检查与状态\"\n\t\techo -e \" ${CYAN}5)${NC} 📋 查看日志\"\n\t\techo -e \" ${CYAN}6)${NC} 🔄 重启 Gateway\"\n\t\techo \"\"\n\t\techo -e \" ${DIM}━━━ 高级选项 ━━━${NC}\"\n\t\techo -e \" ${CYAN}7)${NC} 🔧 高级配置\"\n\t\techo -e \" ${CYAN}8)${NC} ♻️ 重置配置\"\n\t\techo -e \" ${CYAN}9)${NC} 📊 显示当前配置概览\"\n\t\techo \"\"\n\t\techo -e \" ${CYAN}0)${NC} 退出\"\n\t\techo \"\"\n\t\tprompt_with_default \"请选择\" \"1\" menu_choice\n\n\t\tcase \"$menu_choice\" in\n\t\t\t1) configure_model ;;\n\t\t\t2) set_active_model ;;\n\t\t\t3) configure_channels ;;\n\t\t\t4) health_check ;;\n\t\t\t5)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}=== OpenClaw 日志 ===${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\tlogread -e openclaw 2>/dev/null | tail -100 || echo \" (无法读取日志)\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"按回车继续\" \"\" _\n\t\t\t\t;;\n\t\t\t6) restart_gateway ;;\n\t\t\t7) advanced_menu ;;\n\t\t\t8) reset_to_defaults ;;\n\t\t\t9) show_current_config ;;\n\t\t\t0)\n\t\t\t\techo -e \" ${GREEN}再见!${NC}\"\n\t\t\t\texit 0\n\t\t\t\t;;\n\t\t\t*) echo -e \" ${YELLOW}无效选择${NC}\" ;;\n\t\tesac\n\tdone\n}\n\n# ── 高级配置菜单 ──\nadvanced_menu() {\n\twhile true; do\n\t\tlocal gw_port gw_bind gw_mode log_level acp_dispatch\n\t\tgw_port=$(json_get \"gateway.port\" 2>/dev/null || echo \"18789\")\n\t\tgw_bind=$(json_get \"gateway.bind\" 2>/dev/null || echo \"lan\")\n\t\tgw_mode=$(json_get \"gateway.mode\" 2>/dev/null || echo \"local\")\n\t\tlog_level=$(json_get \"gateway.logLevel\" 2>/dev/null || echo \"\")\n\t\tacp_dispatch=$(json_get \"acp.dispatch.enabled\" 2>/dev/null || echo \"false\")\n\n\t\techo \"\"\n\t\techo -e \" ${BOLD}🔧 高级配置${NC}\"\n\t\techo \"\"\n\t\techo -e \" ${CYAN}1)${NC} Gateway 端口 ${DIM}(当前: ${gw_port})${NC}\"\n\t\techo -e \" ${CYAN}2)${NC} Gateway 绑定地址 ${DIM}(当前: ${gw_bind})${NC}\"\n\t\techo -e \" ${CYAN}3)${NC} Gateway 运行模式 ${DIM}(当前: ${gw_mode})${NC}\"\n\t\techo -e \" ${CYAN}4)${NC} 日志级别 ${DIM}(当前: ${log_level:-未设置})${NC}\"\n\t\techo -e \" ${CYAN}5)${NC} ACP Dispatch 设置 ${DIM}(当前: ${acp_dispatch})${NC}\"\n\t\techo -e \" ${CYAN}6)${NC} 官方完整配置向导 ${DIM}(oc configure)${NC}\"\n\t\techo -e \" ${CYAN}7)${NC} 查看原始配置 JSON\"\n\t\techo -e \" ${CYAN}8)${NC} 编辑配置文件 ${DIM}(vi / nano)${NC}\"\n\t\techo -e \" ${CYAN}9)${NC} 导出配置备份\"\n\t\techo -e \" ${CYAN}10)${NC} 导入配置\"\n\t\techo -e \" ${CYAN}0)${NC} 返回主菜单\"\n\t\techo \"\"\n\t\tprompt_with_default \"请选择\" \"0\" adv_choice\n\n\t\tcase \"$adv_choice\" in\n\t\t\t1)\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入 Gateway 端口\" \"$gw_port\" new_port\n\t\t\t\tif [ -n \"$new_port\" ] && [ \"$new_port\" != \"$gw_port\" ]; then\n\t\t\t\t\tjson_set \"gateway.port\" \"$new_port\"\n\t\t\t\t\t# 同步到 UCI\n\t\t\t\t\tuci set openclaw.main.port=\"$new_port\" 2>/dev/null\n\t\t\t\t\tuci commit openclaw 2>/dev/null\n\t\t\t\t\techo -e \" ${GREEN}✅ 端口已设置为 ${new_port}${NC}\"\n\t\t\t\t\task_restart\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t2)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}绑定地址选项:${NC}\"\n\t\t\t\techo \" lan - 仅 LAN 接口 (推荐)\"\n\t\t\t\techo \" loopback - 仅本机访问\"\n\t\t\t\techo \" all - 所有接口 (0.0.0.0)\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入绑定地址\" \"$gw_bind\" new_bind\n\t\t\t\tif [ -n \"$new_bind\" ]; then\n\t\t\t\t\tcase \"$new_bind\" in\n\t\t\t\t\t\tlan|loopback|all)\n\t\t\t\t\t\t\tjson_set \"gateway.bind\" \"$new_bind\"\n\t\t\t\t\t\t\tuci set openclaw.main.bind=\"$new_bind\" 2>/dev/null\n\t\t\t\t\t\t\tuci commit openclaw 2>/dev/null\n\t\t\t\t\t\t\techo -e \" ${GREEN}✅ 绑定地址已设置为 ${new_bind}${NC}\"\n\t\t\t\t\t\t\task_restart\n\t\t\t\t\t\t\t;;\n\t\t\t\t\t\t*) echo -e \" ${YELLOW}无效选项${NC}\" ;;\n\t\t\t\t\tesac\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t3)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}运行模式选项:${NC}\"\n\t\t\t\techo \" local - 本地模式 (推荐)\"\n\t\t\t\techo \" remote - 远程模式\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入运行模式\" \"$gw_mode\" new_mode\n\t\t\t\tif [ -n \"$new_mode\" ] && [ \"$new_mode\" != \"$gw_mode\" ]; then\n\t\t\t\t\tjson_set \"gateway.mode\" \"$new_mode\"\n\t\t\t\t\techo -e \" ${GREEN}✅ 运行模式已设置为 ${new_mode}${NC}\"\n\t\t\t\t\task_restart\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t4)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}日志级别选项:${NC}\"\n\t\t\t\techo \" debug, info, warn, error\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入日志级别\" \"${log_level:-info}\" new_level\n\t\t\t\tif [ -n \"$new_level\" ]; then\n\t\t\t\t\tjson_set \"gateway.logLevel\" \"$new_level\"\n\t\t\t\t\techo -e \" ${GREEN}✅ 日志级别已设置为 ${new_level}${NC}\"\n\t\t\t\t\task_restart\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t5)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}ACP Dispatch 选项:${NC}\"\n\t\t\t\techo \" true - 启用 (可能占用大量内存)\"\n\t\t\t\techo \" false - 禁用 (推荐路由器使用)\"\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入设置\" \"$acp_dispatch\" new_acp\n\t\t\t\tcase \"$new_acp\" in\n\t\t\t\t\ttrue|false)\n\t\t\t\t\t\tjson_set \"acp.dispatch.enabled\" \"$new_acp\"\n\t\t\t\t\t\techo -e \" ${GREEN}✅ ACP Dispatch 已设置为 ${new_acp}${NC}\"\n\t\t\t\t\t\task_restart\n\t\t\t\t\t\t;;\n\t\t\t\t\t*) echo -e \" ${YELLOW}无效选项${NC}\" ;;\n\t\t\t\tesac\n\t\t\t\t;;\n\t\t\t6)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}启动官方配置向导...${NC}\"\n\t\t\t\toc_cmd configure\n\t\t\t\task_restart\n\t\t\t\t;;\n\t\t\t7)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}配置文件路径: ${CONFIG_FILE}${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\tif [ -f \"$CONFIG_FILE\" ]; then\n\t\t\t\t\t\"$NODE_BIN\" -e \"console.log(JSON.stringify(JSON.parse(require('fs').readFileSync('${CONFIG_FILE}','utf8')),null,2))\" 2>/dev/null || cat \"$CONFIG_FILE\"\n\t\t\t\telse\n\t\t\t\t\techo \" (配置文件不存在)\"\n\t\t\t\tfi\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"按回车继续\" \"\" _\n\t\t\t\t;;\n\t\t\t8)\n\t\t\t\techo \"\"\n\t\t\t\tif [ -f \"$CONFIG_FILE\" ]; then\n\t\t\t\t\tvi \"$CONFIG_FILE\"\n\t\t\t\t\task_restart\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${YELLOW}配置文件不存在${NC}\"\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t9) backup_restore_menu ;;\n\t\t\t10)\n\t\t\t\techo \"\"\n\t\t\t\techo -e \" ${CYAN}导入配置${NC}\"\n\t\t\t\techo \"\"\n\t\t\t\tlocal backup_dir=\"${OC_STATE_DIR}/backups\"\n\t\t\t\tif [ -d \"$backup_dir\" ]; then\n\t\t\t\t\techo \" 可用备份:\"\n\t\t\t\t\tls -lt \"$backup_dir\"/*.json 2>/dev/null | head -5 | while read -r line; do\n\t\t\t\t\t\techo \" $(echo \"$line\" | awk '{print $NF}')\"\n\t\t\t\t\tdone\n\t\t\t\tfi\n\t\t\t\techo \"\"\n\t\t\t\tprompt_with_default \"请输入备份文件路径\" \"\" import_path\n\t\t\t\tif [ -n \"$import_path\" ] && [ -f \"$import_path\" ]; then\n\t\t\t\t\tcp \"$import_path\" \"$CONFIG_FILE\"\n\t\t\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\"\n\t\t\t\t\techo -e \" ${GREEN}✅ 配置已导入${NC}\"\n\t\t\t\t\task_restart\n\t\t\t\telse\n\t\t\t\t\techo -e \" ${YELLOW}文件不存在${NC}\"\n\t\t\t\tfi\n\t\t\t\t;;\n\t\t\t0) return ;;\n\t\t\t*) echo -e \" ${YELLOW}无效选择${NC}\" ;;\n\t\tesac\n\tdone\n}\n\n# ── 支持命令行参数 ──\ncase \"${1:-}\" in\n\t--tui|--interactive)\n\t\t# 强制启动交互式菜单 (方向键导航)\n\t\tif can_use_interactive; then\n\t\t\tlaunch_interactive_menu\n\t\telse\n\t\t\techo -e \"${YELLOW}⚠️ 当前环境不支持交互模式${NC}\"\n\t\t\techo \"需要: Node.js + TTY 终端\"\n\t\t\texit 1\n\t\tfi\n\t\t;;\n\t--traditional)\n\t\t# 强制使用传统菜单\n\t\texport OC_FORCE_TRADITIONAL=1\n\t\tmain_menu\n\t\t;;\n\t--model)\n\t\t# 直接进入模型配置\n\t\tif can_use_interactive; then\n\t\t\tlaunch_interactive_model_config\n\t\telse\n\t\t\tconfigure_model\n\t\tfi\n\t\t;;\n\t--set)\n\t\tif [ -n \"${2:-}\" ] && [ -n \"${3:-}\" ]; then\n\t\t\tjson_set \"$2\" \"$3\"\n\t\t\tchown openclaw:openclaw \"$CONFIG_FILE\" 2>/dev/null || true\n\t\t\techo -e \"${GREEN}✅ 已设置 $2${NC}\"\n\t\telse\n\t\t\techo \"用法: oc-config.sh --set \"\n\t\tfi\n\t\t;;\n\t--get)\n\t\tif [ -n \"${2:-}\" ]; then\n\t\t\toc_cmd config get \"$2\"\n\t\telse\n\t\t\techo \"用法: oc-config.sh --get \"\n\t\tfi\n\t\t;;\n\t--restart)\n\t\trestart_gateway\n\t\t;;\n\t--backup)\n\t\tbk_dir=\"${OC_STATE_DIR}/backups\"\n\t\tmkdir -p \"$bk_dir\" 2>/dev/null\n\t\techo -e \"${CYAN}正在创建配置备份...${NC}\"\n\t\tcd \"$bk_dir\" && oc_cmd backup create --only-config --no-include-workspace 2>&1\n\t\t;;\n\t--status)\n\t\tshow_current_config\n\t\thealth_check\n\t\t;;\n\t--help|-h)\n\t\techo \"\"\n\t\techo \"OpenClaw AI Gateway — OpenWrt 配置管理工具\"\n\t\techo \"\"\n\t\techo \"用法:\"\n\t\techo \" oc-config.sh # 进入交互式菜单 (自动检测)\"\n\t\techo \" oc-config.sh --tui # 强制使用方向键交互模式\"\n\t\techo \" oc-config.sh --traditional # 强制使用传统数字菜单\"\n\t\techo \" oc-config.sh --model # 直接进入模型配置\"\n\t\techo \" oc-config.sh --set K V # 设置配置项\"\n\t\techo \" oc-config.sh --get K # 读取配置项\"\n\t\techo \" oc-config.sh --restart # 重启 Gateway\"\n\t\techo \" oc-config.sh --status # 查看状态\"\n\t\techo \"\"\n\t\techo \"交互模式快捷键:\"\n\t\techo \" ↑↓ 导航选项\"\n\t\techo \" 回车 确认选择\"\n\t\techo \" ESC/q 返回上级\"\n\t\techo \" 字符 搜索过滤\"\n\t\techo \"\"\n\t\t;;\n\t\"\")\n\t\tmain_menu\n\t\t;;\n\t*)\n\t\techo \"未知参数: $1 (使用 --help 查看帮助)\"\n\t\texit 1\n\t\t;;\nesac\n"}}, "CHANGELOG.md": {"26646fa63dc245216dfffa1bf2f9e58036944749": {"t": 1785674200.2267966, "n": 31701, "h": "a1a65fe94b5b8935", "c": "# Changelog\n\n本项目所有重大变更都将记录在此文件中。\n\n格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。\n\n## [2.0.11] - 2026-07-10\n\n### 修复微信扫码后 Gateway 丢失插件\n\n- 微信安装、升级和登录前置检查改为使用官方 `plugins enable openclaw-weixin`、`plugins registry --refresh` 和 `plugins inspect` 完成闭环。\n- 只有官方 npm 安装记录、SQLite 注册表和微信 channel capability 全部验证通过后,LuCI 才返回安装成功。\n- 启动自愈不再删除 OpenClaw 官方生成的 `plugins.entries.openclaw-weixin`,并在 Gateway 启动前以 `openclaw` 用户刷新和验证注册表。\n- 修复扫码认证已保存,但 Gateway 重启后只加载 `memory-core` 并报 `invalid channels.start channel` 的问题。\n\n### 验证\n\n- 增加插件启用、SQLite 注册表刷新、加载能力校验和冷启动自愈的契约断言。\n\n---\n\n## [2.0.10] - 2026-07-08\n\n### 修复微信渠道配对\n\n- 适配 OpenClaw 2026.6.11 SQLite `installed_plugin_index`,微信安装与升级改用官方 `openclaw plugins install --pin` 写入插件索引。\n- 启动时检测旧 npm 直装目录并一次性迁移到官方插件索引,同时补齐 `plugins.allow` 和 `channels.openclaw-weixin.enabled`。\n- 微信安装、升级和登录流程增加 `https://ilinkai.weixin.qq.com` 连通性检查,日志直接显示 HTTP、TLS 或 timeout 结果。\n- 兼容没有 `su` / `runuser` 的 OpenWrt 固件,微信安装、升级、登录和下线改用 `start-stop-daemon` 兜底以 `openclaw` 用户执行。\n- 微信安装网络探测优先使用 `curl`,避免低内存/精简 musl 固件上 Node `fetch` 触发 undici Wasm OOM。\n- 不再只用 npm 直装并写入已废弃的 `plugins.installs`,避免扫码成功后配置保存导致插件注册丢失。\n- 微信插件安装/升级不再预先停止 Gateway,避免反复安装触发 procd crash-loop。\n- LuCI 状态页和 `status_service` 增强 procd 状态识别,能区分真实启动中、stale pidfile 和 crash-loop 抑制,不再误显示“正在启动”。\n- 微信登录前补充 Node、python3、插件目录、账号状态目录和配置写权限检查,并清理残留登录进程与旧二维码状态。\n- 登录失败时在 LuCI 页面展示真实日志详情,不再只显示“登录失败”。\n- 二维码链接提取更稳,页面明确提示“点击链接后用微信扫码”。\n- README 补充微信插件正确配对流程和常见失败原因。\n\n### 修复权限混乱\n\n- 新增统一权限修复工具 `openclaw-permissions.sh`。\n- 避免把整个 `OC_DATA` 或 `OC_STATE_DIR` 递归改成 `openclaw`。\n- `npm/projects` 及插件源码保持 `openclaw` 可写,匹配官方 managed npm generation 生命周期。\n- retained npm generation 目录保持可清理,修复 Gateway 清理旧 generation 时的 `EACCES`。\n- legacy `extensions` / `archived-extensions` 保持 root-owned,降低 OpenClaw 插件安全检查误报风险。\n\n### 更新一万AI分享粉丝专享 API\n\n- `gpt-5.5` 默认上下文声明调整为 1,000,000 tokens,单次请求的实际可用上限仍以上游 API 为准。\n\n### 修复检测升级\n\n- “快捷操作 → 检测升级”改为语义版本比较,只在远端版本真正高于当前版本时提示升级。\n- 本地版本高于 GitHub latest 时不再误判为可升级。\n- 已实测旧版 `2.0.8` 可通过 LuCI 检测并一键升级到 `2.0.9`,升级日志返回成功且服务保持正常。\n\n### 修复干净安装启动失败\n\n- 修复首次安装后 `doctor --fix` 可能移除 `gateway.auth.token`,导致 Gateway 绑定 LAN 时因缺少认证直接退出,页面显示“启动失败,退出码 78”。\n- `doctor --fix` 执行环境补齐 `NODE_ICU_DATA` 和 Node/OpenClaw PATH,避免部分 musl 固件上配置迁移阶段触发 ICU/Unicode 正则错误。\n- Gateway 启动时增加 `OPENCLAW_GATEWAY_TOKEN` 环境变量兜底,确保 JSON 配置被迁移工具改写后仍能使用 UCI token 启动。\n- `.run` 安装器补齐运行依赖安装,避免精简固件缺少 GNU tar 时 Node.js `.tar.xz` 解压失败。\n- `openclaw-env setup` 改为先完整解压验证 Node.js,再替换正式目录;安装失败时保留已有运行目录,避免重装失败清空 `/opt/openclaw`。\n\n### 修复 Web 控制台连续对话\n\n- 启动 Gateway 前自动修补 OpenClaw 2026.6.11 的 WebChat 会话初始化冲突,避免 Web 控制台第一条能回答、第二条报 `reply session initialization conflicted`。\n- WebChat 会话不再因上一轮 assistant transcript 写入时间被误判为需要 rollover。\n- 当同一 sessionId 的 WebChat 写入在用户消息追加后发生 revision 变化时,自动合并当前 session entry 后继续提交。\n\n### 验证\n\n- 通过 shell / Node / Lua 语法检查。\n- 通过契约测试 `tests/test_openclaw_contracts.sh`。\n\n---\n\n## [2.0.9] - 2026-07-04\n\n### 修复微信 npm 插件注册\n\n- 微信插件安装和升级成功后,自动把 npm 安装目录注册到 OpenClaw 配置的 `plugins.installs.openclaw-weixin`。\n- 同步确保 `plugins.allow` 包含 `openclaw-weixin`,并启用 `channels.openclaw-weixin.enabled`,避免登录时再次提示安装插件。\n\n### 测试\n\n- 增加微信 npm 插件安装后配置注册的契约断言。\n\n---\n\n## [2.0.8] - 2026-07-04\n\n### 修复 ARM64 Node.js 下载资产不一致\n\n- 默认 Node.js 更新为 `22.23.0`,与 GitHub Release 和长期 `node-bins` 中的 ARM64 musl 资产保持一致。\n- 同步更新 `build-node-musl.yml` 手动构建参数,避免后续手动构建继续产出旧版本 Node.js。\n- `node-bins` release 已补齐 `node-v22.23.0-linux-arm64-musl.tar.xz`,用于 ARM64 OpenWrt/iStoreOS 在线安装。\n\n### 测试\n\n- 更新 Node.js 默认版本、Node 版本比较和 ARM64 musl 打包契约测试。\n\n---\n\n## [2.0.7] - 2026-07-04\n\n### 适配 OpenClaw v2026.6.11\n\n- `OC_TESTED_VERSION` 更新为 `2026.6.11`,继续默认安装 npm `latest` 稳定标签。\n- 保持默认 Node.js `22.22.2` 和最低要求 `22.19.0`,满足 OpenClaw `2026.6.11` 的 `engines.node`。\n- README 同步当前适配版本:OpenClaw `2026.6.11`、微信插件 `@tencent-weixin/openclaw-weixin@2.4.6`、微信 CLI `2.1.4`。\n\n### 修复微信插件安装、登录和卸载\n\n- 微信插件安装和升级时,如缺少 `python3`,后台自动尝试 `opkg update && opkg install python3-light`,失败后在安装日志给出手动命令。\n- 微信登录前只修复 npm cache、tmp、微信账号状态目录和配置文件权限,避免递归改写插件代码目录属主。\n- 微信登录增加 `openclaw` 用户写权限预检,目录不可写时直接在二维码日志返回明确错误。\n- 微信卸载会清理 legacy extensions、npm projects 中的微信插件包、微信账号状态目录,并用 Node.js 结构化删除 `plugins.allow`、`plugins.installs`、`plugins.entries`、`channels`、`channel` 中的 `openclaw-weixin/weixin` 配置。\n- 备份 API 改用统一安装路径 helper,兼容用户把 `install_path` 误填成 `/mnt/data/openclaw` 的场景。\n\n### 测试\n\n- 更新 OpenClaw 默认版本契约到 `2026.6.11`。\n- 增加微信插件自动安装 `python3-light`、登录权限预检和卸载清理 npm projects/config 的契约断言。\n\n### 致谢\n\n感谢以下用户通过 Issue 或 PR 提供问题反馈、复现信息和修复思路:\n\n- @hotwa (#85)\n- @djbadboyvip (#94)、@alan9771 (#93, #82)、@luckymai3688 (#92, #91)、@lucian521 (#90)\n- @svgr110 (#89)、@Cuscito (#88)、@hwliu11 (#84)、@sam528300-lab (#78)\n- @yiyibuguai (#75)、@xuguoliang189 (#64)、@ampcwin (#70)、@okareyouok (#60)\n\n---\n\n## [2.0.6] - 2026-06-08\n\n### 适配 OpenClaw v2026.6.1\n\n- `OC_TESTED_VERSION` 更新为 `2026.6.1`,默认稳定版跟随 npm `latest` 稳定标签。\n- 默认 Node.js 运行时更新为 Alpine musl LTS `22.22.2`,最低要求同步提升到 `22.19.0`,匹配 OpenClaw 2026.6.x 的 `engines.node`。\n- 安装脚本不再从当前 Node 版本静默回退到旧版 V1 tarball,避免下载失败后装入过低 Node.js 并在 OpenClaw 安装后失败。\n- ARM64 musl 预编译 Node.js 构建流程改用 Alpine 原生 LTS 包,避免 glibc 二进制转换为 musl 后运行失败,并校验默认路径和自定义安装路径均可运行。\n\n### 修复安装、控制台与状态体验\n\n- Node.js 下载失败时保留明确失败路径,避免 ARM64 musl 缺少当前版本资产时误判为可继续安装。\n- Web 控制台改为新窗口打开,继续强制使用 HTTP Gateway URL 和最新 token,规避 LuCI HTTPS、iframe 安全头和旧 token 混用问题。\n- 状态接口和页面继续区分“运行中 / 启动中 / 启动失败”,并保留 procd Gateway 退出码,便于定位升级后的启动失败。\n- 保留微信插件安装预检和权限修复链路,覆盖 `python3`、`openclaw` 用户、npm cache、tmp 和数据目录权限。\n\n### 测试\n\n- 更新 OpenClaw/Node.js 版本契约测试。\n- 新增控制台新窗口、状态失败展示、ARM64 musl Node 打包与路径兼容契约测试。\n\n### 致谢\n\n感谢以下用户通过 Issue 或 PR 提供问题反馈、复现信息和修复思路:\n\n- @djbadboyvip (#94)、@alan9771 (#93, #82)、@luckymai3688 (#92, #91)、@lucian521 (#90)\n- @svgr110 (#89)、@Cuscito (#88)、@ayysama (#87)、@jameslau-tech (#86)、@hwliu11 (#84)\n- @zhuxf8 (#81)、@yiyibuguai (#80)、@nbagui (#79)、@sam528300-lab (#78)、@hotwa (#85)\n\n---\n\n## [2.0.5] - 2026-05-15\n\n### 适配 OpenClaw v2026.5.12\n\n- `OC_TESTED_VERSION` 更新为 `2026.5.12`,默认稳定版固定到 npm `latest` 稳定标签。\n- 保持 Node.js 默认运行时 `22.16.0`,安装后按 OpenClaw `engines.node` 最低要求执行强校验;版本过低会直接失败并输出明确日志。\n- 文档补充 `2026.5.14-beta.2` 不作为默认稳定版的说明。\n\n### 修复权限与路径问题\n\n- 保留公开 UCI 字段 `openclaw.main.install_path`,新增统一路径规范化逻辑。\n- 兼容用户误填 `/mnt/data/openclaw`,避免实际路径变成 `/mnt/data/openclaw/openclaw`。\n- 拒绝相对路径、空白字符、shell 特殊字符和 `/tmp`、`/usr`、`/overlay` 等危险根目录。\n- `profile.d/openclaw.sh` 不再全局 `export HOME=\"$OC_DATA\"`,仅在 `openclaw` 命令包装器内注入 HOME,避免影响 SSH/zsh/oh-my-zsh。\n- 卸载逻辑移除 `chmod -R 777`,并将 `rm -rf` 限定到规范化后的 OpenClaw 运行根目录。\n- 安装前新增写入探针,提前识别 overlay 已满、只读挂载或目标挂载点不可写。\n\n### 修复微信插件链路\n\n- 微信插件安装和升级前检查 `python3`、`openclaw` 系统用户、npm cache/tmp/data 写权限。\n- 统一以 `openclaw` 用户运行微信插件 CLI,并显式传入 `OPENCLAW_CONFIG_PATH`、`NPM_CONFIG_CACHE`、`TMPDIR`。\n- 自动迁移旧渠道名 `weixin` 到 `openclaw-weixin`。\n- 保留 Web PTY `cmd=wechat` 初始化能力,微信向导入口不被破坏。\n\n### 修复 LuCI 与打包\n\n- 状态页区分“启动中”和“启动失败”,并显示 procd gateway 退出码。\n- Web 控制台每次加载前重新读取 token,强制使用正确的 HTTP Gateway URL,避免旧 token 或 HTTPS 混用。\n- `Makefile`、`.run`、`.ipk` 打包清单补齐 `wechat.htm`、rpcd ACL、共享 shell/Lua helper。\n- 主包不再直接生成 `openclaw.zh-cn.lmo`,避免与 `luci-i18n-openclaw-zh-cn` 文件冲突。\n\n### 测试\n\n- 新增路径规范化、卸载保护、profile HOME、Node 版本比较、打包清单、微信安装预检和控制台 token URL 的契约测试入口。\n\n### 致谢\n\n感谢以下用户通过 Issue 或 PR 提供问题反馈、复现信息和修复思路:\n\n- @svgr110 (#89)、@Cuscito (#88)、@ayysama (#87)、@Jameslau-tech (#86, #66)\n- @hotwa (#85, #83, #63)、@hwliu11 (#84)、@alan9771 (#82, #65)、@zhuxf8 (#81)\n- @yiyibuguai (#80, #75)、@nbagui (#79)、@sam528300-lab (#78)、@392377870 (#77)\n- @ouyangmland (#76, #68, #67)、@lucian521 (#74)、@jiangxiaoqiang-cloud (#73)、@LuckyMai (#72, #62)\n- @xuguoliang189 (#71, #64)、@ampcwin (#70)、@natserver (#69)、@jh620026 (#61)、@okareyouok (#60)\n\n---\n\n## [2.0.4] - 2026-04-10\n\n### 适配 OpenClaw v2026.4.9\n\n本次更新适配 OpenClaw 最新稳定版本 (2026.4.9),包含多项破坏性配置变更处理和安全策略适配。\n\n#### 版本变更\n- **OC_TESTED_VERSION**: 从 2026.3.28 更新到 2026.4.9 (跨 12 个版本)\n- **Node.js 版本**: 保持 22.16.0 (v2026.4.x 最低要求 >= 22.14.0)\n\n#### 更新日志 UI 深度优化\n\n参照 GitHub Releases 和 App Store 更新页面的设计规范,重构「检测升级」按钮触发的更新日志显示区域:\n\n- **Markdown 渲染引擎重构** (`ocMarkdownToHtml` 函数):\n - 中英文混排字体栈优化:`PingFang SC` / `Microsoft YaHei` / `Noto Sans SC` + 系统字体回退\n - 字体渲染增强:`text-rendering: optimizeLegibility` + 抗锯齿平滑\n - 标题层级比例重构:一级标题 (20px) → 二级 (17px) → 三级 (15px) → 四级 (14px)\n - 代码块样式:圆角边框 + 等宽字体栈 (`SF Mono`, Consolas, Menlo)\n - 链接悬停效果:底部边框渐显动画\n - 列表标记美化:统一的 `list-style-position: inside` + 行高优化 (1.75)\n\n- **版本状态徽章设计**:\n - 「有新版本」状态:渐变金色背景 + 橙色边框\n - 「已是最新」状态:渐变绿色背景 + 绿色边框\n - 「无法检查」状态:灰色背景 + 中性边框\n - 版本号使用等宽字体徽章样式,增强可读性\n\n- **更新日志容器设计**:\n - 卡片式布局:圆角 + 微阴影 + 白色背景\n - 版本标题栏:渐变背景 + 蓝色版本徽章 + GitHub 跳转链接\n - 内容区滚动条美化:`scrollbar-width: thin` + 自定义颜色\n - 操作按钮区:分组设计 + 阴影增强\n\n- **视觉细节优化**:\n - 段落间距:12px 统一间距\n - 行高优化:中文场景 1.75-1.8\n - 字间距微调:标题 `-0.02em`,正文 `0.01em`\n - 颜色层级:主标题 `#1f2328` → 正文 `#32383f` → 次要文本 `#656d76`\n\n#### 破坏性变更处理 (v2026.4.5)\n\nOpenClaw v2026.4.5 移除了多项旧版配置别名,本版本新增自动清理逻辑:\n\n- **已废弃配置清理**: `sync_uci_to_json()` 新增清理以下废弃字段\n - `talk.voiceId` / `talk.apiKey` — 语音功能配置迁移\n - `browser.ssrfPolicy.allowPrivateNetwork` — SSRF 策略重构\n - `hooks.internal.handlers` — 内部钩子迁移\n - `channel.*.allow` / `group.*.allow` / `room.*.allow` — 迁移到 `enabled` 字段\n - `agents.defaults.cliBackends` — CLI 后端配置废弃\n\n- **配置迁移工具集成**: 服务启动时自动调用 `openclaw doctor --fix` 迁移遗留配置\n\n#### 权限模型适配 (v2026.4.7/4.9)\n\nOpenClaw 新增了严格的环境变量安全检查,阻止危险环境变量覆盖:\n\n- **环境变量安全审计**: `start_service()` 注入环境变量前进行路径安全验证\n - 阻止包含特殊字符 (`$`, `` ` ``, `|`, `&`) 的路径\n - 阻止 `/proc/*`, `/sys/*`, `/dev/*` 等危险目录\n - 确保 `JAVA_HOME`, `RUST_*`, `GIT_*`, `KUBECONFIG`, `AWS_*` 等危险变量不被意外注入\n\n- **命令授权策略更新**: 适配 v2026.4.7 的权限收紧\n - `/allowlist add/remove` 现需要所有者授权 (LuCI 会话验证已满足)\n - `gateway config.apply` 阻止修改 exec 审批路径\n\n#### 安全更新\n\n- **依赖安全**: `basic-ftp` 强制升级到 5.2.1 (修复 CRLF 命令注入漏洞 CVE)\n- **浏览器 SSRF**: 新增安全策略配置选项 (默认不启用内网访问白名单)\n\n#### 改进\n\n- **配置验证增强**: 服务启动时先执行配置 schema 验证,失败时自动尝试修复\n- **iframe 安全头修补**: 适配 v2026.4.x 新的安全头位置\n - v2026.4.x 将 `X-Frame-Options` 和 `frame-ancestors` 设置从 `gateway-cli-*.js` 迁移到 `server.impl-*.js`\n - `patch_iframe_headers()` 函数现已支持两个位置的安全头修补\n - 解决 Web 控制台 iframe 嵌入被阻止的问题\n- **错误提示优化**: 配置迁移失败时输出详细的诊断信息\n- **移除 Gemini CLI 安装**: OpenClaw v2026.4.x 已废弃 `google-gemini-cli-auth` 插件\n - 节省约 155MB 磁盘空间\n - 减少安装时间约 30~60 秒\n - 用户配置 Google Gemini 请使用 API Key 方式 (推荐)\n- **修复微信插件加载失败**: 清理 jiti 缓存目录解决权限冲突\n - jiti 编译 TypeScript 时在 `/tmp/jiti` 创建缓存\n - 服务启动前自动清理,避免 openclaw 用户无法写入\n\n#### 修复 (2026-04-10)\n\n- **权限修复 EACCES 错误**: `doctor --fix` 以 root 运行后创建的文件导致 Gateway 无法写入\n - 根因: `doctor --fix` 在服务启动时以 root 身份执行,可能创建 root 所有的配置文件\n - 症状: `EACCES: permission denied, open '.../agents/main/agent/models.json.xxx.tmp'`\n - 修复: `init.d/openclaw` 在 `doctor --fix` 后自动修复非 extensions 目录权限\n - 同时修复 `openclaw-env factory-reset` 后的权限问题\n\n- **OpenWrt 兼容性**: 无 `ss` 命令时 Gateway 重启检测失败\n - 根因: `oc-config-interactive.js` 直接调用 `ss` 命令,部分精简固件未安装\n - 修复: 优先使用 `ss`,不存在时回退到 `netstat`\n\n- **新增交互式菜单引擎**: `oc-menu-engine.js`\n - 方向键导航、搜索过滤、粘贴支持 (Bracketed Paste Mode)\n - 纯 Node.js 实现,零外部依赖\n - 用于 SSH 命令行配置体验优化\n\n#### 技术细节\n\n**新增函数**:\n- `_validate_path()`: 环境变量路径安全验证\n- `_run_config_migration()`: 执行 OpenClaw 官方配置迁移工具\n\n**修改文件**:\n- `root/etc/init.d/openclaw`: 配置同步逻辑、环境变量注入、权限修复\n- `root/usr/bin/openclaw-env`: OC_TESTED_VERSION 版本号、factory-reset 权限修复\n- `root/usr/share/openclaw/oc-config-interactive.js`: ss/netstat 兼容性\n- `root/usr/share/openclaw/oc-menu-engine.js`: 新增交互式菜单引擎\n\n---\n\n## [2.0.3] - 2026-04-03\n\n### 修复\n\n- **微信插件安装失败 (exit: 127)**: 当 Gateway 未运行或路径检测失败时,`npx` 命令指向不存在的路径导致安装失败\n - 根因: `get_actual_install_path()` 依赖 Gateway 进程运行时返回正确路径,Gateway 未启动时返回默认值 `/opt/openclaw`,但实际安装路径可能是 `/mnt/data/openclaw` 等自定义路径\n - 修复: `action_wechat_install()` 和 `action_wechat_upgrade_plugin()` 新增多层路径校验逻辑:\n 1. 先检查默认检测到的 `npx` 是否存在\n 2. 不存在时回退到 UCI 配置的 `install_path`\n 3. 最后遍历常见备用路径 (`/mnt/data/openclaw`, `/opt/openclaw`, `/overlay/upper/opt/openclaw`)\n 4. 所有路径都无效时返回明确的错误提示,而非模糊的 exit 127\n\n---\n\n## [2.0.2] - 2026-03-31\n\n### 新增功能\n\n#### 适配微信通道插件 (openclaw-weixin)\n\n- **多账号操作面板**: 新增完善的多账号登录登出功能机制,重构 Lua API (`login_cmd`/`logout_cmd`/`install_cmd`/`upgrade_cmd`) 使其支持精细化的多实例运行调度。\n- **登录交互优化**: 解决由于未完成登录流程导致的环境锁定问题,后台新增自愈能力 (`pkill -f 'channels login.*openclaw-weixin'`)。\n- **安全沙箱无缝衔接**: 修复了 Web 触发命令时的 `blocked plugin candidate: suspicious ownership` 安全警告,完整平滑降权至 `openclaw` 用户组执行。\n\n**💡 微信通道使用细节提醒:**\n1. **多开隔离**:所有上号的微信机器人互不干扰,尽管在 Web 控制台大盘上聊天日志看起来像在一起,但由于后端存在独立的 `BotID / AccountID / Session` 多元结构体鉴权区分,他们在底层是**绝对隔离**的记忆!\n2. **退出登录**:遇到登录卡死或想强制下线,点击界面对应微信号的“退出登录”即可强制切除对应后端通道进程。\n\n#### 支持自定义安装路径\n\n- **功能描述**: 用户现在可以将 OpenClaw 运行环境安装到自定义路径(如第二块硬盘 `/mnt/data`)\n- **LuCI 界面**: 安装对话框增加自定义路径输入框,支持实时检测目标路径的可用空间\n- **系统检测**: 安装前系统配置检测会检测自定义路径的磁盘空间,而非默认的 `/opt`\n- **服务状态**: 状态 API 返回当前安装路径信息\n- **最小空间提示**: 安装界面提示最小需要 2GB 可用空间\n\n#### 技术实现\n\n- 新增 UCI 配置项 `openclaw.main.install_path`,默认值为 `/opt`\n- 程序会在用户指定路径下自动创建 `openclaw` 目录进行安装\n - 例如:用户输入 `/mnt/data`,实际安装路径为 `/mnt/data/openclaw`\n- 所有脚本和配置文件支持从 UCI 读取自定义路径:\n - `openclaw-env`: 通过 `OC_INSTALL_PATH` 环境变量或 UCI 配置\n - `init.d/openclaw`: 启动时从 UCI 读取路径\n - `oc-config.sh`: 支持自定义路径的环境变量\n - `profile.d/openclaw.sh`: SSH 环境变量支持\n - `uci-defaults/99-openclaw`: 首次安装初始化支持\n\n\n### 感谢\n\n感谢 @hotwa 提供的修改思路和建议。\n\n---\n\n## [2.0.1] - 2026-03-30\n\n### 适配 OpenClaw v2026.3.28\n\n#### 版本变更\n- **OC_TESTED_VERSION**: 从 2026.3.13 更新到 2026.3.28\n- **磁盘空间要求**: 从 1.5GB 提升到 2GB (OpenClaw v2026.3.28 包体积约 200MB)\n\n#### 兼容性分析\n- **Node.js 版本**: v2026.3.28 要求 >= 22.14.0 (降低了要求,从 v2026.3.13 的 >= 22.16.0)\n- **入口文件**: 无变化,仍为 `openclaw.mjs`\n- **配置 Schema**: 向后兼容,无需迁移\n- **API**: 向后兼容\n\n#### 包体积变化\n- v2026.3.13: ~94MB (4,730 文件)\n- v2026.3.28: ~200MB (19,887 文件)\n- 文件数量增加 4x+,包体积增加 2x+\n\n#### 新增 Plugin SDK 导出 (20+)\n\n新增 AI 提供商原生支持 SDK:\n- `plugin-sdk/xai` — xAI (Grok) API 支持\n- `plugin-sdk/vllm` — vLLM 高性能推理引擎支持\n- `plugin-sdk/ollama` — Ollama 本地模型原生 SDK\n- `plugin-sdk/openai` — OpenAI 原生 SDK\n- `plugin-sdk/sglang` — SGLang 推理引擎支持\n- `plugin-sdk/chutes` — Chutes AI 平台支持\n- `plugin-sdk/google` — Google AI SDK\n- `plugin-sdk/nvidia` — NVIDIA NIM API 支持\n- `plugin-sdk/venice` — Venice AI 支持\n- `plugin-sdk/minimax` — MiniMax API 原生 SDK\n- `plugin-sdk/mistral` — Mistral AI 原生 SDK\n- `plugin-sdk/qianfan` — 百度千帆大模型 SDK\n\n新增功能模块 SDK:\n- `plugin-sdk/zod` — Zod schema 验证支持\n- `plugin-sdk/setup` — 安装配置向导 SDK\n- `plugin-sdk/routing` — 模型路由配置 SDK\n- `plugin-sdk/speech` — 语音处理 SDK\n- `plugin-sdk/browser` — 浏览器自动化 SDK\n\n新增顶级导出:\n- `extension-api` — 扩展 API 入口 (用于插件开发)\n\n#### 移除的依赖\n\n以下渠道依赖被移除 (功能整合到核心或不再维护):\n- `grammy` — Telegram Bot 框架 (改用内置实现)\n- `@grammyjs/runner` — Telegram 运行器\n- `@grammyjs/transformer-throttler` — Telegram 限流器\n- `@whiskeysockets/baileys` — WhatsApp Web API (改用 matrix-js-sdk)\n\n#### 新增依赖\n\n核心依赖:\n- `uuid@^13.0.0` — UUID 生成\n- `gaxios@7.1.4` — Google API HTTP 客户端\n- `matrix-js-sdk@41.2.0` — Matrix 协议支持 (替代 WhatsApp)\n- `@anthropic-ai/vertex-sdk@^0.14.4` — Anthropic Vertex AI 支持\n\n#### 依赖版本升级\n\n核心依赖:\n- `ws`: 8.19.0 → 8.20.0\n- `hono`: 4.12.7 → 4.12.9\n- `file-type`: 21.3.2 → 22.0.0\n- `undici`: 7.24.1 → 7.24.6\n- `sqlite-vec`: 0.1.7-alpha.2 → 0.1.7\n\nAI/ML 依赖:\n- `@mariozechner/pi-ai`: 0.58.0 → 0.63.1\n- `@mariozechner/pi-tui`: 0.58.0 → 0.63.1\n- `@mariozechner/pi-agent-core`: 0.58.0 → 0.63.1\n- `@mariozechner/pi-coding-agent`: 0.58.0 → 0.63.1\n- `@modelcontextprotocol/sdk`: 1.27.1 → 1.28.0\n- `@agentclientprotocol/sdk`: 0.16.1 → 0.17.0\n- `@aws-sdk/client-bedrock`: 3.1009.0 → 3.1019.0\n\n#### pnpm 配置变更\n\n新增 `ignoredBuiltDependencies`:\n- `@discordjs/opus` — 跳过构建\n- `koffi` — 跳过构建\n\n新增 `onlyBuiltDependencies`:\n- `@tloncorp/tlon-skill` — 需要构建\n\n#### 中间版本变更 (v2026.3.22 ~ v2026.3.24)\n\nv2026.3.22:\n- Node.js 最低版本从 22.16.0 降低到 22.14.0\n- 大量 plugin-sdk 模块重构\n\nv2026.3.23:\n- 修复版本发布问题\n- 稳定性改进\n\nv2026.3.24:\n- 依赖安全更新\n- 性能优化\n\n#### 升级建议\n\n1. **磁盘空间**: 确保至少 2GB 可用空间\n2. **Node.js**: v22.16.0 完全兼容,无需降级\n3. **配置迁移**: 现有配置向后兼容,无需手动干预\n4. **备份**: 升级前建议执行 `openclaw backup create --only-config`\n\n## [2.0.0] - 2026-03-16\n\n### 重大变更\n- **配置管理菜单重构**: 主菜单采用分组样式,更清晰的导航结构\n - AI 模型配置:配置 AI 模型和提供商、设置活动模型\n - 消息渠道:配置消息渠道 (电报/QQ/飞书)\n - 系统管理:健康检查与状态、查看日志、重启 Gateway\n - 高级选项:高级配置、重置配置、显示当前配置概览\n- **新增高级配置菜单**: 独立的高级配置入口,包含:\n - Gateway 端口/绑定地址/运行模式配置\n - 日志级别设置\n - ACP Dispatch 开关\n - 官方完整配置向导入口\n - 原始 JSON 查看/编辑\n - 配置备份导出/导入\n\n### 修复\n- **QQ 机器人插件配置名称不匹配** (#XX): OpenClaw v2026.3.13 加强了配置验证,`plugins.allow` 中的插件名称必须与实际安装的插件名完全匹配\n - 问题:旧版本写入的是 `openclaw-qqbot`,但实际插件名是 `@tencent-connect/openclaw-qqbot`\n - 影响:配置验证失败导致 Gateway 启动后立即退出,procd 进入 crash loop 保护\n - 修复:新增 `fix_plugin_config` 函数自动检测并修正不匹配的插件名称\n - 修复:`configure_qq` 安装插件后调用 `ensure_qqbot_plugin_allowed` 确保正确的插件名写入配置\n - 修复:`init.d` 服务启动前自动修复配置中的插件名称\n- **安装运行环境报错** (#28): 部分系统缺少 `libstdcpp6` 导致 Node.js 无法运行,安装 pnpm 时卡住\n - 依赖声明新增 `libstdcpp6`,安装时自动拉取 C++ 标准库\n - Node.js 验证逻辑改进:检测到运行失败时提示缺失的库并给出修复命令\n- **环境变量路径混乱** (#42): 用户通过 SSH 直接运行 `openclaw` 命令时,CLI 使用默认 `HOME=/root` 导致配置文件和 skills 散落在 `/root/.openclaw/` 而非正确的 `/opt/openclaw/data/.openclaw/`\n - 新增 `/etc/profile.d/openclaw.sh` 全局环境变量脚本,SSH 登录后自动设置正确的 PATH、OPENCLAW_HOME 等变量\n - 升级时自动迁移 `/root/.openclaw/` 下的 skills、sessions、openclaw.json 到正确路径\n - 用户现在可以直接运行 `npm`、`npx`、`openclaw config` 等命令\n\n### 新增\n- **全局环境变量**: `/etc/profile.d/openclaw.sh` 为 SSH 用户提供:\n - PATH 包含 Node.js 和 OpenClaw bin 目录\n - OPENCLAW_HOME、OPENCLAW_STATE_DIR、OPENCLAW_CONFIG_PATH 正确指向安装路径\n - `openclaw` 命令别名(当全局安装时)\n- **查看日志功能**: 主菜单新增「查看日志」选项,显示最近 100 条 OpenClaw 日志\n\n### 变更\n- Makefile 新增 `/etc/profile.d/openclaw.sh` 安装步骤\n- 依赖声明新增 `libstdcpp6`\n- **飞书 Bot 配置流程优化**: 参考 QQ Bot 实现,大幅简化配置步骤\n - 新增 App ID 格式验证(`cli_xxx` 格式)\n - 新增 App Secret 长度检查\n - 使用 OpenClaw CLI 一键配置(`oc_cmd channels add --channel feishu`)\n - 配置保存后自动验证\n - 新增详细的事件订阅、权限配置、插件安装指引\n\n### 适配 OpenClaw v2026.3.13\n\n#### 升级说明\n- **Node.js 版本升级**: 从 22.15.1 升级到 22.16.0 (OpenClaw v2026.3.11+ 最低要求)\n- **OpenClaw 版本升级**: 从 v2026.3.8 升级到 v2026.3.13\n\n#### 重要安全修复\n- WebSocket 跨站劫持漏洞修复 (GHSA-5wcw-8jjv-m286)\n- 设备配对安全增强:切换到短期引导令牌 (GHSA-99qw-6mr3-36qr)\n- 命令审批安全加固:Unicode 不可见字符转义、执行检测规范化\n- 多渠道 Webhook 安全增强:飞书/LINE/Zalo 签名验证强化\n\n#### 新功能支持\n- **Fast Mode**: OpenAI/Anthropic 快速响应模式支持\n- **Control UI 重构**: 新版 Dashboard-v2 模块化界面\n- **Ollama 本地向导**: 支持本地或云端+本地混合模式\n- **Kubernetes 部署**: 新增 K8s 部署清单和文档\n- **Docker 时区**: 新增 `OPENCLAW_TZ` 环境变量支持\n\n#### 破坏性变更处理\n- **Cron 主动投递收紧**: 升级后建议运行 `openclaw doctor --fix` 迁移旧版 cron 存储\n- **插件安全策略**: 禁用隐式工作区插件自动加载,需显式信任决策\n- **Node.js 最低版本**: 要求 >= 22.16.0 (已在 openclaw-env 中更新)\n\n#### 配置兼容性\n- 现有配置文件完全兼容,无需手动迁移\n- 已预设 `gateway.controlUi.dangerouslyDisableDeviceAuth=true` 禁用设备认证\n- 已预设 `gateway.controlUi.allowInsecureAuth=true` 允许不安全认证\n\n## [1.0.15] - 2026-03-13\n\n### 修复\n- **QQBot 插件 3 层死锁修复**: 解决插件安装后因 uid 权限→安全策略阻止→配置校验失败的连锁问题\n - 自动检测插件 blocked/loaded/目录存在 3 种状态\n - 插件安装后自动 `chown root:root` 修复权限\n - 安装失败但目录存在时不再阻断配置流程\n\n### 新增\n- **覆盖安装防护**: 离线安装器在覆盖安装前先停止已有服务,避免文件被占用\n- **离线 .run 安装包**: 构建包含 Node.js + OpenClaw + LuCI 插件的全合一自解压包,用户**无需联网**即可完成安装\n- **musl 架构支持**: 离线包支持 x86_64-musl、aarch64-musl 两种架构 (OpenWrt/iStoreOS 均使用 musl)\n- **依赖预下载脚本** (`scripts/download_deps.sh`): 在构建机上预下载所有离线依赖\n- **node_modules 精简**: 自动删除文档、测试、TypeScript 源码等非必要文件,减小 30%+ 体积\n- **磁盘空间预检查**: 安装前检测可用空间是否满足 500MB 最低要求\n- **架构/libc 自动检测**: 安装时自动校验当前设备是否匹配安装包架构\n\n### 文档\n- **README**: 添加离线安装方式(无需联网),更新目录结构\n\n### 变更\n- **离线包不依赖 curl/openssl/git**: 离线安装模式下 opkg 注册的依赖简化为 luci-compat + luci-base\n\n## [1.0.14] - 2026-03-12\n\n### 备份管理增强 & QQ 机器人支持\n\n#### 新增\n- **备份列表可视化**: LuCI「💾 备份/恢复」对话框现在展示所有备份的结构化列表:\n - 📄 仅配置 / 📦 完整备份 类型标签(从 manifest.json 读取 `onlyConfig` 字段精确判断)\n - 备份时间、文件大小\n - 每个备份支持**单独恢复**和**删除**操作\n - 创建/删除备份后列表自动刷新\n- **备份删除 API**: Controller 新增 `action=delete` 操作(含路径穿越安全校验)\n- **QQ 机器人配置**: `oc-config.sh` 渠道菜单新增「QQ 机器人」选项(选项 1,推荐国内用户),支持:\n - 自动安装 `@tencent-connect/openclaw-qqbot` 插件\n - App ID / App Secret 输入校验\n - 通过 `openclaw channels add` CLI 一键配置\n- **消息渠道状态显示**: 状态面板新增「消息渠道」行,自动检测已配置的渠道(QQ、Telegram、Discord、飞书、Slack)\n\n#### 变更\n- **备份恢复**: 从\"从最新备份恢复\"改为在列表中选择任意备份进行恢复\n- **描述文本**: 各页面描述新增\"QQ\"渠道说明\n\n#### 修复\n- **JS 语法错误导致所有按钮失效**: 备份对话框 HTML 被错误地插入 `