Build: fix RPC sendable params and CLI imports

This commit is contained in:
Peter Steinberger
2025-12-09 03:33:16 +01:00
parent a8b26570e0
commit c568284f1b
4 changed files with 17 additions and 10 deletions

View File

@@ -1,6 +1,10 @@
import Foundation import Foundation
import OSLog import OSLog
struct ControlRequestParams: @unchecked Sendable {
let raw: [String: AnyHashable]
}
actor AgentRPC { actor AgentRPC {
static let shared = AgentRPC() static let shared = AgentRPC()
@@ -173,13 +177,13 @@ actor AgentRPC {
} }
} }
func controlRequest(method: String, params: [String: Any]? = nil) async throws -> Data { func controlRequest(method: String, params: ControlRequestParams? = nil) async throws -> Data {
if self.process?.isRunning != true { if self.process?.isRunning != true {
try await self.start() try await self.start()
} }
let id = UUID().uuidString let id = UUID().uuidString
var frame: [String: Any] = ["type": "control-request", "id": id, "method": method] var frame: [String: Any] = ["type": "control-request", "id": id, "method": method]
if let params { frame["params"] = params } if let params { frame["params"] = params.raw }
let data = try JSONSerialization.data(withJSONObject: frame) let data = try JSONSerialization.data(withJSONObject: frame)
guard let stdinHandle else { throw RpcError(message: "stdin missing") } guard let stdinHandle else { throw RpcError(message: "stdin missing") }
return try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Data, Error>) in return try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Data, Error>) in

View File

@@ -71,6 +71,7 @@ final class ControlChannel: ObservableObject {
enum ConnectionState: Equatable { enum ConnectionState: Equatable {
case disconnected case disconnected
case connecting
case connected case connected
case degraded(String) case degraded(String)
} }
@@ -87,6 +88,7 @@ final class ControlChannel: ObservableObject {
func configure() async { func configure() async {
do { do {
self.state = .connecting
try await AgentRPC.shared.start() try await AgentRPC.shared.start()
self.state = .connected self.state = .connected
} catch { } catch {
@@ -96,11 +98,11 @@ final class ControlChannel: ObservableObject {
func configure(mode: Mode) async throws { func configure(mode: Mode) async throws {
// Mode is retained for API compatibility; transport is always stdio now. // Mode is retained for API compatibility; transport is always stdio now.
try await self.configure() await self.configure()
} }
func health(timeout: TimeInterval? = nil) async throws -> Data { func health(timeout: TimeInterval? = nil) async throws -> Data {
let params = timeout.map { ["timeoutMs": Int($0 * 1000)] } let params = timeout.map { ControlRequestParams(raw: ["timeoutMs": AnyHashable(Int($0 * 1000))]) }
do { do {
let start = Date() let start = Date()
let payload = try await AgentRPC.shared.controlRequest(method: "health", params: params) let payload = try await AgentRPC.shared.controlRequest(method: "health", params: params)
@@ -120,7 +122,7 @@ final class ControlChannel: ObservableObject {
return try? JSONDecoder().decode(ControlHeartbeatEvent.self, from: data) return try? JSONDecoder().decode(ControlHeartbeatEvent.self, from: data)
} }
func request(method: String, params: [String: Any]? = nil) async throws -> Data { func request(method: String, params: ControlRequestParams? = nil) async throws -> Data {
do { do {
let data = try await AgentRPC.shared.controlRequest(method: method, params: params) let data = try await AgentRPC.shared.controlRequest(method: method, params: params)
self.state = .connected self.state = .connected
@@ -132,7 +134,9 @@ final class ControlChannel: ObservableObject {
} }
func sendSystemEvent(_ text: String) async throws { func sendSystemEvent(_ text: String) async throws {
_ = try await self.request(method: "system-event", params: ["text": text]) _ = try await self.request(
method: "system-event",
params: ControlRequestParams(raw: ["text": AnyHashable(text)]))
} }
} }

View File

@@ -79,14 +79,14 @@ struct VoiceWakeChimeCatalog {
private static let discoveredSoundMap: [String: URL] = { private static let discoveredSoundMap: [String: URL] = {
var map: [String: URL] = [:] var map: [String: URL] = [:]
for root in self.searchRoots { for root in Self.searchRoots {
guard let contents = try? FileManager.default.contentsOfDirectory( guard let contents = try? FileManager.default.contentsOfDirectory(
at: root, at: root,
includingPropertiesForKeys: nil, includingPropertiesForKeys: nil,
options: [.skipsHiddenFiles]) options: [.skipsHiddenFiles])
else { continue } else { continue }
for url in contents where self.allowedExtensions.contains(url.pathExtension.lowercased()) { for url in contents where Self.allowedExtensions.contains(url.pathExtension.lowercased()) {
let name = url.deletingPathExtension().lastPathComponent let name = url.deletingPathExtension().lastPathComponent
// Preserve the first match in priority order. // Preserve the first match in priority order.
if map[name] == nil { if map[name] == nil {

View File

@@ -12,7 +12,6 @@ import {
getLastHeartbeatEvent, getLastHeartbeatEvent,
onHeartbeatEvent, onHeartbeatEvent,
} from "../infra/heartbeat-events.js"; } from "../infra/heartbeat-events.js";
import { onAgentEvent } from "../infra/agent-events.js";
import { getResolvedLoggerSettings } from "../logging.js"; import { getResolvedLoggerSettings } from "../logging.js";
import { import {
loginWeb, loginWeb,
@@ -35,8 +34,8 @@ import {
} from "../webchat/server.js"; } from "../webchat/server.js";
import { createDefaultDeps, logWebSelfId } from "./deps.js"; import { createDefaultDeps, logWebSelfId } from "./deps.js";
import { onAgentEvent } from "../infra/agent-events.js"; import { onAgentEvent } from "../infra/agent-events.js";
import { enqueueSystemEvent } from "../infra/system-events.js";
import { import {
enqueueSystemEvent,
listSystemPresence, listSystemPresence,
updateSystemPresence, updateSystemPresence,
} from "../infra/system-presence.js"; } from "../infra/system-presence.js";