agent: deliver via rpc and voice forward
This commit is contained in:
@@ -13,17 +13,25 @@ actor AgentRPC {
|
||||
|
||||
private struct RpcError: Error { let message: String }
|
||||
|
||||
func send(text: String, thinking: String?, session: String) async -> (ok: Bool, text: String?, error: String?) {
|
||||
func send(
|
||||
text: String,
|
||||
thinking: String?,
|
||||
session: String,
|
||||
deliver: Bool,
|
||||
to: String?) async -> (ok: Bool, text: String?, error: String?)
|
||||
{
|
||||
guard process?.isRunning == true else {
|
||||
return (false, nil, "rpc worker not running")
|
||||
}
|
||||
do {
|
||||
let payload: [String: Any] = [
|
||||
var payload: [String: Any] = [
|
||||
"type": "send",
|
||||
"text": text,
|
||||
"session": session,
|
||||
"thinking": thinking ?? "default",
|
||||
"deliver": deliver,
|
||||
]
|
||||
if let to { payload["to"] = to }
|
||||
let data = try JSONSerialization.data(withJSONObject: payload)
|
||||
guard let stdinHandle else { throw RpcError(message: "stdin missing") }
|
||||
stdinHandle.write(data)
|
||||
|
||||
@@ -24,7 +24,7 @@ let modelCatalogPathKey = "clawdis.modelCatalogPath"
|
||||
let modelCatalogReloadKey = "clawdis.modelCatalogReload"
|
||||
let voiceWakeSupported: Bool = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26
|
||||
let cliHelperSearchPaths = ["/usr/local/bin", "/opt/homebrew/bin"]
|
||||
let defaultVoiceWakeForwardCommand = "clawdis-mac agent --message \"${text}\" --thinking low"
|
||||
let defaultVoiceWakeForwardCommand = "clawdis-mac agent --message \"${text}\" --thinking low --session main --deliver"
|
||||
let defaultVoiceWakeForwardPort = 22
|
||||
// Allow enough time for remote agent responses (LLM replies often take >10s).
|
||||
let defaultVoiceWakeForwardTimeout: TimeInterval = 30
|
||||
|
||||
@@ -72,14 +72,16 @@ final class ClawdisXPCService: NSObject, ClawdisXPCProtocol {
|
||||
}
|
||||
return await ShellRunner.run(command: command, cwd: cwd, env: env, timeout: timeoutSec)
|
||||
|
||||
case let .agent(message, thinking, session):
|
||||
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,
|
||||
session: sessionKey)
|
||||
session: sessionKey,
|
||||
deliver: deliver,
|
||||
to: to)
|
||||
return rpcResult.ok
|
||||
? Response(ok: true, message: rpcResult.text ?? "sent")
|
||||
: Response(ok: false, message: rpcResult.error ?? "failed to send")
|
||||
@@ -89,12 +91,16 @@ final class ClawdisXPCService: NSObject, ClawdisXPCProtocol {
|
||||
private static func runAgentCLI(
|
||||
message: String,
|
||||
thinking: String?,
|
||||
session: String) async -> (ok: Bool, text: String?, error: String?)
|
||||
session: String,
|
||||
deliver: Bool,
|
||||
to: String?) async -> (ok: Bool, text: String?, error: String?)
|
||||
{
|
||||
let projectRoot = CommandResolver.projectRootPath()
|
||||
var command = CommandResolver.clawdisCommand(subcommand: "agent")
|
||||
command += ["--message", message, "--json"]
|
||||
if !session.isEmpty { command += ["--to", session] }
|
||||
if let to { command += ["--to", to] }
|
||||
if deliver { command += ["--deliver"] }
|
||||
if !session.isEmpty { command += ["--session-id", session] }
|
||||
if let thinking { command += ["--thinking", thinking] }
|
||||
|
||||
let process = Process()
|
||||
|
||||
@@ -140,6 +140,8 @@ struct ClawdisCLI {
|
||||
var message: String?
|
||||
var thinking: String?
|
||||
var session: String?
|
||||
var deliver = false
|
||||
var to: String?
|
||||
|
||||
while !args.isEmpty {
|
||||
let arg = args.removeFirst()
|
||||
@@ -147,6 +149,8 @@ struct ClawdisCLI {
|
||||
case "--message": message = args.popFirst()
|
||||
case "--thinking": thinking = args.popFirst()
|
||||
case "--session": session = args.popFirst()
|
||||
case "--deliver": deliver = true
|
||||
case "--to": to = args.popFirst()
|
||||
default:
|
||||
// Support bare message as last argument
|
||||
if message == nil {
|
||||
@@ -156,7 +160,7 @@ struct ClawdisCLI {
|
||||
}
|
||||
|
||||
guard let message else { throw CLIError.help }
|
||||
return .agent(message: message, thinking: thinking, session: session)
|
||||
return .agent(message: message, thinking: thinking, session: session, deliver: deliver, to: to)
|
||||
|
||||
default:
|
||||
throw CLIError.help
|
||||
@@ -178,7 +182,7 @@ struct ClawdisCLI {
|
||||
clawdis-mac run [--cwd <path>] [--env KEY=VAL] [--timeout <sec>] [--needs-screen-recording] <command ...>
|
||||
clawdis-mac status
|
||||
clawdis-mac rpc-status
|
||||
clawdis-mac agent --message <text> [--thinking <low|default|high>] [--session <key>]
|
||||
clawdis-mac agent --message <text> [--thinking <low|default|high>] [--session <key>] [--deliver] [--to <E.164>]
|
||||
clawdis-mac --help
|
||||
|
||||
Returns JSON to stdout:
|
||||
|
||||
@@ -25,7 +25,7 @@ public enum Request: Sendable {
|
||||
timeoutSec: Double?,
|
||||
needsScreenRecording: Bool)
|
||||
case status
|
||||
case agent(message: String, thinking: String?, session: String?)
|
||||
case agent(message: String, thinking: String?, session: String?, deliver: Bool, to: String?)
|
||||
case rpcStatus
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ extension Request: Codable {
|
||||
case caps, interactive
|
||||
case displayID, windowID, format
|
||||
case command, cwd, env, timeoutSec, needsScreenRecording
|
||||
case message, thinking, session
|
||||
case message, thinking, session, deliver, to
|
||||
case rpcStatus
|
||||
}
|
||||
|
||||
@@ -98,11 +98,13 @@ extension Request: Codable {
|
||||
case .status:
|
||||
try container.encode(Kind.status, forKey: .type)
|
||||
|
||||
case let .agent(message, thinking, session):
|
||||
case let .agent(message, thinking, session, deliver, to):
|
||||
try container.encode(Kind.agent, forKey: .type)
|
||||
try container.encode(message, forKey: .message)
|
||||
try container.encodeIfPresent(thinking, forKey: .thinking)
|
||||
try container.encodeIfPresent(session, forKey: .session)
|
||||
try container.encode(deliver, forKey: .deliver)
|
||||
try container.encodeIfPresent(to, forKey: .to)
|
||||
|
||||
case .rpcStatus:
|
||||
try container.encode(Kind.rpcStatus, forKey: .type)
|
||||
@@ -145,7 +147,9 @@ extension Request: Codable {
|
||||
let message = try container.decode(String.self, forKey: .message)
|
||||
let thinking = try container.decodeIfPresent(String.self, forKey: .thinking)
|
||||
let session = try container.decodeIfPresent(String.self, forKey: .session)
|
||||
self = .agent(message: message, thinking: thinking, session: session)
|
||||
let deliver = try container.decode(Bool.self, forKey: .deliver)
|
||||
let to = try container.decodeIfPresent(String.self, forKey: .to)
|
||||
self = .agent(message: message, thinking: thinking, session: session, deliver: deliver, to: to)
|
||||
|
||||
case .rpcStatus:
|
||||
self = .rpcStatus
|
||||
|
||||
@@ -11,7 +11,7 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func rejectEmptyMessage() async {
|
||||
let result = await AgentRPC.shared.send(text: "", thinking: nil, session: "main")
|
||||
let result = await AgentRPC.shared.send(text: "", thinking: nil, session: "main", deliver: false, to: nil)
|
||||
#expect(result.ok == false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user