Menu: add debug submenu actions
This commit is contained in:
104
apps/macos/Sources/Clawdis/DebugActions.swift
Normal file
104
apps/macos/Sources/Clawdis/DebugActions.swift
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,22 +154,11 @@ struct DebugSettings: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button("Send Test Notification") {
|
Button("Send Test Notification") {
|
||||||
Task {
|
Task { await DebugActions.sendTestNotification() }
|
||||||
_ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
Button("Open Agent Events") {
|
Button("Open Agent Events") {
|
||||||
let window = NSWindow(
|
DebugActions.openAgentEventsWindow()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
@@ -206,7 +195,7 @@ struct DebugSettings: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Button("Restart app") { self.relaunch() }
|
Button("Restart app") { self.relaunch() }
|
||||||
Button("Reveal app in Finder") { self.revealApp() }
|
Button("Reveal app in Finder") { self.revealApp() }
|
||||||
Button("Restart Gateway") { self.restartRelay() }
|
Button("Restart Gateway") { DebugActions.restartGateway() }
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
Spacer(minLength: 8)
|
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() {
|
private func chooseCatalogFile() {
|
||||||
let panel = NSOpenPanel()
|
let panel = NSOpenPanel()
|
||||||
panel.title = "Select models.generated.ts"
|
panel.title = "Select models.generated.ts"
|
||||||
@@ -292,62 +248,16 @@ struct DebugSettings: View {
|
|||||||
self.debugSendStatus = nil
|
self.debugSendStatus = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = """
|
let result = await DebugActions.sendDebugVoice()
|
||||||
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 {
|
await MainActor.run {
|
||||||
let result = await VoiceWakeForwarder.forward(transcript: message, config: config)
|
self.debugSendInFlight = false
|
||||||
await MainActor.run {
|
switch result {
|
||||||
self.debugSendInFlight = false
|
case let .success(message):
|
||||||
switch result {
|
self.debugSendStatus = message
|
||||||
case .success:
|
self.debugSendError = nil
|
||||||
self.debugSendStatus = "Forwarded. Await reply."
|
case let .failure(message):
|
||||||
self.debugSendError = nil
|
self.debugSendStatus = message
|
||||||
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)"
|
|
||||||
self.debugSendError = nil
|
self.debugSendError = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ struct MenuContent: View {
|
|||||||
if let updater, updater.isAvailable {
|
if let updater, updater.isAvailable {
|
||||||
Button("Check for Updates…") { updater.checkForUpdates(nil) }
|
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) }
|
Button("Quit") { NSApplication.shared.terminate(nil) }
|
||||||
}
|
}
|
||||||
.task(id: self.state.swabbleEnabled) {
|
.task(id: self.state.swabbleEnabled) {
|
||||||
|
|||||||
Reference in New Issue
Block a user