This PR includes three main improvements:
1. Tailscale Binary Detection with Fallback Strategies
- Added findTailscaleBinary() with multi-strategy detection:
* PATH lookup via 'which' command
* Known macOS app path (/Applications/Tailscale.app/Contents/MacOS/Tailscale)
* find /Applications for Tailscale.app
* locate database lookup
- Added getTailscaleBinary() with caching
- Updated all Tailscale operations to use detected binary
- Added TUI warning when Tailscale binary not found for serve/funnel modes
2. Custom Gateway IP Binding with Fallback
- New bind mode "custom" allowing user-specified IP with fallback to 0.0.0.0
- Removed "tailnet" mode (folded into "auto")
- All modes now support graceful fallback: custom (if fail → 0.0.0.0), loopback (127.0.0.1 → 0.0.0.0), auto (tailnet → 0.0.0.0), lan (0.0.0.0)
- Added customBindHost config option for custom bind mode
- Added canBindTo() helper to test IP availability before binding
- Updated configure and onboarding wizards with new bind mode options
3. Health Probe Password Auth Fix
- Gateway probe now tries both new and old passwords
- Fixes issue where password change fails health check if gateway hasn't restarted yet
- Uses nextConfig password first, falls back to baseConfig password if needed
Files changed:
- src/infra/tailscale.ts: Binary detection + caching
- src/gateway/net.ts: IP binding with fallback logic
- src/config/types.ts: BridgeBindMode type + customBindHost field
- src/commands/configure.ts: Health probe dual-password try + Tailscale detection warning + bind mode UI
- src/wizard/onboarding.ts: Tailscale detection warning + bind mode UI
- src/gateway/server.ts: Use new resolveGatewayBindHost
- src/gateway/call.ts: Updated preferTailnet logic (removed "tailnet" mode)
- src/commands/onboard-types.ts: Updated GatewayBind type
- src/commands/onboard-helpers.ts: resolveControlUiLinks updated
- src/cli/*.ts: Updated bind mode casts
- src/gateway/call.test.ts: Removed "tailnet" mode test
Claude's extended thinking feature generates thought_signature fields
(message IDs like "msg_abc123...") in content blocks. When these are
sent to Google's Gemini API, it expects Base64-encoded bytes and
rejects Claude's format with a 400 error.
This commit adds stripThoughtSignatures() to remove these fields from
assistant message content blocks during sanitization, enabling session
histories to be shared across different providers (e.g., Claude → Gemini).
Fixes cross-provider session bug where switching from Claude-thinking
to Gemini (or vice versa) would fail with:
"Invalid value at 'thought_signature' (TYPE_BYTES), Base64 decoding failed"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refactors tests to use a shared config object and adds stricter assertions to verify that the config is passed correctly to createTelegramBot. Ensures the bindings property is checked in the test expectations.
The Telegram webhook and monitor now accept and pass through accountId and config parameters, enabling routing and configuration per Telegram account. Tests have been updated to verify correct bot instantiation and DM routing based on accountId bindings.