Merge pull request #960 from kkarimi/fix/mac-node-bridge-tunnel-865
macOS: prefer bridge tunnel port in remote modemain
commit
409e33d9c2
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -86,7 +86,28 @@
|
|||
- UI: use application-defined WebSocket close code (browser compatibility). (#918) — thanks @rahthakor.
|
||||
- TUI: render picker overlays via the overlay stack so /models and /settings display. (#921) — thanks @grizzdank.
|
||||
- TUI: add a bright spinner + elapsed time in the status line for send/stream/run states.
|
||||
- TUI: show LLM error messages (rate limits, auth, etc.) instead of `(no output)`.
|
||||
- Gateway/Dev: ensure `pnpm gateway:dev` always uses the dev profile config + state (`~/.clawdbot-dev`).
|
||||
|
||||
#### Agents / Auth / Tools / Sandbox
|
||||
- Agents: make user time zone and 24-hour time explicit in the system prompt. (#859) — thanks @CashWilliams.
|
||||
- Agents: strip downgraded tool call text without eating adjacent replies and filter thinking-tag leaks. (#905) — thanks @erikpr1994.
|
||||
- Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli.
|
||||
- Agents: scrub tuple `items` schemas for Gemini tool calls. (#926, fixes #746) — thanks @grp06.
|
||||
- Agents: stabilize sub-agent announce status from runtime outcomes and normalize Result/Notes. (#835) — thanks @roshanasingh4.
|
||||
- Auth: normalize Claude Code CLI profile mode to oauth and auto-migrate config. (#855) — thanks @sebslight.
|
||||
- Embedded runner: suppress raw API error payloads from replies. (#924) — thanks @grp06.
|
||||
- Logging: tolerate `EIO` from console writes to avoid gateway crashes. (#925, fixes #878) — thanks @grp06.
|
||||
- Sandbox: restore `docker.binds` config validation and preserve configured PATH for `docker exec`. (#873) — thanks @akonyer.
|
||||
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
||||
|
||||
#### macOS / Apps
|
||||
- macOS: ensure launchd log directory exists with a test-only override. (#909) — thanks @roshanasingh4.
|
||||
- macOS: format ConnectionsStore config to satisfy SwiftFormat lint. (#852) — thanks @mneves75.
|
||||
- macOS: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor.
|
||||
- macOS: reuse launchd gateway auth and skip wizard when gateway config already exists. (#917)
|
||||
- macOS: prefer the default bridge tunnel port in remote mode for node bridge connectivity; document macOS remote control + bridge tunnels. (#960, fixes #865) — thanks @kkarimi.
|
||||
- Apps: use canonical main session keys from gateway defaults across macOS/iOS/Android to avoid creating bare `main` sessions.
|
||||
- macOS: fix cron preview/testing payload to use `channel` key. (#867) — thanks @wes-davis.
|
||||
- Telegram: honor `channels.telegram.timeoutSeconds` for grammY API requests. (#863) — thanks @Snaver.
|
||||
- Telegram: split long captions into media + follow-up text messages. (#907) - thanks @jalehman.
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ final class MenuSessionsInjector: NSObject, NSMenuDelegate {
|
|||
|
||||
extension MenuSessionsInjector {
|
||||
// MARK: - Injection
|
||||
|
||||
private var mainSessionKey: String { WorkActivityStore.shared.mainSessionKey }
|
||||
|
||||
private func inject(into menu: NSMenu) {
|
||||
|
|
|
|||
|
|
@ -312,9 +312,23 @@ final class MacNodeModeCoordinator {
|
|||
}
|
||||
|
||||
let remotePort = Self.remoteBridgePort()
|
||||
let preferredLocalPort = Self.loopbackBridgePort()
|
||||
if let preferredLocalPort {
|
||||
self.logger.info(
|
||||
"mac node bridge tunnel starting " +
|
||||
"preferredLocalPort=\(preferredLocalPort, privacy: .public) " +
|
||||
"remotePort=\(remotePort, privacy: .public)")
|
||||
} else {
|
||||
self.logger.info(
|
||||
"mac node bridge tunnel starting " +
|
||||
"preferredLocalPort=none " +
|
||||
"remotePort=\(remotePort, privacy: .public)")
|
||||
}
|
||||
self.tunnel = try await RemotePortTunnel.create(
|
||||
remotePort: remotePort,
|
||||
allowRemoteUrlOverride: false)
|
||||
preferredLocalPort: preferredLocalPort,
|
||||
allowRemoteUrlOverride: false,
|
||||
allowRandomLocalPort: true)
|
||||
if let localPort = self.tunnel?.localPort,
|
||||
let port = NWEndpoint.Port(rawValue: localPort)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -110,6 +110,32 @@ Tip: compare against `pnpm clawdbot gateway discover --json` to see whether the
|
|||
macOS app’s discovery pipeline (NWBrowser + tailnet DNS‑SD fallback) differs from
|
||||
the Node CLI’s `dns-sd` based discovery.
|
||||
|
||||
## Remote connection plumbing (SSH tunnels)
|
||||
|
||||
When the macOS app runs in **Remote** mode, it opens SSH tunnels so local UI
|
||||
components can talk to a remote Gateway as if it were on localhost. There are
|
||||
two independent tunnels:
|
||||
|
||||
### Control tunnel (Gateway control/WebSocket port)
|
||||
- **Purpose:** health checks, status, Web Chat, config, and other control-plane calls.
|
||||
- **Local port:** the Gateway port (default `18789`), always stable.
|
||||
- **Remote port:** the same Gateway port on the remote host.
|
||||
- **Behavior:** no random local port; the app reuses an existing healthy tunnel
|
||||
or restarts it if needed.
|
||||
- **SSH shape:** `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode +
|
||||
ExitOnForwardFailure + keepalive options.
|
||||
|
||||
### Node bridge tunnel (macOS node mode)
|
||||
- **Purpose:** connect the macOS node to the Gateway **Bridge** protocol (TCP JSONL).
|
||||
- **Remote port:** `gatewayPort + 1` (default `18790`), derived from the Gateway port.
|
||||
- **Local port preference:** `CLAWDBOT_BRIDGE_PORT` or the default `18790`.
|
||||
- **Behavior:** prefer the default bridge port for consistency; fall back to a
|
||||
random local port if the preferred one is busy. The node then connects to the
|
||||
resolved local port.
|
||||
|
||||
For setup steps, see [macOS remote access](/platforms/mac/remote). For protocol
|
||||
details, see [Bridge protocol](/gateway/bridge-protocol).
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Gateway runbook](/gateway)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { pollUntil } from "../../../test/helpers/poll.js";
|
|||
import { approveNodePairing, listNodePairing } from "../node-pairing.js";
|
||||
import { configureNodeBridgeSocket, startNodeBridgeServer } from "./server.js";
|
||||
|
||||
const pairingTimeoutMs = process.platform === "win32" ? 8000 : 3000;
|
||||
const suiteTimeoutMs = process.platform === "win32" ? 20000 : 10000;
|
||||
|
||||
function createLineReader(socket: net.Socket) {
|
||||
let buffer = "";
|
||||
const pending: Array<(line: string) => void> = [];
|
||||
|
|
@ -55,9 +58,8 @@ async function waitForSocketConnect(socket: net.Socket) {
|
|||
});
|
||||
}
|
||||
|
||||
describe("node bridge server", () => {
|
||||
describe("node bridge server", { timeout: suiteTimeoutMs }, () => {
|
||||
let baseDir = "";
|
||||
const pairingTimeoutMs = process.platform === "win32" ? 8000 : 3000;
|
||||
|
||||
const pickNonLoopbackIPv4 = () => {
|
||||
const ifaces = os.networkInterfaces();
|
||||
|
|
|
|||
Loading…
Reference in New Issue