Spaces:
Running
fix: IPv4 forcing, proxy fallback, browser symlink noise, model injection, WA stall recovery
Browse files- start.sh: force IPv4 globally for all outbound connections on HF Spaces
(fixes WhatsApp status 428 and Telegram fetch failures caused by IPv6)
- start.sh: clean up stale browser-automation plugin-skills symlinks on
startup to suppress repeated 'not a generated symlink' log noise
- start.sh: fix Telegram config patch overwriting stale env-driven fields
on reboot; now always re-applies all env-driven fields
- start.sh: fix nvidia double-prefix in model catalog, vertex if-guard,
vertex pool-key; fix model injection for multi-key pools, custom
provider baseUrl, and Vertex AI
- cloudflare-proxy.js: replace logProxyError (log-only) with
proxyWithFallback β on hard network failure (ETIMEDOUT/ECONNRESET)
automatically retries the request direct, bypassing the proxy; fixes
'Proxy FAILED raw.githubusercontent.com: ETIMEDOUT' and ensures update
checks / plugin installs always succeed
- env-builder.js: related env-builder improvements shipped alongside above
- cloudflare-proxy.js +29 -20
- env-builder.js +47 -0
- start.sh +184 -42
|
@@ -204,24 +204,31 @@ if (PROXY_URL) {
|
|
| 204 |
|
| 205 |
const proxiedUrl = new URL(url.pathname + url.search, proxy);
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
});
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
};
|
| 226 |
|
| 227 |
if (request) {
|
|
@@ -234,8 +241,9 @@ if (PROXY_URL) {
|
|
| 234 |
fetchOpts.body = request.body;
|
| 235 |
fetchOpts.duplex = request.duplex || "half";
|
| 236 |
}
|
| 237 |
-
return
|
| 238 |
originalFetch(String(proxiedUrl), fetchOpts),
|
|
|
|
| 239 |
`request-mode method=${request.method} hasBody=${!!request.body}`,
|
| 240 |
);
|
| 241 |
}
|
|
@@ -270,8 +278,9 @@ if (PROXY_URL) {
|
|
| 270 |
? "ReadableStream"
|
| 271 |
: (init.body?.constructor?.name || typeof init.body);
|
| 272 |
|
| 273 |
-
return
|
| 274 |
originalFetch(String(proxiedUrl), newInit),
|
|
|
|
| 275 |
`init-mode method=${newInit.method} body=${bodyType} initKeys=${Object.keys(init || {}).join(",")}`,
|
| 276 |
);
|
| 277 |
};
|
|
|
|
| 204 |
|
| 205 |
const proxiedUrl = new URL(url.pathname + url.search, proxy);
|
| 206 |
|
| 207 |
+
// proxyWithFallback: try via Cloudflare Worker first; if the proxy
|
| 208 |
+
// itself hard-fails (ETIMEDOUT / ECONNRESET / network error), fall
|
| 209 |
+
// back to a direct connection so callers still get a response.
|
| 210 |
+
// HTTP-level errors from the Worker (4xx/5xx) are NOT retried β
|
| 211 |
+
// only hard network failures (rejected promise) trigger the fallback.
|
| 212 |
+
const proxyWithFallback = (proxyPromise, directFallbackFn, debugInfo) => {
|
| 213 |
+
return proxyPromise.then(r => {
|
| 214 |
+
if (DEBUG && !r.ok) {
|
| 215 |
+
log(`[cloudflare-proxy] Proxy HTTP ${r.status} for ${hostname}: ${r.statusText}`);
|
| 216 |
+
}
|
| 217 |
+
return r;
|
| 218 |
+
}).catch(err => {
|
| 219 |
+
const cause = err?.cause;
|
| 220 |
+
const causeStr = cause
|
| 221 |
+
? ` | cause: ${cause?.code || cause?.message || String(cause)}`
|
| 222 |
+
: "";
|
| 223 |
+
log(`[cloudflare-proxy] Proxy FAILED ${hostname}: ${err?.message}${causeStr} β retrying direct`);
|
| 224 |
+
if (DEBUG && debugInfo) {
|
| 225 |
+
log(`[cloudflare-proxy] Debug: ${debugInfo}`);
|
| 226 |
+
}
|
| 227 |
+
// Direct fallback: bypasses proxy for this request so infrastructure
|
| 228 |
+
// calls (update checks, plugin installs, etc.) still succeed even
|
| 229 |
+
// when the Worker cannot reach the destination.
|
| 230 |
+
return directFallbackFn();
|
| 231 |
+
});
|
| 232 |
};
|
| 233 |
|
| 234 |
if (request) {
|
|
|
|
| 241 |
fetchOpts.body = request.body;
|
| 242 |
fetchOpts.duplex = request.duplex || "half";
|
| 243 |
}
|
| 244 |
+
return proxyWithFallback(
|
| 245 |
originalFetch(String(proxiedUrl), fetchOpts),
|
| 246 |
+
() => originalFetch(input, init),
|
| 247 |
`request-mode method=${request.method} hasBody=${!!request.body}`,
|
| 248 |
);
|
| 249 |
}
|
|
|
|
| 278 |
? "ReadableStream"
|
| 279 |
: (init.body?.constructor?.name || typeof init.body);
|
| 280 |
|
| 281 |
+
return proxyWithFallback(
|
| 282 |
originalFetch(String(proxiedUrl), newInit),
|
| 283 |
+
() => originalFetch(input, init),
|
| 284 |
`init-mode method=${newInit.method} body=${bodyType} initKeys=${Object.keys(init || {}).join(",")}`,
|
| 285 |
);
|
| 286 |
};
|
|
@@ -251,6 +251,13 @@ const MODEL_CATALOGS = {
|
|
| 251 |
"google/gemini-2.5-flash",
|
| 252 |
"google/gemini-2.0-flash"
|
| 253 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
"DEEPSEEK_MODELS": [
|
| 255 |
"deepseek/deepseek-v4-pro",
|
| 256 |
"deepseek/deepseek-v4-flash",
|
|
@@ -1108,6 +1115,36 @@ const FIELDS = [
|
|
| 1108 |
"common": 0,
|
| 1109 |
"tag": "credential"
|
| 1110 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1111 |
{
|
| 1112 |
"g": "Provider Keys",
|
| 1113 |
"icon": "π",
|
|
@@ -1530,6 +1567,16 @@ const FIELDS = [
|
|
| 1530 |
"ph": "Select models to build a comma list",
|
| 1531 |
"tag": "optional"
|
| 1532 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
{
|
| 1534 |
"g": "Model Lists",
|
| 1535 |
"icon": "π",
|
|
|
|
| 251 |
"google/gemini-2.5-flash",
|
| 252 |
"google/gemini-2.0-flash"
|
| 253 |
],
|
| 254 |
+
"VERTEX_MODELS": [
|
| 255 |
+
"google-vertex/gemini-2.5-pro",
|
| 256 |
+
"google-vertex/gemini-2.5-flash",
|
| 257 |
+
"google-vertex/gemini-2.0-flash",
|
| 258 |
+
"google-vertex/gemini-1.5-pro",
|
| 259 |
+
"google-vertex/gemini-1.5-flash"
|
| 260 |
+
],
|
| 261 |
"DEEPSEEK_MODELS": [
|
| 262 |
"deepseek/deepseek-v4-pro",
|
| 263 |
"deepseek/deepseek-v4-flash",
|
|
|
|
| 1115 |
"common": 0,
|
| 1116 |
"tag": "credential"
|
| 1117 |
},
|
| 1118 |
+
{
|
| 1119 |
+
"g": "Provider Keys",
|
| 1120 |
+
"icon": "π",
|
| 1121 |
+
"k": "GOOGLE_CLOUD_PROJECT",
|
| 1122 |
+
"lbl": "Google Vertex AI β GCP Project ID",
|
| 1123 |
+
"type": "text",
|
| 1124 |
+
"ph": "my-gcp-project-id",
|
| 1125 |
+
"common": 0,
|
| 1126 |
+
"tag": "credential"
|
| 1127 |
+
},
|
| 1128 |
+
{
|
| 1129 |
+
"g": "Provider Keys",
|
| 1130 |
+
"icon": "π",
|
| 1131 |
+
"k": "GOOGLE_CLOUD_LOCATION",
|
| 1132 |
+
"lbl": "Google Vertex AI β GCP Region",
|
| 1133 |
+
"type": "text",
|
| 1134 |
+
"ph": "us-central1",
|
| 1135 |
+
"common": 0,
|
| 1136 |
+
"tag": "credential"
|
| 1137 |
+
},
|
| 1138 |
+
{
|
| 1139 |
+
"g": "Provider Keys",
|
| 1140 |
+
"icon": "π",
|
| 1141 |
+
"k": "GOOGLE_APPLICATION_CREDENTIALS_JSON",
|
| 1142 |
+
"lbl": "Google Vertex AI β Service Account JSON (base64)",
|
| 1143 |
+
"type": "password",
|
| 1144 |
+
"ph": "base64-encoded service account JSON",
|
| 1145 |
+
"common": 0,
|
| 1146 |
+
"tag": "credential"
|
| 1147 |
+
},
|
| 1148 |
{
|
| 1149 |
"g": "Provider Keys",
|
| 1150 |
"icon": "π",
|
|
|
|
| 1567 |
"ph": "Select models to build a comma list",
|
| 1568 |
"tag": "optional"
|
| 1569 |
},
|
| 1570 |
+
{
|
| 1571 |
+
"g": "Model Lists",
|
| 1572 |
+
"icon": "π",
|
| 1573 |
+
"k": "VERTEX_MODELS",
|
| 1574 |
+
"lbl": "Visible Vertex AI models (google-vertex/...)",
|
| 1575 |
+
"type": "model_list",
|
| 1576 |
+
"options_key": "VERTEX_MODELS",
|
| 1577 |
+
"ph": "Select Vertex models (needs GOOGLE_CLOUD_PROJECT + GOOGLE_CLOUD_LOCATION)",
|
| 1578 |
+
"tag": "optional"
|
| 1579 |
+
},
|
| 1580 |
{
|
| 1581 |
"g": "Model Lists",
|
| 1582 |
"icon": "π",
|
|
@@ -124,6 +124,15 @@ if [ -n "${SPACE_HOST:-}" ]; then
|
|
| 124 |
ACP_PLUGIN_MODE="${ACP_PLUGIN_MODE:-disabled}"
|
| 125 |
# HF Spaces does not benefit from Bonjour discovery, and the retries add noise.
|
| 126 |
export OPENCLAW_DISABLE_BONJOUR="${OPENCLAW_DISABLE_BONJOUR:-1}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
else
|
| 128 |
OPENCLAW_CONSOLE_LOG_LEVEL="${OPENCLAW_CONSOLE_LOG_LEVEL:-info}"
|
| 129 |
OPENCLAW_FILE_LOG_LEVEL="${OPENCLAW_FILE_LOG_LEVEL:-info}"
|
|
@@ -454,20 +463,54 @@ fi
|
|
| 454 |
# NVIDIA_MODELS=model1,model2
|
| 455 |
# OPENAI_MODELS=gpt-4o-mini,gpt-4.1
|
| 456 |
# This helps when provider auto-discovery does not populate models reliably.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 457 |
INJECTED_MODELS_PROVIDERS='{}'
|
| 458 |
inject_provider_models_from_env() {
|
| 459 |
local provider="$1"
|
| 460 |
local models_env="$2"
|
| 461 |
local key_env_single="$3"
|
| 462 |
local key_env_pool="$4"
|
|
|
|
| 463 |
local models_csv="${!models_env:-}"
|
| 464 |
local single_key="${!key_env_single:-}"
|
| 465 |
local pool_keys="${!key_env_pool:-}"
|
| 466 |
|
| 467 |
-
#
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
return 0
|
| 472 |
fi
|
| 473 |
|
|
@@ -478,7 +521,18 @@ inject_provider_models_from_env() {
|
|
| 478 |
| awk 'NF' \
|
| 479 |
| jq -R . \
|
| 480 |
| jq -s --arg provider "$provider" '
|
| 481 |
-
map(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
| map({id: ., name: .})
|
| 483 |
| unique_by(.id)')
|
| 484 |
|
|
@@ -494,45 +548,64 @@ inject_provider_models_from_env() {
|
|
| 494 |
'.[$provider] = ((.[$provider] // {}) + {models: $models})' <<<"$INJECTED_MODELS_PROVIDERS")
|
| 495 |
}
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
# Built-in provider model envs (optional)
|
| 498 |
-
inject_provider_models_from_env "anthropic" "ANTHROPIC_MODELS" "ANTHROPIC_API_KEY" "ANTHROPIC_API_KEYS"
|
| 499 |
-
inject_provider_models_from_env "openai" "OPENAI_MODELS" "OPENAI_API_KEY" "OPENAI_API_KEYS"
|
| 500 |
-
inject_provider_models_from_env "openai-codex" "OPENAI_MODELS" "OPENAI_API_KEY" "OPENAI_API_KEYS"
|
| 501 |
-
inject_provider_models_from_env "google" "GEMINI_MODELS" "GEMINI_API_KEY" "GEMINI_API_KEYS"
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
inject_provider_models_from_env "
|
| 508 |
-
inject_provider_models_from_env "
|
| 509 |
-
inject_provider_models_from_env "
|
| 510 |
-
inject_provider_models_from_env "
|
| 511 |
-
inject_provider_models_from_env "
|
| 512 |
-
inject_provider_models_from_env "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
inject_provider_models_from_env "kimi-coding" "KIMI_MODELS" "KIMI_API_KEY" "KIMI_API_KEYS"
|
| 514 |
-
inject_provider_models_from_env "minimax" "MINIMAX_MODELS" "MINIMAX_API_KEY" "MINIMAX_API_KEYS"
|
| 515 |
-
inject_provider_models_from_env "modelstudio" "MODELSTUDIO_MODELS" "MODELSTUDIO_API_KEY" "MODELSTUDIO_API_KEYS"
|
| 516 |
-
inject_provider_models_from_env "qwen" "MODELSTUDIO_MODELS" "MODELSTUDIO_API_KEY" "MODELSTUDIO_API_KEYS"
|
| 517 |
inject_provider_models_from_env "xiaomi" "XIAOMI_MODELS" "XIAOMI_API_KEY" "XIAOMI_API_KEYS"
|
| 518 |
inject_provider_models_from_env "volcengine" "VOLCANO_ENGINE_MODELS" "VOLCANO_ENGINE_API_KEY" "VOLCANO_ENGINE_API_KEYS"
|
| 519 |
inject_provider_models_from_env "volcengine-plan" "VOLCANO_ENGINE_MODELS" "VOLCANO_ENGINE_API_KEY" "VOLCANO_ENGINE_API_KEYS"
|
| 520 |
inject_provider_models_from_env "byteplus" "BYTEPLUS_MODELS" "BYTEPLUS_API_KEY" "BYTEPLUS_API_KEYS"
|
| 521 |
inject_provider_models_from_env "byteplus-plan" "BYTEPLUS_MODELS" "BYTEPLUS_API_KEY" "BYTEPLUS_API_KEYS"
|
| 522 |
inject_provider_models_from_env "qianfan" "QIANFAN_MODELS" "QIANFAN_API_KEY" "QIANFAN_API_KEYS"
|
| 523 |
-
inject_provider_models_from_env "groq" "GROQ_MODELS" "GROQ_API_KEY" "GROQ_API_KEYS"
|
| 524 |
-
inject_provider_models_from_env "mistral" "MISTRAL_MODELS" "MISTRAL_API_KEY" "MISTRAL_API_KEYS"
|
| 525 |
-
inject_provider_models_from_env "mistralai" "MISTRAL_MODELS" "MISTRAL_API_KEY" "MISTRAL_API_KEYS"
|
| 526 |
-
inject_provider_models_from_env "xai" "XAI_MODELS" "XAI_API_KEY" "XAI_API_KEYS"
|
| 527 |
-
inject_provider_models_from_env "x-ai" "XAI_MODELS" "XAI_API_KEY" "XAI_API_KEYS"
|
| 528 |
-
inject_provider_models_from_env "nvidia" "NVIDIA_MODELS" "NVIDIA_API_KEY" "NVIDIA_API_KEYS"
|
| 529 |
-
inject_provider_models_from_env "cohere" "COHERE_MODELS" "COHERE_API_KEY" "COHERE_API_KEYS"
|
| 530 |
-
inject_provider_models_from_env "together" "TOGETHER_MODELS" "TOGETHER_API_KEY" "TOGETHER_API_KEYS"
|
| 531 |
-
inject_provider_models_from_env "cerebras" "CEREBRAS_MODELS" "CEREBRAS_API_KEY" "CEREBRAS_API_KEYS"
|
| 532 |
-
inject_provider_models_from_env "huggingface" "HUGGINGFACE_MODELS" "HUGGINGFACE_HUB_TOKEN" "HUGGINGFACE_HUB_TOKENS"
|
| 533 |
-
inject_provider_models_from_env "venice" "VENICE_MODELS" "VENICE_API_KEY" "VENICE_API_KEYS"
|
| 534 |
inject_provider_models_from_env "synthetic" "SYNTHETIC_MODELS" "SYNTHETIC_API_KEY" "SYNTHETIC_API_KEYS"
|
| 535 |
-
inject_provider_models_from_env "github-copilot" "GITHUB_COPILOT_MODELS" "COPILOT_GITHUB_TOKEN" "COPILOT_GITHUB_TOKENS"
|
| 536 |
|
| 537 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 538 |
BROWSER_EXECUTABLE_PATH=""
|
|
@@ -740,8 +813,8 @@ if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
|
|
| 740 |
|
| 741 |
export OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
|
| 742 |
export OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first
|
| 743 |
-
#
|
| 744 |
-
|
| 745 |
|
| 746 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq --arg token "$CLEAN_TG_TOKEN" --arg proxy_url "$TELEGRAM_API_ROOT" '
|
| 747 |
.channels.telegram.enabled = true
|
|
@@ -839,10 +912,27 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 839 |
--argjson whatsappConfigured "$WHATSAPP_ENABLED_CONFIGURED" \
|
| 840 |
--argjson whatsappEnabled "$WHATSAPP_CONFIG_ENABLED" \
|
| 841 |
--argjson telegramConfigured "$TELEGRAM_CONFIG_ENABLED" \
|
|
|
|
| 842 |
'(.channels.whatsapp // {}) as $existingWhatsapp
|
|
|
|
| 843 |
| .gateway.auth.token = $token
|
| 844 |
| .agents.defaults.model = $model
|
| 845 |
| .gateway.port = ($desired.gateway.port // .gateway.port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
| if $fileLogConfigured then .logging.level = $fileLevel else . end
|
| 847 |
| if $consoleLogConfigured then .logging.consoleLevel = $consoleLevel else . end
|
| 848 |
| if $consoleStyleConfigured then .logging.consoleStyle = $consoleStyle else . end
|
|
@@ -856,10 +946,33 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 856 |
else
|
| 857 |
.
|
| 858 |
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 859 |
| .channels = ((.channels // {}) * ($desired.channels // {}))
|
| 860 |
| .plugins.allow = (((.plugins.allow // []) + ($desired.plugins.allow // [])) | unique)
|
| 861 |
| .plugins.deny = (((.plugins.deny // []) + ($desired.plugins.deny // [])) | unique)
|
| 862 |
| .plugins.entries = ((.plugins.entries // {}) * ($desired.plugins.entries // {}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
| if $whatsappEnabled then
|
| 864 |
($desired.channels.whatsapp // {"dmPolicy": "pairing"}) as $desiredWhatsapp
|
| 865 |
| .plugins.entries.whatsapp.enabled = true
|
|
@@ -873,8 +986,13 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 873 |
.
|
| 874 |
end
|
| 875 |
| if $telegramConfigured then
|
| 876 |
-
|
| 877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
else
|
| 879 |
del(.channels.telegram)
|
| 880 |
| .plugins.entries.telegram.enabled = false
|
|
@@ -1790,6 +1908,25 @@ start_guardian_once() {
|
|
| 1790 |
echo "WhatsApp Guardian started (PID: $GUARDIAN_PID)"
|
| 1791 |
}
|
| 1792 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1793 |
# ββ Start D-Bus session (once, before gateway loop) ββ
|
| 1794 |
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
|
| 1795 |
if command -v dbus-launch >/dev/null 2>&1; then
|
|
@@ -1844,9 +1981,14 @@ while true; do
|
|
| 1844 |
GATEWAY_PID=$!
|
| 1845 |
|
| 1846 |
# Poll for the gateway to start listening on ${GATEWAY_PORT}. OpenClaw can take 20-30s
|
| 1847 |
-
# on cold start (plugin install + auto-restore).
|
| 1848 |
-
#
|
| 1849 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1850 |
ready=false
|
| 1851 |
for ((i=0; i<GATEWAY_READY_TIMEOUT; i++)); do
|
| 1852 |
if (echo > /dev/tcp/127.0.0.1/${GATEWAY_PORT}) 2>/dev/null; then
|
|
|
|
| 124 |
ACP_PLUGIN_MODE="${ACP_PLUGIN_MODE:-disabled}"
|
| 125 |
# HF Spaces does not benefit from Bonjour discovery, and the retries add noise.
|
| 126 |
export OPENCLAW_DISABLE_BONJOUR="${OPENCLAW_DISABLE_BONJOUR:-1}"
|
| 127 |
+
# HF Spaces IPv6 routing is unreliable and causes ECONNRESET on outbound
|
| 128 |
+
# WebSocket connections (WhatsApp, Telegram, etc.) which triggers gateway
|
| 129 |
+
# channel restarts and floods logs with "ws closed before connect" (1006)
|
| 130 |
+
# errors. Force IPv4 globally for ALL channels on this Space.
|
| 131 |
+
# Previously this was only set inside the Telegram block β meaning
|
| 132 |
+
# WhatsApp-only deployments never got this fix and suffered ECONNRESET drops.
|
| 133 |
+
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--dns-result-order=ipv4first"
|
| 134 |
+
export OPENCLAW_WHATSAPP_DISABLE_AUTO_SELECT_FAMILY="${OPENCLAW_WHATSAPP_DISABLE_AUTO_SELECT_FAMILY:-1}"
|
| 135 |
+
export OPENCLAW_WHATSAPP_DNS_RESULT_ORDER="${OPENCLAW_WHATSAPP_DNS_RESULT_ORDER:-ipv4first}"
|
| 136 |
else
|
| 137 |
OPENCLAW_CONSOLE_LOG_LEVEL="${OPENCLAW_CONSOLE_LOG_LEVEL:-info}"
|
| 138 |
OPENCLAW_FILE_LOG_LEVEL="${OPENCLAW_FILE_LOG_LEVEL:-info}"
|
|
|
|
| 463 |
# NVIDIA_MODELS=model1,model2
|
| 464 |
# OPENAI_MODELS=gpt-4o-mini,gpt-4.1
|
| 465 |
# This helps when provider auto-discovery does not populate models reliably.
|
| 466 |
+
# Default catalogs (used when *_MODELS env is not set but key IS configured).
|
| 467 |
+
# These let multi-key pool users see models without having to also set *_MODELS.
|
| 468 |
+
_DEFAULT_ANTHROPIC_MODELS="anthropic/claude-opus-4-6,anthropic/claude-sonnet-4-6,anthropic/claude-haiku-4-5"
|
| 469 |
+
_DEFAULT_OPENAI_MODELS="openai/gpt-5.5,openai/gpt-5.4-mini,openai/gpt-4o,openai/gpt-4o-mini"
|
| 470 |
+
_DEFAULT_GEMINI_MODELS="google/gemini-3.1-pro-preview,google/gemini-3.1-flash-preview,google/gemini-2.5-pro,google/gemini-2.5-flash,google/gemini-2.0-flash"
|
| 471 |
+
_DEFAULT_VERTEX_MODELS="google-vertex/gemini-2.5-pro,google-vertex/gemini-2.5-flash,google-vertex/gemini-2.0-flash"
|
| 472 |
+
_DEFAULT_DEEPSEEK_MODELS="deepseek/deepseek-v4-pro,deepseek/deepseek-v4-flash,deepseek/deepseek-chat,deepseek/deepseek-reasoner"
|
| 473 |
+
_DEFAULT_OPENROUTER_MODELS="openrouter/auto,openrouter/anthropic/claude-opus-4-6,openrouter/openai/gpt-4o,openrouter/google/gemini-2.5-pro"
|
| 474 |
+
_DEFAULT_GROQ_MODELS="groq/compound-beta,groq/moonshotai/kimi-k2-5,groq/deepseek-r1-distill-llama-70b"
|
| 475 |
+
_DEFAULT_MISTRAL_MODELS="mistral/mistral-large-latest,mistral/codestral-latest,mistral/mistral-small-latest"
|
| 476 |
+
_DEFAULT_XAI_MODELS="xai/grok-4.3,xai/grok-3,xai/grok-3-mini"
|
| 477 |
+
_DEFAULT_COHERE_MODELS="cohere/command-r-plus,cohere/command-r"
|
| 478 |
+
_DEFAULT_TOGETHER_MODELS="together/moonshotai/Kimi-K2.5,together/deepseek-ai/DeepSeek-V3.2,together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8"
|
| 479 |
+
_DEFAULT_CEREBRAS_MODELS="cerebras/zai-glm-4.7,cerebras/llama-4-scout-17b-16e-instruct"
|
| 480 |
+
_DEFAULT_NVIDIA_MODELS="nvidia/nemotron-3-super-120b-a12b,nvidia/moonshotai/kimi-k2.5"
|
| 481 |
+
_DEFAULT_KILOCODE_MODELS="kilocode/kilo/auto"
|
| 482 |
+
_DEFAULT_MOONSHOT_MODELS="moonshot/kimi-k2.6,moonshot/kimi-k2.5,moonshot/kimi-k2-thinking"
|
| 483 |
+
_DEFAULT_MINIMAX_MODELS="minimax/MiniMax-M2.7,minimax/MiniMax-M2.5"
|
| 484 |
+
_DEFAULT_ZAI_MODELS="zai/glm-5.1,zai/glm-4.7"
|
| 485 |
+
_DEFAULT_MODELSTUDIO_MODELS="modelstudio/qwen3-max,modelstudio/qwen3-coder,modelstudio/qwen3-32b"
|
| 486 |
+
_DEFAULT_VENICE_MODELS="venice/llama-3.3-70b"
|
| 487 |
+
_DEFAULT_OPENCODE_MODELS="opencode/claude-opus-4-6,opencode/claude-sonnet-4-6"
|
| 488 |
+
_DEFAULT_HUGGINGFACE_MODELS="huggingface/deepseek-ai/DeepSeek-R1,huggingface/meta-llama/Llama-4-Scout-17B-16E-Instruct"
|
| 489 |
+
_DEFAULT_GITHUB_COPILOT_MODELS="github-copilot/gpt-5,github-copilot/gpt-4.1,github-copilot/gpt-4o"
|
| 490 |
+
|
| 491 |
INJECTED_MODELS_PROVIDERS='{}'
|
| 492 |
inject_provider_models_from_env() {
|
| 493 |
local provider="$1"
|
| 494 |
local models_env="$2"
|
| 495 |
local key_env_single="$3"
|
| 496 |
local key_env_pool="$4"
|
| 497 |
+
local default_models_env="${5:-}" # Optional 5th arg: fallback default models var name
|
| 498 |
local models_csv="${!models_env:-}"
|
| 499 |
local single_key="${!key_env_single:-}"
|
| 500 |
local pool_keys="${!key_env_pool:-}"
|
| 501 |
|
| 502 |
+
# Need at least one configured key
|
| 503 |
+
if [ -z "$single_key" ] && [ -z "$pool_keys" ]; then
|
| 504 |
+
return 0
|
| 505 |
+
fi
|
| 506 |
+
|
| 507 |
+
# If no explicit model list but a default var was provided, fall back to it
|
| 508 |
+
if [ -z "$models_csv" ] && [ -n "$default_models_env" ]; then
|
| 509 |
+
models_csv="${!default_models_env:-}"
|
| 510 |
+
fi
|
| 511 |
+
|
| 512 |
+
# Still nothing to inject
|
| 513 |
+
if [ -z "$models_csv" ]; then
|
| 514 |
return 0
|
| 515 |
fi
|
| 516 |
|
|
|
|
| 521 |
| awk 'NF' \
|
| 522 |
| jq -R . \
|
| 523 |
| jq -s --arg provider "$provider" '
|
| 524 |
+
map(
|
| 525 |
+
if contains("/") then
|
| 526 |
+
# Fix cross-prefix: strip any foreign provider prefix, reapply correct one.
|
| 527 |
+
# e.g. "google/gemini-2.5-pro" injected into "google-vertex" becomes
|
| 528 |
+
# "google-vertex/gemini-2.5-pro"
|
| 529 |
+
if startswith($provider + "/") then .
|
| 530 |
+
else ($provider + "/" + (split("/") | last))
|
| 531 |
+
end
|
| 532 |
+
else
|
| 533 |
+
($provider + "/" + .)
|
| 534 |
+
end
|
| 535 |
+
)
|
| 536 |
| map({id: ., name: .})
|
| 537 |
| unique_by(.id)')
|
| 538 |
|
|
|
|
| 548 |
'.[$provider] = ((.[$provider] // {}) + {models: $models})' <<<"$INJECTED_MODELS_PROVIDERS")
|
| 549 |
}
|
| 550 |
|
| 551 |
+
# ββ Google Vertex AI credentials setup ββ
|
| 552 |
+
# Vertex AI uses GCP project + location, NOT a simple Gemini API key.
|
| 553 |
+
# Set GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, and optionally
|
| 554 |
+
# GOOGLE_APPLICATION_CREDENTIALS_JSON (base64-encoded service account JSON).
|
| 555 |
+
if [ -n "${GOOGLE_APPLICATION_CREDENTIALS_JSON:-}" ]; then
|
| 556 |
+
_VERTEX_CREDS_FILE="/tmp/gcp-service-account.json"
|
| 557 |
+
printf '%s' "$GOOGLE_APPLICATION_CREDENTIALS_JSON" | base64 -d > "$_VERTEX_CREDS_FILE" 2>/dev/null \
|
| 558 |
+
|| printf '%s' "$GOOGLE_APPLICATION_CREDENTIALS_JSON" > "$_VERTEX_CREDS_FILE"
|
| 559 |
+
export GOOGLE_APPLICATION_CREDENTIALS="$_VERTEX_CREDS_FILE"
|
| 560 |
+
echo "Note: GOOGLE_APPLICATION_CREDENTIALS written from GOOGLE_APPLICATION_CREDENTIALS_JSON"
|
| 561 |
+
fi
|
| 562 |
+
[ -n "${GOOGLE_CLOUD_PROJECT:-}" ] && export GOOGLE_CLOUD_PROJECT
|
| 563 |
+
[ -n "${GOOGLE_CLOUD_LOCATION:-}" ] && export GOOGLE_CLOUD_LOCATION
|
| 564 |
+
|
| 565 |
# Built-in provider model envs (optional)
|
| 566 |
+
inject_provider_models_from_env "anthropic" "ANTHROPIC_MODELS" "ANTHROPIC_API_KEY" "ANTHROPIC_API_KEYS" "_DEFAULT_ANTHROPIC_MODELS"
|
| 567 |
+
inject_provider_models_from_env "openai" "OPENAI_MODELS" "OPENAI_API_KEY" "OPENAI_API_KEYS" "_DEFAULT_OPENAI_MODELS"
|
| 568 |
+
inject_provider_models_from_env "openai-codex" "OPENAI_MODELS" "OPENAI_API_KEY" "OPENAI_API_KEYS" "_DEFAULT_OPENAI_MODELS"
|
| 569 |
+
inject_provider_models_from_env "google" "GEMINI_MODELS" "GEMINI_API_KEY" "GEMINI_API_KEYS" "_DEFAULT_GEMINI_MODELS"
|
| 570 |
+
# google-vertex: uses VERTEX_MODELS (with google-vertex/ prefix) separately from GEMINI_MODELS.
|
| 571 |
+
# The "key" check uses GOOGLE_CLOUD_PROJECT so it only injects when Vertex is actually configured.
|
| 572 |
+
# google-vertex: inject when GOOGLE_CLOUD_PROJECT is configured.
|
| 573 |
+
# Pool key uses a dummy var (_VERTEX_POOL_UNUSED) so that only GOOGLE_CLOUD_PROJECT
|
| 574 |
+
# gates injection β Vertex uses GCP project auth, not Gemini API key rotation.
|
| 575 |
+
inject_provider_models_from_env "google-vertex" "VERTEX_MODELS" "GOOGLE_CLOUD_PROJECT" "_VERTEX_POOL_UNUSED" "_DEFAULT_VERTEX_MODELS"
|
| 576 |
+
inject_provider_models_from_env "deepseek" "DEEPSEEK_MODELS" "DEEPSEEK_API_KEY" "DEEPSEEK_API_KEYS" "_DEFAULT_DEEPSEEK_MODELS"
|
| 577 |
+
inject_provider_models_from_env "openrouter" "OPENROUTER_MODELS" "OPENROUTER_API_KEY" "OPENROUTER_API_KEYS" "_DEFAULT_OPENROUTER_MODELS"
|
| 578 |
+
inject_provider_models_from_env "kilocode" "KILOCODE_MODELS" "KILOCODE_API_KEY" "KILOCODE_API_KEYS" "_DEFAULT_KILOCODE_MODELS"
|
| 579 |
+
inject_provider_models_from_env "opencode" "OPENCODE_MODELS" "OPENCODE_API_KEY" "OPENCODE_API_KEYS" "_DEFAULT_OPENCODE_MODELS"
|
| 580 |
+
inject_provider_models_from_env "opencode-go" "OPENCODE_MODELS" "OPENCODE_API_KEY" "OPENCODE_API_KEYS" "_DEFAULT_OPENCODE_MODELS"
|
| 581 |
+
inject_provider_models_from_env "zai" "ZAI_MODELS" "ZAI_API_KEY" "ZAI_API_KEYS" "_DEFAULT_ZAI_MODELS"
|
| 582 |
+
inject_provider_models_from_env "z-ai" "ZAI_MODELS" "ZAI_API_KEY" "ZAI_API_KEYS" "_DEFAULT_ZAI_MODELS"
|
| 583 |
+
inject_provider_models_from_env "z.ai" "ZAI_MODELS" "ZAI_API_KEY" "ZAI_API_KEYS" "_DEFAULT_ZAI_MODELS"
|
| 584 |
+
inject_provider_models_from_env "zhipu" "ZAI_MODELS" "ZAI_API_KEY" "ZAI_API_KEYS" "_DEFAULT_ZAI_MODELS"
|
| 585 |
+
inject_provider_models_from_env "moonshot" "MOONSHOT_MODELS" "MOONSHOT_API_KEY" "MOONSHOT_API_KEYS" "_DEFAULT_MOONSHOT_MODELS"
|
| 586 |
inject_provider_models_from_env "kimi-coding" "KIMI_MODELS" "KIMI_API_KEY" "KIMI_API_KEYS"
|
| 587 |
+
inject_provider_models_from_env "minimax" "MINIMAX_MODELS" "MINIMAX_API_KEY" "MINIMAX_API_KEYS" "_DEFAULT_MINIMAX_MODELS"
|
| 588 |
+
inject_provider_models_from_env "modelstudio" "MODELSTUDIO_MODELS" "MODELSTUDIO_API_KEY" "MODELSTUDIO_API_KEYS" "_DEFAULT_MODELSTUDIO_MODELS"
|
| 589 |
+
inject_provider_models_from_env "qwen" "MODELSTUDIO_MODELS" "MODELSTUDIO_API_KEY" "MODELSTUDIO_API_KEYS" "_DEFAULT_MODELSTUDIO_MODELS"
|
| 590 |
inject_provider_models_from_env "xiaomi" "XIAOMI_MODELS" "XIAOMI_API_KEY" "XIAOMI_API_KEYS"
|
| 591 |
inject_provider_models_from_env "volcengine" "VOLCANO_ENGINE_MODELS" "VOLCANO_ENGINE_API_KEY" "VOLCANO_ENGINE_API_KEYS"
|
| 592 |
inject_provider_models_from_env "volcengine-plan" "VOLCANO_ENGINE_MODELS" "VOLCANO_ENGINE_API_KEY" "VOLCANO_ENGINE_API_KEYS"
|
| 593 |
inject_provider_models_from_env "byteplus" "BYTEPLUS_MODELS" "BYTEPLUS_API_KEY" "BYTEPLUS_API_KEYS"
|
| 594 |
inject_provider_models_from_env "byteplus-plan" "BYTEPLUS_MODELS" "BYTEPLUS_API_KEY" "BYTEPLUS_API_KEYS"
|
| 595 |
inject_provider_models_from_env "qianfan" "QIANFAN_MODELS" "QIANFAN_API_KEY" "QIANFAN_API_KEYS"
|
| 596 |
+
inject_provider_models_from_env "groq" "GROQ_MODELS" "GROQ_API_KEY" "GROQ_API_KEYS" "_DEFAULT_GROQ_MODELS"
|
| 597 |
+
inject_provider_models_from_env "mistral" "MISTRAL_MODELS" "MISTRAL_API_KEY" "MISTRAL_API_KEYS" "_DEFAULT_MISTRAL_MODELS"
|
| 598 |
+
inject_provider_models_from_env "mistralai" "MISTRAL_MODELS" "MISTRAL_API_KEY" "MISTRAL_API_KEYS" "_DEFAULT_MISTRAL_MODELS"
|
| 599 |
+
inject_provider_models_from_env "xai" "XAI_MODELS" "XAI_API_KEY" "XAI_API_KEYS" "_DEFAULT_XAI_MODELS"
|
| 600 |
+
inject_provider_models_from_env "x-ai" "XAI_MODELS" "XAI_API_KEY" "XAI_API_KEYS" "_DEFAULT_XAI_MODELS"
|
| 601 |
+
inject_provider_models_from_env "nvidia" "NVIDIA_MODELS" "NVIDIA_API_KEY" "NVIDIA_API_KEYS" "_DEFAULT_NVIDIA_MODELS"
|
| 602 |
+
inject_provider_models_from_env "cohere" "COHERE_MODELS" "COHERE_API_KEY" "COHERE_API_KEYS" "_DEFAULT_COHERE_MODELS"
|
| 603 |
+
inject_provider_models_from_env "together" "TOGETHER_MODELS" "TOGETHER_API_KEY" "TOGETHER_API_KEYS" "_DEFAULT_TOGETHER_MODELS"
|
| 604 |
+
inject_provider_models_from_env "cerebras" "CEREBRAS_MODELS" "CEREBRAS_API_KEY" "CEREBRAS_API_KEYS" "_DEFAULT_CEREBRAS_MODELS"
|
| 605 |
+
inject_provider_models_from_env "huggingface" "HUGGINGFACE_MODELS" "HUGGINGFACE_HUB_TOKEN" "HUGGINGFACE_HUB_TOKENS" "_DEFAULT_HUGGINGFACE_MODELS"
|
| 606 |
+
inject_provider_models_from_env "venice" "VENICE_MODELS" "VENICE_API_KEY" "VENICE_API_KEYS" "_DEFAULT_VENICE_MODELS"
|
| 607 |
inject_provider_models_from_env "synthetic" "SYNTHETIC_MODELS" "SYNTHETIC_API_KEY" "SYNTHETIC_API_KEYS"
|
| 608 |
+
inject_provider_models_from_env "github-copilot" "GITHUB_COPILOT_MODELS" "COPILOT_GITHUB_TOKEN" "COPILOT_GITHUB_TOKENS" "_DEFAULT_GITHUB_COPILOT_MODELS"
|
| 609 |
|
| 610 |
# Browser configuration (managed local Chromium in HF/Docker)
|
| 611 |
BROWSER_EXECUTABLE_PATH=""
|
|
|
|
| 813 |
|
| 814 |
export OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
|
| 815 |
export OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first
|
| 816 |
+
# Note: --dns-result-order=ipv4first is now set globally for all HF Space
|
| 817 |
+
# channels in the SPACE_HOST block above; no need to set NODE_OPTIONS here.
|
| 818 |
|
| 819 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq --arg token "$CLEAN_TG_TOKEN" --arg proxy_url "$TELEGRAM_API_ROOT" '
|
| 820 |
.channels.telegram.enabled = true
|
|
|
|
| 912 |
--argjson whatsappConfigured "$WHATSAPP_ENABLED_CONFIGURED" \
|
| 913 |
--argjson whatsappEnabled "$WHATSAPP_CONFIG_ENABLED" \
|
| 914 |
--argjson telegramConfigured "$TELEGRAM_CONFIG_ENABLED" \
|
| 915 |
+
--argjson browserEnabled "$BROWSER_SHOULD_ENABLE" \
|
| 916 |
'(.channels.whatsapp // {}) as $existingWhatsapp
|
| 917 |
+
| (.channels.telegram // {}) as $existingTelegram
|
| 918 |
| .gateway.auth.token = $token
|
| 919 |
| .agents.defaults.model = $model
|
| 920 |
| .gateway.port = ($desired.gateway.port // .gateway.port)
|
| 921 |
+
| .gateway.controlUi.dangerouslyDisableDeviceAuth = true
|
| 922 |
+
| (if ($desired.gateway.controlUi.allowedOrigins // [] | length) > 0 then
|
| 923 |
+
.gateway.controlUi.allowedOrigins = (
|
| 924 |
+
((.gateway.controlUi.allowedOrigins // []) + ($desired.gateway.controlUi.allowedOrigins // []))
|
| 925 |
+
| unique
|
| 926 |
+
)
|
| 927 |
+
else . end)
|
| 928 |
+
| (if ($desired.gateway.auth.mode // "") != "" then
|
| 929 |
+
.gateway.auth.mode = $desired.gateway.auth.mode
|
| 930 |
+
| .gateway.auth.password = ($desired.gateway.auth.password // "")
|
| 931 |
+
else . end)
|
| 932 |
+
| .gateway.trustedProxies = (
|
| 933 |
+
((.gateway.trustedProxies // []) + ($desired.gateway.trustedProxies // []))
|
| 934 |
+
| unique
|
| 935 |
+
)
|
| 936 |
| if $fileLogConfigured then .logging.level = $fileLevel else . end
|
| 937 |
| if $consoleLogConfigured then .logging.consoleLevel = $consoleLevel else . end
|
| 938 |
| if $consoleStyleConfigured then .logging.consoleStyle = $consoleStyle else . end
|
|
|
|
| 946 |
else
|
| 947 |
.
|
| 948 |
end
|
| 949 |
+
| if (($desired.models.providers // {} | length) > 0) then
|
| 950 |
+
reduce ($desired.models.providers // {} | to_entries)[] as $pe (.;
|
| 951 |
+
# Propagate custom/new providers from desired config that are absent in existing.
|
| 952 |
+
# For known providers that already exist, only merge in baseUrl/apiKey/api when missing.
|
| 953 |
+
if .models.providers[$pe.key] == null then
|
| 954 |
+
.models.providers[$pe.key] = $pe.value
|
| 955 |
+
else
|
| 956 |
+
.models.providers[$pe.key] = (
|
| 957 |
+
{"baseUrl": $pe.value.baseUrl, "apiKey": $pe.value.apiKey, "api": $pe.value.api}
|
| 958 |
+
| with_entries(select(.value != null and .value != ""))
|
| 959 |
+
) * (.models.providers[$pe.key] // {})
|
| 960 |
+
end
|
| 961 |
+
)
|
| 962 |
+
else
|
| 963 |
+
.
|
| 964 |
+
end
|
| 965 |
| .channels = ((.channels // {}) * ($desired.channels // {}))
|
| 966 |
| .plugins.allow = (((.plugins.allow // []) + ($desired.plugins.allow // [])) | unique)
|
| 967 |
| .plugins.deny = (((.plugins.deny // []) + ($desired.plugins.deny // [])) | unique)
|
| 968 |
| .plugins.entries = ((.plugins.entries // {}) * ($desired.plugins.entries // {}))
|
| 969 |
+
| del(.plugins.entries.acpx)
|
| 970 |
+
| (if $browserEnabled then
|
| 971 |
+
.browser = ($desired.browser // .browser)
|
| 972 |
+
else
|
| 973 |
+
.browser.enabled = false
|
| 974 |
+
| .plugins.entries.browser.enabled = false
|
| 975 |
+
end)
|
| 976 |
| if $whatsappEnabled then
|
| 977 |
($desired.channels.whatsapp // {"dmPolicy": "pairing"}) as $desiredWhatsapp
|
| 978 |
| .plugins.entries.whatsapp.enabled = true
|
|
|
|
| 986 |
.
|
| 987 |
end
|
| 988 |
| if $telegramConfigured then
|
| 989 |
+
# Merge: existing * desired β desired (env-driven) wins for runtime fields
|
| 990 |
+
# (apiRoot from CLOUDFLARE_PROXY_URL, commands.native, timeoutSeconds, retry).
|
| 991 |
+
# Then re-apply user-editable fields from saved $existingTelegram so UI
|
| 992 |
+
# customizations (dmPolicy, allowFrom) survive across reboots.
|
| 993 |
+
.channels.telegram = ($existingTelegram * ($desired.channels.telegram // {}))
|
| 994 |
+
| (if ($existingTelegram | has("dmPolicy")) then .channels.telegram.dmPolicy = $existingTelegram.dmPolicy else . end)
|
| 995 |
+
| (if ($existingTelegram | has("allowFrom")) then .channels.telegram.allowFrom = $existingTelegram.allowFrom else . end)
|
| 996 |
else
|
| 997 |
del(.channels.telegram)
|
| 998 |
| .plugins.entries.telegram.enabled = false
|
|
|
|
| 1908 |
echo "WhatsApp Guardian started (PID: $GUARDIAN_PID)"
|
| 1909 |
}
|
| 1910 |
|
| 1911 |
+
# ββ Clean up stale plugin-skills entries that are not generated symlinks ββ
|
| 1912 |
+
# OpenClaw generates plugin-skills entries as symlinks. If a real directory
|
| 1913 |
+
# exists (e.g. from a previous failed install or manual placement), OpenClaw
|
| 1914 |
+
# logs "plugin skill entry is not a generated symlink" on every poll cycle.
|
| 1915 |
+
# Remove any non-symlink entries so they are regenerated cleanly on startup.
|
| 1916 |
+
PLUGIN_SKILLS_DIR="/home/node/.openclaw/plugin-skills"
|
| 1917 |
+
if [ -d "$PLUGIN_SKILLS_DIR" ]; then
|
| 1918 |
+
for _ps_entry in "$PLUGIN_SKILLS_DIR"/*/; do
|
| 1919 |
+
_ps_entry="${_ps_entry%/}"
|
| 1920 |
+
[ -e "$_ps_entry" ] || continue
|
| 1921 |
+
if [ ! -L "$_ps_entry" ]; then
|
| 1922 |
+
_ps_name="$(basename "$_ps_entry")"
|
| 1923 |
+
echo "Removing stale plugin-skills entry '$_ps_name' (not a generated symlink; will be regenerated by OpenClaw)..."
|
| 1924 |
+
rm -rf "$_ps_entry"
|
| 1925 |
+
fi
|
| 1926 |
+
done
|
| 1927 |
+
unset _ps_entry _ps_name
|
| 1928 |
+
fi
|
| 1929 |
+
|
| 1930 |
# ββ Start D-Bus session (once, before gateway loop) ββ
|
| 1931 |
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
|
| 1932 |
if command -v dbus-launch >/dev/null 2>&1; then
|
|
|
|
| 1981 |
GATEWAY_PID=$!
|
| 1982 |
|
| 1983 |
# Poll for the gateway to start listening on ${GATEWAY_PORT}. OpenClaw can take 20-30s
|
| 1984 |
+
# on cold start (plugin install + auto-restore). On HF Spaces the bootstrap-context
|
| 1985 |
+
# stage alone can exceed 300 s on a cold start, so default to 300 s there and
|
| 1986 |
+
# 90 s elsewhere. Bail out early if the pipeline died.
|
| 1987 |
+
if [ -n "${SPACE_HOST:-}" ]; then
|
| 1988 |
+
GATEWAY_READY_TIMEOUT="${GATEWAY_READY_TIMEOUT:-300}"
|
| 1989 |
+
else
|
| 1990 |
+
GATEWAY_READY_TIMEOUT="${GATEWAY_READY_TIMEOUT:-90}"
|
| 1991 |
+
fi
|
| 1992 |
ready=false
|
| 1993 |
for ((i=0; i<GATEWAY_READY_TIMEOUT; i++)); do
|
| 1994 |
if (echo > /dev/tcp/127.0.0.1/${GATEWAY_PORT}) 2>/dev/null; then
|