From 645def75353d3f0a34977af2f9c396109e82070b Mon Sep 17 00:00:00 2001 From: Bryan Marty Date: Mon, 9 Mar 2026 19:45:37 +0000 Subject: [PATCH] fix: treat config-based remote gateway (gateway.mode=remote) as remote target resolveGatewayTarget() previously returned undefined when no gatewayUrl override was provided, even when gateway.mode=remote routes the call to gateway.remote.url. This caused isRemoteGateway to be false in that path, so deliveryContext was forwarded to the remote host and could stamp the restart sentinel with the local chat route, misdelivering post-restart wake messages. Fix: check gateway.mode=remote in the no-override branch and return 'remote' so deliveryContext is suppressed for config-based remote targets the same way it is for explicit gatewayUrl overrides. Adds a test covering the config-based remote mode case (no gatewayUrl). Closes #34580 (P1 review comment). --- src/agents/tools/gateway-tool.test.ts | 27 +++++++++++++++++++++++++++ src/agents/tools/gateway.ts | 18 ++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/agents/tools/gateway-tool.test.ts b/src/agents/tools/gateway-tool.test.ts index 6e0c9e14e51..2e3cab0f520 100644 --- a/src/agents/tools/gateway-tool.test.ts +++ b/src/agents/tools/gateway-tool.test.ts @@ -170,6 +170,33 @@ describe("createGatewayTool – live delivery context guard", () => { expect(sentinelPayload?.deliveryContext?.to).toBe("123456789"); }); + it("does not forward live RPC delivery context when gateway.mode=remote is configured (no URL override)", async () => { + // When gateway.mode=remote is set in config, callGatewayTool() routes to + // gateway.remote.url without an explicit gatewayUrl param. resolveGatewayTarget + // must return "remote" in this case so deliveryContext is suppressed, preventing + // the remote sentinel from being stamped with the local chat route. + mocks.callGatewayTool.mockClear(); + // No gatewayUrl override — config-based remote mode + mocks.readGatewayCallOptions.mockReturnValueOnce({}); + mocks.resolveGatewayTarget.mockReturnValueOnce("remote"); + const tool = createGatewayTool({ + agentSessionKey: "agent:main:main", + agentChannel: "discord", + agentTo: "123456789", + }); + + await execTool(tool, { + action: "config.patch", + raw: '{"key":"value"}', + baseHash: "abc123", + sessionKey: "agent:main:main", + note: "config-remote patch (no URL override)", + }); + + const forwardedParams = getCallArg>(mocks.callGatewayTool, 0, 2); + expect(forwardedParams?.deliveryContext).toBeUndefined(); + }); + it("does not forward live RPC delivery context when gatewayUrl targets a remote gateway", async () => { // A remote gateway has its own extractDeliveryInfo(sessionKey) — forwarding // the local agent's deliveryContext would write a sentinel with the wrong diff --git a/src/agents/tools/gateway.ts b/src/agents/tools/gateway.ts index 1c372403f2a..3f663dc0508 100644 --- a/src/agents/tools/gateway.ts +++ b/src/agents/tools/gateway.ts @@ -115,15 +115,21 @@ function resolveGatewayOverrideToken(params: { /** * Resolves whether a GatewayCallOptions points to a local or remote gateway. - * Returns undefined when no gatewayUrl override is present (default local gateway). - * Local loopback overrides (127.0.0.1, localhost, [::1]) return "local"; - * all other URL overrides return "remote". + * Returns "remote" when a remote gatewayUrl override is present, OR when + * gateway.mode=remote is configured and no override is provided (config-based remote). + * Returns "local" for explicit loopback URL overrides (127.0.0.1, localhost, [::1]). + * Returns undefined only when no override is present and gateway.mode is not "remote" + * (i.e. the default local gateway). */ export function resolveGatewayTarget(opts?: GatewayCallOptions): GatewayOverrideTarget | undefined { - if (trimToUndefined(opts?.gatewayUrl) === undefined) { - return undefined; - } const cfg = loadConfig(); + if (trimToUndefined(opts?.gatewayUrl) === undefined) { + // No explicit URL override — fall back to config-based mode. + // When gateway.mode=remote, callGatewayTool() routes to the configured + // gateway.remote.url, so this is effectively a remote target even without + // an explicit gatewayUrl param. + return cfg.gateway?.mode === "remote" ? "remote" : undefined; + } return validateGatewayUrlOverrideForAgentTools({ cfg, urlOverride: String(opts?.gatewayUrl),