Skip to content

Commit 9bc7032

Browse files
committed
fix(control-ui): preserve loopback client version labels
1 parent 7ef899a commit 9bc7032

3 files changed

Lines changed: 67 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
2020

2121
### Fixes
2222

23+
- Control UI/Gateway: preserve WebChat client version labels across localhost, 127.0.0.1, and IPv6 loopback aliases on the same port, avoiding misleading `vcontrol-ui` connection logs while investigating duplicate-message reports. Refs #72753 and #72742. Thanks @LumenFromTheFuture and @allesgutefy.
2324
- Memory-core: run one-shot memory CLI commands through transient builtin and QMD managers so `memory index`, `memory status --index`, and `memory search` no longer start long-lived file watchers that can hit macOS `EMFILE` limits. Fixes #59101; carries forward #49851. Thanks @mbear469210-coder and @maoyuanxue.
2425
- Memory-core: re-resolve the active runtime config whenever `memory_search` or `memory_get` executes, so provider changes made by `config.patch` stop leaving stale embedding backends behind in existing tool instances. Fixes #61098. Thanks @BradGroux and @Linux2010.
2526
- WebChat: keep bare `/new` and `/reset` startup instructions out of visible chat history while preserving `/reset <note>` as user-visible transcript text. Fixes #72369. Thanks @collynes and @haishmg.

ui/src/ui/app-gateway.node.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,6 +1025,33 @@ describe("resolveControlUiClientVersion", () => {
10251025
).toBe("2026.3.7");
10261026
});
10271027

1028+
it("returns serverVersion for loopback aliases on the same port", () => {
1029+
expect(
1030+
resolveControlUiClientVersion({
1031+
gatewayUrl: "ws://127.0.0.1:18789",
1032+
serverVersion: "2026.4.24",
1033+
pageUrl: "http://localhost:18789/chat",
1034+
}),
1035+
).toBe("2026.4.24");
1036+
expect(
1037+
resolveControlUiClientVersion({
1038+
gatewayUrl: "ws://[::1]:18789",
1039+
serverVersion: "2026.4.24",
1040+
pageUrl: "http://127.0.0.1:18789/chat",
1041+
}),
1042+
).toBe("2026.4.24");
1043+
});
1044+
1045+
it("omits serverVersion for loopback aliases on different ports", () => {
1046+
expect(
1047+
resolveControlUiClientVersion({
1048+
gatewayUrl: "ws://127.0.0.1:18789",
1049+
serverVersion: "2026.4.24",
1050+
pageUrl: "http://localhost:19889/chat",
1051+
}),
1052+
).toBeUndefined();
1053+
});
1054+
10281055
it("omits serverVersion for cross-origin targets", () => {
10291056
expect(
10301057
resolveControlUiClientVersion({

ui/src/ui/app-gateway.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export function resolveControlUiClientVersion(params: {
265265
const page = new URL(pageUrl);
266266
const gateway = new URL(params.gatewayUrl, page);
267267
const allowedProtocols = new Set(["ws:", "wss:", "http:", "https:"]);
268-
if (!allowedProtocols.has(gateway.protocol) || gateway.host !== page.host) {
268+
if (!allowedProtocols.has(gateway.protocol) || !isSameControlUiVersionEndpoint(page, gateway)) {
269269
return undefined;
270270
}
271271
return serverVersion;
@@ -274,6 +274,44 @@ export function resolveControlUiClientVersion(params: {
274274
}
275275
}
276276

277+
function isSameControlUiVersionEndpoint(page: URL, gateway: URL): boolean {
278+
if (gateway.host === page.host) {
279+
return true;
280+
}
281+
return (
282+
isLoopbackHostname(page.hostname) &&
283+
isLoopbackHostname(gateway.hostname) &&
284+
resolveUrlEffectivePort(page) === resolveUrlEffectivePort(gateway)
285+
);
286+
}
287+
288+
function isLoopbackHostname(hostname: string): boolean {
289+
const normalized = hostname.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
290+
return (
291+
normalized === "localhost" ||
292+
normalized === "::1" ||
293+
normalized === "0:0:0:0:0:0:0:1" ||
294+
normalized === "127.0.0.1" ||
295+
normalized.startsWith("127.")
296+
);
297+
}
298+
299+
function resolveUrlEffectivePort(url: URL): string {
300+
if (url.port) {
301+
return url.port;
302+
}
303+
switch (url.protocol) {
304+
case "http:":
305+
case "ws:":
306+
return "80";
307+
case "https:":
308+
case "wss:":
309+
return "443";
310+
default:
311+
return "";
312+
}
313+
}
314+
277315
function normalizeSessionKeyForDefaults(
278316
value: string | undefined,
279317
defaults: SessionDefaultsSnapshot,

0 commit comments

Comments
 (0)