mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-27 22:25:52 +00:00
fix(gateway): fallback health check to CLI for OpenClaw 2026.3
This commit is contained in:
+123
-20
@@ -1,10 +1,69 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
|
||||
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || path.join(process.env.HOME || "", ".openclaw");
|
||||
const CONFIG_PATH = path.join(OPENCLAW_HOME, "openclaw.json");
|
||||
const DEGRADED_LATENCY_MS = 1500;
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function parseJsonFromMixedOutput(output: string): any {
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
if (output[i] !== "{") continue;
|
||||
let depth = 0;
|
||||
let inString = false;
|
||||
let escaped = false;
|
||||
for (let j = i; j < output.length; j++) {
|
||||
const ch = output[j];
|
||||
if (inString) {
|
||||
if (escaped) escaped = false;
|
||||
else if (ch === "\\") escaped = true;
|
||||
else if (ch === "\"") inString = false;
|
||||
continue;
|
||||
}
|
||||
if (ch === "\"") {
|
||||
inString = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === "{") depth++;
|
||||
else if (ch === "}") {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
const candidate = output.slice(i, j + 1).trim();
|
||||
try {
|
||||
return JSON.parse(candidate);
|
||||
} catch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error("Failed to parse JSON from openclaw gateway status output");
|
||||
}
|
||||
|
||||
async function probeGatewayViaCli(token: string, timeoutMs = 5000): Promise<{ ok: boolean; error?: string }> {
|
||||
try {
|
||||
const args = ["gateway", "status", "--json", "--timeout", String(timeoutMs)];
|
||||
if (token) args.push("--token", token);
|
||||
const { stdout, stderr } = await execFileAsync("openclaw", args, {
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
env: { ...process.env, FORCE_COLOR: "0" },
|
||||
});
|
||||
const parsed = parseJsonFromMixedOutput(`${stdout}\n${stderr || ""}`);
|
||||
const ok = parsed?.rpc?.ok === true;
|
||||
const error =
|
||||
typeof parsed?.rpc?.error === "string" && parsed.rpc.error.trim()
|
||||
? parsed.rpc.error.trim()
|
||||
: undefined;
|
||||
return { ok, error };
|
||||
} catch (err: any) {
|
||||
const message = err?.message || "Failed to run openclaw gateway status";
|
||||
return { ok: false, error: message };
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const startedAt = Date.now();
|
||||
@@ -21,41 +80,85 @@ export async function GET() {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const resp = await fetch(url, { headers, signal: controller.signal });
|
||||
const resp = await fetch(url, { headers, signal: controller.signal, cache: "no-store" });
|
||||
clearTimeout(timeout);
|
||||
const checkedAt = Date.now();
|
||||
const responseMs = checkedAt - startedAt;
|
||||
|
||||
if (!resp.ok) {
|
||||
if (resp.ok) {
|
||||
const checkedAt = Date.now();
|
||||
const responseMs = checkedAt - startedAt;
|
||||
const data = await resp.json().catch(() => null);
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
error: `HTTP ${resp.status}`,
|
||||
status: "down",
|
||||
ok: true,
|
||||
data,
|
||||
status: responseMs > DEGRADED_LATENCY_MS ? "degraded" : "healthy",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
webUrl: `http://localhost:${port}/chat${token ? '?token=' + encodeURIComponent(token) : ''}`,
|
||||
});
|
||||
}
|
||||
|
||||
const data = await resp.json().catch(() => null);
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
data,
|
||||
status: responseMs > DEGRADED_LATENCY_MS ? "degraded" : "healthy",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
webUrl: `http://localhost:${port}/chat${token ? '?token=' + encodeURIComponent(token) : ''}`,
|
||||
});
|
||||
} catch (err: any) {
|
||||
// OpenClaw 2026.3.x no longer serves /api/health; fallback to CLI probe.
|
||||
const cli = await probeGatewayViaCli(token, 5000);
|
||||
const checkedAt = Date.now();
|
||||
const responseMs = checkedAt - startedAt;
|
||||
const msg = err.cause?.code === "ECONNREFUSED"
|
||||
if (cli.ok) {
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
data: null,
|
||||
status: "healthy",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
webUrl: `http://localhost:${port}/chat${token ? '?token=' + encodeURIComponent(token) : ''}`,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
error: cli.error || `HTTP ${resp.status}`,
|
||||
status: "down",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
});
|
||||
} catch (err: any) {
|
||||
// If HTTP probe fails due transport/runtime issues, attempt CLI probe before declaring down.
|
||||
const raw = err.cause?.code === "ECONNREFUSED"
|
||||
? "Gateway 未运行"
|
||||
: err.name === "AbortError"
|
||||
? "请求超时"
|
||||
: err.message;
|
||||
const token = (() => {
|
||||
try {
|
||||
const rawCfg = fs.readFileSync(CONFIG_PATH, "utf-8");
|
||||
const cfg = JSON.parse(rawCfg);
|
||||
return cfg.gateway?.auth?.token || "";
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
})();
|
||||
const cli = await probeGatewayViaCli(token, 5000);
|
||||
const checkedAt = Date.now();
|
||||
const responseMs = checkedAt - startedAt;
|
||||
if (cli.ok) {
|
||||
const port = (() => {
|
||||
try {
|
||||
const rawCfg = fs.readFileSync(CONFIG_PATH, "utf-8");
|
||||
const cfg = JSON.parse(rawCfg);
|
||||
return cfg.gateway?.port || 18789;
|
||||
} catch {
|
||||
return 18789;
|
||||
}
|
||||
})();
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
data: null,
|
||||
status: "healthy",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
webUrl: `http://localhost:${port}/chat${token ? '?token=' + encodeURIComponent(token) : ''}`,
|
||||
});
|
||||
}
|
||||
return NextResponse.json({
|
||||
ok: false,
|
||||
error: msg,
|
||||
error: cli.error || raw,
|
||||
status: "down",
|
||||
checkedAt,
|
||||
responseMs,
|
||||
|
||||
Reference in New Issue
Block a user