mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 02:01:25 +00:00
Revert "Onboarding: fix webchat URL loopback and canonical session"
This reverts commit 59e0e7e4ff.
This commit is contained in:
@@ -1,11 +1,9 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
buildWebchatUrl,
|
|
||||||
normalizeGatewayTokenInput,
|
normalizeGatewayTokenInput,
|
||||||
openUrl,
|
openUrl,
|
||||||
resolveBrowserOpenCommand,
|
resolveBrowserOpenCommand,
|
||||||
resolveControlUiLinks,
|
resolveControlUiLinks,
|
||||||
resolveLocalBrowserControlUiLinks,
|
|
||||||
validateGatewayPasswordInput,
|
validateGatewayPasswordInput,
|
||||||
} from "./onboard-helpers.js";
|
} from "./onboard-helpers.js";
|
||||||
|
|
||||||
@@ -109,35 +107,6 @@ describe("resolveControlUiLinks", () => {
|
|||||||
expect(links.httpUrl).toBe("http://127.0.0.1:18789/");
|
expect(links.httpUrl).toBe("http://127.0.0.1:18789/");
|
||||||
expect(links.wsUrl).toBe("ws://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", () => {
|
describe("normalizeGatewayTokenInput", () => {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
import { cancel, isCancel } from "@clack/prompts";
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { inspect } from "node:util";
|
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 { 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 { 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 { callGateway } from "../gateway/call.js";
|
||||||
import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js";
|
import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js";
|
||||||
import { pickPrimaryLanIPv4, isValidIPv4 } from "../gateway/net.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 { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
||||||
import { isWSL } from "../infra/wsl.js";
|
import { isWSL } from "../infra/wsl.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
|
||||||
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import {
|
import {
|
||||||
CONFIG_DIR,
|
CONFIG_DIR,
|
||||||
@@ -25,7 +26,6 @@ import {
|
|||||||
} from "../utils.js";
|
} from "../utils.js";
|
||||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
import type { NodeManagerChoice, OnboardMode, ResetScope } from "./onboard-types.js";
|
|
||||||
|
|
||||||
export function guardCancel<T>(value: T | symbol, runtime: RuntimeEnv): T {
|
export function guardCancel<T>(value: T | symbol, runtime: RuntimeEnv): T {
|
||||||
if (isCancel(value)) {
|
if (isCancel(value)) {
|
||||||
@@ -454,17 +454,12 @@ function summarizeError(err: unknown): string {
|
|||||||
|
|
||||||
export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR;
|
export const DEFAULT_WORKSPACE = DEFAULT_AGENT_WORKSPACE_DIR;
|
||||||
|
|
||||||
type ControlUiLinksParams = {
|
export function resolveControlUiLinks(params: {
|
||||||
port: number;
|
port: number;
|
||||||
bind?: "auto" | "lan" | "loopback" | "custom" | "tailnet";
|
bind?: "auto" | "lan" | "loopback" | "custom" | "tailnet";
|
||||||
customBindHost?: string;
|
customBindHost?: string;
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
};
|
}): { httpUrl: string; wsUrl: string } {
|
||||||
|
|
||||||
export function resolveControlUiLinks(params: ControlUiLinksParams): {
|
|
||||||
httpUrl: string;
|
|
||||||
wsUrl: string;
|
|
||||||
} {
|
|
||||||
const port = params.port;
|
const port = params.port;
|
||||||
const bind = params.bind ?? "loopback";
|
const bind = params.bind ?? "loopback";
|
||||||
const customBindHost = params.customBindHost?.trim();
|
const customBindHost = params.customBindHost?.trim();
|
||||||
@@ -489,39 +484,3 @@ export function resolveControlUiLinks(params: ControlUiLinksParams): {
|
|||||||
wsUrl: `ws://${host}:${port}${wsPath}`,
|
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();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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<typeof import("../commands/onboard-helpers.js")>();
|
|
||||||
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>): 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 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
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 { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js";
|
||||||
import { formatCliCommand } from "../cli/command-format.js";
|
import { formatCliCommand } from "../cli/command-format.js";
|
||||||
import {
|
import {
|
||||||
@@ -13,28 +18,20 @@ import {
|
|||||||
import { formatHealthCheckFailure } from "../commands/health-format.js";
|
import { formatHealthCheckFailure } from "../commands/health-format.js";
|
||||||
import { healthCommand } from "../commands/health.js";
|
import { healthCommand } from "../commands/health.js";
|
||||||
import {
|
import {
|
||||||
buildWebchatUrl,
|
|
||||||
detectBrowserOpenSupport,
|
detectBrowserOpenSupport,
|
||||||
formatControlUiSshHint,
|
formatControlUiSshHint,
|
||||||
openUrl,
|
openUrl,
|
||||||
probeGatewayReachable,
|
probeGatewayReachable,
|
||||||
resolveCanonicalMainSessionKey,
|
|
||||||
waitForGatewayReachable,
|
waitForGatewayReachable,
|
||||||
resolveControlUiLinks,
|
resolveControlUiLinks,
|
||||||
resolveLocalBrowserControlUiLinks,
|
|
||||||
} from "../commands/onboard-helpers.js";
|
} 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 { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { isSystemdUserServiceAvailable } from "../daemon/systemd.js";
|
import { isSystemdUserServiceAvailable } from "../daemon/systemd.js";
|
||||||
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
|
||||||
import { restoreTerminalState } from "../terminal/restore.js";
|
import { restoreTerminalState } from "../terminal/restore.js";
|
||||||
import { runTui } from "../tui/tui.js";
|
import { runTui } from "../tui/tui.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
import { setupOnboardingShellCompletion } from "./onboarding.completion.js";
|
import { setupOnboardingShellCompletion } from "./onboarding.completion.js";
|
||||||
import type { GatewayWizardSettings, WizardFlow } from "./onboarding.types.js";
|
|
||||||
import type { WizardPrompter } from "./prompts.js";
|
|
||||||
|
|
||||||
type FinalizeOnboardingOptions = {
|
type FinalizeOnboardingOptions = {
|
||||||
flow: WizardFlow;
|
flow: WizardFlow;
|
||||||
@@ -253,22 +250,10 @@ export async function finalizeOnboardingWizard(
|
|||||||
customBindHost: settings.customBindHost,
|
customBindHost: settings.customBindHost,
|
||||||
basePath: controlUiBasePath,
|
basePath: controlUiBasePath,
|
||||||
});
|
});
|
||||||
const localBrowserLinks = resolveLocalBrowserControlUiLinks({
|
|
||||||
bind: settings.bind,
|
|
||||||
port: settings.port,
|
|
||||||
customBindHost: settings.customBindHost,
|
|
||||||
basePath: controlUiBasePath,
|
|
||||||
});
|
|
||||||
const canonicalSessionKey = resolveCanonicalMainSessionKey(nextConfig);
|
|
||||||
const authedUrl =
|
const authedUrl =
|
||||||
settings.authMode === "token" && settings.gatewayToken
|
settings.authMode === "token" && settings.gatewayToken
|
||||||
? `${localBrowserLinks.httpUrl}#token=${encodeURIComponent(settings.gatewayToken)}`
|
? `${links.httpUrl}#token=${encodeURIComponent(settings.gatewayToken)}`
|
||||||
: localBrowserLinks.httpUrl;
|
: links.httpUrl;
|
||||||
const webchatUrl = buildWebchatUrl({
|
|
||||||
httpUrl: localBrowserLinks.httpUrl,
|
|
||||||
sessionKey: canonicalSessionKey,
|
|
||||||
token: settings.authMode === "token" ? settings.gatewayToken : undefined,
|
|
||||||
});
|
|
||||||
const gatewayProbe = await probeGatewayReachable({
|
const gatewayProbe = await probeGatewayReachable({
|
||||||
url: links.wsUrl,
|
url: links.wsUrl,
|
||||||
token: settings.authMode === "token" ? settings.gatewayToken : undefined,
|
token: settings.authMode === "token" ? settings.gatewayToken : undefined,
|
||||||
@@ -288,11 +273,10 @@ export async function finalizeOnboardingWizard(
|
|||||||
|
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
`Web UI: ${localBrowserLinks.httpUrl}`,
|
`Web UI: ${links.httpUrl}`,
|
||||||
settings.authMode === "token" && settings.gatewayToken
|
settings.authMode === "token" && settings.gatewayToken
|
||||||
? `Web UI (with token): ${authedUrl}`
|
? `Web UI (with token): ${authedUrl}`
|
||||||
: undefined,
|
: undefined,
|
||||||
`WebChat: ${webchatUrl}`,
|
|
||||||
`Gateway WS: ${links.wsUrl}`,
|
`Gateway WS: ${links.wsUrl}`,
|
||||||
gatewayStatusLine,
|
gatewayStatusLine,
|
||||||
"Docs: https://docs.openclaw.ai/web/control-ui",
|
"Docs: https://docs.openclaw.ai/web/control-ui",
|
||||||
@@ -358,7 +342,7 @@ export async function finalizeOnboardingWizard(
|
|||||||
} else if (hatchChoice === "web") {
|
} else if (hatchChoice === "web") {
|
||||||
const browserSupport = await detectBrowserOpenSupport();
|
const browserSupport = await detectBrowserOpenSupport();
|
||||||
if (browserSupport.ok) {
|
if (browserSupport.ok) {
|
||||||
controlUiOpened = await openUrl(webchatUrl);
|
controlUiOpened = await openUrl(authedUrl);
|
||||||
if (!controlUiOpened) {
|
if (!controlUiOpened) {
|
||||||
controlUiOpenHint = formatControlUiSshHint({
|
controlUiOpenHint = formatControlUiSshHint({
|
||||||
port: settings.port,
|
port: settings.port,
|
||||||
@@ -375,7 +359,7 @@ export async function finalizeOnboardingWizard(
|
|||||||
}
|
}
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
`WebChat link: ${webchatUrl}`,
|
`Dashboard link (with token): ${authedUrl}`,
|
||||||
controlUiOpened
|
controlUiOpened
|
||||||
? "Opened in your browser. Keep that tab to control OpenClaw."
|
? "Opened in your browser. Keep that tab to control OpenClaw."
|
||||||
: "Copy/paste this URL in a browser on this machine 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) {
|
if (shouldOpenControlUi) {
|
||||||
const browserSupport = await detectBrowserOpenSupport();
|
const browserSupport = await detectBrowserOpenSupport();
|
||||||
if (browserSupport.ok) {
|
if (browserSupport.ok) {
|
||||||
controlUiOpened = await openUrl(webchatUrl);
|
controlUiOpened = await openUrl(authedUrl);
|
||||||
if (!controlUiOpened) {
|
if (!controlUiOpened) {
|
||||||
controlUiOpenHint = formatControlUiSshHint({
|
controlUiOpenHint = formatControlUiSshHint({
|
||||||
port: settings.port,
|
port: settings.port,
|
||||||
@@ -436,7 +420,7 @@ export async function finalizeOnboardingWizard(
|
|||||||
|
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
`WebChat link: ${webchatUrl}`,
|
`Dashboard link (with token): ${authedUrl}`,
|
||||||
controlUiOpened
|
controlUiOpened
|
||||||
? "Opened in your browser. Keep that tab to control OpenClaw."
|
? "Opened in your browser. Keep that tab to control OpenClaw."
|
||||||
: "Copy/paste this URL in a browser on this machine to control OpenClaw.",
|
: "Copy/paste this URL in a browser on this machine to control OpenClaw.",
|
||||||
|
|||||||
Reference in New Issue
Block a user