Menu: add debug submenu actions

This commit is contained in:
Peter Steinberger
2025-12-09 17:57:21 +01:00
parent 7017756140
commit 80c7b04831
3 changed files with 130 additions and 102 deletions

View File

@@ -0,0 +1,104 @@
import AppKit
import Foundation
import SwiftUI
enum DebugActions {
@MainActor
static func openAgentEventsWindow() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 620, height: 420),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false)
window.title = "Agent Events"
window.isReleasedWhenClosed = false
window.contentView = NSHostingView(rootView: AgentEventsWindow())
window.center()
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
@MainActor
static func openLog() {
let path = self.pinoLogPath()
let url = URL(fileURLWithPath: path)
guard FileManager.default.fileExists(atPath: path) else {
let alert = NSAlert()
alert.messageText = "Log file not found"
alert.informativeText = path
alert.runModal()
return
}
NSWorkspace.shared.activateFileViewerSelecting([url])
}
static func sendTestNotification() async {
_ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil)
}
static func sendDebugVoice() async -> Result<String, String> {
let message = """
This is a debug test from the Mac app. Reply with "Debug test works (and a funny pun)" \
if you received that.
"""
let config = await MainActor.run { AppStateStore.shared.voiceWakeForwardConfig }
let shouldForward = config.enabled
if shouldForward {
let result = await VoiceWakeForwarder.forward(transcript: message, config: config)
switch result {
case .success:
return .success("Forwarded. Await reply.")
case let .failure(error):
let detail = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
return .failure("Forward failed: \(detail)")
}
}
do {
let status = await AgentRPC.shared.status()
if !status.ok {
try await AgentRPC.shared.start()
}
let rpcResult = await AgentRPC.shared.send(
text: message,
thinking: "low",
session: "main",
deliver: true,
to: nil)
if rpcResult.ok {
return .success("Sent locally via voice wake path.")
} else {
let reason = rpcResult.error?.trimmingCharacters(in: .whitespacesAndNewlines)
let detail = (reason?.isEmpty == false)
? reason!
: "No error returned. Check /tmp/clawdis.log or rpc output."
return .failure("Local send failed: \(detail)")
}
} catch {
let detail = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
return .failure("Local send failed: \(detail)")
}
}
static func restartGateway() {
Task { @MainActor in
RelayProcessManager.shared.stop()
try? await Task.sleep(nanoseconds: 300_000_000)
RelayProcessManager.shared.setActive(true)
}
}
private static func pinoLogPath() -> String {
let df = DateFormatter()
df.calendar = Calendar(identifier: .iso8601)
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd"
let today = df.string(from: Date())
let rolling = URL(fileURLWithPath: "/tmp/clawdis/clawdis-\(today).log").path
if FileManager.default.fileExists(atPath: rolling) { return rolling }
return "/tmp/clawdis.log"
}
}

View File

