diff --git a/apps/macos/Sources/Clawdis/GatewayConnection.swift b/apps/macos/Sources/Clawdis/GatewayConnection.swift index 4c15708a0..dabec1d8e 100644 --- a/apps/macos/Sources/Clawdis/GatewayConnection.swift +++ b/apps/macos/Sources/Clawdis/GatewayConnection.swift @@ -165,9 +165,11 @@ extension GatewayConnection { channel: String? = nil, idempotencyKey: String = UUID().uuidString) async -> (ok: Bool, error: String?) { + let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return (false, "message empty") } do { let params: [String: Any] = [ - "message": message, + "message": trimmed, "sessionKey": sessionKey, "thinking": thinking ?? "default", "deliver": deliver, diff --git a/apps/macos/Tests/ClawdisIPCTests/AgentRPCTests.swift b/apps/macos/Tests/ClawdisIPCTests/AgentRPCTests.swift index 4f9e0c6ff..c23e0b6f1 100644 --- a/apps/macos/Tests/ClawdisIPCTests/AgentRPCTests.swift +++ b/apps/macos/Tests/ClawdisIPCTests/AgentRPCTests.swift @@ -2,16 +2,21 @@ import Testing @testable import Clawdis @testable import ClawdisIPC -@Suite(.serialized) struct AgentRPCTests { +@Suite(.serialized) struct GatewayConnectionControlTests { @Test func statusFailsWhenProcessMissing() async { - let result = await AgentRPC.shared.status() + let result = await GatewayConnection.shared.status() // We don't assert ok because the worker may not be available in CI. // Instead, ensure the call returns without throwing and provides a message. #expect(result.ok == true || result.error != nil) } @Test func rejectEmptyMessage() async { - let result = await AgentRPC.shared.send(text: "", thinking: nil, sessionKey: "main", deliver: false, to: nil) + let result = await GatewayConnection.shared.sendAgent( + message: "", + thinking: nil, + sessionKey: "main", + deliver: false, + to: nil) #expect(result.ok == false) } } diff --git a/docs/ios/spec.md b/docs/ios/spec.md index 38bf4cda8..d1815661c 100644 --- a/docs/ios/spec.md +++ b/docs/ios/spec.md @@ -31,7 +31,7 @@ Non-goals (v1): ## Current repo reality (constraints we respect) - The Gateway WebSocket server binds to `127.0.0.1:18789` (`src/gateway/server.ts`) with an optional `CLAWDIS_GATEWAY_TOKEN`. - macOS “Canvas” exists today, but is **mac-only** and controlled via mac app IPC (`clawdis-mac canvas ...`) rather than the Gateway protocol (`docs/mac/canvas.md`). -- Voice wake forwards via `GatewayChannel` to Gateway `agent` (mac app: `VoiceWakeForwarder` → `AgentRPC`). +- Voice wake forwards via `GatewayChannel` to Gateway `agent` (mac app: `VoiceWakeForwarder` → `GatewayConnection.sendAgent`). ## Recommended topology (B): Gateway-owned Bridge + loopback Gateway Keep the Node gateway loopback-only; expose a dedicated **gateway-owned bridge** to the LAN/tailnet. diff --git a/docs/refactor/new-arch.md b/docs/refactor/new-arch.md index 5ad9be475..0217ae41b 100644 --- a/docs/refactor/new-arch.md +++ b/docs/refactor/new-arch.md @@ -97,7 +97,7 @@ Goal: replace legacy gateway/stdin/TCP control with a single WebSocket Gateway, ## Phase 5 — Clients migration - **macOS app**: - - Replace stdio/SSH RPC with WS client (tunneled via SSH/Tailscale for remote). ✅ AgentRPC/ControlChannel now use Gateway WS. + - Replace stdio/SSH RPC with WS client (tunneled via SSH/Tailscale for remote). ✅ GatewayConnection/ControlChannel now use Gateway WS. - Implement handshake, snapshot hydration, subscriptions to `presence`, `tick`, `agent`, `shutdown`. ✅ snapshot + presence events broadcast to InstancesStore; agent events still to wire to UI if desired. - Remove immediate `health/system-presence` fetch on connect. ✅ presence hydrated from snapshot; periodic refresh kept as fallback. - Handle connect failures (`res ok:false`) and retry with backoff if version/token mismatched. ✅ macOS GatewayChannel reconnects with exponential backoff.