Fix local gateway detail probes on loopback
This commit is contained in:
parent
b49e1386d0
commit
7bbaad2eea
@ -5,6 +5,7 @@ import {
|
||||
isProbeReachable,
|
||||
isScopeLimitedProbeFailure,
|
||||
renderProbeSummaryLine,
|
||||
resolveProbeBudgetMs,
|
||||
resolveAuthForTarget,
|
||||
} from "./helpers.js";
|
||||
|
||||
@ -273,3 +274,12 @@ describe("probe reachability classification", () => {
|
||||
expect(renderProbeSummaryLine(probe, false)).toContain("RPC: failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveProbeBudgetMs", () => {
|
||||
it("gives local loopback probes enough time for detail RPCs", () => {
|
||||
expect(resolveProbeBudgetMs(10_000, "localLoopback")).toBe(3000);
|
||||
expect(resolveProbeBudgetMs(1200, "localLoopback")).toBe(1200);
|
||||
expect(resolveProbeBudgetMs(10_000, "sshTunnel")).toBe(2000);
|
||||
expect(resolveProbeBudgetMs(10_000, "explicit")).toBe(1500);
|
||||
});
|
||||
});
|
||||
|
||||
@ -118,7 +118,9 @@ export function resolveTargets(cfg: OpenClawConfig, explicitUrl?: string): Gatew
|
||||
|
||||
export function resolveProbeBudgetMs(overallMs: number, kind: TargetKind): number {
|
||||
if (kind === "localLoopback") {
|
||||
return Math.min(800, overallMs);
|
||||
// Full localhost detail probes can take longer than the old 800ms budget,
|
||||
// especially when they exercise status + heartbeat + presence RPCs.
|
||||
return Math.min(3000, overallMs);
|
||||
}
|
||||
if (kind === "sshTunnel") {
|
||||
return Math.min(2000, overallMs);
|
||||
|
||||
@ -28,6 +28,7 @@ let startMode: StartMode = "hello";
|
||||
let closeCode = 1006;
|
||||
let closeReason = "";
|
||||
let helloMethods: string[] | undefined = ["health", "secrets.resolve"];
|
||||
const deviceIdentityMarker = { id: "test-device-identity" };
|
||||
|
||||
vi.mock("./client.js", () => ({
|
||||
describeGatewayCloseCode: (code: number) => {
|
||||
@ -73,6 +74,10 @@ vi.mock("./client.js", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../infra/device-identity.js", () => ({
|
||||
loadOrCreateDeviceIdentity: () => deviceIdentityMarker,
|
||||
}));
|
||||
|
||||
const { buildGatewayConnectionDetails, callGateway, callGatewayCli, callGatewayScoped } =
|
||||
await import("./call.js");
|
||||
|
||||
@ -209,7 +214,7 @@ describe("callGateway url resolution", () => {
|
||||
expect(lastClientOptions?.token).toBe("explicit-token");
|
||||
});
|
||||
|
||||
it("does not attach device identity for local loopback shared-token auth", async () => {
|
||||
it("attaches device identity for local loopback shared-token auth", async () => {
|
||||
setLocalLoopbackGatewayConfig();
|
||||
|
||||
await callGateway({
|
||||
@ -219,7 +224,7 @@ describe("callGateway url resolution", () => {
|
||||
|
||||
expect(lastClientOptions?.url).toBe("ws://127.0.0.1:18789");
|
||||
expect(lastClientOptions?.token).toBe("explicit-token");
|
||||
expect(lastClientOptions?.deviceIdentity).toBeUndefined();
|
||||
expect(lastClientOptions?.deviceIdentity).toEqual(deviceIdentityMarker);
|
||||
});
|
||||
|
||||
it("uses OPENCLAW_GATEWAY_URL env override in remote mode when remote URL is missing", async () => {
|
||||
|
||||
@ -81,20 +81,15 @@ export type GatewayConnectionDetails = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
function shouldAttachDeviceIdentityForGatewayCall(params: {
|
||||
function shouldAttachDeviceIdentityForGatewayCall(_params: {
|
||||
url: string;
|
||||
token?: string;
|
||||
password?: string;
|
||||
}): boolean {
|
||||
if (!(params.token || params.password)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(params.url);
|
||||
return !["127.0.0.1", "::1", "localhost"].includes(parsed.hostname);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
// Even when local CLI calls authenticate with a shared gateway token/password,
|
||||
// we still want to attach device identity so paired operator scopes remain
|
||||
// available for detail RPCs such as status / system-presence / last-heartbeat.
|
||||
return true;
|
||||
}
|
||||
|
||||
export type ExplicitGatewayAuth = {
|
||||
|
||||
@ -51,7 +51,7 @@ describe("probeGateway", () => {
|
||||
});
|
||||
|
||||
expect(gatewayClientState.options?.scopes).toEqual(["operator.read"]);
|
||||
expect(gatewayClientState.options?.deviceIdentity).toBeNull();
|
||||
expect(gatewayClientState.options?.deviceIdentity).toBeUndefined();
|
||||
expect(gatewayClientState.requests).toEqual([
|
||||
"health",
|
||||
"status",
|
||||
|
||||
@ -4,7 +4,6 @@ import type { SystemPresence } from "../infra/system-presence.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { GatewayClient } from "./client.js";
|
||||
import { READ_SCOPE } from "./method-scopes.js";
|
||||
import { isLoopbackHost } from "./net.js";
|
||||
|
||||
export type GatewayProbeAuth = {
|
||||
token?: string;
|
||||
@ -41,14 +40,6 @@ export async function probeGateway(opts: {
|
||||
let connectError: string | null = null;
|
||||
let close: GatewayProbeClose | null = null;
|
||||
|
||||
const disableDeviceIdentity = (() => {
|
||||
try {
|
||||
return isLoopbackHost(new URL(opts.url).hostname);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
return await new Promise<GatewayProbeResult>((resolve) => {
|
||||
let settled = false;
|
||||
const settle = (result: Omit<GatewayProbeResult, "url">) => {
|
||||
@ -70,7 +61,6 @@ export async function probeGateway(opts: {
|
||||
clientVersion: "dev",
|
||||
mode: GATEWAY_CLIENT_MODES.PROBE,
|
||||
instanceId,
|
||||
deviceIdentity: disableDeviceIdentity ? null : undefined,
|
||||
onConnectError: (err) => {
|
||||
connectError = formatErrorMessage(err);
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user