fix(macos): restore control + webchat build
parent
e2a93e17f9
commit
39c232548c
|
|
@ -10,89 +10,87 @@ enum ControlRequestHandler {
|
||||||
{
|
{
|
||||||
// Keep `status` responsive even if the main actor is busy.
|
// Keep `status` responsive even if the main actor is busy.
|
||||||
let paused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)
|
let paused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)
|
||||||
if paused, request != .status {
|
if paused, case .status = request {
|
||||||
|
// allow status through
|
||||||
|
} else if paused {
|
||||||
return Response(ok: false, message: "clawdis paused")
|
return Response(ok: false, message: "clawdis paused")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch request {
|
switch request {
|
||||||
case let .notify(title, body, sound, priority, delivery):
|
case let .notify(title, body, sound, priority, delivery):
|
||||||
let notify = NotifyRequest(
|
let notify = NotifyRequest(
|
||||||
title: title,
|
title: title,
|
||||||
body: body,
|
body: body,
|
||||||
sound: sound,
|
sound: sound,
|
||||||
priority: priority,
|
priority: priority,
|
||||||
delivery: delivery
|
delivery: delivery)
|
||||||
)
|
return await self.handleNotify(notify, notifier: notifier)
|
||||||
return await self.handleNotify(notify, notifier: notifier)
|
|
||||||
|
|
||||||
case let .ensurePermissions(caps, interactive):
|
case let .ensurePermissions(caps, interactive):
|
||||||
return await self.handleEnsurePermissions(caps: caps, interactive: interactive)
|
return await self.handleEnsurePermissions(caps: caps, interactive: interactive)
|
||||||
|
|
||||||
case .status:
|
case .status:
|
||||||
return paused
|
return paused
|
||||||
? Response(ok: false, message: "clawdis paused")
|
? Response(ok: false, message: "clawdis paused")
|
||||||
: Response(ok: true, message: "ready")
|
: Response(ok: true, message: "ready")
|
||||||
|
|
||||||
case .rpcStatus:
|
case .rpcStatus:
|
||||||
return await self.handleRPCStatus()
|
return await self.handleRPCStatus()
|
||||||
|
|
||||||
case let .runShell(command, cwd, env, timeoutSec, needsSR):
|
case let .runShell(command, cwd, env, timeoutSec, needsSR):
|
||||||
return await self.handleRunShell(
|
return await self.handleRunShell(
|
||||||
command: command,
|
command: command,
|
||||||
cwd: cwd,
|
cwd: cwd,
|
||||||
env: env,
|
env: env,
|
||||||
timeoutSec: timeoutSec,
|
timeoutSec: timeoutSec,
|
||||||
needsSR: needsSR
|
needsSR: needsSR)
|
||||||
)
|
|
||||||
|
|
||||||
case let .agent(message, thinking, session, deliver, to):
|
case let .agent(message, thinking, session, deliver, to):
|
||||||
return await self.handleAgent(
|
return await self.handleAgent(
|
||||||
message: message,
|
message: message,
|
||||||
thinking: thinking,
|
thinking: thinking,
|
||||||
session: session,
|
session: session,
|
||||||
deliver: deliver,
|
deliver: deliver,
|
||||||
to: to
|
to: to)
|
||||||
)
|
|
||||||
|
|
||||||
case let .canvasShow(session, path, placement):
|
case let .canvasShow(session, path, placement):
|
||||||
return await self.handleCanvasShow(session: session, path: path, placement: placement)
|
return await self.handleCanvasShow(session: session, path: path, placement: placement)
|
||||||
|
|
||||||
case let .canvasHide(session):
|
case let .canvasHide(session):
|
||||||
return await self.handleCanvasHide(session: session)
|
return await self.handleCanvasHide(session: session)
|
||||||
|
|
||||||
case let .canvasGoto(session, path, placement):
|
case let .canvasGoto(session, path, placement):
|
||||||
return await self.handleCanvasGoto(session: session, path: path, placement: placement)
|
return await self.handleCanvasGoto(session: session, path: path, placement: placement)
|
||||||
|
|
||||||
case let .canvasEval(session, javaScript):
|
case let .canvasEval(session, javaScript):
|
||||||
return await self.handleCanvasEval(session: session, javaScript: javaScript)
|
return await self.handleCanvasEval(session: session, javaScript: javaScript)
|
||||||
|
|
||||||
case let .canvasSnapshot(session, outPath):
|
case let .canvasSnapshot(session, outPath):
|
||||||
return await self.handleCanvasSnapshot(session: session, outPath: outPath)
|
return await self.handleCanvasSnapshot(session: session, outPath: outPath)
|
||||||
|
|
||||||
case .nodeList:
|
case .nodeList:
|
||||||
return await self.handleNodeList()
|
return await self.handleNodeList()
|
||||||
|
|
||||||
case let .nodeInvoke(nodeId, command, paramsJSON):
|
case let .nodeInvoke(nodeId, command, paramsJSON):
|
||||||
return await self.handleNodeInvoke(
|
return await self.handleNodeInvoke(
|
||||||
nodeId: nodeId,
|
nodeId: nodeId,
|
||||||
command: command,
|
command: command,
|
||||||
paramsJSON: paramsJSON,
|
paramsJSON: paramsJSON,
|
||||||
logger: logger
|
logger: logger)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private struct NotifyRequest {
|
private struct NotifyRequest {
|
||||||
var title: String
|
var title: String
|
||||||
var body: String
|
var body: String
|
||||||
var sound: String?
|
var sound: String?
|
||||||
var priority: NotificationPriority?
|
var priority: NotificationPriority?
|
||||||
var delivery: NotificationDelivery?
|
var delivery: NotificationDelivery?
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func handleNotify(_ request: NotifyRequest, notifier: NotificationManager) async -> Response {
|
private static func handleNotify(_ request: NotifyRequest, notifier: NotificationManager) async -> Response {
|
||||||
let chosenSound = request.sound?.trimmingCharacters(in: .whitespacesAndNewlines)
|
let chosenSound = request.sound?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let chosenDelivery = request.delivery ?? .system
|
let chosenDelivery = request.delivery ?? .system
|
||||||
|
|
||||||
switch chosenDelivery {
|
switch chosenDelivery {
|
||||||
case .system:
|
case .system:
|
||||||
|
|
@ -100,8 +98,7 @@ enum ControlRequestHandler {
|
||||||
title: request.title,
|
title: request.title,
|
||||||
body: request.body,
|
body: request.body,
|
||||||
sound: chosenSound,
|
sound: chosenSound,
|
||||||
priority: request.priority
|
priority: request.priority)
|
||||||
)
|
|
||||||
return ok ? Response(ok: true) : Response(ok: false, message: "notification not authorized")
|
return ok ? Response(ok: true) : Response(ok: false, message: "notification not authorized")
|
||||||
case .overlay:
|
case .overlay:
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
|
|
@ -113,8 +110,7 @@ enum ControlRequestHandler {
|
||||||
title: request.title,
|
title: request.title,
|
||||||
body: request.body,
|
body: request.body,
|
||||||
sound: chosenSound,
|
sound: chosenSound,
|
||||||
priority: request.priority
|
priority: request.priority)
|
||||||
)
|
|
||||||
if ok { return Response(ok: true) }
|
if ok { return Response(ok: true) }
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
NotifyOverlayController.shared.present(title: request.title, body: request.body)
|
NotifyOverlayController.shared.present(title: request.title, body: request.body)
|
||||||
|
|
@ -141,8 +137,8 @@ enum ControlRequestHandler {
|
||||||
cwd: String?,
|
cwd: String?,
|
||||||
env: [String: String]?,
|
env: [String: String]?,
|
||||||
timeoutSec: Double?,
|
timeoutSec: Double?,
|
||||||
needsSR: Bool
|
needsSR: Bool) async -> Response
|
||||||
) async -> Response {
|
{
|
||||||
if needsSR {
|
if needsSR {
|
||||||
let authorized = await PermissionManager
|
let authorized = await PermissionManager
|
||||||
.ensure([.screenRecording], interactive: false)[.screenRecording] ?? false
|
.ensure([.screenRecording], interactive: false)[.screenRecording] ?? false
|
||||||
|
|
@ -156,8 +152,8 @@ enum ControlRequestHandler {
|
||||||
thinking: String?,
|
thinking: String?,
|
||||||
session: String?,
|
session: String?,
|
||||||
deliver: Bool,
|
deliver: Bool,
|
||||||
to: String?
|
to: String?) async -> Response
|
||||||
) async -> Response {
|
{
|
||||||
let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
guard !trimmed.isEmpty else { return Response(ok: false, message: "message empty") }
|
guard !trimmed.isEmpty else { return Response(ok: false, message: "message empty") }
|
||||||
let sessionKey = session ?? "main"
|
let sessionKey = session ?? "main"
|
||||||
|
|
@ -167,8 +163,7 @@ enum ControlRequestHandler {
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
deliver: deliver,
|
deliver: deliver,
|
||||||
to: to,
|
to: to,
|
||||||
channel: nil
|
channel: nil)
|
||||||
)
|
|
||||||
return rpcResult.ok
|
return rpcResult.ok
|
||||||
? Response(ok: true, message: rpcResult.text ?? "sent")
|
? Response(ok: true, message: rpcResult.text ?? "sent")
|
||||||
: Response(ok: false, message: rpcResult.error ?? "failed to send")
|
: Response(ok: false, message: rpcResult.error ?? "failed to send")
|
||||||
|
|
@ -181,8 +176,8 @@ enum ControlRequestHandler {
|
||||||
private static func handleCanvasShow(
|
private static func handleCanvasShow(
|
||||||
session: String,
|
session: String,
|
||||||
path: String?,
|
path: String?,
|
||||||
placement: CanvasPlacement?
|
placement: CanvasPlacement?) async -> Response
|
||||||
) async -> Response {
|
{
|
||||||
guard self.canvasEnabled() else { return Response(ok: false, message: "Canvas disabled by user") }
|
guard self.canvasEnabled() else { return Response(ok: false, message: "Canvas disabled by user") }
|
||||||
do {
|
do {
|
||||||
let dir = try await MainActor.run {
|
let dir = try await MainActor.run {
|
||||||
|
|
@ -235,9 +230,8 @@ enum ControlRequestHandler {
|
||||||
let ids = await BridgeServer.shared.connectedNodeIds()
|
let ids = await BridgeServer.shared.connectedNodeIds()
|
||||||
let payload = (try? JSONSerialization.data(
|
let payload = (try? JSONSerialization.data(
|
||||||
withJSONObject: ["connectedNodeIds": ids],
|
withJSONObject: ["connectedNodeIds": ids],
|
||||||
options: [.prettyPrinted]
|
options: [.prettyPrinted]))
|
||||||
))
|
.flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
|
||||||
.flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
|
|
||||||
return Response(ok: true, payload: Data(payload.utf8))
|
return Response(ok: true, payload: Data(payload.utf8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,8 +239,8 @@ enum ControlRequestHandler {
|
||||||
nodeId: String,
|
nodeId: String,
|
||||||
command: String,
|
command: String,
|
||||||
paramsJSON: String?,
|
paramsJSON: String?,
|
||||||
logger: Logger
|
logger: Logger) async -> Response
|
||||||
) async -> Response {
|
{
|
||||||
do {
|
do {
|
||||||
let res = try await BridgeServer.shared.invoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON)
|
let res = try await BridgeServer.shared.invoke(nodeId: nodeId, command: command, paramsJSON: paramsJSON)
|
||||||
if res.ok {
|
if res.ok {
|
||||||
|
|
|
||||||
|
|
@ -181,42 +181,41 @@ final class WebChatServer: @unchecked Sendable {
|
||||||
status: 403,
|
status: 403,
|
||||||
mime: "text/plain",
|
mime: "text/plain",
|
||||||
body: forbidden,
|
body: forbidden,
|
||||||
contentLength: forbidden.count,
|
|
||||||
includeBody: includeBody,
|
includeBody: includeBody,
|
||||||
over: connection)
|
over: connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let data = try? Data(contentsOf: fileURL) else {
|
guard let data = try? Data(contentsOf: fileURL) else {
|
||||||
webChatServerLogger.error("WebChatServer 404 missing \(fileURL.lastPathComponent, privacy: .public)")
|
webChatServerLogger.error("WebChatServer 404 missing \(fileURL.lastPathComponent, privacy: .public)")
|
||||||
self.send(
|
self.send(
|
||||||
status: 404,
|
status: 404,
|
||||||
mime: "text/plain",
|
mime: "text/plain",
|
||||||
body: Data("Not Found".utf8),
|
body: Data("Not Found".utf8),
|
||||||
includeBody: includeBody,
|
includeBody: includeBody,
|
||||||
over: connection)
|
over: connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let mime = self.mimeType(forExtension: fileURL.pathExtension)
|
let mime = self.mimeType(forExtension: fileURL.pathExtension)
|
||||||
self.send(
|
self.send(
|
||||||
status: 200,
|
status: 200,
|
||||||
mime: mime,
|
mime: mime,
|
||||||
body: data,
|
body: data,
|
||||||
includeBody: includeBody,
|
includeBody: includeBody,
|
||||||
over: connection)
|
over: connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func send(
|
private func send(
|
||||||
status: Int,
|
status: Int,
|
||||||
mime: String,
|
mime: String,
|
||||||
body: Data,
|
body: Data,
|
||||||
includeBody: Bool,
|
includeBody: Bool,
|
||||||
over connection: NWConnection)
|
over connection: NWConnection)
|
||||||
{
|
{
|
||||||
let contentLength = body.count
|
let contentLength = body.count
|
||||||
let headers = "HTTP/1.1 \(status) \(statusText(status))\r\n" +
|
let headers = "HTTP/1.1 \(status) \(statusText(status))\r\n" +
|
||||||
"Content-Length: \(contentLength)\r\n" +
|
"Content-Length: \(contentLength)\r\n" +
|
||||||
"Content-Type: \(mime)\r\n" +
|
"Content-Type: \(mime)\r\n" +
|
||||||
"Connection: close\r\n\r\n"
|
"Connection: close\r\n\r\n"
|
||||||
var response = Data(headers.utf8)
|
var response = Data(headers.utf8)
|
||||||
if includeBody {
|
if includeBody {
|
||||||
response.append(body)
|
response.append(body)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue