Bryan Marty e13a94df72
fix: classify tunneled loopback gateway URLs as remote when mode=remote is configured
When gateway.mode=remote is configured with a non-loopback remote.url,
a loopback gatewayUrl (ws://127.0.0.1:...) is likely an SSH port-forward
tunnel endpoint (ssh -N -L <local>:remote-host:<remote>). Previously,
resolveGatewayTarget() classified any loopback URL as 'local', causing
gateway-tool to forward live deliveryContext into remote config.apply /
config.patch / update.run writes. Because server handlers prefer
params.deliveryContext, post-restart wake messages were misrouted to the
caller's local chat context instead of the remote session.

Fix both classification sites:
1. validateGatewayUrlOverrideForAgentTools: when a loopback URL hits
   localAllowed, check isNonLoopbackRemoteUrlConfigured(cfg); if true,
   return 'remote' (tunnel) rather than 'local'.
2. resolveGatewayTarget fallback (rejected URL path): same check for
   the isLoopback branch — prefer 'remote' when mode=remote with a
   non-loopback remote.url is present.

Add isNonLoopbackRemoteUrlConfigured() helper (returns true iff
gateway.mode=remote AND gateway.remote.url is a non-loopback hostname).

Tests: add SSH tunnel cases in gateway.test.ts; update the
'OPENCLAW_GATEWAY_URL takes precedence' test which now correctly
returns 'remote' when mode=remote with non-loopback remote.url; add
a variant without remote config to cover the 'local' precedence case.
2026-03-21 04:16:07 +00:00
..
2026-03-15 21:39:49 -07:00
2026-03-17 07:06:25 +00:00
2026-03-17 07:06:25 +00:00