fix(web): fall back to profile gateway WS target
This commit is contained in:
parent
8542f07783
commit
0820d7211f
@ -65,6 +65,7 @@ function installMockWsModule() {
|
||||
string,
|
||||
ResFrame | ((frame: ReqFrame) => ResFrame)
|
||||
> = {};
|
||||
static failOpenForUrls = new Set<string>();
|
||||
|
||||
readyState = 0;
|
||||
methods: string[] = [];
|
||||
@ -78,6 +79,10 @@ function installMockWsModule() {
|
||||
this.constructorOpts = opts ?? {};
|
||||
MockNodeWebSocket.instances.push(this);
|
||||
queueMicrotask(() => {
|
||||
if (MockNodeWebSocket.failOpenForUrls.has(this.constructorUrl)) {
|
||||
this.emit("error", new Error("mock gateway open failure"));
|
||||
return;
|
||||
}
|
||||
this.readyState = MockNodeWebSocket.OPEN;
|
||||
this.emit("open");
|
||||
});
|
||||
@ -306,6 +311,35 @@ describe("agent-runner", () => {
|
||||
proc.kill("SIGTERM");
|
||||
});
|
||||
|
||||
it("falls back to config gateway port when env port is stale", async () => {
|
||||
const MockWs = installMockWsModule();
|
||||
delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM;
|
||||
process.env.OPENCLAW_GATEWAY_PORT = "19001";
|
||||
MockWs.failOpenForUrls.add("ws://127.0.0.1:19001/");
|
||||
|
||||
const { spawnAgentProcess } = await import("./agent-runner.js");
|
||||
const proc = spawnAgentProcess("hello");
|
||||
|
||||
await waitFor(
|
||||
() => MockWs.instances.length >= 2,
|
||||
{ attempts: 80, delayMs: 10 },
|
||||
);
|
||||
|
||||
const [primaryAttempt] = MockWs.instances;
|
||||
const fallbackAttempt = MockWs.instances.find(
|
||||
(instance) => instance.constructorUrl !== primaryAttempt?.constructorUrl,
|
||||
);
|
||||
expect(primaryAttempt?.constructorUrl).toBe("ws://127.0.0.1:19001/");
|
||||
expect(fallbackAttempt).toBeDefined();
|
||||
|
||||
await waitFor(
|
||||
() => Boolean(fallbackAttempt?.methods.includes("connect")),
|
||||
{ attempts: 80, delayMs: 10 },
|
||||
);
|
||||
|
||||
proc.kill("SIGTERM");
|
||||
});
|
||||
|
||||
it("does not use child_process.spawn for WebSocket transport", async () => {
|
||||
installMockWsModule();
|
||||
delete process.env.IRONCLAW_WEB_FORCE_LEGACY_STREAM;
|
||||
|
||||
@ -266,7 +266,7 @@ function readGatewayConfigFromStateDir(
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveGatewayConnectionSettings(): GatewayConnectionSettings {
|
||||
function resolveGatewayConnectionCandidates(): GatewayConnectionSettings[] {
|
||||
const envUrl = process.env.OPENCLAW_GATEWAY_URL?.trim();
|
||||
const envToken = process.env.OPENCLAW_GATEWAY_TOKEN?.trim();
|
||||
const envPassword = process.env.OPENCLAW_GATEWAY_PASSWORD?.trim();
|
||||
@ -278,31 +278,58 @@ function resolveGatewayConnectionSettings(): GatewayConnectionSettings {
|
||||
const remote = asRecord(gateway?.remote);
|
||||
const auth = asRecord(gateway?.auth);
|
||||
|
||||
const gatewayPort = envPort ?? parsePort(gateway?.port) ?? DEFAULT_GATEWAY_PORT;
|
||||
const configGatewayPort = parsePort(gateway?.port) ?? DEFAULT_GATEWAY_PORT;
|
||||
const gatewayPort = envPort ?? configGatewayPort;
|
||||
const gatewayMode =
|
||||
typeof gateway?.mode === "string" ? gateway.mode.trim().toLowerCase() : "";
|
||||
const remoteUrl =
|
||||
typeof remote?.url === "string" ? remote.url.trim() : undefined;
|
||||
const useRemote = !envUrl && gatewayMode === "remote" && Boolean(remoteUrl);
|
||||
|
||||
const rawUrl = envUrl || (useRemote ? remoteUrl! : `ws://127.0.0.1:${gatewayPort}`);
|
||||
const url = normalizeWsUrl(rawUrl, gatewayPort);
|
||||
|
||||
const token =
|
||||
envToken ||
|
||||
const configToken =
|
||||
(useRemote && typeof remote?.token === "string"
|
||||
? remote.token.trim()
|
||||
: undefined) ||
|
||||
(typeof auth?.token === "string" ? auth.token.trim() : undefined);
|
||||
|
||||
const password =
|
||||
envPassword ||
|
||||
const configPassword =
|
||||
(useRemote && typeof remote?.password === "string"
|
||||
? remote.password.trim()
|
||||
: undefined) ||
|
||||
(typeof auth?.password === "string" ? auth.password.trim() : undefined);
|
||||
|
||||
return { url, token, password };
|
||||
const primaryRawUrl = envUrl || (useRemote ? remoteUrl! : `ws://127.0.0.1:${gatewayPort}`);
|
||||
const primary: GatewayConnectionSettings = {
|
||||
url: normalizeWsUrl(primaryRawUrl, gatewayPort),
|
||||
token: envToken || configToken,
|
||||
password: envPassword || configPassword,
|
||||
};
|
||||
|
||||
const configRawUrl = useRemote
|
||||
? remoteUrl!
|
||||
: `ws://127.0.0.1:${configGatewayPort}`;
|
||||
const fallback: GatewayConnectionSettings = {
|
||||
url: normalizeWsUrl(configRawUrl, configGatewayPort),
|
||||
token: configToken,
|
||||
password: configPassword,
|
||||
};
|
||||
|
||||
const candidates = [primary];
|
||||
if (fallback.url !== primary.url) {
|
||||
candidates.push(fallback);
|
||||
}
|
||||
|
||||
const deduped: GatewayConnectionSettings[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const candidate of candidates) {
|
||||
const key = `${candidate.url}|${candidate.token ?? ""}|${candidate.password ?? ""}`;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
deduped.push(candidate);
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
export function buildConnectParams(
|
||||
@ -540,6 +567,26 @@ class GatewayWsClient {
|
||||
}
|
||||
}
|
||||
|
||||
async function openGatewayClient(
|
||||
onEvent: (frame: GatewayEventFrame) => void,
|
||||
onClose: (code: number, reason: string) => void,
|
||||
): Promise<{ client: GatewayWsClient; settings: GatewayConnectionSettings }> {
|
||||
const candidates = resolveGatewayConnectionCandidates();
|
||||
let lastError: Error | null = null;
|
||||
for (const settings of candidates) {
|
||||
const client = new GatewayWsClient(settings, onEvent, onClose);
|
||||
try {
|
||||
await client.open();
|
||||
return { client, settings };
|
||||
} catch (error) {
|
||||
lastError =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
throw lastError ?? new Error("Gateway WebSocket connection failed");
|
||||
}
|
||||
|
||||
class GatewayProcessHandle
|
||||
extends EventEmitter
|
||||
implements AgentProcessHandle
|
||||
@ -570,13 +617,11 @@ class GatewayProcessHandle
|
||||
|
||||
private async start(): Promise<void> {
|
||||
try {
|
||||
const settings = resolveGatewayConnectionSettings();
|
||||
this.client = new GatewayWsClient(
|
||||
settings,
|
||||
const { client, settings } = await openGatewayClient(
|
||||
(frame) => this.handleGatewayEvent(frame),
|
||||
(code, reason) => this.handleSocketClose(code, reason),
|
||||
);
|
||||
await this.client.open();
|
||||
this.client = client;
|
||||
const connectRes = await this.client.request(
|
||||
"connect",
|
||||
buildConnectParams(settings),
|
||||
@ -844,12 +889,13 @@ export async function callGatewayRpc(
|
||||
params?: Record<string, unknown>,
|
||||
options?: { timeoutMs?: number },
|
||||
): Promise<GatewayResFrame> {
|
||||
const settings = resolveGatewayConnectionSettings();
|
||||
let closed = false;
|
||||
const client = new GatewayWsClient(settings, () => {}, () => {
|
||||
closed = true;
|
||||
});
|
||||
await client.open();
|
||||
const { client, settings } = await openGatewayClient(
|
||||
() => {},
|
||||
() => {
|
||||
closed = true;
|
||||
},
|
||||
);
|
||||
try {
|
||||
const connect = await client.request(
|
||||
"connect",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user