diff --git a/apps/macos/Sources/Clawdis/AppState.swift b/apps/macos/Sources/Clawdis/AppState.swift index 9619089f6..2f4eb007d 100644 --- a/apps/macos/Sources/Clawdis/AppState.swift +++ b/apps/macos/Sources/Clawdis/AppState.swift @@ -161,6 +161,14 @@ final class AppState: ObservableObject { didSet { self.ifNotPreview { UserDefaults.standard.set(self.webChatPort, forKey: webChatPortKey) } } } + @Published var attachExistingGatewayOnly: Bool { + didSet { + self.ifNotPreview { + UserDefaults.standard.set(self.attachExistingGatewayOnly, forKey: attachExistingGatewayOnlyKey) + } + } + } + @Published var remoteTarget: String { didSet { self.ifNotPreview { UserDefaults.standard.set(self.remoteTarget, forKey: remoteTargetKey) } } } @@ -238,6 +246,7 @@ final class AppState: ObservableObject { self.webChatSwiftUIEnabled = UserDefaults.standard.object(forKey: webChatSwiftUIEnabledKey) as? Bool ?? false let storedPort = UserDefaults.standard.integer(forKey: webChatPortKey) self.webChatPort = storedPort > 0 ? storedPort : 18788 + self.attachExistingGatewayOnly = UserDefaults.standard.bool(forKey: attachExistingGatewayOnlyKey) if !self.isPreview { Task.detached(priority: .utility) { [weak self] in @@ -353,6 +362,7 @@ extension AppState { state.remoteTarget = "user@example.com" state.remoteIdentity = "~/.ssh/id_ed25519" state.remoteProjectRoot = "~/Projects/clawdis" + state.attachExistingGatewayOnly = false return state } } @@ -380,6 +390,10 @@ enum AppStateStore { let stored = UserDefaults.standard.integer(forKey: webChatPortKey) return stored > 0 ? stored : 18788 } + + static var attachExistingGatewayOnly: Bool { + UserDefaults.standard.bool(forKey: attachExistingGatewayOnlyKey) + } } extension AppState { diff --git a/apps/macos/Sources/Clawdis/Constants.swift b/apps/macos/Sources/Clawdis/Constants.swift index da2849c9f..140e953c6 100644 --- a/apps/macos/Sources/Clawdis/Constants.swift +++ b/apps/macos/Sources/Clawdis/Constants.swift @@ -28,6 +28,7 @@ let webChatSwiftUIEnabledKey = "clawdis.webChatSwiftUIEnabled" let webChatPortKey = "clawdis.webChatPort" let modelCatalogPathKey = "clawdis.modelCatalogPath" let modelCatalogReloadKey = "clawdis.modelCatalogReload" +let attachExistingGatewayOnlyKey = "clawdis.gateway.attachExistingOnly" let heartbeatsEnabledKey = "clawdis.heartbeatsEnabled" let voiceWakeSupported: Bool = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26 let cliHelperSearchPaths = ["/usr/local/bin", "/opt/homebrew/bin"] diff --git a/apps/macos/Sources/Clawdis/DebugSettings.swift b/apps/macos/Sources/Clawdis/DebugSettings.swift index a475d0645..5166807f0 100644 --- a/apps/macos/Sources/Clawdis/DebugSettings.swift +++ b/apps/macos/Sources/Clawdis/DebugSettings.swift @@ -23,6 +23,7 @@ struct DebugSettings: View { @State private var portKillStatus: String? @State private var pendingKill: DebugActions.PortListener? @AppStorage(webChatSwiftUIEnabledKey) private var webChatSwiftUIEnabled: Bool = false + @AppStorage(attachExistingGatewayOnlyKey) private var attachExistingGatewayOnly: Bool = false var body: some View { ScrollView(.vertical) { @@ -57,6 +58,9 @@ struct DebugSettings: View { .foregroundStyle(.secondary) } } + Toggle("Only attach to existing gateway (don’t spawn locally)", isOn: self.$attachExistingGatewayOnly) + .toggleStyle(.switch) + .help("When enabled in local mode, the mac app will only connect to an already-running gateway and will not start one itself.") VStack(alignment: .leading, spacing: 4) { Text("Gateway stdout/stderr") .font(.caption.weight(.semibold)) diff --git a/apps/macos/Sources/Clawdis/GatewayProcessManager.swift b/apps/macos/Sources/Clawdis/GatewayProcessManager.swift index 287ec0bb5..232364b0e 100644 --- a/apps/macos/Sources/Clawdis/GatewayProcessManager.swift +++ b/apps/macos/Sources/Clawdis/GatewayProcessManager.swift @@ -112,6 +112,14 @@ final class GatewayProcessManager: ObservableObject { if await self.attachExistingGatewayIfAvailable() { return } + // Respect debug toggle: only attach, never spawn, when enabled. + if AppStateStore.attachExistingGatewayOnly { + await MainActor.run { + self.status = .stopped + self.appendLog("[gateway] attach-only enabled; not spawning local gateway\n") + } + return + } await self.spawnGateway() } } diff --git a/apps/macos/Sources/Clawdis/Utilities.swift b/apps/macos/Sources/Clawdis/Utilities.swift index d6eb5a19e..0ab27add9 100644 --- a/apps/macos/Sources/Clawdis/Utilities.swift +++ b/apps/macos/Sources/Clawdis/Utilities.swift @@ -495,6 +495,10 @@ enum CommandResolver { projectRoot: projectRoot) } + static var attachExistingGatewayOnly: Bool { + UserDefaults.standard.bool(forKey: attachExistingGatewayOnlyKey) + } + static func connectionModeIsRemote() -> Bool { self.connectionSettings().mode == .remote }