fix(gateway): pass actual version to Control UI client instead of "dev"

The GatewayClient, CLI WS client, and browser Control UI all sent
"dev" as their clientVersion during handshake, making it impossible
to distinguish builds in gateway logs and health snapshots.

- GatewayClient and CLI WS client now use the resolved VERSION constant
- Control UI reads serverVersion from the bootstrap endpoint and
  forwards it when connecting
- Bootstrap contract extended with serverVersion field

Closes #35209
This commit is contained in:
SidQin-cyber
2026-03-05 12:29:57 +08:00
committed by Tak Hoffman
parent d9b69a6145
commit 2c87c5b1ba
10 changed files with 20 additions and 3 deletions

View File

@@ -17,6 +17,7 @@ import {
type GatewayClientMode,
type GatewayClientName,
} from "../utils/message-channel.js";
import { VERSION } from "../version.js";
import { GatewayClient } from "./client.js";
import { resolveGatewayCredentialsFromConfig } from "./credentials.js";
import {
@@ -628,7 +629,7 @@ async function executeGatewayRequestWithScopes<T>(params: {
instanceId: opts.instanceId ?? randomUUID(),
clientName: opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
clientDisplayName: opts.clientDisplayName,
clientVersion: opts.clientVersion ?? "dev",
clientVersion: opts.clientVersion ?? VERSION,
platform: opts.platform,
mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI,
role: "operator",

View File

@@ -13,6 +13,7 @@ import {
} from "../infra/device-identity.js";
import { clearDevicePairing } from "../infra/device-pairing.js";
import { normalizeFingerprint } from "../infra/tls/fingerprint.js";
import { VERSION } from "../version.js";
import { rawDataToString } from "../infra/ws.js";
import { logDebug, logError } from "../logger.js";
import {
@@ -21,6 +22,7 @@ import {
type GatewayClientMode,
type GatewayClientName,
} from "../utils/message-channel.js";
import { VERSION } from "../version.js";
import { buildDeviceAuthPayloadV3 } from "./device-auth.js";
import { isSecureWebSocketUrl } from "./net.js";
import {
@@ -302,7 +304,7 @@ export class GatewayClient {
client: {
id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
displayName: this.opts.clientDisplayName,
version: this.opts.clientVersion ?? "dev",
version: this.opts.clientVersion ?? VERSION,
platform,
deviceFamily: this.opts.deviceFamily,
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,

View File

@@ -5,4 +5,5 @@ export type ControlUiBootstrapConfig = {
assistantName: string;
assistantAvatar: string;
assistantAgentId: string;
serverVersion?: string;
};

View File

@@ -25,6 +25,7 @@ import {
normalizeControlUiBasePath,
resolveAssistantAvatarUrl,
} from "./control-ui-shared.js";
import { resolveRuntimeServiceVersion } from "../version.js";
const ROOT_PREFIX = "/";
const CONTROL_UI_ASSETS_MISSING_MESSAGE =
@@ -350,6 +351,7 @@ export function handleControlUiHttpRequest(
assistantName: identity.name,
assistantAvatar: avatarValue ?? identity.avatar,
assistantAgentId: identity.agentId,
serverVersion: resolveRuntimeServiceVersion(process.env),
} satisfies ControlUiBootstrapConfig);
return true;
}

View File

@@ -69,6 +69,7 @@ type GatewayHost = {
assistantName: string;
assistantAvatar: string | null;
assistantAgentId: string | null;
serverVersion: string | null;
sessionKey: string;
chatRunId: string | null;
refreshSessionsAfterChat: Set<string>;
@@ -150,6 +151,7 @@ export function connectGateway(host: GatewayHost) {
token: host.settings.token.trim() ? host.settings.token : undefined,
password: host.password.trim() ? host.password : undefined,
clientName: "openclaw-control-ui",
clientVersion: host.serverVersion ?? undefined,
mode: "webchat",
instanceId: host.clientInstanceId,
onHello: (hello) => {

View File

@@ -27,6 +27,7 @@ type LifecycleHost = {
assistantName: string;
assistantAvatar: string | null;
assistantAgentId: string | null;
serverVersion: string | null;
chatHasAutoScrolled: boolean;
chatManualRefreshInFlight: boolean;
chatLoading: boolean;

View File

@@ -135,6 +135,7 @@ export class OpenClawApp extends LitElement {
@state() assistantName = bootAssistantIdentity.name;
@state() assistantAvatar = bootAssistantIdentity.avatar;
@state() assistantAgentId = bootAssistantIdentity.agentId ?? null;
@state() serverVersion: string | null = null;
@state() sessionKey = this.settings.sessionKey;
@state() chatLoading = false;

View File

@@ -13,6 +13,7 @@ describe("loadControlUiBootstrapConfig", () => {
assistantName: "Ops",
assistantAvatar: "O",
assistantAgentId: "main",
serverVersion: "2026.3.2",
}),
});
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
@@ -22,6 +23,7 @@ describe("loadControlUiBootstrapConfig", () => {
assistantName: "Assistant",
assistantAvatar: null,
assistantAgentId: null,
serverVersion: null,
};
await loadControlUiBootstrapConfig(state);
@@ -33,6 +35,7 @@ describe("loadControlUiBootstrapConfig", () => {
expect(state.assistantName).toBe("Ops");
expect(state.assistantAvatar).toBe("O");
expect(state.assistantAgentId).toBe("main");
expect(state.serverVersion).toBe("2026.3.2");
vi.unstubAllGlobals();
});
@@ -46,6 +49,7 @@ describe("loadControlUiBootstrapConfig", () => {
assistantName: "Assistant",
assistantAvatar: null,
assistantAgentId: null,
serverVersion: null,
};
await loadControlUiBootstrapConfig(state);
@@ -68,6 +72,7 @@ describe("loadControlUiBootstrapConfig", () => {
assistantName: "Assistant",
assistantAvatar: null,
assistantAgentId: null,
serverVersion: null,
};
await loadControlUiBootstrapConfig(state);

View File

@@ -10,6 +10,7 @@ export type ControlUiBootstrapState = {
assistantName: string;
assistantAvatar: string | null;
assistantAgentId: string | null;
serverVersion: string | null;
};
export async function loadControlUiBootstrapConfig(state: ControlUiBootstrapState) {
@@ -43,6 +44,7 @@ export async function loadControlUiBootstrapConfig(state: ControlUiBootstrapStat
state.assistantName = normalized.name;
state.assistantAvatar = normalized.avatar;
state.assistantAgentId = normalized.agentId ?? null;
state.serverVersion = parsed.serverVersion ?? null;
} catch {
// Ignore bootstrap failures; UI will update identity after connecting.
}

View File

@@ -233,7 +233,7 @@ export class GatewayBrowserClient {
maxProtocol: 3,
client: {
id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.CONTROL_UI,
version: this.opts.clientVersion ?? "dev",
version: this.opts.clientVersion ?? "control-ui",
platform: this.opts.platform ?? navigator.platform ?? "web",
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
instanceId: this.opts.instanceId,