166 lines
7.3 KiB
Swift
166 lines
7.3 KiB
Swift
import ClawdisIPC
|
|
import Foundation
|
|
import OSLog
|
|
|
|
enum ControlRequestHandler {
|
|
static func process(
|
|
request: Request,
|
|
notifier: NotificationManager = NotificationManager(),
|
|
logger: Logger = Logger(subsystem: "com.steipete.clawdis", category: "control")) async throws -> Response
|
|
{
|
|
// Keep `status` responsive even if the main actor is busy.
|
|
let paused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)
|
|
if paused {
|
|
switch request {
|
|
case .status:
|
|
break
|
|
default:
|
|
return Response(ok: false, message: "clawdis paused")
|
|
}
|
|
}
|
|
|
|
switch request {
|
|
case let .notify(title, body, sound, priority, delivery):
|
|
let chosenSound = sound?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let chosenDelivery = delivery ?? .system
|
|
|
|
switch chosenDelivery {
|
|
case .system:
|
|
let ok = await notifier.send(title: title, body: body, sound: chosenSound, priority: priority)
|
|
return ok ? Response(ok: true) : Response(ok: false, message: "notification not authorized")
|
|
|
|
case .overlay:
|
|
await MainActor.run {
|
|
NotifyOverlayController.shared.present(title: title, body: body)
|
|
}
|
|
return Response(ok: true)
|
|
|
|
case .auto:
|
|
let ok = await notifier.send(title: title, body: body, sound: chosenSound, priority: priority)
|
|
if ok { return Response(ok: true) }
|
|
await MainActor.run {
|
|
NotifyOverlayController.shared.present(title: title, body: body)
|
|
}
|
|
return Response(ok: true, message: "notification not authorized; used overlay")
|
|
}
|
|
|
|
case let .ensurePermissions(caps, interactive):
|
|
let statuses = await PermissionManager.ensure(caps, interactive: interactive)
|
|
let missing = statuses.filter { !$0.value }.map(\.key.rawValue)
|
|
let ok = missing.isEmpty
|
|
let msg = ok ? "all granted" : "missing: \(missing.joined(separator: ","))"
|
|
return Response(ok: ok, message: msg)
|
|
|
|
case .status:
|
|
return paused ? Response(ok: false, message: "clawdis paused") : Response(ok: true, message: "ready")
|
|
|
|
case .rpcStatus:
|
|
let result = await AgentRPC.shared.status()
|
|
return Response(ok: result.ok, message: result.error)
|
|
|
|
case let .runShell(command, cwd, env, timeoutSec, needsSR):
|
|
if needsSR {
|
|
let authorized = await PermissionManager
|
|
.ensure([.screenRecording], interactive: false)[.screenRecording] ?? false
|
|
guard authorized else { return Response(ok: false, message: "screen recording permission missing") }
|
|
}
|
|
return await ShellExecutor.run(command: command, cwd: cwd, env: env, timeout: timeoutSec)
|
|
|
|
case let .agent(message, thinking, session, deliver, to):
|
|
let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return Response(ok: false, message: "message empty") }
|
|
let sessionKey = session ?? "main"
|
|
let rpcResult = await AgentRPC.shared.send(
|
|
text: trimmed,
|
|
thinking: thinking,
|
|
sessionKey: sessionKey,
|
|
deliver: deliver,
|
|
to: to,
|
|
channel: nil)
|
|
return rpcResult.ok
|
|
? Response(ok: true, message: rpcResult.text ?? "sent")
|
|
: Response(ok: false, message: rpcResult.error ?? "failed to send")
|
|
|
|
case let .canvasShow(session, path, placement):
|
|
let canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
|
guard canvasEnabled else {
|
|
return Response(ok: false, message: "Canvas disabled by user")
|
|
}
|
|
do {
|
|
let dir = try await MainActor.run { try CanvasManager.shared.show(
|
|
sessionKey: session,
|
|
path: path,
|
|
placement: placement) }
|
|
return Response(ok: true, message: dir)
|
|
} catch {
|
|
return Response(ok: false, message: error.localizedDescription)
|
|
}
|
|
|
|
case let .canvasHide(session):
|
|
await MainActor.run { CanvasManager.shared.hide(sessionKey: session) }
|
|
return Response(ok: true)
|
|
|
|
case let .canvasGoto(session, path, placement):
|
|
let canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
|
guard canvasEnabled else {
|
|
return Response(ok: false, message: "Canvas disabled by user")
|
|
}
|
|
do {
|
|
try await MainActor.run { try CanvasManager.shared.goto(
|
|
sessionKey: session,
|
|
path: path,
|
|
placement: placement) }
|
|
return Response(ok: true)
|
|
} catch {
|
|
return Response(ok: false, message: error.localizedDescription)
|
|
}
|
|
|
|
case let .canvasEval(session, javaScript):
|
|
let canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
|
guard canvasEnabled else {
|
|
return Response(ok: false, message: "Canvas disabled by user")
|
|
}
|
|
do {
|
|
let result = try await CanvasManager.shared.eval(sessionKey: session, javaScript: javaScript)
|
|
return Response(ok: true, payload: Data(result.utf8))
|
|
} catch {
|
|
return Response(ok: false, message: error.localizedDescription)
|
|
}
|
|
|
|
case let .canvasSnapshot(session, outPath):
|
|
let canvasEnabled = UserDefaults.standard.object(forKey: canvasEnabledKey) as? Bool ?? true
|
|
guard canvasEnabled else {
|
|
return Response(ok: false, message: "Canvas disabled by user")
|
|
}
|
|
do {
|
|
let path = try await CanvasManager.shared.snapshot(sessionKey: session, outPath: outPath)
|
|
return Response(ok: true, message: path)
|
|
} catch {
|
|
return Response(ok: false, message: error.localizedDescription)
|
|
}
|
|
|
|
case .nodeList:
|
|
let ids = await BridgeServer.shared.connectedNodeIds()
|
|
let payload = (try? JSONSerialization.data(
|
|
withJSONObject: ["connectedNodeIds": ids],
|
|
options: [.prettyPrinted]))
|
|
.flatMap { String(data: $0, encoding: .utf8) }
|
|
?? "{}"
|
|
return Response(ok: true, payload: Data(payload.utf8))
|
|
|
|
case let .nodeInvoke(nodeId, command, paramsJSON):
|
|
do {
|
|
let res = try await BridgeServer.shared.invoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON)
|
|
if res.ok {
|
|
let payload = res.payloadJSON ?? ""
|
|
return Response(ok: true, payload: Data(payload.utf8))
|
|
}
|
|
let errText = res.error?.message ?? "node invoke failed"
|
|
return Response(ok: false, message: errText)
|
|
} catch {
|
|
return Response(ok: false, message: error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|