@@ -154,22 +154,11 @@ struct DebugSettings: View {
}
}
Button("Send Test Notification") {
Task {
_ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil)
}
Task { await DebugActions.sendTestNotification() }
}
.buttonStyle(.bordered)
Button("Open Agent Events") {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 620, height: 420),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false)
window.title = "Agent Events"
window.isReleasedWhenClosed = false
window.contentView = NSHostingView(rootView: AgentEventsWindow())
window.center()
window.makeKeyAndOrderFront(nil)
DebugActions.openAgentEventsWindow()
}
.buttonStyle(.borderedProminent)
VStack(alignment: .leading, spacing: 6) {
@@ -206,7 +195,7 @@ struct DebugSettings: View {
HStack {
Button("Restart app") { self.relaunch() }
Button("Reveal app in Finder") { self.revealApp() }
Button("Restart Gateway") { self.restartRelay() }
Button("Restart Gateway") { DebugActions.restartGateway() }
}
.buttonStyle(.bordered)
Spacer(minLength: 8)
@@ -221,39 +210,6 @@ struct DebugSettings: View {
}
}
private var pinoLogPath: String {
let df = DateFormatter()
df.calendar = Calendar(identifier: .iso8601)
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd"
let today = df.string(from: Date())
// Prefer rolling log; fall back to legacy single-file path.
let rolling = URL(fileURLWithPath: "/tmp/clawdis/clawdis-\(today).log").path
if FileManager.default.fileExists(atPath: rolling) { return rolling }
return "/tmp/clawdis.log"
}
private func openLog() {
let path = self.pinoLogPath
let url = URL(fileURLWithPath: path)
if !FileManager.default.fileExists(atPath: path) {
let alert = NSAlert()
alert.messageText = "Log file not found"
alert.informativeText = path
alert.runModal()
return
}
NSWorkspace.shared.activateFileViewerSelecting([url])
}
private func restartRelay() {
Task { @MainActor in
self.relayManager.stop()
try? await Task.sleep(nanoseconds: 300_000_000)
self.relayManager.setActive(true)
}
}
private func chooseCatalogFile() {
let panel = NSOpenPanel()
panel.title = "Select models.generated.ts"
@@ -292,62 +248,16 @@ struct DebugSettings: View {
self.debugSendStatus = nil
}
let message = """
This is a debug test from the Mac app. Reply with "Debug test works (and a funny pun)" \
if you received that.
"""
let config = await MainActor.run { AppStateStore.shared.voiceWakeForwardConfig }
let shouldForward = config.enabled
let result = await DebugActions.sendDebugVoice()
if shouldForward {
let result = await VoiceWakeForwarder.forward(transcript: message, config: config)
await MainActor.run {
self.debugSendInFlight = false
switch result {
case .success:
self.debugSendStatus = "Forwarded. Await reply."
self.debugSendError = nil
case let .failure(error):
let detail = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
self.debugSendStatus = "Forward failed: \(detail)"
self.debugSendError = nil
}
}
return
}
do {
let status = await AgentRPC.shared.status()
if !status.ok {
try await AgentRPC.shared.start()
}
let rpcResult = await AgentRPC.shared.send(
text: message,
thinking: "low",
session: "main",
deliver: true,
to: nil)
await MainActor.run {
self.debugSendInFlight = false
if rpcResult.ok {
self.debugSendStatus = "Sent locally via voice wake path."
self.debugSendError = nil
} else {
let reason = rpcResult.error?.trimmingCharacters(in: .whitespacesAndNewlines)
let detail = (reason?.isEmpty == false)
? reason!
: "No error returned. Check /tmp/clawdis.log or rpc output."
self.debugSendStatus = "Local send failed: \(detail)"
self.debugSendError = nil
}
}
} catch {
await MainActor.run {
self.debugSendInFlight = false
let detail = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines)
self.debugSendStatus = "Local send failed: \(detail)"
await MainActor.run {
self.debugSendInFlight = false
switch result {
case let .success(message):
self.debugSendStatus = message
self.debugSendError = nil
case let .failure(message):
self.debugSendStatus = message
self.debugSendError = nil
}
}

View File

@@ -41,6 +41,20 @@ struct MenuContent: View {
if let updater, updater.isAvailable {
Button("Check for Updates…") { updater.checkForUpdates(nil) }
}
if self.state.debugPaneEnabled {
Menu("Debug") {
Button("Open Agent Events…") { DebugActions.openAgentEventsWindow() }
Button("Open Log") { DebugActions.openLog() }
Button("Send Debug Voice Text") {
Task { _ = await DebugActions.sendDebugVoice() }
}
Button("Send Test Notification") {
Task { await DebugActions.sendTestNotification() }
}
Divider()
Button("Restart Gateway") { DebugActions.restartGateway() }
}
}
Button("Quit") { NSApplication.shared.terminate(nil) }
}
.task(id: self.state.swabbleEnabled) {