mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-25 01:03:05 +00:00
@@ -244,13 +244,14 @@ describe("daemon-cli coverage", () => {
|
||||
expect(inspectPortUsage).toHaveBeenCalledWith(19001);
|
||||
|
||||
const parsed = parseFirstJsonRuntimeLine<{
|
||||
gateway?: { port?: number; portSource?: string; probeUrl?: string };
|
||||
gateway?: { port?: number; portSource?: string; probeUrl?: string; version?: string | null };
|
||||
config?: { mismatch?: boolean };
|
||||
rpc?: { url?: string; ok?: boolean };
|
||||
}>();
|
||||
expect(parsed.gateway?.port).toBe(19001);
|
||||
expect(parsed.gateway?.portSource).toBe("service args");
|
||||
expect(parsed.gateway?.probeUrl).toBe("ws://127.0.0.1:19001");
|
||||
expect(parsed.gateway?.version).toBeNull();
|
||||
expect(parsed.config?.mismatch).toBe(true);
|
||||
expect(parsed.rpc?.url).toBe("ws://127.0.0.1:19001");
|
||||
expect(parsed.rpc?.ok).toBe(true);
|
||||
|
||||
@@ -110,6 +110,7 @@ describe("probeGatewayStatus", () => {
|
||||
}
|
||||
expect(result.server?.version).toBe("2026.5.6");
|
||||
expect(result.server?.connId).toBe("conn-1");
|
||||
expect(result.version).toBe("2026.5.6");
|
||||
});
|
||||
|
||||
it("uses a real status RPC when requireRpc is enabled", async () => {
|
||||
@@ -243,6 +244,69 @@ describe("probeGatewayStatus", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses status.runtimeVersion when read-probe handshake metadata is unavailable", async () => {
|
||||
callGatewayMock.mockReset();
|
||||
probeGatewayMock.mockReset();
|
||||
callGatewayMock.mockResolvedValueOnce({ runtimeVersion: "2026.4.24", status: "ok" });
|
||||
probeGatewayMock.mockRejectedValueOnce(new Error("probe timed out after status"));
|
||||
|
||||
const result = await probeGatewayStatus({
|
||||
url: "ws://127.0.0.1:19191",
|
||||
token: "temp-token",
|
||||
timeoutMs: 5_000,
|
||||
requireRpc: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
kind: "read",
|
||||
capability: "read_only",
|
||||
auth: undefined,
|
||||
version: "2026.4.24",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers read-probe server metadata over status.runtimeVersion", async () => {
|
||||
callGatewayMock.mockReset();
|
||||
probeGatewayMock.mockReset();
|
||||
callGatewayMock.mockResolvedValueOnce({ runtimeVersion: "2026.4.23", status: "ok" });
|
||||
probeGatewayMock.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
auth: {
|
||||
role: "operator",
|
||||
scopes: ["operator.read"],
|
||||
capability: "read_only",
|
||||
},
|
||||
server: {
|
||||
version: "2026.4.24",
|
||||
connId: "conn-1",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await probeGatewayStatus({
|
||||
url: "ws://127.0.0.1:19191",
|
||||
token: "temp-token",
|
||||
timeoutMs: 5_000,
|
||||
requireRpc: true,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
kind: "read",
|
||||
capability: "read_only",
|
||||
auth: {
|
||||
role: "operator",
|
||||
scopes: ["operator.read"],
|
||||
capability: "read_only",
|
||||
},
|
||||
server: {
|
||||
version: "2026.4.24",
|
||||
connId: "conn-1",
|
||||
},
|
||||
version: "2026.4.24",
|
||||
});
|
||||
});
|
||||
|
||||
it("surfaces probe close details when the handshake fails", async () => {
|
||||
callGatewayMock.mockReset();
|
||||
probeGatewayMock.mockReset();
|
||||
|
||||
@@ -34,6 +34,16 @@ function resolveGatewayStatusProbeDetails(result: GatewayStatusProbeResult) {
|
||||
return "authProbe" in result ? result.authProbe : result;
|
||||
}
|
||||
|
||||
function readRuntimeVersionFromStatusPayload(payload: unknown): string | null {
|
||||
if (!payload || typeof payload !== "object") {
|
||||
return null;
|
||||
}
|
||||
const runtimeVersion = (payload as { runtimeVersion?: unknown }).runtimeVersion;
|
||||
return typeof runtimeVersion === "string" && runtimeVersion.trim().length > 0
|
||||
? runtimeVersion.trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
export async function probeGatewayStatus(opts: {
|
||||
url: string;
|
||||
token?: string;
|
||||
@@ -48,6 +58,7 @@ export async function probeGatewayStatus(opts: {
|
||||
}) {
|
||||
const kind = (opts.requireRpc ? "read" : "connect") satisfies GatewayStatusProbeKind;
|
||||
try {
|
||||
let statusRuntimeVersion: string | null = null;
|
||||
const result = await withProgress<GatewayStatusProbeResult>(
|
||||
{
|
||||
label: "Checking gateway status...",
|
||||
@@ -71,7 +82,7 @@ export async function probeGatewayStatus(opts: {
|
||||
};
|
||||
if (opts.requireRpc) {
|
||||
const { callGateway } = await import("../../gateway/call.js");
|
||||
await callGateway({
|
||||
const statusPayload = await callGateway({
|
||||
url: opts.url,
|
||||
token: opts.token,
|
||||
password: opts.password,
|
||||
@@ -81,6 +92,7 @@ export async function probeGatewayStatus(opts: {
|
||||
timeoutMs: opts.timeoutMs,
|
||||
...(opts.configPath ? { configPath: opts.configPath } : {}),
|
||||
});
|
||||
statusRuntimeVersion = readRuntimeVersionFromStatusPayload(statusPayload);
|
||||
const authProbe = await probeGateway(probeOpts).catch(() => null);
|
||||
return { ok: true as const, authProbe };
|
||||
}
|
||||
@@ -91,6 +103,7 @@ export async function probeGatewayStatus(opts: {
|
||||
const auth = probeDetails?.auth;
|
||||
const server = probeDetails?.server;
|
||||
const serverSummary = server ? { server } : {};
|
||||
const version = server?.version ?? ("authProbe" in result ? statusRuntimeVersion : null);
|
||||
if (result.ok) {
|
||||
return {
|
||||
ok: true,
|
||||
@@ -103,6 +116,7 @@ export async function probeGatewayStatus(opts: {
|
||||
: auth?.capability,
|
||||
auth,
|
||||
...serverSummary,
|
||||
...(version != null ? { version } : {}),
|
||||
} as const;
|
||||
}
|
||||
return {
|
||||
@@ -111,6 +125,7 @@ export async function probeGatewayStatus(opts: {
|
||||
capability: auth?.capability,
|
||||
auth,
|
||||
...serverSummary,
|
||||
...(version != null ? { version } : {}),
|
||||
error: resolveProbeFailureMessage(result),
|
||||
} as const;
|
||||
} catch (err) {
|
||||
|
||||
@@ -17,6 +17,7 @@ const callGatewayStatusProbe = vi.fn<
|
||||
url?: string;
|
||||
error?: string | null;
|
||||
server?: { version?: string | null; connId?: string | null };
|
||||
version?: string | null;
|
||||
}>
|
||||
>(async (_opts?: unknown) => ({
|
||||
ok: true,
|
||||
@@ -272,6 +273,7 @@ describe("gatherDaemonStatus", () => {
|
||||
expect(probeInput.token).toBe("daemon-token");
|
||||
expect(status.gateway?.probeUrl).toBe("wss://127.0.0.1:19001");
|
||||
expect(status.gateway?.tlsEnabled).toBe(true);
|
||||
expect(status.gateway?.version).toBe("2026.5.6");
|
||||
expect(status.rpc?.url).toBe("wss://127.0.0.1:19001");
|
||||
expect(status.rpc?.ok).toBe(true);
|
||||
expect(status.rpc?.server).toEqual({ version: "2026.5.6", connId: "conn-1" });
|
||||
@@ -282,6 +284,25 @@ describe("gatherDaemonStatus", () => {
|
||||
expect(inspectGatewayRestart).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to probe version when server metadata is unavailable", async () => {
|
||||
callGatewayStatusProbe.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
url: "ws://127.0.0.1:19001",
|
||||
error: null,
|
||||
version: "2026.5.7",
|
||||
});
|
||||
|
||||
const status = await gatherDaemonStatus({
|
||||
rpc: {},
|
||||
probe: true,
|
||||
deep: false,
|
||||
});
|
||||
|
||||
expect(status.gateway?.version).toBe("2026.5.7");
|
||||
expect(status.rpc?.version).toBe("2026.5.7");
|
||||
expect(status.rpc?.server).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards requireRpc and configPath to the daemon probe", async () => {
|
||||
await gatherDaemonStatus({
|
||||
rpc: {},
|
||||
|
||||
@@ -60,6 +60,7 @@ type GatewayStatusSummary = {
|
||||
portSource: "service args" | "env/config";
|
||||
probeUrl: string;
|
||||
probeNote?: string;
|
||||
version?: string | null;
|
||||
};
|
||||
|
||||
type PortStatusSummary = {
|
||||
@@ -314,6 +315,7 @@ export type DaemonStatus = {
|
||||
version?: string | null;
|
||||
connId?: string | null;
|
||||
};
|
||||
version?: string | null;
|
||||
error?: string;
|
||||
url?: string;
|
||||
authWarning?: string;
|
||||
@@ -622,6 +624,11 @@ export async function gatherDaemonStatus(
|
||||
)
|
||||
.catch(() => undefined)
|
||||
: undefined;
|
||||
const gatewayVersion = opts.probe
|
||||
? ((rpc && "server" in rpc ? rpc.server?.version : undefined) ??
|
||||
(rpc && "version" in rpc ? rpc.version : undefined) ??
|
||||
null)
|
||||
: undefined;
|
||||
|
||||
let lastError: string | undefined;
|
||||
if (loaded && runtime?.status === "running" && portStatus && portStatus.status !== "busy") {
|
||||
@@ -647,7 +654,14 @@ export async function gatherDaemonStatus(
|
||||
daemon: daemonConfigSummary,
|
||||
...(configMismatch ? { mismatch: true } : {}),
|
||||
},
|
||||
gateway,
|
||||
gateway: {
|
||||
...gateway,
|
||||
...(opts.probe
|
||||
? {
|
||||
version: gatewayVersion,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
port: portStatus,
|
||||
...(portCliStatus ? { portCli: portCliStatus } : {}),
|
||||
...(establishedClients ? { connections: establishedClients } : {}),
|
||||
|
||||
@@ -321,6 +321,44 @@ describe("printDaemonStatus", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prints gateway version from gathered gateway status when probe server metadata is absent", () => {
|
||||
printDaemonStatus(
|
||||
{
|
||||
cli: {
|
||||
version: "2026.4.23",
|
||||
entrypoint: "/usr/local/bin/openclaw",
|
||||
},
|
||||
service: {
|
||||
label: "LaunchAgent",
|
||||
loaded: true,
|
||||
loadedText: "loaded",
|
||||
notLoadedText: "not loaded",
|
||||
runtime: { status: "running", pid: 8000 },
|
||||
},
|
||||
gateway: {
|
||||
bindMode: "loopback",
|
||||
bindHost: "127.0.0.1",
|
||||
port: 18789,
|
||||
portSource: "env/config",
|
||||
probeUrl: "ws://127.0.0.1:18789",
|
||||
version: "2026.5.7",
|
||||
},
|
||||
rpc: {
|
||||
ok: true,
|
||||
kind: "read",
|
||||
capability: "read_only",
|
||||
url: "ws://127.0.0.1:18789",
|
||||
version: "2026.5.7",
|
||||
},
|
||||
extraServices: [],
|
||||
},
|
||||
{ json: false },
|
||||
);
|
||||
|
||||
expectMockLineContains(runtime.log, "Gateway version: 2026.5.7");
|
||||
expectMockLineContains(runtime.error, "this OpenClaw command is version 2026.4.23");
|
||||
});
|
||||
|
||||
it("prints restart handoff diagnostics when deep status gathered one", () => {
|
||||
printDaemonStatus(
|
||||
{
|
||||
|
||||
@@ -220,7 +220,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
spacer();
|
||||
}
|
||||
|
||||
const gatewayVersion = rpc?.server?.version?.trim();
|
||||
const gatewayVersion = rpc?.server?.version?.trim() || status.gateway?.version?.trim();
|
||||
const cliVersionLine = formatCliVersionLine(status.cli);
|
||||
if (gatewayVersion) {
|
||||
if (cliVersionLine) {
|
||||
|
||||
Reference in New Issue
Block a user