Spaces:
Running
Running
env-builder improvements, gateway/browser robustness, and key-rotator fixes
Browse files- health-server.js +0 -1
- jupyter-devdata-sync.py +1 -0
- multi-provider-key-rotator.cjs +25 -1
- start.sh +15 -1
- wa-guardian.js +18 -9
health-server.js
CHANGED
|
@@ -72,7 +72,6 @@ if (_spacPrivacyEnv === "public") {
|
|
| 72 |
SPACE_IS_PRIVATE = false;
|
| 73 |
_privacyDetectionDone = true;
|
| 74 |
console.log("[health-server] Space privacy: public (SPACE_PRIVACY env var override)");
|
| 75 |
-
privacyDetectionReady.then ? void 0 : null;
|
| 76 |
_privacyDetectionResolve && _privacyDetectionResolve();
|
| 77 |
} else if (_spacPrivacyEnv === "private") {
|
| 78 |
// User explicitly set SPACE_PRIVACY=private — skip API call entirely
|
|
|
|
| 72 |
SPACE_IS_PRIVATE = false;
|
| 73 |
_privacyDetectionDone = true;
|
| 74 |
console.log("[health-server] Space privacy: public (SPACE_PRIVACY env var override)");
|
|
|
|
| 75 |
_privacyDetectionResolve && _privacyDetectionResolve();
|
| 76 |
} else if (_spacPrivacyEnv === "private") {
|
| 77 |
// User explicitly set SPACE_PRIVACY=private — skip API call entirely
|
jupyter-devdata-sync.py
CHANGED
|
@@ -174,6 +174,7 @@ def is_jupyter_running(port: int = 8888) -> bool:
|
|
| 174 |
return False
|
| 175 |
|
| 176 |
def restore_once(api, rid: str):
|
|
|
|
| 177 |
from huggingface_hub.errors import RepositoryNotFoundError
|
| 178 |
tmp = Path(tempfile.mkdtemp(prefix="devdata-restore-"))
|
| 179 |
try:
|
|
|
|
| 174 |
return False
|
| 175 |
|
| 176 |
def restore_once(api, rid: str):
|
| 177 |
+
from huggingface_hub import snapshot_download
|
| 178 |
from huggingface_hub.errors import RepositoryNotFoundError
|
| 179 |
tmp = Path(tempfile.mkdtemp(prefix="devdata-restore-"))
|
| 180 |
try:
|
multi-provider-key-rotator.cjs
CHANGED
|
@@ -192,6 +192,12 @@ const PROVIDERS = [
|
|
| 192 |
envPlural: 'MODELSTUDIO_API_KEYS',
|
| 193 |
envSingular:'MODELSTUDIO_API_KEY',
|
| 194 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
];
|
| 197 |
|
|
@@ -320,7 +326,25 @@ function patchFetch() {
|
|
| 320 |
// Gemini: key URL query param mein jaata hai, Bearer nahi
|
| 321 |
const url = new URL(typeof input === 'string' ? input : input.url);
|
| 322 |
url.searchParams.set('key', key);
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
} else {
|
| 325 |
const headers = init.headers || (input && input.headers) || undefined;
|
| 326 |
const patchedHeaders = setAuthHeader(headers, key);
|
|
|
|
| 192 |
envPlural: 'MODELSTUDIO_API_KEYS',
|
| 193 |
envSingular:'MODELSTUDIO_API_KEY',
|
| 194 |
},
|
| 195 |
+
{
|
| 196 |
+
name: 'synthetic',
|
| 197 |
+
hostname: /(?:^|\.)synthetic\.local$/i,
|
| 198 |
+
envPlural: 'SYNTHETIC_API_KEYS',
|
| 199 |
+
envSingular: 'SYNTHETIC_API_KEY',
|
| 200 |
+
},
|
| 201 |
|
| 202 |
];
|
| 203 |
|
|
|
|
| 326 |
// Gemini: key URL query param mein jaata hai, Bearer nahi
|
| 327 |
const url = new URL(typeof input === 'string' ? input : input.url);
|
| 328 |
url.searchParams.set('key', key);
|
| 329 |
+
if (typeof input === 'string') {
|
| 330 |
+
input = url.toString();
|
| 331 |
+
} else {
|
| 332 |
+
// Do NOT pass the Request object as init — that clones (consumes) the body stream.
|
| 333 |
+
// Instead patch only the URL via init object; fetch spec merges headers from Request.
|
| 334 |
+
init = {
|
| 335 |
+
method: input.method,
|
| 336 |
+
headers: input.headers,
|
| 337 |
+
body: input.body,
|
| 338 |
+
mode: input.mode,
|
| 339 |
+
credentials: input.credentials,
|
| 340 |
+
cache: input.cache,
|
| 341 |
+
redirect: input.redirect,
|
| 342 |
+
referrer: input.referrer,
|
| 343 |
+
integrity: input.integrity,
|
| 344 |
+
...init,
|
| 345 |
+
};
|
| 346 |
+
input = url.toString();
|
| 347 |
+
}
|
| 348 |
} else {
|
| 349 |
const headers = init.headers || (input && input.headers) || undefined;
|
| 350 |
const patchedHeaders = setAuthHeader(headers, key);
|
start.sh
CHANGED
|
@@ -359,8 +359,10 @@ CONFIG_JSON=$(jq \
|
|
| 359 |
--arg fileLevel "$OPENCLAW_FILE_LOG_LEVEL" \
|
| 360 |
--arg consoleLevel "$OPENCLAW_CONSOLE_LOG_LEVEL" \
|
| 361 |
--arg consoleStyle "$OPENCLAW_CONSOLE_LOG_STYLE" \
|
|
|
|
| 362 |
'.gateway.auth.token = $token
|
| 363 |
| .agents.defaults.model = $model
|
|
|
|
| 364 |
| .logging.level = $fileLevel
|
| 365 |
| .logging.consoleLevel = $consoleLevel
|
| 366 |
| .logging.consoleStyle = $consoleStyle' <<<"$CONFIG_JSON")
|
|
@@ -373,7 +375,7 @@ CUSTOM_MODEL_NAME="${CUSTOM_MODEL_NAME:-$CUSTOM_MODEL_ID}"
|
|
| 373 |
CUSTOM_API_KEY="${CUSTOM_API_KEY:-$LLM_API_KEY}"
|
| 374 |
CUSTOM_API_TYPE="${CUSTOM_API_TYPE:-openai-completions}"
|
| 375 |
CUSTOM_CONTEXT_WINDOW="${CUSTOM_CONTEXT_WINDOW:-128000}"
|
| 376 |
-
CUSTOM_MAX_TOKENS="${CUSTOM_MAX_TOKENS:-
|
| 377 |
|
| 378 |
if [ -n "$CUSTOM_PROVIDER_NAME" ] || [ -n "$CUSTOM_BASE_URL" ] || [ -n "$CUSTOM_MODEL_ID" ]; then
|
| 379 |
CUSTOM_PROVIDER_NORMALIZED=$(printf '%s' "$CUSTOM_PROVIDER_NAME" | tr '[:upper:]' '[:lower:]')
|
|
@@ -715,6 +717,10 @@ WHATSAPP_CONFIG_ENABLED=false
|
|
| 715 |
if [ "$WHATSAPP_ENABLED_NORMALIZED" = "true" ]; then
|
| 716 |
WHATSAPP_CONFIG_ENABLED=true
|
| 717 |
fi
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
if [ -f "$EXISTING_CONFIG" ]; then
|
| 719 |
echo "Restored config found — patching required fields and runtime channel/plugin toggles..."
|
| 720 |
PATCHED=$(jq \
|
|
@@ -729,6 +735,7 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 729 |
--argjson consoleStyleConfigured "$OPENCLAW_CONSOLE_LOG_STYLE_CONFIGURED" \
|
| 730 |
--argjson whatsappConfigured "$WHATSAPP_ENABLED_CONFIGURED" \
|
| 731 |
--argjson whatsappEnabled "$WHATSAPP_CONFIG_ENABLED" \
|
|
|
|
| 732 |
'(.channels.whatsapp // {}) as $existingWhatsapp
|
| 733 |
| .gateway.auth.token = $token
|
| 734 |
| .agents.defaults.model = $model
|
|
@@ -750,6 +757,13 @@ if [ -f "$EXISTING_CONFIG" ]; then
|
|
| 750 |
| del(.channels.whatsapp)
|
| 751 |
else
|
| 752 |
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
end' \
|
| 754 |
"$EXISTING_CONFIG" 2>/dev/null)
|
| 755 |
|
|
|
|
| 359 |
--arg fileLevel "$OPENCLAW_FILE_LOG_LEVEL" \
|
| 360 |
--arg consoleLevel "$OPENCLAW_CONSOLE_LOG_LEVEL" \
|
| 361 |
--arg consoleStyle "$OPENCLAW_CONSOLE_LOG_STYLE" \
|
| 362 |
+
--arg port "$GATEWAY_PORT" \
|
| 363 |
'.gateway.auth.token = $token
|
| 364 |
| .agents.defaults.model = $model
|
| 365 |
+
| .gateway.port = ($port | tonumber)
|
| 366 |
| .logging.level = $fileLevel
|
| 367 |
| .logging.consoleLevel = $consoleLevel
|
| 368 |
| .logging.consoleStyle = $consoleStyle' <<<"$CONFIG_JSON")
|
|
|
|
| 375 |
CUSTOM_API_KEY="${CUSTOM_API_KEY:-$LLM_API_KEY}"
|
| 376 |
CUSTOM_API_TYPE="${CUSTOM_API_TYPE:-openai-completions}"
|
| 377 |
CUSTOM_CONTEXT_WINDOW="${CUSTOM_CONTEXT_WINDOW:-128000}"
|
| 378 |
+
CUSTOM_MAX_TOKENS="${CUSTOM_MAX_TOKENS:-8192}"
|
| 379 |
|
| 380 |
if [ -n "$CUSTOM_PROVIDER_NAME" ] || [ -n "$CUSTOM_BASE_URL" ] || [ -n "$CUSTOM_MODEL_ID" ]; then
|
| 381 |
CUSTOM_PROVIDER_NORMALIZED=$(printf '%s' "$CUSTOM_PROVIDER_NAME" | tr '[:upper:]' '[:lower:]')
|
|
|
|
| 717 |
if [ "$WHATSAPP_ENABLED_NORMALIZED" = "true" ]; then
|
| 718 |
WHATSAPP_CONFIG_ENABLED=true
|
| 719 |
fi
|
| 720 |
+
TELEGRAM_CONFIG_ENABLED=false
|
| 721 |
+
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
|
| 722 |
+
TELEGRAM_CONFIG_ENABLED=true
|
| 723 |
+
fi
|
| 724 |
if [ -f "$EXISTING_CONFIG" ]; then
|
| 725 |
echo "Restored config found — patching required fields and runtime channel/plugin toggles..."
|
| 726 |
PATCHED=$(jq \
|
|
|
|
| 735 |
--argjson consoleStyleConfigured "$OPENCLAW_CONSOLE_LOG_STYLE_CONFIGURED" \
|
| 736 |
--argjson whatsappConfigured "$WHATSAPP_ENABLED_CONFIGURED" \
|
| 737 |
--argjson whatsappEnabled "$WHATSAPP_CONFIG_ENABLED" \
|
| 738 |
+
--argjson telegramConfigured "$TELEGRAM_CONFIG_ENABLED" \
|
| 739 |
'(.channels.whatsapp // {}) as $existingWhatsapp
|
| 740 |
| .gateway.auth.token = $token
|
| 741 |
| .agents.defaults.model = $model
|
|
|
|
| 757 |
| del(.channels.whatsapp)
|
| 758 |
else
|
| 759 |
.
|
| 760 |
+
end
|
| 761 |
+
| if $telegramConfigured then
|
| 762 |
+
.channels.telegram = (($desired.channels.telegram // {}) * (.channels.telegram // {}))
|
| 763 |
+
| .channels.telegram.botToken = $desired.channels.telegram.botToken
|
| 764 |
+
else
|
| 765 |
+
del(.channels.telegram)
|
| 766 |
+
| .plugins.entries.telegram.enabled = false
|
| 767 |
end' \
|
| 768 |
"$EXISTING_CONFIG" 2>/dev/null)
|
| 769 |
|
wa-guardian.js
CHANGED
|
@@ -125,7 +125,8 @@ async function createConnection() {
|
|
| 125 |
});
|
| 126 |
}
|
| 127 |
|
| 128 |
-
async function callRpc(ws, method, params) {
|
|
|
|
| 129 |
return new Promise((resolve, reject) => {
|
| 130 |
const id = randomUUID();
|
| 131 |
const handler = (data) => {
|
|
@@ -148,7 +149,7 @@ async function callRpc(ws, method, params) {
|
|
| 148 |
reject(sendErr);
|
| 149 |
return;
|
| 150 |
}
|
| 151 |
-
setTimeout(() => { ws.removeListener("message", handler); reject(new Error("RPC Timeout")); },
|
| 152 |
});
|
| 153 |
}
|
| 154 |
|
|
@@ -188,7 +189,7 @@ async function checkStatus() {
|
|
| 188 |
}
|
| 189 |
|
| 190 |
console.log("[guardian] Waiting for pairing completion...");
|
| 191 |
-
const waitRes = await callRpc(ws, "web.login.wait", { timeoutMs: WAIT_TIMEOUT });
|
| 192 |
const result = waitRes.payload || waitRes.result;
|
| 193 |
const message = result?.message || "";
|
| 194 |
const linkedAfter515 = !result?.connected && message.includes("515");
|
|
@@ -202,19 +203,27 @@ async function checkStatus() {
|
|
| 202 |
lastConnectedAt = Date.now();
|
| 203 |
writeStatus({ configured: true, connected: true, pairing: false });
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
if (linkedAfter515) {
|
| 206 |
console.log("[guardian] 515 after scan: credentials saved, reloading config to start WhatsApp...");
|
| 207 |
} else {
|
| 208 |
console.log("[guardian] Pairing completed! Reloading config...");
|
| 209 |
}
|
| 210 |
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
}
|
| 216 |
-
|
| 217 |
-
shouldStop = true;
|
| 218 |
setTimeout(() => process.exit(0), 1000);
|
| 219 |
} else if (!message.includes("No active") && !message.includes("Still waiting")) {
|
| 220 |
console.log(`[guardian] Wait result: ${message}`);
|
|
|
|
| 125 |
});
|
| 126 |
}
|
| 127 |
|
| 128 |
+
async function callRpc(ws, method, params, timeoutMs) {
|
| 129 |
+
const ms = timeoutMs !== undefined ? timeoutMs : 10000; // default 10s for normal calls
|
| 130 |
return new Promise((resolve, reject) => {
|
| 131 |
const id = randomUUID();
|
| 132 |
const handler = (data) => {
|
|
|
|
| 149 |
reject(sendErr);
|
| 150 |
return;
|
| 151 |
}
|
| 152 |
+
setTimeout(() => { ws.removeListener("message", handler); reject(new Error("RPC Timeout")); }, ms);
|
| 153 |
});
|
| 154 |
}
|
| 155 |
|
|
|
|
| 189 |
}
|
| 190 |
|
| 191 |
console.log("[guardian] Waiting for pairing completion...");
|
| 192 |
+
const waitRes = await callRpc(ws, "web.login.wait", { timeoutMs: WAIT_TIMEOUT }, WAIT_TIMEOUT + 5000);
|
| 193 |
const result = waitRes.payload || waitRes.result;
|
| 194 |
const message = result?.message || "";
|
| 195 |
const linkedAfter515 = !result?.connected && message.includes("515");
|
|
|
|
| 203 |
lastConnectedAt = Date.now();
|
| 204 |
writeStatus({ configured: true, connected: true, pairing: false });
|
| 205 |
|
| 206 |
+
// Set shouldStop BEFORE config.apply — gateway restart during apply must not
|
| 207 |
+
// leave guardian running (it would then incorrectly wipe valid credentials).
|
| 208 |
+
shouldStop = true;
|
| 209 |
+
|
| 210 |
if (linkedAfter515) {
|
| 211 |
console.log("[guardian] 515 after scan: credentials saved, reloading config to start WhatsApp...");
|
| 212 |
} else {
|
| 213 |
console.log("[guardian] Pairing completed! Reloading config...");
|
| 214 |
}
|
| 215 |
|
| 216 |
+
try {
|
| 217 |
+
const getRes = await callRpc(ws, "config.get", {});
|
| 218 |
+
if (getRes.payload?.raw && getRes.payload?.hash) {
|
| 219 |
+
await callRpc(ws, "config.apply", { raw: getRes.payload.raw, baseHash: getRes.payload.hash });
|
| 220 |
+
console.log("[guardian] Configuration re-applied.");
|
| 221 |
+
}
|
| 222 |
+
} catch (applyErr) {
|
| 223 |
+
// Gateway restarted during config.apply — that is expected and fine.
|
| 224 |
+
// shouldStop is already true so we will not retry or attempt logout.
|
| 225 |
+
console.log(`[guardian] Config re-apply interrupted (gateway restarting): ${applyErr.message}`);
|
| 226 |
}
|
|
|
|
|
|
|
| 227 |
setTimeout(() => process.exit(0), 1000);
|
| 228 |
} else if (!message.includes("No active") && !message.includes("Still waiting")) {
|
| 229 |
console.log(`[guardian] Wait result: ${message}`);
|