diff --git a/src/agents/tools/gateway.test.ts b/src/agents/tools/gateway.test.ts index 8f12e33c462..9f0d16eb361 100644 --- a/src/agents/tools/gateway.test.ts +++ b/src/agents/tools/gateway.test.ts @@ -75,6 +75,15 @@ describe("resolveGatewayTarget – env URL override classification", () => { expect(resolveGatewayTarget()).toBeUndefined(); }); + it("returns undefined (local) when gateway.mode=remote with a loopback remote.url (no tunnel evidence)", () => { + // A configured loopback remote.url (e.g. ws://127.0.0.1:18789) is indistinguishable + // from a local gateway on a custom port. Without a non-loopback URL proving SSH tunnel + // usage, classify as local so deliveryContext is preserved and post-restart wake + // messages are not misrouted via stale extractDeliveryInfo routing. + setConfig({ gateway: { mode: "remote", remote: { url: "ws://127.0.0.1:18789" } } }); + expect(resolveGatewayTarget()).toBeUndefined(); + }); + it("returns undefined when gateway.mode=remote but gateway.remote.url is empty string", () => { setConfig({ gateway: { mode: "remote", remote: { url: " " } } }); expect(resolveGatewayTarget()).toBeUndefined(); diff --git a/src/agents/tools/gateway.ts b/src/agents/tools/gateway.ts index 84aa6101f40..613f57343e3 100644 --- a/src/agents/tools/gateway.ts +++ b/src/agents/tools/gateway.ts @@ -226,12 +226,12 @@ export function resolveGatewayTarget(opts?: GatewayCallOptions): GatewayOverride } } } - // No env override. When mode=remote with a configured remote URL → truly remote. - // When mode=remote but remote.url is absent, callGateway falls back to local loopback — - // classify that as local (undefined) so deliveryContext is not suppressed. - const remoteUrl = - cfg.gateway?.mode === "remote" ? trimToUndefined(cfg.gateway?.remote?.url) : undefined; - return cfg.gateway?.mode === "remote" && remoteUrl !== undefined ? "remote" : undefined; + // No env override. Classify as "remote" only when mode=remote is configured with a + // non-loopback remote URL. Loopback remote.url (e.g. ws://127.0.0.1:18789) is + // indistinguishable from a local gateway on a custom port; without a non-loopback + // URL proving SSH tunnel usage, treat it as local so deliveryContext is preserved + // and post-restart wake messages are not misrouted. + return isNonLoopbackRemoteUrlConfigured(cfg) ? "remote" : undefined; } return validateGatewayUrlOverrideForAgentTools({ cfg,