diff --git a/CLAUDE.md b/CLAUDE.md index bb51e3e..4ef48aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -87,7 +87,7 @@ Both portable and desktop versions auto-find a free port in range 18789–18799 - **China mirrors**: All downloads use `npmmirror.com` — Node.js binaries from `npmmirror.com/mirrors/node`, npm packages from `registry.npmmirror.com` - **Environment variables**: `OPENCLAW_HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH` control where OpenClaw reads config - **macOS quarantine**: Mac scripts run `xattr -rd com.apple.quarantine` to remove Gatekeeper blocks -- **Config format**: `{"gateway":{"mode":"local","auth":{"token":"uclaw"}},"agent":{"model":"...","apiKey":"..."}}` +- **Config format**: `{"gateway":{"mode":"local","auth":{"token":"uclaw"}},"models":{"mode":"merge","providers":{"xxx":{...}}},"agents":{"defaults":{"model":{"primary":"provider/model"}}}}` - **Config hot-reload**: OpenClaw watches `openclaw.json` and applies changes without restart ## What NOT to Commit diff --git a/install/install.ps1 b/install/install.ps1 index bfc9f3e..fb07cee 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -520,31 +520,46 @@ if ($hasConfig) { # Write config if ($cfg.provider -eq "custom" -and $cfg.baseUrl) { + $providerName = "custom" $configJson = @" { "gateway": { "mode": "local", "auth": { "token": "uclaw" } }, - "agent": { - "model": "$($cfg.model)", - "apiKey": "$apiKey", - "baseUrl": "$($cfg.baseUrl)" - } + "models": { + "mode": "merge", + "providers": { + "$providerName": { + "baseUrl": "$($cfg.baseUrl)", + "apiKey": "$apiKey", + "api": "openai-completions", + "models": [{ "id": "$($cfg.model)" }] + } + } + }, + "agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } } } "@ } else { + $providerName = $cfg.provider $configJson = @" { "gateway": { "mode": "local", "auth": { "token": "uclaw" } }, - "agent": { - "model": "$($cfg.model)", - "apiKey": "$apiKey", - "provider": "$($cfg.provider)" - } + "models": { + "mode": "merge", + "providers": { + "$providerName": { + "apiKey": "$apiKey", + "api": "$(if ($providerName -eq 'anthropic') {'anthropic'} else {'openai-completions'})", + "models": [{ "id": "$($cfg.model)" }] + } + } + }, + "agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } } } "@ } diff --git a/install/install.sh b/install/install.sh index 264a80c..7e250d6 100755 --- a/install/install.sh +++ b/install/install.sh @@ -887,11 +887,18 @@ else "mode": "local", "auth": { "token": "uclaw" } }, - "agent": { - "model": "$MODEL_NAME", - "apiKey": "$API_KEY", - "baseUrl": "$BASE_URL" - } + "models": { + "mode": "merge", + "providers": { + "custom": { + "baseUrl": "$BASE_URL", + "apiKey": "$API_KEY", + "api": "openai-completions", + "models": [{ "id": "$MODEL_NAME" }] + } + } + }, + "agents": { "defaults": { "model": { "primary": "custom/$MODEL_NAME" } } } } CFGEOF elif [ "$PROVIDER" = "anthropic" ]; then @@ -901,11 +908,17 @@ CFGEOF "mode": "local", "auth": { "token": "uclaw" } }, - "agent": { - "model": "$MODEL_NAME", - "apiKey": "$API_KEY", - "provider": "anthropic" - } + "models": { + "mode": "merge", + "providers": { + "anthropic": { + "apiKey": "$API_KEY", + "api": "anthropic", + "models": [{ "id": "$MODEL_NAME" }] + } + } + }, + "agents": { "defaults": { "model": { "primary": "anthropic/$MODEL_NAME" } } } } CFGEOF elif [ "$PROVIDER" = "openai" ]; then @@ -915,11 +928,17 @@ CFGEOF "mode": "local", "auth": { "token": "uclaw" } }, - "agent": { - "model": "$MODEL_NAME", - "apiKey": "$API_KEY", - "provider": "openai" - } + "models": { + "mode": "merge", + "providers": { + "openai": { + "apiKey": "$API_KEY", + "api": "openai-completions", + "models": [{ "id": "$MODEL_NAME" }] + } + } + }, + "agents": { "defaults": { "model": { "primary": "openai/$MODEL_NAME" } } } } CFGEOF fi diff --git a/portable/Config.html b/portable/Config.html index ebd872d..b570e91 100644 --- a/portable/Config.html +++ b/portable/Config.html @@ -603,6 +603,8 @@ async function launchOpenClaw() { try { const res = await fetch('/api/config'); const existing = res.ok ? await res.json() : {}; + // 清除旧版废弃键,防止 OpenClaw 报 "agent.* was moved" 错误 + delete existing.agent; existing.channels = Object.assign(existing.channels || {}, channels); await fetch('/api/config', { method: 'POST', diff --git a/portable/U-Claw.html b/portable/U-Claw.html index 1c40dbc..0a6440a 100644 --- a/portable/U-Claw.html +++ b/portable/U-Claw.html @@ -172,21 +172,38 @@ function switchOS(os) { document.querySelectorAll('.os-tab')[os === 'mac' ? 0 : 1].classList.add('active'); } -// Check if gateway is running -function checkStatus() { - fetch('http://127.0.0.1:18789/', { mode: 'no-cors', cache: 'no-cache' }) - .then(() => { - document.getElementById('statusDot').className = 'status-dot on'; - document.getElementById('statusText').textContent = 'OpenClaw 运行中'; - const link = document.getElementById('statusLink'); - link.style.display = 'inline'; - link.href = 'http://127.0.0.1:18789/#token=uclaw'; - }) - .catch(() => { - document.getElementById('statusDot').className = 'status-dot off'; - document.getElementById('statusText').textContent = 'OpenClaw 未运行 — 请先双击启动脚本'; - document.getElementById('statusLink').style.display = 'none'; - }); +// Check if gateway is running (scan 18789-18799) +var _gwPort = null; +async function checkStatus() { + var found = null; + if (_gwPort) { + // Fast path: check last known port first + try { + await fetch('http://127.0.0.1:' + _gwPort + '/', { mode: 'no-cors', cache: 'no-cache' }); + found = _gwPort; + } catch(e) { _gwPort = null; } + } + if (!found) { + for (var p = 18789; p <= 18799; p++) { + try { + await fetch('http://127.0.0.1:' + p + '/', { mode: 'no-cors', cache: 'no-cache' }); + found = p; + _gwPort = p; + break; + } catch(e) {} + } + } + if (found) { + document.getElementById('statusDot').className = 'status-dot on'; + document.getElementById('statusText').textContent = 'OpenClaw 运行中 (端口 ' + found + ')'; + var link = document.getElementById('statusLink'); + link.style.display = 'inline'; + link.href = 'http://127.0.0.1:' + found + '/#token=uclaw'; + } else { + document.getElementById('statusDot').className = 'status-dot off'; + document.getElementById('statusText').textContent = 'OpenClaw 未运行 — 请先双击启动脚本'; + document.getElementById('statusLink').style.display = 'none'; + } } checkStatus(); setInterval(checkStatus, 5000); diff --git a/portable/config-server/public/index.html b/portable/config-server/public/index.html index 508aa2f..3e55e06 100644 --- a/portable/config-server/public/index.html +++ b/portable/config-server/public/index.html @@ -642,6 +642,8 @@ async function saveChannelsAndOpen() { try { var res = await fetch('/api/config'); var existing = res.ok ? await res.json() : {}; + // 清除旧版废弃键,防止 OpenClaw 报 "agent.* was moved" 错误 + delete existing.agent; existing.channels = Object.assign(existing.channels || {}, channels); await fetch('/api/config', { method: 'POST', @@ -652,7 +654,7 @@ async function saveChannelsAndOpen() { } catch(e) {} } - window.open('http://127.0.0.1:18789/#token=uclaw', '_blank'); + window.open(gatewayUrl(), '_blank'); } // --- View config --- @@ -677,15 +679,35 @@ function showToast(msg, isError) { setTimeout(function() { t.className = 'toast'; }, 3000); } +// --- Find active gateway port (18789-18799) --- +var _gatewayPort = null; +async function findGatewayPort() { + for (var p = 18789; p <= 18799; p++) { + try { + await fetch('http://127.0.0.1:' + p + '/', { mode: 'no-cors' }); + _gatewayPort = p; + return p; + } catch(e) {} + } + _gatewayPort = null; + return null; +} + +function gatewayUrl() { + var p = _gatewayPort || 18789; + return 'http://127.0.0.1:' + p + '/#token=uclaw'; +} + // --- Check gateway status --- async function checkGateway() { var dot = document.getElementById('statusDot'); var text = document.getElementById('statusText'); - try { - await fetch('http://127.0.0.1:18789/', { mode: 'no-cors' }); + var port = await findGatewayPort(); + if (port) { dot.className = 'dot on'; - text.innerHTML = 'Gateway 运行中 · 打开 Dashboard →'; - } catch(e) { + text.innerHTML = 'Gateway 运行中 (端口 ' + port + ') · 打开 Dashboard →'; + document.getElementById('gatewayUrl').textContent = 'http://127.0.0.1:' + port; + } else { dot.className = 'dot off'; text.innerHTML = 'Gateway 未启动 · 请先运行 Windows-Start.bat 或 Mac-Start.command'; } diff --git a/portable/config-server/server.js b/portable/config-server/server.js index 2383874..d892d5a 100644 --- a/portable/config-server/server.js +++ b/portable/config-server/server.js @@ -398,6 +398,8 @@ const server = http.createServer((req, res) => { req.on('end', () => { try { const config = JSON.parse(body); + // 清除旧版废弃键,防止 OpenClaw 报 "agent.* was moved" 错误 + delete config.agent; const dir = path.dirname(CONFIG_PATH); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2)); diff --git a/u-claw-app/resources/Config.html b/u-claw-app/resources/Config.html index ebd872d..b570e91 100644 --- a/u-claw-app/resources/Config.html +++ b/u-claw-app/resources/Config.html @@ -603,6 +603,8 @@ async function launchOpenClaw() { try { const res = await fetch('/api/config'); const existing = res.ok ? await res.json() : {}; + // 清除旧版废弃键,防止 OpenClaw 报 "agent.* was moved" 错误 + delete existing.agent; existing.channels = Object.assign(existing.channels || {}, channels); await fetch('/api/config', { method: 'POST', diff --git a/usb-release/Config.html b/usb-release/Config.html index ebd872d..b570e91 100644 --- a/usb-release/Config.html +++ b/usb-release/Config.html @@ -603,6 +603,8 @@ async function launchOpenClaw() { try { const res = await fetch('/api/config'); const existing = res.ok ? await res.json() : {}; + // 清除旧版废弃键,防止 OpenClaw 报 "agent.* was moved" 错误 + delete existing.agent; existing.channels = Object.assign(existing.channels || {}, channels); await fetch('/api/config', { method: 'POST', diff --git a/website/cloud.html b/website/cloud.html index bb8d607..a8b30d3 100644 --- a/website/cloud.html +++ b/website/cloud.html @@ -483,11 +483,13 @@
// 虾盘 U 盘已预装此配置,注册后填入 apiKey 即用 { - "agent": { - "model": "deepseek-chat", - "apiKey": "sk-xp-your-key-here", - "baseUrl": "https://api.u-claw.org/v1" - } + "models": { "mode": "merge", "providers": { + "uclaw": { + "baseUrl": "https://api.u-claw.org/v1", + "apiKey": "sk-xp-your-key-here" + } + }}, + "agents": { "defaults": { "model": { "primary": "uclaw/deepseek-chat" }}} }@@ -594,11 +596,13 @@
// 出厂预装,插上 U 盘即生效 { - "agent": { - "model": "deepseek-chat", - "apiKey": "注册后填入你的 Key", - "baseUrl": "https://api.u-claw.org/v1" - } + "models": { "mode": "merge", "providers": { + "uclaw": { + "baseUrl": "https://api.u-claw.org/v1", + "apiKey": "注册后填入你的 Key" + } + }}, + "agents": { "defaults": { "model": { "primary": "uclaw/deepseek-chat" }}} }diff --git a/website/guide.html b/website/guide.html index 2c85fec..67910a9 100644 --- a/website/guide.html +++ b/website/guide.html @@ -592,11 +592,15 @@ cp -R portable/ /Volumes/你的U盘/U-Claw/
{
"gateway": {"mode": "local", "auth": {"token": "uclaw"}},
- "agent": {
- "model": "deepseek-chat",
- "apiKey": "sk-xxx",
- "baseUrl": "https://api.deepseek.com/v1"
- },
+ "models": {"mode": "merge", "providers": {
+ "deepseek": {
+ "baseUrl": "https://api.deepseek.com/v1",
+ "apiKey": "sk-xxx",
+ "api": "openai-completions",
+ "models": [{"id": "deepseek-chat"}]
+ }
+ }},
+ "agents": {"defaults": {"model": {"primary": "deepseek/deepseek-chat"}}},
"channels": {"qqbot": {"token": "appid:secret"}}
}
diff --git a/website/install-silent.ps1 b/website/install-silent.ps1
index e4bb918..7e69774 100644
--- a/website/install-silent.ps1
+++ b/website/install-silent.ps1
@@ -198,31 +198,46 @@ if ($SkipConfig) {
}
if ($resolvedProvider -eq "custom" -and $resolvedBaseUrl) {
+ $pName = "custom"
$configJson = @"
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$resolvedModel",
- "apiKey": "$ApiKey",
- "baseUrl": "$resolvedBaseUrl"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "$pName": {
+ "baseUrl": "$resolvedBaseUrl",
+ "apiKey": "$ApiKey",
+ "api": "openai-completions",
+ "models": [{ "id": "$resolvedModel" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "$pName/$resolvedModel" } } }
}
"@
} else {
+ $pName = $resolvedProvider
$configJson = @"
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$resolvedModel",
- "apiKey": "$ApiKey",
- "provider": "$resolvedProvider"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "$pName": {
+ "apiKey": "$ApiKey",
+ "api": "$(if ($pName -eq 'anthropic') {'anthropic'} else {'openai-completions'})",
+ "models": [{ "id": "$resolvedModel" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "$pName/$resolvedModel" } } }
}
"@
}
diff --git a/website/install.ps1 b/website/install.ps1
index bfc9f3e..fb07cee 100644
--- a/website/install.ps1
+++ b/website/install.ps1
@@ -520,31 +520,46 @@ if ($hasConfig) {
# Write config
if ($cfg.provider -eq "custom" -and $cfg.baseUrl) {
+ $providerName = "custom"
$configJson = @"
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$($cfg.model)",
- "apiKey": "$apiKey",
- "baseUrl": "$($cfg.baseUrl)"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "$providerName": {
+ "baseUrl": "$($cfg.baseUrl)",
+ "apiKey": "$apiKey",
+ "api": "openai-completions",
+ "models": [{ "id": "$($cfg.model)" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } }
}
"@
} else {
+ $providerName = $cfg.provider
$configJson = @"
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$($cfg.model)",
- "apiKey": "$apiKey",
- "provider": "$($cfg.provider)"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "$providerName": {
+ "apiKey": "$apiKey",
+ "api": "$(if ($providerName -eq 'anthropic') {'anthropic'} else {'openai-completions'})",
+ "models": [{ "id": "$($cfg.model)" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "$providerName/$($cfg.model)" } } }
}
"@
}
diff --git a/website/install.sh b/website/install.sh
index 264a80c..7e250d6 100755
--- a/website/install.sh
+++ b/website/install.sh
@@ -887,11 +887,18 @@ else
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$MODEL_NAME",
- "apiKey": "$API_KEY",
- "baseUrl": "$BASE_URL"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "custom": {
+ "baseUrl": "$BASE_URL",
+ "apiKey": "$API_KEY",
+ "api": "openai-completions",
+ "models": [{ "id": "$MODEL_NAME" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "custom/$MODEL_NAME" } } }
}
CFGEOF
elif [ "$PROVIDER" = "anthropic" ]; then
@@ -901,11 +908,17 @@ CFGEOF
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$MODEL_NAME",
- "apiKey": "$API_KEY",
- "provider": "anthropic"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "anthropic": {
+ "apiKey": "$API_KEY",
+ "api": "anthropic",
+ "models": [{ "id": "$MODEL_NAME" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "anthropic/$MODEL_NAME" } } }
}
CFGEOF
elif [ "$PROVIDER" = "openai" ]; then
@@ -915,11 +928,17 @@ CFGEOF
"mode": "local",
"auth": { "token": "uclaw" }
},
- "agent": {
- "model": "$MODEL_NAME",
- "apiKey": "$API_KEY",
- "provider": "openai"
- }
+ "models": {
+ "mode": "merge",
+ "providers": {
+ "openai": {
+ "apiKey": "$API_KEY",
+ "api": "openai-completions",
+ "models": [{ "id": "$MODEL_NAME" }]
+ }
+ }
+ },
+ "agents": { "defaults": { "model": { "primary": "openai/$MODEL_NAME" } } }
}
CFGEOF
fi