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),