fix(control-ui): preserve loopback client version labels

This commit is contained in:
Peter Steinberger
2026-04-27 11:56:53 +01:00
parent 7ef899ad96
commit 9bc703213b
3 changed files with 67 additions and 1 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- 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.
- 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.
- 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.
- 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.

View File

@@ -1025,6 +1025,33 @@ describe("resolveControlUiClientVersion", () => {
).toBe("2026.3.7");
});
it("returns serverVersion for loopback aliases on the same port", () => {
expect(
resolveControlUiClientVersion({
gatewayUrl: "ws://127.0.0.1:18789",
serverVersion: "2026.4.24",
pageUrl: "http://localhost:18789/chat",
}),
).toBe("2026.4.24");
expect(
resolveControlUiClientVersion({
gatewayUrl: "ws://[::1]:18789",
serverVersion: "2026.4.24",
pageUrl: "http://127.0.0.1:18789/chat",
}),
).toBe("2026.4.24");
});
it("omits serverVersion for loopback aliases on different ports", () => {
expect(
resolveControlUiClientVersion({
gatewayUrl: "ws://127.0.0.1:18789",
serverVersion: "2026.4.24",
pageUrl: "http://localhost:19889/chat",
}),
).toBeUndefined();
});
it("omits serverVersion for cross-origin targets", () => {
expect(
resolveControlUiClientVersion({

View File

@@ -265,7 +265,7 @@ export function resolveControlUiClientVersion(params: {
const page = new URL(pageUrl);
const gateway = new URL(params.gatewayUrl, page);
const allowedProtocols = new Set(["ws:", "wss:", "http:", "https:"]);
if (!allowedProtocols.has(gateway.protocol) || gateway.host !== page.host) {
if (!allowedProtocols.has(gateway.protocol) || !isSameControlUiVersionEndpoint(page, gateway)) {
return undefined;
}
return serverVersion;
@@ -274,6 +274,44 @@ export function resolveControlUiClientVersion(params: {
}
}
function isSameControlUiVersionEndpoint(page: URL, gateway: URL): boolean {
if (gateway.host === page.host) {
return true;
}
return (
isLoopbackHostname(page.hostname) &&
isLoopbackHostname(gateway.hostname) &&
resolveUrlEffectivePort(page) === resolveUrlEffectivePort(gateway)
);
}
function isLoopbackHostname(hostname: string): boolean {
const normalized = hostname.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
return (
normalized === "localhost" ||
normalized === "::1" ||
normalized === "0:0:0:0:0:0:0:1" ||
normalized === "127.0.0.1" ||
normalized.startsWith("127.")
);
}
function resolveUrlEffectivePort(url: URL): string {
if (url.port) {
return url.port;
}
switch (url.protocol) {
case "http:":
case "ws:":
return "80";
case "https:":
case "wss:":
return "443";
default:
return "";
}
}
function normalizeSessionKeyForDefaults(
value: string | undefined,
defaults: SessionDefaultsSnapshot,