From 0d1eceb9cf3a466f2cb595e7f66286b27712f6ab Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Mon, 16 Feb 2026 20:29:54 -0500 Subject: [PATCH] Revert "Onboarding: fix webchat URL loopback and canonical session" This reverts commit 59e0e7e4ffeb6853e4be24f3c25c5ceb1a212f2e. --- src/commands/onboard-helpers.e2e.test.ts | 31 ------ src/commands/onboard-helpers.ts | 55 ++-------- src/wizard/onboarding.finalize.test.ts | 124 ----------------------- src/wizard/onboarding.finalize.ts | 40 +++----- 4 files changed, 19 insertions(+), 231 deletions(-) delete mode 100644 src/wizard/onboarding.finalize.test.ts diff --git a/src/commands/onboard-helpers.e2e.test.ts b/src/commands/onboard-helpers.e2e.test.ts index 3ea706109..f0d7a1843 100644 --- a/src/commands/onboard-helpers.e2e.test.ts +++ b/src/commands/onboard-helpers.e2e.test.ts @@ -1,11 +1,9 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - buildWebchatUrl, normalizeGatewayTokenInput, openUrl, resolveBrowserOpenCommand, resolveControlUiLinks, - resolveLocalBrowserControlUiLinks, validateGatewayPasswordInput, } from "./onboard-helpers.js"; @@ -109,35 +107,6 @@ describe("resolveControlUiLinks", () => { expect(links.httpUrl).toBe("http://127.0.0.1:18789/"); expect(links.wsUrl).toBe("ws://127.0.0.1:18789"); }); - - it("coerces lan bind to loopback for local browser links", () => { - const links = resolveLocalBrowserControlUiLinks({ - port: 18789, - bind: "lan", - }); - expect(links.httpUrl).toBe("http://127.0.0.1:18789/"); - expect(links.wsUrl).toBe("ws://127.0.0.1:18789"); - }); -}); - -describe("buildWebchatUrl", () => { - it("encodes canonical session key exactly once", () => { - const url = buildWebchatUrl({ - httpUrl: "http://127.0.0.1:18789/", - sessionKey: "agent:main:main", - }); - expect(url).toBe("http://127.0.0.1:18789/chat?session=agent%3Amain%3Amain"); - }); - - it("preserves base path and appends token in fragment", () => { - const url = buildWebchatUrl({ - httpUrl: "http://127.0.0.1:18789/ui/", - sessionKey: "agent:main:main", - token: "abc 123", - }); - expect(url).toBe("http://127.0.0.1:18789/ui/chat?session=agent%3Amain%3Amain#token=abc%20123"); - expect(url).not.toContain("%2520"); - }); }); describe("normalizeGatewayTokenInput", () => { diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index 457a10279..f1c40d3b7 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -1,12 +1,14 @@ +import { cancel, isCancel } from "@clack/prompts"; import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import { inspect } from "node:util"; -import { cancel, isCancel } from "@clack/prompts"; -import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js"; import type { OpenClawConfig } from "../config/config.js"; +import type { RuntimeEnv } from "../runtime.js"; +import type { NodeManagerChoice, OnboardMode, ResetScope } from "./onboard-types.js"; +import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace } from "../agents/workspace.js"; import { CONFIG_PATH } from "../config/config.js"; -import { resolveMainSessionKey, resolveSessionTranscriptsDirForAgent } from "../config/sessions.js"; +import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js"; import { callGateway } from "../gateway/call.js"; import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js"; import { pickPrimaryLanIPv4, isValidIPv4 } from "../gateway/net.js"; @@ -14,7 +16,6 @@ import { isSafeExecutableValue } from "../infra/exec-safety.js"; import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js"; import { isWSL } from "../infra/wsl.js"; import { runCommandWithTimeout } from "../process/exec.js"; -import type { RuntimeEnv } from "../runtime.js"; import { stylePromptTitle } from "../terminal/prompt-style.js"; import { CONFIG_DIR, @@ -25,7 +26,6 @@ import { } from "../utils.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; import { VERSION } from "../version.js"; -import type { NodeManagerChoice, OnboardMode, ResetScope } from "./onboard-types.js"; export function guardCancel(value: T | symbol, runtime: RuntimeEnv): T { if (isCancel(value)) { @@ -454,17 +454,12 @@ function summarizeError(err: unknown): string { export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR; -type ControlUiLinksParams = { +export function resolveControlUiLinks(params: { port: number; bind?: "auto" | "lan" | "loopback" | "custom" | "tailnet"; customBindHost?: string; basePath?: string; -}; - -export function resolveControlUiLinks(params: ControlUiLinksParams): { - httpUrl: string; - wsUrl: string; -} { +}): { httpUrl: string; wsUrl: string } { const port = params.port; const bind = params.bind ?? "loopback"; const customBindHost = params.customBindHost?.trim(); @@ -489,39 +484,3 @@ export function resolveControlUiLinks(params: ControlUiLinksParams): { wsUrl: `ws://${host}:${port}${wsPath}`, }; } - -export function resolveLocalBrowserControlUiLinks(params: ControlUiLinksParams): { - httpUrl: string; - wsUrl: string; -} { - const bind = params.bind === "lan" ? "loopback" : params.bind; - return resolveControlUiLinks({ - ...params, - bind, - }); -} - -export function resolveCanonicalMainSessionKey(cfg: OpenClawConfig): string { - return resolveMainSessionKey(cfg); -} - -export function buildWebchatUrl(params: { - httpUrl: string; - sessionKey: string; - token?: string; -}): string { - const base = new URL(params.httpUrl); - if (!base.pathname.endsWith("/")) { - base.pathname = `${base.pathname}/`; - } - - const chatUrl = new URL("chat", base); - chatUrl.searchParams.set("session", params.sessionKey.trim()); - - const token = params.token?.trim(); - if (token) { - chatUrl.hash = `token=${encodeURIComponent(token)}`; - } - - return chatUrl.toString(); -} diff --git a/src/wizard/onboarding.finalize.test.ts b/src/wizard/onboarding.finalize.test.ts deleted file mode 100644 index deead2cc8..000000000 --- a/src/wizard/onboarding.finalize.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import type { OnboardOptions } from "../commands/onboard-types.js"; -import type { RuntimeEnv } from "../runtime.js"; -import { finalizeOnboardingWizard } from "./onboarding.finalize.js"; -import type { WizardPrompter } from "./prompts.js"; - -const mocks = vi.hoisted(() => ({ - detectBrowserOpenSupport: vi.fn(async () => ({ ok: true })), - openUrl: vi.fn(async () => true), - probeGatewayReachable: vi.fn(async () => ({ ok: true })), - ensureControlUiAssetsBuilt: vi.fn(async () => ({ ok: true })), - setupOnboardingShellCompletion: vi.fn(async () => {}), - runTui: vi.fn(async () => {}), -})); - -vi.mock("../commands/onboard-helpers.js", async (importActual) => { - const actual = await importActual(); - return { - ...actual, - detectBrowserOpenSupport: mocks.detectBrowserOpenSupport, - openUrl: mocks.openUrl, - probeGatewayReachable: mocks.probeGatewayReachable, - }; -}); - -vi.mock("../infra/control-ui-assets.js", () => ({ - ensureControlUiAssetsBuilt: mocks.ensureControlUiAssetsBuilt, -})); - -vi.mock("./onboarding.completion.js", () => ({ - setupOnboardingShellCompletion: mocks.setupOnboardingShellCompletion, -})); - -vi.mock("../tui/tui.js", () => ({ - runTui: mocks.runTui, -})); - -function createPrompter(overrides?: Partial): WizardPrompter { - const select: WizardPrompter["select"] = vi.fn(async (params) => { - if (params.message === "How do you want to hatch your bot?") { - return "web" as never; - } - return (params.initialValue ?? params.options[0]?.value) as never; - }); - const multiselect: WizardPrompter["multiselect"] = vi.fn(async () => []); - - return { - intro: vi.fn(async () => {}), - outro: vi.fn(async () => {}), - note: vi.fn(async () => {}), - select, - multiselect, - text: vi.fn(async () => ""), - confirm: vi.fn(async () => false), - progress: vi.fn(() => ({ - update: vi.fn(), - stop: vi.fn(), - })), - ...overrides, - }; -} - -function createRuntime(): RuntimeEnv { - return { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn((code: number): never => { - throw new Error(`exit:${code}`); - }), - }; -} - -describe("finalizeOnboardingWizard", () => { - afterEach(() => { - vi.restoreAllMocks(); - mocks.detectBrowserOpenSupport.mockClear(); - mocks.openUrl.mockClear(); - mocks.probeGatewayReachable.mockClear(); - mocks.ensureControlUiAssetsBuilt.mockClear(); - mocks.setupOnboardingShellCompletion.mockClear(); - mocks.runTui.mockClear(); - }); - - it("opens loopback webchat URL with canonical main session key when bind=lan", async () => { - vi.spyOn(process, "platform", "get").mockReturnValue("darwin"); - const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboarding-finalize-")); - - const opts: OnboardOptions = { - installDaemon: false, - skipHealth: true, - skipProviders: true, - skipSkills: true, - }; - const prompter = createPrompter(); - const runtime = createRuntime(); - - await finalizeOnboardingWizard({ - flow: "quickstart", - opts, - baseConfig: {}, - nextConfig: {}, - workspaceDir, - settings: { - port: 18789, - bind: "lan", - authMode: "token", - gatewayToken: "test token", - tailscaleMode: "off", - tailscaleResetOnExit: false, - }, - prompter, - runtime, - }); - - expect(mocks.openUrl).toHaveBeenCalledWith( - "http://127.0.0.1:18789/chat?session=agent%3Amain%3Amain#token=test%20token", - ); - - await fs.rm(workspaceDir, { recursive: true, force: true }); - }); -}); diff --git a/src/wizard/onboarding.finalize.ts b/src/wizard/onboarding.finalize.ts index 2139438d6..b73979915 100644 --- a/src/wizard/onboarding.finalize.ts +++ b/src/wizard/onboarding.finalize.ts @@ -1,5 +1,10 @@ import fs from "node:fs/promises"; import path from "node:path"; +import type { OnboardOptions } from "../commands/onboard-types.js"; +import type { OpenClawConfig } from "../config/config.js"; +import type { RuntimeEnv } from "../runtime.js"; +import type { GatewayWizardSettings, WizardFlow } from "./onboarding.types.js"; +import type { WizardPrompter } from "./prompts.js"; import { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js"; import { formatCliCommand } from "../cli/command-format.js"; import { @@ -13,28 +18,20 @@ import { import { formatHealthCheckFailure } from "../commands/health-format.js"; import { healthCommand } from "../commands/health.js"; import { - buildWebchatUrl, detectBrowserOpenSupport, formatControlUiSshHint, openUrl, probeGatewayReachable, - resolveCanonicalMainSessionKey, waitForGatewayReachable, resolveControlUiLinks, - resolveLocalBrowserControlUiLinks, } from "../commands/onboard-helpers.js"; -import type { OnboardOptions } from "../commands/onboard-types.js"; -import type { OpenClawConfig } from "../config/config.js"; import { resolveGatewayService } from "../daemon/service.js"; import { isSystemdUserServiceAvailable } from "../daemon/systemd.js"; import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js"; -import type { RuntimeEnv } from "../runtime.js"; import { restoreTerminalState } from "../terminal/restore.js"; import { runTui } from "../tui/tui.js"; import { resolveUserPath } from "../utils.js"; import { setupOnboardingShellCompletion } from "./onboarding.completion.js"; -import type { GatewayWizardSettings, WizardFlow } from "./onboarding.types.js"; -import type { WizardPrompter } from "./prompts.js"; type FinalizeOnboardingOptions = { flow: WizardFlow; @@ -253,22 +250,10 @@ export async function finalizeOnboardingWizard( customBindHost: settings.customBindHost, basePath: controlUiBasePath, }); - const localBrowserLinks = resolveLocalBrowserControlUiLinks({ - bind: settings.bind, - port: settings.port, - customBindHost: settings.customBindHost, - basePath: controlUiBasePath, - }); - const canonicalSessionKey = resolveCanonicalMainSessionKey(nextConfig); const authedUrl = settings.authMode === "token" && settings.gatewayToken - ? `${localBrowserLinks.httpUrl}#token=${encodeURIComponent(settings.gatewayToken)}` - : localBrowserLinks.httpUrl; - const webchatUrl = buildWebchatUrl({ - httpUrl: localBrowserLinks.httpUrl, - sessionKey: canonicalSessionKey, - token: settings.authMode === "token" ? settings.gatewayToken : undefined, - }); + ? `${links.httpUrl}#token=${encodeURIComponent(settings.gatewayToken)}` + : links.httpUrl; const gatewayProbe = await probeGatewayReachable({ url: links.wsUrl, token: settings.authMode === "token" ? settings.gatewayToken : undefined, @@ -288,11 +273,10 @@ export async function finalizeOnboardingWizard( await prompter.note( [ - `Web UI: ${localBrowserLinks.httpUrl}`, + `Web UI: ${links.httpUrl}`, settings.authMode === "token" && settings.gatewayToken ? `Web UI (with token): ${authedUrl}` : undefined, - `WebChat: ${webchatUrl}`, `Gateway WS: ${links.wsUrl}`, gatewayStatusLine, "Docs: https://docs.openclaw.ai/web/control-ui", @@ -358,7 +342,7 @@ export async function finalizeOnboardingWizard( } else if (hatchChoice === "web") { const browserSupport = await detectBrowserOpenSupport(); if (browserSupport.ok) { - controlUiOpened = await openUrl(webchatUrl); + controlUiOpened = await openUrl(authedUrl); if (!controlUiOpened) { controlUiOpenHint = formatControlUiSshHint({ port: settings.port, @@ -375,7 +359,7 @@ export async function finalizeOnboardingWizard( } await prompter.note( [ - `WebChat link: ${webchatUrl}`, + `Dashboard link (with token): ${authedUrl}`, controlUiOpened ? "Opened in your browser. Keep that tab to control OpenClaw." : "Copy/paste this URL in a browser on this machine to control OpenClaw.", @@ -418,7 +402,7 @@ export async function finalizeOnboardingWizard( if (shouldOpenControlUi) { const browserSupport = await detectBrowserOpenSupport(); if (browserSupport.ok) { - controlUiOpened = await openUrl(webchatUrl); + controlUiOpened = await openUrl(authedUrl); if (!controlUiOpened) { controlUiOpenHint = formatControlUiSshHint({ port: settings.port, @@ -436,7 +420,7 @@ export async function finalizeOnboardingWizard( await prompter.note( [ - `WebChat link: ${webchatUrl}`, + `Dashboard link (with token): ${authedUrl}`, controlUiOpened ? "Opened in your browser. Keep that tab to control OpenClaw." : "Copy/paste this URL in a browser on this machine to control OpenClaw.",