mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(config): keep named profiles off the default gateway port (#122751)
* fix(config): derive profile gateway ports like the Mac app instead of defaulting to 18789 * fix(config): use canonical profile normalization
This commit is contained in:
committed by
GitHub
parent
d17264f5fd
commit
8bbc0a9bc7
@@ -82,6 +82,8 @@ struct AppProfile: Equatable, Sendable {
|
||||
|
||||
var defaultGatewayPort: Int {
|
||||
guard let name else { return 18789 }
|
||||
// Keep byte-for-byte aligned with src/config/paths.ts resolveGatewayPort so the app and CLI
|
||||
// connect to the same profile Gateway.
|
||||
var hash: UInt32 = 2_166_136_261
|
||||
for byte in name.utf8 {
|
||||
hash = (hash ^ UInt32(byte)) &* 16_777_619
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
resolveGatewayPublicOrigin,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolveGatewayPort } from "openclaw/plugin-sdk/core";
|
||||
|
||||
const DEFAULT_GATEWAY_PORT = 18789;
|
||||
type ViewerBaseUrlFieldName = "baseUrl" | "viewerBaseUrl";
|
||||
|
||||
export function buildViewerUrl(params: {
|
||||
@@ -55,8 +55,7 @@ export function normalizeViewerBaseUrl(
|
||||
|
||||
function resolveGatewayBaseUrl(config: OpenClawConfig): string {
|
||||
const scheme = config.gateway?.tls?.enabled ? "https" : "http";
|
||||
const port =
|
||||
typeof config.gateway?.port === "number" ? config.gateway.port : DEFAULT_GATEWAY_PORT;
|
||||
const port = resolveGatewayPort(config);
|
||||
const customHost = config.gateway?.customBindHost?.trim();
|
||||
|
||||
if (config.gateway?.bind === "custom" && customHost) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Mattermost plugin module implements interactions behavior.
|
||||
import { createHmac } from "node:crypto";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { resolveGatewayPort } from "openclaw/plugin-sdk/core";
|
||||
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
@@ -134,7 +135,7 @@ export function computeInteractionCallbackUrl(
|
||||
if (callbackBaseUrl) {
|
||||
return `${normalizeCallbackBaseUrl(callbackBaseUrl)}${path}`;
|
||||
}
|
||||
const port = typeof cfg?.gateway?.port === "number" ? cfg.gateway.port : 18789;
|
||||
const port = resolveGatewayPort(cfg);
|
||||
let host =
|
||||
cfg?.gateway?.customBindHost && !isWildcardBindHost(cfg.gateway.customBindHost)
|
||||
? cfg.gateway.customBindHost.trim()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const listSkillCommandsForAgents = vi.hoisted(() => vi.fn());
|
||||
const parseTcpPort = vi.hoisted(() => vi.fn());
|
||||
const fetchMattermostUserTeams = vi.hoisted(() => vi.fn());
|
||||
const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value));
|
||||
const isSlashCommandsEnabled = vi.hoisted(() => vi.fn());
|
||||
@@ -13,7 +12,6 @@ const activateSlashCommands = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./runtime-api.js", () => ({
|
||||
listSkillCommandsForAgents,
|
||||
parseTcpPort,
|
||||
}));
|
||||
|
||||
vi.mock("./client.js", async () => {
|
||||
@@ -60,7 +58,6 @@ describe("mattermost monitor slash", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
listSkillCommandsForAgents.mockReset();
|
||||
parseTcpPort.mockReset();
|
||||
fetchMattermostUserTeams.mockReset();
|
||||
normalizeMattermostBaseUrl.mockClear();
|
||||
isSlashCommandsEnabled.mockReset();
|
||||
@@ -95,7 +92,6 @@ describe("mattermost monitor slash", () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "18888");
|
||||
resolveSlashCommandConfig.mockReturnValue({ enabled: true, nativeSkills: true });
|
||||
isSlashCommandsEnabled.mockReturnValue(true);
|
||||
parseTcpPort.mockReturnValue(18888);
|
||||
fetchMattermostUserTeams.mockResolvedValue([{ id: "team-1" }, { id: "team-2" }]);
|
||||
resolveCallbackUrl.mockReturnValue("https://openclaw.test/slash");
|
||||
listSkillCommandsForAgents.mockReturnValue([
|
||||
@@ -171,7 +167,6 @@ describe("mattermost monitor slash", () => {
|
||||
vi.stubEnv("OPENCLAW_GATEWAY_PORT", "65536");
|
||||
resolveSlashCommandConfig.mockReturnValue({ enabled: true, nativeSkills: false });
|
||||
isSlashCommandsEnabled.mockReturnValue(true);
|
||||
parseTcpPort.mockReturnValue(null);
|
||||
fetchMattermostUserTeams.mockResolvedValue([{ id: "team-1" }]);
|
||||
resolveCallbackUrl.mockReturnValue("https://openclaw.test/slash");
|
||||
registerSlashCommands.mockResolvedValue([{ token: "token-1", trigger: "ping" }]);
|
||||
@@ -185,7 +180,6 @@ describe("mattermost monitor slash", () => {
|
||||
botUserId: "bot-user",
|
||||
});
|
||||
|
||||
expect(parseTcpPort).toHaveBeenCalledWith("65536");
|
||||
expect(resolveCallbackUrl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ gatewayPort: 18789 }),
|
||||
);
|
||||
@@ -194,7 +188,6 @@ describe("mattermost monitor slash", () => {
|
||||
it("warns on loopback callback urls and reports partial team failures", async () => {
|
||||
resolveSlashCommandConfig.mockReturnValue({ enabled: true, nativeSkills: false });
|
||||
isSlashCommandsEnabled.mockReturnValue(true);
|
||||
parseTcpPort.mockReturnValue(null);
|
||||
fetchMattermostUserTeams.mockResolvedValue([{ id: "team-1" }, { id: "team-2" }]);
|
||||
resolveCallbackUrl.mockReturnValue("http://127.0.0.1:18789/slash");
|
||||
registerSlashCommands
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Mattermost plugin module implements monitor slash behavior.
|
||||
import { resolveGatewayPort } from "openclaw/plugin-sdk/core";
|
||||
import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import type { ResolvedMattermostAccount } from "./accounts.js";
|
||||
import {
|
||||
@@ -6,12 +7,7 @@ import {
|
||||
normalizeMattermostBaseUrl,
|
||||
type MattermostClient,
|
||||
} from "./client.js";
|
||||
import {
|
||||
listSkillCommandsForAgents,
|
||||
parseTcpPort,
|
||||
type OpenClawConfig,
|
||||
type RuntimeEnv,
|
||||
} from "./runtime-api.js";
|
||||
import { listSkillCommandsForAgents, type OpenClawConfig, type RuntimeEnv } from "./runtime-api.js";
|
||||
import {
|
||||
DEFAULT_COMMAND_SPECS,
|
||||
isSlashCommandsEnabled,
|
||||
@@ -150,11 +146,9 @@ export async function registerMattermostMonitorSlashCommands(params: {
|
||||
|
||||
try {
|
||||
const teams = await fetchMattermostUserTeams(params.client, params.botUserId);
|
||||
const envPort = parseTcpPort(process.env.OPENCLAW_GATEWAY_PORT);
|
||||
const slashGatewayPort = envPort ?? params.cfg.gateway?.port ?? 18789;
|
||||
const slashCallbackUrl = resolveCallbackUrl({
|
||||
config: slashConfig,
|
||||
gatewayPort: slashGatewayPort,
|
||||
gatewayPort: resolveGatewayPort(params.cfg),
|
||||
gatewayHost: params.cfg.gateway?.customBindHost ?? undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -37,4 +37,3 @@ export {
|
||||
readRequestBodyWithLimit,
|
||||
} from "openclaw/plugin-sdk/webhook-ingress";
|
||||
export { isTrustedProxyAddress, resolveClientIp } from "openclaw/plugin-sdk/core";
|
||||
export { parseTcpPort } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
@@ -322,10 +322,36 @@ describe("oauth paths", () => {
|
||||
describe("gateway port resolution", () => {
|
||||
it("prefers numeric env values over config", () => {
|
||||
expect(
|
||||
resolveGatewayPort({ gateway: { port: 19002 } }, envWith({ OPENCLAW_GATEWAY_PORT: "19001" })),
|
||||
resolveGatewayPort(
|
||||
{ gateway: { port: 19002 } },
|
||||
envWith({ OPENCLAW_GATEWAY_PORT: "19001", OPENCLAW_PROFILE: "work" }),
|
||||
),
|
||||
).toBe(19001);
|
||||
expect(
|
||||
resolveGatewayPort({ gateway: { port: 19002 } }, envWith({ OPENCLAW_PROFILE: "work" })),
|
||||
).toBe(19002);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ profile: "ct2", expected: 45696 },
|
||||
{ profile: "p1402", expected: 55636 },
|
||||
{ profile: "p2380", expected: 55636 },
|
||||
])("derives the byte-exact profile port for $profile", ({ profile, expected }) => {
|
||||
const port = resolveGatewayPort({}, envWith({ OPENCLAW_PROFILE: profile }));
|
||||
expect(port).toBe(expected);
|
||||
expect(port).toBeGreaterThanOrEqual(20000);
|
||||
expect(port).toBeLessThan(60000);
|
||||
});
|
||||
|
||||
it.each([undefined, "default", "Default", "../escape"])(
|
||||
"keeps the default port for profile %j",
|
||||
(profile) => {
|
||||
expect(resolveGatewayPort({}, envWith({ OPENCLAW_PROFILE: profile }))).toBe(
|
||||
DEFAULT_GATEWAY_PORT,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("accepts Compose-style IPv4 host publish values from env", () => {
|
||||
expect(
|
||||
resolveGatewayPort(
|
||||
|
||||
+12
-2
@@ -2,7 +2,7 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveProfileStateDir } from "../cli/profile-utils.js";
|
||||
import { normalizeProfileName, resolveProfileStateDir } from "../cli/profile-utils.js";
|
||||
import { resolveGatewayNativeServiceIdentityConflict } from "../daemon/constants.js";
|
||||
import { resolveHomeRelativePath, resolveRequiredHomeDir } from "../infra/home-dir.js";
|
||||
import { parseTcpPort } from "../infra/tcp-port.js";
|
||||
@@ -468,5 +468,15 @@ export function resolveGatewayPort(
|
||||
return configPort;
|
||||
}
|
||||
}
|
||||
return DEFAULT_GATEWAY_PORT;
|
||||
const profile = normalizeProfileName(env.OPENCLAW_PROFILE);
|
||||
if (!profile) {
|
||||
return DEFAULT_GATEWAY_PORT;
|
||||
}
|
||||
// Keep byte-for-byte aligned with AppProfile.defaultGatewayPort in
|
||||
// apps/macos/Sources/OpenClaw/AppProfile.swift so both surfaces connect to the same Gateway.
|
||||
let hash = 2_166_136_261;
|
||||
for (const byte of Buffer.from(profile, "utf8")) {
|
||||
hash = Math.imul(hash ^ byte, 16_777_619) >>> 0;
|
||||
}
|
||||
return 20_000 + (hash % 40_000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user