diff --git a/apps/macos/Sources/OpenClaw/DebugSettings.swift b/apps/macos/Sources/OpenClaw/DebugSettings.swift index e12a4e52839..97ce692696c 100644 --- a/apps/macos/Sources/OpenClaw/DebugSettings.swift +++ b/apps/macos/Sources/OpenClaw/DebugSettings.swift @@ -92,14 +92,6 @@ struct DebugSettings: View { self.launchAgentWriteDisabled = GatewayLaunchAgentManager.isLaunchAgentWriteDisabled() return } - if newValue { - Task { - _ = await GatewayLaunchAgentManager.set( - enabled: false, - bundlePath: Bundle.main.bundlePath, - port: GatewayEnvironment.gatewayPort()) - } - } } Text( diff --git a/apps/macos/Sources/OpenClaw/GatewayLaunchAgentManager.swift b/apps/macos/Sources/OpenClaw/GatewayLaunchAgentManager.swift index bc57055fb61..44791637353 100644 --- a/apps/macos/Sources/OpenClaw/GatewayLaunchAgentManager.swift +++ b/apps/macos/Sources/OpenClaw/GatewayLaunchAgentManager.swift @@ -5,7 +5,12 @@ enum GatewayLaunchAgentManager { private static let disableLaunchAgentMarker = ".openclaw/disable-launchagent" private static var disableLaunchAgentMarkerURL: URL { - FileManager().homeDirectoryForCurrentUser + #if DEBUG + if let testingDisableLaunchAgentMarkerURL { + return testingDisableLaunchAgentMarkerURL + } + #endif + return FileManager().homeDirectoryForCurrentUser .appendingPathComponent(self.disableLaunchAgentMarker) } @@ -19,6 +24,10 @@ enum GatewayLaunchAgentManager { return false } + static func applyAttachOnlyRuntimeOverride() -> String? { + self.setLaunchAgentWriteDisabled(true) + } + static func setLaunchAgentWriteDisabled(_ disabled: Bool) -> String? { let marker = self.disableLaunchAgentMarkerURL if disabled { @@ -144,6 +153,15 @@ extension GatewayLaunchAgentManager { timeout: Double, quiet: Bool) async -> CommandResult { + #if DEBUG + if self.testingInterceptDaemonCommands { + self.testingDaemonCommandCalls.append(args) + return CommandResult( + success: true, + payload: Data("{\"ok\":true}".utf8), + message: nil) + } + #endif let command = CommandResolver.openclawCommand( subcommand: "gateway", extraArgs: self.withJsonFlag(args), @@ -187,4 +205,26 @@ extension GatewayLaunchAgentManager { private static func summarize(_ text: String) -> String? { TextSummarySupport.summarizeLastLine(text) } + + #if DEBUG + nonisolated(unsafe) private static var testingDisableLaunchAgentMarkerURL: URL? + nonisolated(unsafe) private static var testingInterceptDaemonCommands = false + nonisolated(unsafe) private static var testingDaemonCommandCalls: [[String]] = [] + + static func setTestingDisableLaunchAgentMarkerURL(_ url: URL?) { + self.testingDisableLaunchAgentMarkerURL = url + } + + static func setTestingInterceptDaemonCommands(_ intercept: Bool) { + self.testingInterceptDaemonCommands = intercept + } + + static func clearTestingDaemonCommandCalls() { + self.testingDaemonCommandCalls.removeAll(keepingCapacity: false) + } + + static func testingDaemonCommandCallsSnapshot() -> [[String]] { + self.testingDaemonCommandCalls + } + #endif } diff --git a/apps/macos/Sources/OpenClaw/MenuBar.swift b/apps/macos/Sources/OpenClaw/MenuBar.swift index 5f2c2a06f96..6e29f8a0f52 100644 --- a/apps/macos/Sources/OpenClaw/MenuBar.swift +++ b/apps/macos/Sources/OpenClaw/MenuBar.swift @@ -98,16 +98,10 @@ struct OpenClawApp: App { private static func applyAttachOnlyOverrideIfNeeded() { let args = CommandLine.arguments guard args.contains("--attach-only") || args.contains("--no-launchd") else { return } - if let error = GatewayLaunchAgentManager.setLaunchAgentWriteDisabled(true) { + if let error = GatewayLaunchAgentManager.applyAttachOnlyRuntimeOverride() { Self.logger.error("attach-only flag failed: \(error, privacy: .public)") return } - Task { - _ = await GatewayLaunchAgentManager.set( - enabled: false, - bundlePath: Bundle.main.bundlePath, - port: GatewayEnvironment.gatewayPort()) - } Self.logger.info("attach-only flag enabled") } diff --git a/apps/macos/Tests/OpenClawIPCTests/GatewayLaunchAgentManagerTests.swift b/apps/macos/Tests/OpenClawIPCTests/GatewayLaunchAgentManagerTests.swift index e7d0f598957..4ca9aeab8fc 100644 --- a/apps/macos/Tests/OpenClawIPCTests/GatewayLaunchAgentManagerTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/GatewayLaunchAgentManagerTests.swift @@ -3,6 +3,29 @@ import Testing @testable import OpenClaw struct GatewayLaunchAgentManagerTests { + @Test func `attach only runtime override does not uninstall gateway launch agent`() throws { + let dir = FileManager().temporaryDirectory + .appendingPathComponent("openclaw-attach-only-\(UUID().uuidString)", isDirectory: true) + let marker = dir.appendingPathComponent("disable-launchagent") + try FileManager().createDirectory(at: dir, withIntermediateDirectories: true) + defer { try? FileManager().removeItem(at: dir) } + defer { + GatewayLaunchAgentManager.setTestingDisableLaunchAgentMarkerURL(nil) + GatewayLaunchAgentManager.setTestingInterceptDaemonCommands(false) + GatewayLaunchAgentManager.clearTestingDaemonCommandCalls() + } + + GatewayLaunchAgentManager.setTestingDisableLaunchAgentMarkerURL(marker) + GatewayLaunchAgentManager.setTestingInterceptDaemonCommands(true) + GatewayLaunchAgentManager.clearTestingDaemonCommandCalls() + + let error = GatewayLaunchAgentManager.applyAttachOnlyRuntimeOverride() + + #expect(error == nil) + #expect(FileManager().fileExists(atPath: marker.path)) + #expect(GatewayLaunchAgentManager.testingDaemonCommandCallsSnapshot().isEmpty) + } + @Test func `launch agent plist snapshot parses args and env`() throws { let url = FileManager().temporaryDirectory .appendingPathComponent("openclaw-launchd-\(UUID().uuidString).plist")