mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* Recommend Tailscale when the remote Orca runtime is unreachable When a remote-runtime connection fails (RemoteRuntimeClientError "Could not connect to the remote Orca runtime."), append an actionable Tailscale hint to the user-facing error, branched on whether the endpoint is already on a tailnet: - Non-Tailscale endpoint: recommend connecting both devices over Tailscale and pairing with its Tailscale address, with a download link. - Tailscale endpoint (*.ts.net or 100.64.0.0/10): point at the real causes — server offline on the tailnet, or Funnel reverted to tailnet-only — and note that already-paired devices reconnect without re-pairing. Applied at the desktop transport chokepoint (status probe, in-use calls, and subscriptions — connection failures reject, so the hint is applied to the thrown error, not just ok:false responses) and at the web client's connect/timeout sites. New pure shared helper mirrors withMacTailscaleDnsHint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review: scope CGNAT hint to IPv4 literals, track re-paired endpoint - isTailscaleEndpoint: gate the 100.64.0.0/10 check on a full IPv4 literal so DNS names like 100.64.0.1.example.com no longer get tailnet-specific advice. - callRuntimeEnvironment: capture the endpoint the queued closure actually used, so a re-pair between enqueue and dispatch can't append the wrong hint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Recognize Tailscale IPv6 endpoints and trailing-dot FQDNs in hint The remote-runtime Tailscale hint classified IPv6 Tailscale nodes (fd7a:115c:a1e0::/48) and trailing-dot FQDNs as non-Tailscale, so a user already reaching their server over Tailscale by IPv6 literal was wrongly told to 'connect both devices to Tailscale'. Pairing endpoints can carry bracketed IPv6 literals (resolvePairingEndpoint), so this is a reachable path. Normalize the extracted host (strip brackets and the trailing FQDN dot) and add an IPv6 ULA-range check. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: s546126 <268420947+s546126@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
89 lines
3.8 KiB
TypeScript
89 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
isTailscaleEndpoint,
|
|
withRemoteRuntimeTailscaleHint
|
|
} from './remote-runtime-tailscale-hint'
|
|
|
|
const UNREACHABLE = 'Could not connect to the remote Orca runtime.'
|
|
|
|
describe('isTailscaleEndpoint', () => {
|
|
it('matches MagicDNS hostnames', () => {
|
|
expect(isTailscaleEndpoint('wss://example-host.tailnet.ts.net')).toBe(true)
|
|
expect(isTailscaleEndpoint('ws://host.ts.net:6768')).toBe(true)
|
|
})
|
|
|
|
it('matches the 100.64.0.0/10 CGNAT range', () => {
|
|
expect(isTailscaleEndpoint('ws://100.64.0.5:6768')).toBe(true)
|
|
expect(isTailscaleEndpoint('ws://100.127.255.255:6768')).toBe(true)
|
|
})
|
|
|
|
it('matches Tailscale IPv6 (fd7a:115c:a1e0::/48) literals', () => {
|
|
// Pairing endpoints can carry a bracketed IPv6 literal (resolvePairingEndpoint).
|
|
expect(isTailscaleEndpoint('wss://[fd7a:115c:a1e0::1]:443')).toBe(true)
|
|
expect(isTailscaleEndpoint('ws://[fd7a:115c:a1e0:ab12:4843:cd96:626b:1]:6768')).toBe(true)
|
|
expect(isTailscaleEndpoint('ws://[2001:db8::1]:6768')).toBe(false)
|
|
expect(isTailscaleEndpoint('ws://[::1]:6768')).toBe(false)
|
|
})
|
|
|
|
it('rejects non-Tailscale hosts and the surrounding 100.x space', () => {
|
|
expect(isTailscaleEndpoint('ws://192.168.1.10:6768')).toBe(false)
|
|
expect(isTailscaleEndpoint('wss://orca.example.com')).toBe(false)
|
|
expect(isTailscaleEndpoint('ws://100.63.0.1:6768')).toBe(false)
|
|
expect(isTailscaleEndpoint('ws://100.128.0.1:6768')).toBe(false)
|
|
expect(isTailscaleEndpoint('ws://notts.net.evil.com')).toBe(false)
|
|
// A DNS name that merely starts with a CGNAT-shaped label is not a TS IP.
|
|
expect(isTailscaleEndpoint('ws://100.64.0.1.example.com:6768')).toBe(false)
|
|
})
|
|
|
|
it('handles bare hosts without a scheme and empty input', () => {
|
|
expect(isTailscaleEndpoint('host.ts.net')).toBe(true)
|
|
// A trailing-dot FQDN is still the same tailnet host.
|
|
expect(isTailscaleEndpoint('wss://host.ts.net.')).toBe(true)
|
|
expect(isTailscaleEndpoint('')).toBe(false)
|
|
expect(isTailscaleEndpoint(null)).toBe(false)
|
|
expect(isTailscaleEndpoint(undefined)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('withRemoteRuntimeTailscaleHint', () => {
|
|
it('recommends switching to Tailscale when the endpoint is not on a tailnet', () => {
|
|
const result = withRemoteRuntimeTailscaleHint(UNREACHABLE, 'ws://192.168.1.10:6768')
|
|
expect(result).toContain(UNREACHABLE)
|
|
expect(result).toContain('connect both devices to Tailscale')
|
|
expect(result).toContain('https://tailscale.com/download')
|
|
})
|
|
|
|
it('points at tailnet-specific causes when the endpoint is already Tailscale', () => {
|
|
const result = withRemoteRuntimeTailscaleHint(UNREACHABLE, 'wss://example-host.tailnet.ts.net')
|
|
expect(result).toContain('Funnel reverted to tailnet-only')
|
|
expect(result).toContain('already-paired devices reconnect with their saved token')
|
|
expect(result).not.toContain('https://tailscale.com/download')
|
|
})
|
|
|
|
it('covers the close and timeout failure variants', () => {
|
|
expect(
|
|
withRemoteRuntimeTailscaleHint(
|
|
'Remote Orca runtime closed the connection.',
|
|
'ws://192.168.1.10:6768'
|
|
)
|
|
).toContain('connect both devices to Tailscale')
|
|
expect(
|
|
withRemoteRuntimeTailscaleHint(
|
|
'Timed out while connecting to the remote Orca runtime.',
|
|
'wss://host.ts.net'
|
|
)
|
|
).toContain('Funnel reverted to tailnet-only')
|
|
})
|
|
|
|
it('leaves non-connectivity errors untouched', () => {
|
|
const auth = 'Remote Orca runtime rejected the pairing token.'
|
|
expect(withRemoteRuntimeTailscaleHint(auth, 'ws://192.168.1.10:6768')).toBe(auth)
|
|
})
|
|
|
|
it('is idempotent — does not append the hint twice', () => {
|
|
const once = withRemoteRuntimeTailscaleHint(UNREACHABLE, 'ws://192.168.1.10:6768')
|
|
const twice = withRemoteRuntimeTailscaleHint(once, 'ws://192.168.1.10:6768')
|
|
expect(twice).toBe(once)
|
|
})
|
|
})
|