From de04e21aa14096edb1e417b35c26a248594eb814 Mon Sep 17 00:00:00 2001 From: zhi Date: Thu, 26 Feb 2026 08:45:37 +0000 Subject: [PATCH] fix: wait for gateway ready before post-install model validation Root cause: gateway restart is async (systemd), but validateNoReplyModelAvailable() ran immediately after, hitting a race condition where the new gateway process hadn't finished initializing yet. This caused 'model not listed' validation failures, triggering config rollback even though the config was correct. Changes: - Add waitForGatewayReady() that polls 'openclaw gateway status' for RPC probe - Add retry loop (5 attempts, 2s interval) to validateNoReplyModelAvailable() - Fix CONFIG.example.json: contextWindow 4096->200000, maxTokens 64->8192 (OpenClaw requires minimum 16000 contextWindow) --- docs/CONFIG.example.json | 4 +- no-reply-api/package-lock.json | 12 ++++++ scripts/install-whispergate-openclaw.mjs | 48 +++++++++++++++++++----- 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 no-reply-api/package-lock.json diff --git a/docs/CONFIG.example.json b/docs/CONFIG.example.json index 0c887fb..d14f120 100644 --- a/docs/CONFIG.example.json +++ b/docs/CONFIG.example.json @@ -40,8 +40,8 @@ "reasoning": false, "input": ["text"], "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }, - "contextWindow": 4096, - "maxTokens": 64 + "contextWindow": 200000, + "maxTokens": 8192 } ] } diff --git a/no-reply-api/package-lock.json b/no-reply-api/package-lock.json new file mode 100644 index 0000000..3e29cb5 --- /dev/null +++ b/no-reply-api/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "whispergate-no-reply-api", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "whispergate-no-reply-api", + "version": "0.1.0" + } + } +} diff --git a/scripts/install-whispergate-openclaw.mjs b/scripts/install-whispergate-openclaw.mjs index 7cc3e7b..5adf132 100755 --- a/scripts/install-whispergate-openclaw.mjs +++ b/scripts/install-whispergate-openclaw.mjs @@ -45,17 +45,41 @@ function runOpenclaw(args, { allowFail = false } = {}) { } } -function validateNoReplyModelAvailable() { +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function waitForGatewayReady(maxWaitMs = 30000) { + const start = Date.now(); + const interval = 1500; + while (Date.now() - start < maxWaitMs) { + const probe = runOpenclaw(["gateway", "status"], { allowFail: true }) || ""; + if (probe.includes("RPC probe: ok")) return true; + console.log(`[whispergate] waiting for gateway ready... (${Math.round((Date.now() - start) / 1000)}s)`); + await sleep(interval); + } + return false; +} + +async function validateNoReplyModelAvailable(retries = 5, delayMs = 2000) { const modelRef = `${NO_REPLY_PROVIDER_ID}/${NO_REPLY_MODEL_ID}`; - const list = runOpenclaw(["models", "list"], { allowFail: true }) || ""; - if (!list.includes(modelRef)) { - throw new Error(`post-install validation failed: model not listed: ${modelRef}`); + + for (let attempt = 1; attempt <= retries; attempt++) { + const list = runOpenclaw(["models", "list"], { allowFail: true }) || ""; + if (list.includes(modelRef)) { + const status = runOpenclaw(["models", "status", "--json"], { allowFail: true }) || ""; + if (status.includes(NO_REPLY_PROVIDER_ID)) { + console.log(`[whispergate] model validation passed on attempt ${attempt}`); + return; + } + } + if (attempt < retries) { + console.log(`[whispergate] model not yet visible (attempt ${attempt}/${retries}), retrying in ${delayMs}ms...`); + await sleep(delayMs); + } } - const status = runOpenclaw(["models", "status", "--json"], { allowFail: true }) || ""; - if (!status.includes(NO_REPLY_PROVIDER_ID)) { - throw new Error(`post-install validation failed: provider not visible in models status: ${NO_REPLY_PROVIDER_ID}`); - } + throw new Error(`post-install validation failed: model not listed after ${retries} attempts: ${modelRef}`); } function getJson(pathKey) { @@ -175,7 +199,13 @@ if (mode === "install") { setJson(PATH_PROVIDERS, providers); runOpenclaw(["gateway", "restart"]); - validateNoReplyModelAvailable(); + console.log("[whispergate] gateway restart issued, waiting for ready..."); + const ready = await waitForGatewayReady(); + if (!ready) { + throw new Error("gateway did not become ready within 30s after restart"); + } + console.log("[whispergate] gateway ready, validating model..."); + await validateNoReplyModelAvailable(); const after = { [PATH_PLUGINS_LOAD]: getJson(PATH_PLUGINS_LOAD),