debug: add voice forward test button

This commit is contained in:
Peter Steinberger
2025-12-07 15:00:02 +01:00
parent 15381c7832
commit 9c9e04c5a0
2 changed files with 69 additions and 9 deletions

View File

@@ -12,6 +12,9 @@ struct DebugSettings: View {
@State private var relayRootInput: String = RelayProcessManager.shared.projectRootPath()
@State private var sessionStorePath: String = SessionLoader.defaultStorePath
@State private var sessionStoreSaveError: String?
@State private var debugSendInFlight = false
@State private var debugSendStatus: String?
@State private var debugSendError: String?
var body: some View {
ScrollView(.vertical) {
@@ -129,6 +132,31 @@ struct DebugSettings: View {
Task { _ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil) }
}
.buttonStyle(.bordered)
VStack(alignment: .leading, spacing: 6) {
Button {
Task { await self.sendVoiceDebug() }
} label: {
Label(
self.debugSendInFlight ? "Sending debug voice…" : "Send debug voice via forwarder",
systemImage: self.debugSendInFlight ? "bolt.horizontal.circle" : "waveform")
}
.buttonStyle(.borderedProminent)
.disabled(self.debugSendInFlight)
if let debugSendStatus {
Text(debugSendStatus)
.font(.caption)
.foregroundStyle(.secondary)
} else if let debugSendError {
Text(debugSendError)
.font(.caption)
.foregroundStyle(.red)
} else {
Text("Sends the same command path as Voice Wake (ssh target + clawdis-mac agent → rpc → node cli → p-agent → WhatsApp).")
.font(.caption)
.foregroundStyle(.secondary)
}
}
HStack {
Button("Restart app") { self.relaunch() }
Button("Reveal app in Finder") { self.revealApp() }
@@ -211,6 +239,30 @@ struct DebugSettings: View {
}
}
private func sendVoiceDebug() async {
await MainActor.run {
self.debugSendInFlight = true
self.debugSendError = nil
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 result = await VoiceWakeForwarder.forward(transcript: message, config: config)
await MainActor.run {
self.debugSendInFlight = false
switch result {
case .success:
self.debugSendStatus = "Sent via \(config.target). Await WhatsApp reply."
self.debugSendError = nil
case let .failure(error):
self.debugSendStatus = nil
self.debugSendError = error.localizedDescription
}
}
}
private func relaunch() {
let url = Bundle.main.bundleURL
let task = Process()

View File

@@ -86,6 +86,7 @@ enum VoiceWakeForwarder {
case launchFailed(String)
case nonZeroExit(Int32, String)
case cliMissingOrFailed(Int32, String)
case disabled
var errorDescription: String? {
switch self {
@@ -101,16 +102,18 @@ enum VoiceWakeForwarder {
return clipped.isEmpty
? "clawdis-mac failed on remote (code \(code))"
: "clawdis-mac failed on remote (code \(code)): \(clipped)"
case .disabled: return "Voice wake forwarding disabled"
}
}
}
static func forward(transcript: String, config: VoiceWakeForwardConfig) async {
guard config.enabled else { return }
@discardableResult
static func forward(transcript: String, config: VoiceWakeForwardConfig) async -> Result<Void, VoiceWakeForwardError> {
guard config.enabled else { return .failure(.disabled) }
let destination = config.target.trimmingCharacters(in: .whitespacesAndNewlines)
guard let parsed = self.parse(target: destination) else {
self.logger.error("voice wake forward skipped: host missing")
return
return .failure(.invalidTarget)
}
let userHost = parsed.user.map { "\($0)@\(parsed.host)" } ?? parsed.host
@@ -144,7 +147,7 @@ enum VoiceWakeForwarder {
try process.run()
} catch {
self.logger.error("voice wake forward failed to start ssh: \(error.localizedDescription, privacy: .public)")
return
return .failure(.launchFailed(error.localizedDescription))
}
if let data = transcript.data(using: .utf8) {
@@ -155,12 +158,17 @@ enum VoiceWakeForwarder {
let out = await self.wait(process, timeout: config.timeout)
if process.terminationStatus == 0 {
self.logger.info("voice wake forward ok host=\(userHost, privacy: .public)")
} else {
// surface the failure instead of being silent
let clipped = out.prefix(240)
self.logger.error(
"voice wake forward failed exit=\(process.terminationStatus) host=\(userHost, privacy: .public) out=\(clipped, privacy: .public)")
return .success(())
}
// surface the failure instead of being silent
let clipped = out.prefix(240)
self.logger.error(
"voice wake forward failed exit=\(process.terminationStatus) host=\(userHost, privacy: .public) out=\(clipped, privacy: .public)")
if process.terminationStatus == 127 {
return .failure(.cliMissingOrFailed(process.terminationStatus, out))
}
return .failure(.nonZeroExit(process.terminationStatus, out))
}
static func checkConnection(config: VoiceWakeForwardConfig) async -> Result<Void, VoiceWakeForwardError> {