From 5f381eebd73a338ff9d69407bd170f3ce37ce34b Mon Sep 17 00:00:00 2001
From: 10000ge10000 <10000ge10000@users.noreply.github.com>
Date: Thu, 5 Mar 2026 15:20:31 +0800
Subject: [PATCH] =?UTF-8?q?release:=20v1.0.4=20=E2=80=94=20=E9=80=82?=
=?UTF-8?q?=E9=85=8D=20OpenClaw=202026.3.2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CHANGELOG.md | 42 ++++++-
Makefile | 1 +
VERSION | 2 +-
luasrc/controller/openclaw.lua | 44 ++++++-
luasrc/model/cbi/openclaw/basic.lua | 49 ++++++--
luasrc/view/openclaw/advanced.htm | 23 ----
luasrc/view/openclaw/status.htm | 2 +
root/etc/init.d/openclaw | 9 ++
root/usr/bin/openclaw-env | 20 ++-
root/usr/share/openclaw/oc-config.sh | 174 +++++++++++++++++++++++++--
scripts/build_ipk.sh | 1 +
scripts/build_run.sh | 1 +
12 files changed, 315 insertions(+), 53 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 034b5ab..5526dee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,37 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)。
-## [1.0.3] - 2026-03-02
+## [1.0.4] - 2026-03-05
+
+### 适配 OpenClaw 2026.3.2
+
+#### 破坏性变更修复
+- **tools.profile 默认值变更**: 2026.3.2 将 `tools.profile` 默认从 `coding` 改为 `messaging`
+ - `sync_uci_to_json()` 每次启动强制写入 `tools.profile=coding`
+ - `openclaw-env init_openclaw()` onboard 命令添加 `--tools-profile coding`
+ - `openclaw-env do_factory_reset()` onboard 命令添加 `--tools-profile coding`
+ - `oc-config.sh` 工厂重置 onboard 命令添加 `--tools-profile coding`
+ - `oc-config.sh` 工厂重置配置写入新增 `tools.profile=coding`
+- **ACP dispatch 默认启用**: 2026.3.2 默认开启 ACP dispatch,路由器内存有限可能导致 OOM
+ - `sync_uci_to_json()` 每次启动强制写入 `acp.dispatch.enabled=false`
+ - `openclaw-env do_factory_reset()` 配置写入新增 `acp.dispatch.enabled=false`
+ - `oc-config.sh` 工厂重置配置写入新增 `acp.dispatch.enabled=false`
+
+#### 新增
+- 健康检查集成 `openclaw config validate --json` 官方配置验证命令
+- 健康检查新增 `gateway health --json` CLI 深度检查 (v2026.3.2 HTTP `/health` 已被 SPA 接管)
+
+#### 修复
+- **Ollama 配置适配**: `api` 从废弃的 `openai-chat-completions` 改为原生 `ollama` API 类型
+- **Ollama baseUrl 格式**: 去掉 `/v1` 后缀,使用官方原生地址格式 (`http://host:11434`)
+- **Ollama apiKey 对齐**: 从 `ollama` 改为官方默认值 `ollama-local`
+- **启动自动迁移**: `sync_uci_to_json` 自动将旧版 Ollama 配置迁移到 v2026.3.2 格式
+
+#### 改进
+- 配置管理页面移除「菜单功能说明」信息框,减少视觉干扰
+- `OC_TESTED_VERSION` 更新至 `2026.3.2`
+
+## [1.0.3] - 2026-03-05
### 修复
- **P0** 配置管理写入错误的 JSON 路径导致 Gateway 崩溃且无法恢复 (#1)
@@ -17,10 +47,20 @@
- **P1** `set_active_model` 手动切换模型时未注册到 `agents.defaults.models`
### 新增
+- **Ollama 本地模型支持**: 快速配置菜单新增 Ollama 选项 (12),支持 localhost/局域网连接、自动检测连通性、自动列出已安装模型、兼容 OpenAI chat completions 格式
- `openclaw-env factory-reset` 非交互式恢复出厂设置命令
- `auth_set_apikey` 函数: 正确写入 API Key 到 `auth-profiles.json`
- `register_and_set_model` 函数: 注册模型到 `agents.defaults.models` 并设为默认
- `register_custom_provider` 函数: 为需要 `baseUrl` 的 OpenAI 兼容供应商注册 `models.providers`
+- 「检测升级」同时检查 OpenClaw 和**插件版本** (通过 GitHub API 获取最新 release)
+- 页面加载时自动静默检查更新,有新版本时「检测升级」按钮显示橙色小红点提醒
+- 状态面板显示当前安装的插件版本号
+- 构建/安装流程部署 `VERSION` 文件到 `/usr/share/openclaw/VERSION`
+- `openclaw-env setup` 安装环境时自动安装 Gemini CLI (Google OAuth 依赖)
+
+### 改进
+- 使用指南顺序调整: ② 配置管理 → ③ Web 控制台 (首次使用更合理的引导顺序)
+- Gemini CLI 安装从配置向导选项 1 移至环境安装阶段,避免进入向导时临时等待
## [1.0.2] - 2026-03-02
diff --git a/Makefile b/Makefile
index 9542dff..e166d55 100644
--- a/Makefile
+++ b/Makefile
@@ -63,6 +63,7 @@ define Package/$(PKG_NAME)/install
$(INSTALL_DATA) ./luasrc/view/openclaw/advanced.htm $(1)/usr/lib/lua/luci/view/openclaw/advanced.htm
$(INSTALL_DATA) ./luasrc/view/openclaw/console.htm $(1)/usr/lib/lua/luci/view/openclaw/console.htm
$(INSTALL_DIR) $(1)/usr/share/openclaw
+ $(INSTALL_DATA) ./VERSION $(1)/usr/share/openclaw/VERSION
$(INSTALL_BIN) ./root/usr/share/openclaw/oc-config.sh $(1)/usr/share/openclaw/oc-config.sh
$(INSTALL_DATA) ./root/usr/share/openclaw/web-pty.js $(1)/usr/share/openclaw/web-pty.js
$(INSTALL_DIR) $(1)/usr/share/openclaw/ui
diff --git a/VERSION b/VERSION
index 21e8796..ee90284 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.3
+1.0.4
diff --git a/luasrc/controller/openclaw.lua b/luasrc/controller/openclaw.lua
index b09e80c..4ac4fb9 100644
--- a/luasrc/controller/openclaw.lua
+++ b/luasrc/controller/openclaw.lua
@@ -93,8 +93,16 @@ function action_status()
uptime = "",
node_version = "",
openclaw_version = "",
+ plugin_version = "",
}
+ -- 插件版本
+ local pvf = io.open("/usr/share/openclaw/VERSION", "r")
+ if pvf then
+ result.plugin_version = pvf:read("*a"):gsub("%s+", "")
+ pvf:close()
+ end
+
-- 检查 Node.js
local node_bin = "/opt/openclaw/node/bin/node"
local f = io.open(node_bin, "r")
@@ -278,10 +286,10 @@ function action_check_update()
local http = require "luci.http"
local sys = require "luci.sys"
- -- 当前版本
+ -- 当前 OpenClaw 版本
local current = get_openclaw_version()
- -- 最新版本 (从 npm registry 查询)
+ -- 最新 OpenClaw 版本 (从 npm registry 查询)
local latest = sys.exec("PATH=/opt/openclaw/node/bin:/opt/openclaw/global/bin:$PATH npm view openclaw version 2>/dev/null"):gsub("%s+", "")
local has_update = false
@@ -289,12 +297,42 @@ function action_check_update()
has_update = true
end
+ -- 插件版本检查 (从 GitHub API 获取最新 release tag)
+ local plugin_current = ""
+ local pf = io.open("/usr/share/openclaw/VERSION", "r")
+ or io.open("/root/luci-app-openclaw/VERSION", "r")
+ if pf then
+ plugin_current = pf:read("*a"):gsub("%s+", "")
+ pf:close()
+ end
+
+ local plugin_latest = ""
+ local plugin_has_update = false
+ -- 仅在请求参数含 check_plugin=1 或 quick=1 时检查插件版本
+ local check_plugin = http.formvalue("check_plugin") or ""
+ local quick = http.formvalue("quick") or ""
+ if check_plugin == "1" or quick == "1" then
+ -- 使用 GitHub API 获取最新 release tag (轻量, 不下载任何文件)
+ local gh_resp = sys.exec("curl -sf --connect-timeout 5 --max-time 10 'https://api.github.com/repos/10000ge10000/luci-app-openclaw/releases/latest' 2>/dev/null | grep -o '\"tag_name\"[[:space:]]*:[[:space:]]*\"[^\"]*\"' | head -1 | cut -d'\"' -f4")
+ gh_resp = gh_resp:gsub("%s+", "")
+ if gh_resp ~= "" then
+ -- tag 可能是 v1.0.3 或 1.0.3
+ plugin_latest = gh_resp:gsub("^v", "")
+ end
+ if plugin_current ~= "" and plugin_latest ~= "" and plugin_current ~= plugin_latest then
+ plugin_has_update = true
+ end
+ end
+
http.prepare_content("application/json")
http.write_json({
status = "ok",
current = current,
latest = latest,
- has_update = has_update
+ has_update = has_update,
+ plugin_current = plugin_current,
+ plugin_latest = plugin_latest,
+ plugin_has_update = plugin_has_update
})
end
diff --git a/luasrc/model/cbi/openclaw/basic.lua b/luasrc/model/cbi/openclaw/basic.lua
index e727ab0..580a13e 100644
--- a/luasrc/model/cbi/openclaw/basic.lua
+++ b/luasrc/model/cbi/openclaw/basic.lua
@@ -35,7 +35,7 @@ act.cfgvalue = function(self, section)
html[#html+1] = ''
html[#html+1] = ''
html[#html+1] = ''
- html[#html+1] = ''
+ html[#html+1] = ''
html[#html+1] = ''
html[#html+1] = ''
html[#html+1] = '
'
@@ -222,22 +222,37 @@ act.cfgvalue = function(self, section)
html[#html+1] = '}catch(e){el.innerHTML="❌ 错误";}'
html[#html+1] = '});}'
- -- 检测升级
+ -- 检测升级 (同时检查 OpenClaw + 插件版本)
html[#html+1] = 'function ocCheckUpdate(){'
html[#html+1] = 'var btn=document.getElementById("btn-check-update");'
html[#html+1] = 'var el=document.getElementById("action-result");'
html[#html+1] = 'var act=document.getElementById("oc-update-action");'
html[#html+1] = 'btn.disabled=true;btn.textContent="⏳ 正在检测...";el.textContent="";act.style.display="none";'
- html[#html+1] = '(new XHR()).get("' .. check_url .. '",null,function(x){'
+ html[#html+1] = '(new XHR()).get("' .. check_url .. '?check_plugin=1",null,function(x){'
html[#html+1] = 'btn.disabled=false;btn.textContent="🔍 检测升级";'
+ html[#html+1] = 'var dot=document.getElementById("update-dot");if(dot)dot.style.display="none";'
html[#html+1] = 'try{var r=JSON.parse(x.responseText);'
- html[#html+1] = 'if(!r.current){el.innerHTML="⚠️ OpenClaw 未安装";return;}'
+ html[#html+1] = 'var msgs=[];'
+ -- OpenClaw 版本检查
+ html[#html+1] = 'if(!r.current){msgs.push("⚠️ OpenClaw 运行环境未安装");}'
+ html[#html+1] = 'else if(r.has_update){msgs.push("📦 OpenClaw: v"+r.current+" → v"+r.latest+" (有新版本)");}'
+ html[#html+1] = 'else{msgs.push("✅ OpenClaw: v"+r.current+" (已是最新)");}'
+ -- 插件版本检查
+ html[#html+1] = 'if(r.plugin_current){'
+ html[#html+1] = 'if(r.plugin_has_update){msgs.push("� 插件: v"+r.plugin_current+" → v"+r.plugin_latest+" (有新版本,请到 GitHub 下载更新)");}'
+ html[#html+1] = 'else if(r.plugin_latest){msgs.push("✅ 插件: v"+r.plugin_current+" (已是最新)");}'
+ html[#html+1] = 'else{msgs.push("🔌 插件: v"+r.plugin_current+" (无法检查最新版本)");}'
+ html[#html+1] = '}'
+ html[#html+1] = 'el.innerHTML=msgs.join("
");'
+ -- 显示 OpenClaw 升级按钮
html[#html+1] = 'if(r.has_update){'
- html[#html+1] = 'el.innerHTML="📦 当前: v"+r.current+" → 最新: v"+r.latest+"";'
html[#html+1] = 'act.style.display="block";'
- html[#html+1] = 'act.innerHTML=\'\';'
- html[#html+1] = '}else{'
- html[#html+1] = 'el.innerHTML="✅ 已是最新版本 (v"+r.current+")";'
+ html[#html+1] = 'act.innerHTML=\'\';'
+ html[#html+1] = '}'
+ -- 插件有更新时添加 GitHub 链接
+ html[#html+1] = 'if(r.plugin_has_update){'
+ html[#html+1] = 'act.style.display="block";'
+ html[#html+1] = 'act.innerHTML=(act.innerHTML||"")+\' 📥 下载插件 v\'+r.plugin_latest+\'\';'
html[#html+1] = '}'
html[#html+1] = '}catch(e){el.innerHTML="❌ 检测失败";}'
html[#html+1] = '});}'
@@ -334,6 +349,20 @@ act.cfgvalue = function(self, section)
html[#html+1] = '}catch(e){el.innerHTML="❌ 请求失败";}'
html[#html+1] = '});}'
+ -- 页面加载时静默检查是否有更新 (仅显示小红点提示)
+ html[#html+1] = '(function(){'
+ html[#html+1] = 'setTimeout(function(){'
+ html[#html+1] = '(new XHR()).get("' .. check_url .. '?quick=1",null,function(x){'
+ html[#html+1] = 'try{var r=JSON.parse(x.responseText);'
+ html[#html+1] = 'if(r.has_update||r.plugin_has_update){'
+ html[#html+1] = 'var dot=document.getElementById("update-dot");'
+ html[#html+1] = 'if(dot)dot.style.display="block";'
+ html[#html+1] = '}'
+ html[#html+1] = '}catch(e){}'
+ html[#html+1] = '});'
+ html[#html+1] = '},2000);'
+ html[#html+1] = '})();'
+
html[#html+1] = ''
return table.concat(html, "\n")
end
@@ -351,8 +380,8 @@ guide.cfgvalue = function()
html[#html+1] = '📖 使用指南
'
html[#html+1] = ''
html[#html+1] = '① 首次使用请点击 「安装运行环境」,安装完成后服务会自动启动
'
- html[#html+1] = '② 进入 「Web 控制台」 配置 AI 模型、消息渠道,直接开始对话
'
- html[#html+1] = '③ 进入 「配置管理」 可使用交互式向导进行高级配置'
+ html[#html+1] = '② 进入 「配置管理」 使用交互式向导快速配置 AI 模型和 API Key
'
+ html[#html+1] = '③ 进入 「Web 控制台」 配置消息渠道,直接开始对话'
html[#html+1] = '
-
-
💡 菜单功能说明:
-
- - 1) 查看当前配置 2) 配置 AI 模型提供商 3) 设定当前活跃模型
- - 4) 配置消息渠道 5) Telegram 配对向导 6) 健康检查 / 诊断
- - 7) 重启网关 8) 查看/编辑原始配置 9) 恢复默认配置
-
-
-
diff --git a/luasrc/view/openclaw/status.htm b/luasrc/view/openclaw/status.htm
index 1d2809b..fc4fca7 100644
--- a/luasrc/view/openclaw/status.htm
+++ b/luasrc/view/openclaw/status.htm
@@ -75,6 +75,7 @@
| 运行时间 | - |
| Node.js | - |
| OpenClaw | - |
+ | 插件版本 | - |
@@ -132,6 +133,7 @@
document.getElementById('oc-st-uptime').textContent = d.uptime || '-';
document.getElementById('oc-st-node').textContent = d.node_version || '未安装';
document.getElementById('oc-st-ocver').textContent = d.openclaw_version || '未安装';
+ document.getElementById('oc-st-plugin').textContent = d.plugin_version ? ('v' + d.plugin_version) : '-';
} catch(e) {
document.getElementById('oc-st-status').innerHTML = '
查询失败';
diff --git a/root/etc/init.d/openclaw b/root/etc/init.d/openclaw
index c556e68..e916929 100755
--- a/root/etc/init.d/openclaw
+++ b/root/etc/init.d/openclaw
@@ -114,6 +114,15 @@ d.gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true;
delete d.gateway.name;
delete d.gateway.bonjour;
delete d.gateway.plugins;
+// v2026.3.2: 禁用 ACP dispatch 防止路由器内存溢出
+if(!d.acp)d.acp={};
+if(!d.acp.dispatch)d.acp.dispatch={};
+d.acp.dispatch.enabled=false;
+// v2026.3.2: tools.profile 默认改为 messaging,路由器场景需强制 coding
+if(!d.tools)d.tools={};
+d.tools.profile='coding';
+// v2026.3.2: 迁移 Ollama provider 到原生 API
+if(d.models&&d.models.providers&&d.models.providers.ollama){const ol=d.models.providers.ollama;if(ol.api==='openai-chat-completions'||ol.api==='openai-completions')ol.api='ollama';if(ol.baseUrl&&ol.baseUrl.endsWith('/v1'))ol.baseUrl=ol.baseUrl.replace(/\/v1$/,'');if(ol.apiKey==='ollama')ol.apiKey='ollama-local';}
fs.writeFileSync(f,JSON.stringify(d,null,2));
" 2>/dev/null
fi
diff --git a/root/usr/bin/openclaw-env b/root/usr/bin/openclaw-env
index 49b3272..3b9b71b 100755
--- a/root/usr/bin/openclaw-env
+++ b/root/usr/bin/openclaw-env
@@ -13,7 +13,7 @@ set -e
NODE_VERSION="${NODE_VERSION:-22.16.0}"
# 经过验证的 OpenClaw 稳定版本 (更新此值需同步测试)
-OC_TESTED_VERSION="2026.3.1"
+OC_TESTED_VERSION="2026.3.2"
# 用户可通过 OC_VERSION 环境变量覆盖安装版本
OC_VERSION="${OC_VERSION:-}"
NODE_BASE="/opt/openclaw/node"
@@ -296,6 +296,18 @@ install_openclaw() {
log_error "OpenClaw 安装验证失败"
exit 1
fi
+
+ # 安装 Gemini CLI (官方模型配置向导的 Google Gemini OAuth 依赖)
+ if [ -x "$NPM_BIN" ]; then
+ echo ""
+ echo "=== 安装 Gemini CLI (Google OAuth 依赖) ==="
+ "$NPM_BIN" install -g @google/gemini-cli --prefix="$OC_GLOBAL" $install_flags 2>&1 | tail -3
+ if command -v gemini >/dev/null 2>&1 || [ -x "$OC_GLOBAL/bin/gemini" ]; then
+ log_info "Gemini CLI 安装成功"
+ else
+ log_warn "Gemini CLI 安装失败 (不影响核心功能,仅影响 Google Gemini OAuth 登录)"
+ fi
+ fi
}
init_openclaw() {
@@ -314,7 +326,7 @@ init_openclaw() {
OPENCLAW_HOME="$OC_DATA" \
OPENCLAW_STATE_DIR="${OC_DATA}/.openclaw" \
OPENCLAW_CONFIG_PATH="${OC_DATA}/.openclaw/openclaw.json" \
- "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk 2>/dev/null || true
+ "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk --tools-profile coding 2>/dev/null || true
log_info "初始化完成"
fi
@@ -526,7 +538,7 @@ do_factory_reset() {
oc_entry=$(find "$OC_GLOBAL" -name "openclaw.mjs" -path "*/openclaw/openclaw.mjs" 2>/dev/null | head -1)
if [ -n "$oc_entry" ]; then
log_info "重新初始化..."
- OPENCLAW_HOME="$OC_DATA" "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk >/dev/null 2>&1 || true
+ OPENCLAW_HOME="$OC_DATA" "$NODE_BIN" "$oc_entry" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 || true
fi
fi
@@ -535,7 +547,7 @@ do_factory_reset() {
local new_token
new_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)
_JS_KEY="gateway.port" _JS_VAL="18789" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i
/dev/null
- for kv in "gateway.bind=lan" "gateway.mode=local" "gateway.auth.mode=token" "gateway.auth.token=${new_token}" "gateway.controlUi.allowInsecureAuth=true" "gateway.controlUi.dangerouslyDisableDeviceAuth=true" "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true" "gateway.tailscale.mode=off"; do
+ for kv in "gateway.bind=lan" "gateway.mode=local" "gateway.auth.mode=token" "gateway.auth.token=${new_token}" "gateway.controlUi.allowInsecureAuth=true" "gateway.controlUi.dangerouslyDisableDeviceAuth=true" "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true" "gateway.tailscale.mode=off" "acp.dispatch.enabled=false" "tools.profile=coding"; do
local k="${kv%%=*}" v="${kv#*=}"
_JS_KEY="$k" _JS_VAL="$v" "$NODE_BIN" -e "const fs=require('fs');let d={};try{d=JSON.parse(fs.readFileSync('${config_file}','utf8'));}catch(e){}const ks=process.env._JS_KEY.split('.');let o=d;for(let i=0;i/dev/null
done
diff --git a/root/usr/share/openclaw/oc-config.sh b/root/usr/share/openclaw/oc-config.sh
index 85b4fdd..7e9c6c3 100755
--- a/root/usr/share/openclaw/oc-config.sh
+++ b/root/usr/share/openclaw/oc-config.sh
@@ -359,7 +359,8 @@ configure_model() {
echo -e " ${CYAN}9)${NC} xAI Grok (Grok-3/3-mini)"
echo -e " ${CYAN}10)${NC} Groq (Llama 4, Llama 3.3)"
echo -e " ${CYAN}11)${NC} 硅基流动 SiliconFlow"
- echo -e " ${CYAN}12)${NC} 自定义 OpenAI 兼容 API"
+ echo -e " ${CYAN}12)${NC} Ollama (本地模型,无需 API Key)"
+ echo -e " ${CYAN}13)${NC} 自定义 OpenAI 兼容 API"
echo -e " ${CYAN}0)${NC} 返回"
echo ""
prompt_with_default "请选择" "1" choice
@@ -373,14 +374,6 @@ configure_model() {
echo -e " ${CYAN}预启用模型认证插件...${NC}"
enable_auth_plugins
echo ""
- # 检查并安装 gemini-cli (官方向导的 Google Gemini OAuth 依赖)
- if ! command -v gemini >/dev/null 2>&1; then
- local npm_bin="${NODE_BASE}/bin/npm"
- if [ -x "$npm_bin" ]; then
- echo -e " ${CYAN}安装 Gemini CLI (官方向导 Google OAuth 依赖)...${NC}"
- "$npm_bin" install -g @google/gemini-cli --prefix="$OC_GLOBAL" >/dev/null 2>&1 || true
- fi
- fi
(oc_cmd configure --section model) || echo -e " ${YELLOW}配置向导已退出${NC}"
echo ""
ask_restart
@@ -718,6 +711,122 @@ configure_model() {
fi
;;
12)
+ echo ""
+ echo -e " ${BOLD}🦙 Ollama 本地模型配置${NC}"
+ echo -e " ${YELLOW}Ollama 在本地或局域网运行大模型,无需 API Key${NC}"
+ echo -e " ${YELLOW}安装 Ollama: https://ollama.com${NC}"
+ echo ""
+ echo -e " ${CYAN}连接方式:${NC}"
+ echo -e " ${CYAN}a)${NC} 本机运行 (localhost:11434)"
+ echo -e " ${CYAN}b)${NC} 局域网其他设备"
+ echo ""
+ prompt_with_default "请选择" "a" ollama_mode
+ local ollama_url=""
+ case "$ollama_mode" in
+ b)
+ prompt_with_default "Ollama 地址 (如 192.168.1.100:11434)" "" ollama_host
+ if [ -n "$ollama_host" ]; then
+ # 补全协议前缀
+ case "$ollama_host" in
+ http://*|https://*) ollama_url="${ollama_host}" ;;
+ *) ollama_url="http://${ollama_host}" ;;
+ esac
+ # v2026.3.2: Ollama 使用原生 API,baseUrl 不带 /v1
+ ollama_url=$(echo "$ollama_url" | sed 's|/v1/*$||;s|/*$||')
+ fi
+ ;;
+ *)
+ ollama_url="http://127.0.0.1:11434"
+ ;;
+ esac
+ if [ -n "$ollama_url" ]; then
+ # 检测 Ollama 是否可达
+ echo ""
+ echo -e " ${CYAN}检测 Ollama 连通性...${NC}"
+ local ollama_base=$(echo "$ollama_url" | sed 's|/v1$||')
+ local ollama_check=$(curl -sf --connect-timeout 3 --max-time 5 "${ollama_base}/api/tags" 2>/dev/null || echo "")
+ if [ -n "$ollama_check" ]; then
+ echo -e " ${GREEN}✅ Ollama 已连接${NC}"
+ # 列出已安装的模型
+ local model_list=$("$NODE_BIN" -e "
+ try{
+ const d=JSON.parse(process.argv[1]);
+ (d.models||[]).forEach((m,i)=>console.log(' '+(i+1)+') '+m.name));
+ }catch(e){}
+ " "$ollama_check" 2>/dev/null)
+ if [ -n "$model_list" ]; then
+ echo -e " ${CYAN}已安装的模型:${NC}"
+ echo "$model_list"
+ echo -e " ${CYAN}m)${NC} 手动输入模型名"
+ echo ""
+ prompt_with_default "请选择模型" "1" ollama_sel
+ if [ "$ollama_sel" = "m" ]; then
+ prompt_with_default "请输入模型名称" "llama3.3" model_name
+ elif echo "$ollama_sel" | grep -qE '^[0-9]+$'; then
+ model_name=$("$NODE_BIN" -e "
+ try{
+ const d=JSON.parse(process.argv[1]);
+ const m=(d.models||[])[parseInt(process.argv[2])-1];
+ console.log(m?m.name:'');
+ }catch(e){console.log('');}
+ " "$ollama_check" "$ollama_sel" 2>/dev/null)
+ if [ -z "$model_name" ]; then
+ echo -e " ${YELLOW}无效选择,使用默认模型${NC}"
+ model_name="llama3.3"
+ fi
+ fi
+ else
+ echo -e " ${YELLOW}未检测到已安装模型,请先在 Ollama 中拉取模型:${NC}"
+ echo -e " ${CYAN} ollama pull llama3.3${NC}"
+ echo ""
+ prompt_with_default "请输入模型名称" "llama3.3" model_name
+ fi
+ else
+ echo -e " ${YELLOW}⚠️ 无法连接 Ollama (${ollama_base})${NC}"
+ echo -e " ${YELLOW} 请确认 Ollama 已启动并可访问${NC}"
+ echo -e " ${CYAN} 提示: 如果 Ollama 在其他设备上,需设置 OLLAMA_HOST=0.0.0.0${NC}"
+ echo ""
+ prompt_with_default "仍要继续配置? (y/n)" "n" force_continue
+ if ! confirm_yes "$force_continue"; then
+ return
+ fi
+ prompt_with_default "请输入模型名称" "llama3.3" model_name
+ fi
+ if [ -n "$model_name" ]; then
+ # Ollama 无需 API Key,使用占位符
+ auth_set_apikey ollama "ollama-local" "ollama:local"
+ # v2026.3.2: Ollama 使用原生 ollama API,不再走 OpenAI 兼容层
+ _RCP_PROV="ollama" _RCP_URL="$ollama_url" _RCP_KEY="ollama-local" _RCP_MID="$model_name" _RCP_MNAME="$model_name" "$NODE_BIN" -e "
+ const fs=require('fs');
+ let d={};
+ try{d=JSON.parse(fs.readFileSync('${CONFIG_FILE}','utf8'));}catch(e){}
+ if(!d.models)d.models={};
+ if(!d.models.providers)d.models.providers={};
+ d.models.mode='merge';
+ d.models.providers['ollama']={
+ baseUrl:process.env._RCP_URL,
+ apiKey:process.env._RCP_KEY,
+ api:'ollama',
+ models:[{
+ id:process.env._RCP_MID,
+ name:process.env._RCP_MNAME,
+ reasoning:false,
+ input:['text'],
+ cost:{input:0,output:0,cacheRead:0,cacheWrite:0},
+ contextWindow:128000,
+ maxTokens:32000
+ }]
+ };
+ fs.writeFileSync('${CONFIG_FILE}',JSON.stringify(d,null,2));
+ " 2>/dev/null
+ chown openclaw:openclaw "$CONFIG_FILE" 2>/dev/null || true
+ register_and_set_model "ollama/${model_name}"
+ echo -e " ${GREEN}✅ Ollama 已配置,活跃模型: ollama/${model_name}${NC}"
+ echo -e " ${CYAN} Ollama 地址: ${ollama_url}${NC}"
+ fi
+ fi
+ ;;
+ 13)
echo ""
echo -e " ${BOLD}自定义 OpenAI 兼容 API${NC}"
echo -e " ${YELLOW}支持任何兼容 OpenAI API 格式的服务商${NC}"
@@ -1108,6 +1217,34 @@ health_check() {
local gw_port=$(json_get gateway.port)
gw_port=${gw_port:-18789}
+ # ── v2026.3.2: 使用官方 config validate 验证配置 ──
+ if command -v openclaw >/dev/null 2>&1 || [ -n "$OC_ENTRY" ]; then
+ echo -e " ${CYAN}验证配置文件格式...${NC}"
+ local validate_out=""
+ validate_out=$(oc_cmd config validate --json 2>/dev/null) || true
+ if [ -n "$validate_out" ]; then
+ local has_errors=$("$NODE_BIN" -e "
+ try{const r=JSON.parse(process.argv[1]);
+ if(r.valid===true){console.log('OK');}
+ else if(r.errors&&r.errors.length>0){r.errors.forEach(e=>console.log('ERR:'+e.message));}
+ else{console.log('OK');}}catch(e){console.log('SKIP');}
+ " "$validate_out" 2>/dev/null)
+ if [ "$has_errors" = "OK" ]; then
+ echo -e " ${GREEN}✅ 配置文件格式有效${NC}"
+ elif [ "$has_errors" = "SKIP" ]; then
+ echo -e " ${YELLOW}⚠️ 无法解析验证结果,跳过${NC}"
+ else
+ echo -e " ${RED}❌ 配置文件存在错误:${NC}"
+ echo "$has_errors" | while IFS= read -r line; do
+ echo -e " ${YELLOW}• ${line#ERR:}${NC}"
+ done
+ fi
+ else
+ echo -e " ${YELLOW}⚠️ config validate 不可用,跳过格式验证${NC}"
+ fi
+ echo ""
+ fi
+
# ── 自动修复: 移除旧版错误写入的顶层 models.xxx 无效键 ──
if [ -f "$CONFIG_FILE" ]; then
local has_bad_models=$("$NODE_BIN" -e "
@@ -1152,6 +1289,19 @@ health_check() {
echo -e " ${RED}❌ HTTP 响应异常 (${http_code})${NC}"
fi
+ # v2026.3.2: 使用 gateway health --json 做深度健康检查 (HTTP /health 已被 SPA 接管)
+ local health_resp=$(oc_cmd gateway health --json 2>/dev/null)
+ if [ -n "$health_resp" ]; then
+ local 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)
+ if [ "$health_ok" = "ok" ]; then
+ echo -e " ${GREEN}✅ Gateway 健康检查正常${NC}"
+ elif [ "$health_ok" = "parse_error" ]; then
+ echo -e " ${YELLOW}⚠️ Gateway 健康检查响应无法解析${NC}"
+ else
+ echo -e " ${YELLOW}⚠️ Gateway 健康检查异常${NC}"
+ fi
+ fi
+
if [ -f "$CONFIG_FILE" ]; then
echo -e " ${GREEN}✅ 配置文件存在${NC}"
else
@@ -1296,9 +1446,9 @@ reset_to_defaults() {
local _node_bin
_node_bin=$(which node 2>/dev/null || echo "$NODE_BIN")
if command -v timeout >/dev/null 2>&1; then
- timeout 10 sh -c "\"$_node_bin\" \"$OC_ENTRY\" onboard --non-interactive --accept-risk" >/dev/null 2>&1 || true
+ timeout 10 sh -c "\"$_node_bin\" \"$OC_ENTRY\" onboard --non-interactive --accept-risk --tools-profile coding" >/dev/null 2>&1 || true
else
- "$_node_bin" "$OC_ENTRY" onboard --non-interactive --accept-risk >/dev/null 2>&1 &
+ "$_node_bin" "$OC_ENTRY" onboard --non-interactive --accept-risk --tools-profile coding >/dev/null 2>&1 &
local _ob_pid=$!
sleep 10
kill "$_ob_pid" 2>/dev/null || true
@@ -1318,6 +1468,8 @@ reset_to_defaults() {
json_set gateway.controlUi.dangerouslyDisableDeviceAuth true
json_set gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback true
json_set gateway.tailscale.mode off
+ json_set acp.dispatch.enabled false
+ json_set tools.profile coding
# 同步 token 到 UCI
. /lib/functions.sh 2>/dev/null || true
diff --git a/scripts/build_ipk.sh b/scripts/build_ipk.sh
index b6042a6..db5909c 100755
--- a/scripts/build_ipk.sh
+++ b/scripts/build_ipk.sh
@@ -60,6 +60,7 @@ cp "$PKG_DIR/luasrc/view/openclaw/"*.htm "$DATA_DIR/usr/lib/lua/luci/view/opencl
# oc-config assets
mkdir -p "$DATA_DIR/usr/share/openclaw"
+cp "$PKG_DIR/VERSION" "$DATA_DIR/usr/share/openclaw/VERSION"
cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$DATA_DIR/usr/share/openclaw/"
chmod +x "$DATA_DIR/usr/share/openclaw/oc-config.sh"
cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$DATA_DIR/usr/share/openclaw/"
diff --git a/scripts/build_run.sh b/scripts/build_run.sh
index 079f86f..4115f61 100755
--- a/scripts/build_run.sh
+++ b/scripts/build_run.sh
@@ -62,6 +62,7 @@ install_files() {
# oc-config assets
mkdir -p "$dest/usr/share/openclaw"
+ cp "$PKG_DIR/VERSION" "$dest/usr/share/openclaw/VERSION"
cp "$PKG_DIR/root/usr/share/openclaw/oc-config.sh" "$dest/usr/share/openclaw/"
chmod +x "$dest/usr/share/openclaw/oc-config.sh"
cp "$PKG_DIR/root/usr/share/openclaw/web-pty.js" "$dest/usr/share/openclaw/"