From b91012b6976a204ada1bfcc40ed3606a9f9da4b6 Mon Sep 17 00:00:00 2001 From: Palash Oswal Date: Tue, 6 Jan 2026 09:30:45 -0500 Subject: [PATCH] fix(cli): don't force localhost gateway url in remote mode Fixes remote gateway setup (remote mode) by not overriding url; adds regression tests. Thanks @oswalpalash. --- src/agents/tools/gateway.test.ts | 31 +++++++++++++ src/agents/tools/gateway.ts | 3 +- src/commands/poll.test.ts | 77 ++++++++++++++++++++++++++++++++ src/commands/poll.ts | 1 - src/commands/send.test.ts | 20 +++++++++ src/commands/send.ts | 1 - 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 src/agents/tools/gateway.test.ts create mode 100644 src/commands/poll.test.ts diff --git a/src/agents/tools/gateway.test.ts b/src/agents/tools/gateway.test.ts new file mode 100644 index 000000000..81c5f3e78 --- /dev/null +++ b/src/agents/tools/gateway.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it, vi } from "vitest"; + +import { callGatewayTool, resolveGatewayOptions } from "./gateway.js"; + +const callGatewayMock = vi.fn(); +vi.mock("../../gateway/call.js", () => ({ + callGateway: (...args: unknown[]) => callGatewayMock(...args), +})); + +describe("gateway tool defaults", () => { + it("leaves url undefined so callGateway can use config", () => { + const opts = resolveGatewayOptions(); + expect(opts.url).toBeUndefined(); + }); + + it("passes through explicit overrides", async () => { + callGatewayMock.mockResolvedValueOnce({ ok: true }); + await callGatewayTool( + "health", + { gatewayUrl: "ws://example", gatewayToken: "t", timeoutMs: 5000 }, + {}, + ); + expect(callGatewayMock).toHaveBeenCalledWith( + expect.objectContaining({ + url: "ws://example", + token: "t", + timeoutMs: 5000, + }), + ); + }); +}); diff --git a/src/agents/tools/gateway.ts b/src/agents/tools/gateway.ts index ae2ca7744..c0ca1b36b 100644 --- a/src/agents/tools/gateway.ts +++ b/src/agents/tools/gateway.ts @@ -9,10 +9,11 @@ export type GatewayCallOptions = { }; export function resolveGatewayOptions(opts?: GatewayCallOptions) { + // Prefer an explicit override; otherwise let callGateway choose based on config. const url = typeof opts?.gatewayUrl === "string" && opts.gatewayUrl.trim() ? opts.gatewayUrl.trim() - : DEFAULT_GATEWAY_URL; + : undefined; const token = typeof opts?.gatewayToken === "string" && opts.gatewayToken.trim() ? opts.gatewayToken.trim() diff --git a/src/commands/poll.test.ts b/src/commands/poll.test.ts new file mode 100644 index 000000000..50b3ba5f6 --- /dev/null +++ b/src/commands/poll.test.ts @@ -0,0 +1,77 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import type { CliDeps } from "../cli/deps.js"; +import { pollCommand } from "./poll.js"; + +let testConfig: Record = {}; +vi.mock("../config/config.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + loadConfig: () => testConfig, + }; +}); + +const callGatewayMock = vi.fn(); +vi.mock("../gateway/call.js", () => ({ + callGateway: (...args: unknown[]) => callGatewayMock(...args), + randomIdempotencyKey: () => "idem-1", +})); + +const runtime = { + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), +}; + +const deps: CliDeps = { + sendMessageWhatsApp: vi.fn(), + sendMessageTelegram: vi.fn(), + sendMessageDiscord: vi.fn(), + sendMessageSlack: vi.fn(), + sendMessageSignal: vi.fn(), + sendMessageIMessage: vi.fn(), +}; + +describe("pollCommand", () => { + beforeEach(() => { + callGatewayMock.mockReset(); + testConfig = {}; + }); + + it("routes through gateway", async () => { + callGatewayMock.mockResolvedValueOnce({ messageId: "p1" }); + await pollCommand( + { + to: "+1", + question: "hi?", + option: ["y", "n"], + }, + deps, + runtime, + ); + expect(callGatewayMock).toHaveBeenCalledWith( + expect.objectContaining({ method: "poll" }), + ); + }); + + it("does not override remote gateway URL", async () => { + callGatewayMock.mockResolvedValueOnce({ messageId: "p1" }); + testConfig = { + gateway: { mode: "remote", remote: { url: "wss://remote.example" } }, + }; + await pollCommand( + { + to: "+1", + question: "hi?", + option: ["y", "n"], + }, + deps, + runtime, + ); + const args = callGatewayMock.mock.calls.at(-1)?.[0] as + | Record + | undefined; + expect(args?.url).toBeUndefined(); + }); +}); diff --git a/src/commands/poll.ts b/src/commands/poll.ts index 5fda34838..44f546c25 100644 --- a/src/commands/poll.ts +++ b/src/commands/poll.ts @@ -57,7 +57,6 @@ export async function pollCommand( toJid?: string; channelId?: string; }>({ - url: "ws://127.0.0.1:18789", method: "poll", params: { to: opts.to, diff --git a/src/commands/send.test.ts b/src/commands/send.test.ts index 03ced5bf2..d42557b87 100644 --- a/src/commands/send.test.ts +++ b/src/commands/send.test.ts @@ -81,6 +81,26 @@ describe("sendCommand", () => { expect(runtime.log).toHaveBeenCalledWith(expect.stringContaining("g1")); }); + it("does not override remote gateway URL", async () => { + callGatewayMock.mockResolvedValueOnce({ messageId: "g2" }); + testConfig = { + gateway: { mode: "remote", remote: { url: "wss://remote.example" } }, + }; + const deps = makeDeps(); + await sendCommand( + { + to: "+1", + message: "hi", + }, + deps, + runtime, + ); + const args = callGatewayMock.mock.calls.at(-1)?.[0] as + | Record + | undefined; + expect(args?.url).toBeUndefined(); + }); + it("passes gifPlayback to gateway send", async () => { callGatewayMock.mockClear(); callGatewayMock.mockResolvedValueOnce({ messageId: "g1" }); diff --git a/src/commands/send.ts b/src/commands/send.ts index 39db2462b..9dda37ea5 100644 --- a/src/commands/send.ts +++ b/src/commands/send.ts @@ -167,7 +167,6 @@ export async function sendCommand( callGateway<{ messageId: string; }>({ - url: "ws://127.0.0.1:18789", method: "send", params: { to: opts.to,