diff --git a/src/commands/onboard-remote.test.ts b/src/commands/onboard-remote.test.ts index 3b676b66d70..277bafce7f5 100644 --- a/src/commands/onboard-remote.test.ts +++ b/src/commands/onboard-remote.test.ts @@ -119,7 +119,7 @@ describe("promptRemoteGatewayConfig", () => { ); }); - it("rejects discovery endpoint when trust confirmation is declined", async () => { + it("falls back to manual URL entry when discovery trust is declined", async () => { detectBinary.mockResolvedValue(true); discoverGatewayBeacons.mockResolvedValue([ { @@ -135,6 +135,14 @@ describe("promptRemoteGatewayConfig", () => { "Select gateway": "0", "Connection method": "direct", }); + const manualUrl = "wss://manual.example.com:18789"; + const text: WizardPrompter["text"] = vi.fn(async (params) => { + if (params.message === "Gateway WebSocket URL") { + expect(params.initialValue).toBe("wss://evil.example:443"); + return manualUrl; + } + return ""; + }) as WizardPrompter["text"]; const confirm: WizardPrompter["confirm"] = vi.fn(async (params) => { if (params.message.startsWith("Discover gateway")) { return true; @@ -148,12 +156,14 @@ describe("promptRemoteGatewayConfig", () => { const prompter = createPrompter({ confirm, select, - text: vi.fn(async () => "") as WizardPrompter["text"], + text, }); - await expect(promptRemoteGatewayConfig({} as OpenClawConfig, prompter)).rejects.toThrow( - "not trusted", - ); + const next = await promptRemoteGatewayConfig({} as OpenClawConfig, prompter); + + expect(next.gateway?.mode).toBe("remote"); + expect(next.gateway?.remote?.url).toBe(manualUrl); + expect(next.gateway?.remote?.tlsFingerprint).toBeUndefined(); }); it("trusts discovery endpoint without fingerprint and omits tlsFingerprint", async () => { diff --git a/src/commands/onboard-remote.ts b/src/commands/onboard-remote.ts index 263771b3ae3..c6128a72d71 100644 --- a/src/commands/onboard-remote.ts +++ b/src/commands/onboard-remote.ts @@ -120,22 +120,19 @@ export async function promptRemoteGatewayConfig( message: `Trust this gateway? Host: ${host}:${port} TLS fingerprint: ${fingerprint ?? "not advertised (connection will not be pinned)"}`, initialValue: false, }); - if (!trusted) { - throw new Error( - `Discovery endpoint ${host}:${port} not trusted. Re-run onboarding or enter the URL manually.`, + if (trusted) { + discoveryTlsFingerprint = fingerprint; + trustedDiscoveryUrl = suggestedUrl; + await prompter.note( + [ + "Direct remote access defaults to TLS.", + `Using: ${suggestedUrl}`, + ...(fingerprint ? [`TLS pin: ${fingerprint}`] : []), + "If your gateway is loopback-only, choose SSH tunnel and keep ws://127.0.0.1:18789.", + ].join("\n"), + "Direct remote", ); } - discoveryTlsFingerprint = fingerprint; - trustedDiscoveryUrl = suggestedUrl; - await prompter.note( - [ - "Direct remote access defaults to TLS.", - `Using: ${suggestedUrl}`, - ...(fingerprint ? [`TLS pin: ${fingerprint}`] : []), - "If your gateway is loopback-only, choose SSH tunnel and keep ws://127.0.0.1:18789.", - ].join("\n"), - "Direct remote", - ); } else { suggestedUrl = DEFAULT_GATEWAY_URL; await prompter.note(