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.