fix: reuse resolver for agent rpc launch
This commit is contained in:
@@ -81,10 +81,13 @@ actor AgentRPC {
|
|||||||
|
|
||||||
func start() async throws {
|
func start() async throws {
|
||||||
let process = Process()
|
let process = Process()
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
let command = CommandResolver.clawdisCommand(subcommand: "rpc")
|
||||||
process.arguments = ["pnpm", "clawdis", "rpc"]
|
process.executableURL = URL(fileURLWithPath: command.first ?? "/usr/bin/env")
|
||||||
let projectRoot = await RelayProcessManager.shared.projectRootPath()
|
process.arguments = Array(command.dropFirst())
|
||||||
process.currentDirectoryURL = URL(fileURLWithPath: projectRoot)
|
process.currentDirectoryURL = URL(fileURLWithPath: CommandResolver.projectRootPath())
|
||||||
|
var env = ProcessInfo.processInfo.environment
|
||||||
|
env["PATH"] = CommandResolver.preferredPaths().joined(separator: ":")
|
||||||
|
process.environment = env
|
||||||
|
|
||||||
let stdinPipe = Pipe()
|
let stdinPipe = Pipe()
|
||||||
let stdoutPipe = Pipe()
|
let stdoutPipe = Pipe()
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ import SystemPackage
|
|||||||
final class RelayProcessManager: ObservableObject {
|
final class RelayProcessManager: ObservableObject {
|
||||||
static let shared = RelayProcessManager()
|
static let shared = RelayProcessManager()
|
||||||
|
|
||||||
private enum Defaults {
|
|
||||||
static let projectRootPath = "clawdis.relayProjectRootPath"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Status: Equatable {
|
enum Status: Equatable {
|
||||||
case stopped
|
case stopped
|
||||||
case starting
|
case starting
|
||||||
@@ -203,87 +199,28 @@ final class RelayProcessManager: ObservableObject {
|
|||||||
return override.split(separator: " ").map(String.init)
|
return override.split(separator: " ").map(String.init)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let clawdisPath = self.findExecutable(named: "clawdis") {
|
return CommandResolver.clawdisCommand(subcommand: "relay")
|
||||||
return [clawdisPath, "relay"]
|
|
||||||
}
|
|
||||||
if let pnpm = self.findExecutable(named: "pnpm") {
|
|
||||||
// Run pnpm from the project root (workingDirectory handles cwd).
|
|
||||||
return [pnpm, "clawdis", "relay"]
|
|
||||||
}
|
|
||||||
if let node = self.findExecutable(named: "node") {
|
|
||||||
let clawdis = self.defaultProjectRoot().appendingPathComponent("bin/clawdis.js").path
|
|
||||||
if FileManager.default.isReadableFile(atPath: clawdis) {
|
|
||||||
return [node, clawdis, "relay"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ["clawdis", "relay"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeEnvironment() -> Environment {
|
private func makeEnvironment() -> Environment {
|
||||||
let merged = self.preferredPaths().joined(separator: ":")
|
let merged = CommandResolver.preferredPaths().joined(separator: ":")
|
||||||
return .inherit.updating([
|
return .inherit.updating([
|
||||||
"PATH": merged,
|
"PATH": merged,
|
||||||
"PNPM_HOME": FileManager.default.homeDirectoryForCurrentUser
|
"PNPM_HOME": FileManager.default.homeDirectoryForCurrentUser
|
||||||
.appendingPathComponent("Library/pnpm").path,
|
.appendingPathComponent("Library/pnpm").path,
|
||||||
"CLAWDIS_PROJECT_ROOT": self.defaultProjectRoot().path,
|
"CLAWDIS_PROJECT_ROOT": CommandResolver.projectRoot().path,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preferredPaths() -> [String] {
|
|
||||||
let current = ProcessInfo.processInfo.environment["PATH"]?
|
|
||||||
.split(separator: ":").map(String.init) ?? []
|
|
||||||
let extras = [
|
|
||||||
self.defaultProjectRoot().appendingPathComponent("node_modules/.bin").path,
|
|
||||||
FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/pnpm").path,
|
|
||||||
"/opt/homebrew/bin",
|
|
||||||
"/usr/local/bin",
|
|
||||||
"/usr/bin",
|
|
||||||
"/bin",
|
|
||||||
]
|
|
||||||
var seen = Set<String>()
|
|
||||||
return (extras + current).filter { seen.insert($0).inserted }
|
|
||||||
}
|
|
||||||
|
|
||||||
private func findExecutable(named name: String) -> String? {
|
|
||||||
for dir in self.preferredPaths() {
|
|
||||||
let candidate = (dir as NSString).appendingPathComponent(name)
|
|
||||||
if FileManager.default.isExecutableFile(atPath: candidate) {
|
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private func defaultProjectRoot() -> URL {
|
private func defaultProjectRoot() -> URL {
|
||||||
if let stored = UserDefaults.standard.string(forKey: Defaults.projectRootPath),
|
CommandResolver.projectRoot()
|
||||||
let url = self.expandPath(stored)
|
|
||||||
{
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
let fallback = FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
.appendingPathComponent("Projects/clawdis")
|
|
||||||
if FileManager.default.fileExists(atPath: fallback.path) {
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
return FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setProjectRoot(path: String) {
|
func setProjectRoot(path: String) {
|
||||||
UserDefaults.standard.set(path, forKey: Defaults.projectRootPath)
|
CommandResolver.setProjectRoot(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func projectRootPath() -> String {
|
func projectRootPath() -> String {
|
||||||
UserDefaults.standard.string(forKey: Defaults.projectRootPath)
|
CommandResolver.projectRootPath()
|
||||||
?? FileManager.default.homeDirectoryForCurrentUser
|
|
||||||
.appendingPathComponent("Projects/clawdis").path
|
|
||||||
}
|
|
||||||
|
|
||||||
private func expandPath(_ path: String) -> URL? {
|
|
||||||
var expanded = path
|
|
||||||
if expanded.hasPrefix("~") {
|
|
||||||
let home = FileManager.default.homeDirectoryForCurrentUser.path
|
|
||||||
expanded.replaceSubrange(expanded.startIndex...expanded.startIndex, with: home)
|
|
||||||
}
|
|
||||||
return URL(fileURLWithPath: expanded)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,3 +166,78 @@ enum CLIInstaller {
|
|||||||
"'" + path.replacingOccurrences(of: "'", with: "'\"'\"'") + "'"
|
"'" + path.replacingOccurrences(of: "'", with: "'\"'\"'") + "'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CommandResolver {
|
||||||
|
private static let projectRootDefaultsKey = "clawdis.relayProjectRootPath"
|
||||||
|
|
||||||
|
static func projectRoot() -> URL {
|
||||||
|
if let stored = UserDefaults.standard.string(forKey: self.projectRootDefaultsKey),
|
||||||
|
let url = self.expandPath(stored) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
let fallback = FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
.appendingPathComponent("Projects/clawdis")
|
||||||
|
if FileManager.default.fileExists(atPath: fallback.path) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return FileManager.default.homeDirectoryForCurrentUser
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setProjectRoot(_ path: String) {
|
||||||
|
UserDefaults.standard.set(path, forKey: self.projectRootDefaultsKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func projectRootPath() -> String {
|
||||||
|
self.projectRoot().path
|
||||||
|
}
|
||||||
|
|
||||||
|
static func preferredPaths() -> [String] {
|
||||||
|
let current = ProcessInfo.processInfo.environment["PATH"]?
|
||||||
|
.split(separator: ":").map(String.init) ?? []
|
||||||
|
let extras = [
|
||||||
|
self.projectRoot().appendingPathComponent("node_modules/.bin").path,
|
||||||
|
FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/pnpm").path,
|
||||||
|
"/opt/homebrew/bin",
|
||||||
|
"/usr/local/bin",
|
||||||
|
"/usr/bin",
|
||||||
|
"/bin",
|
||||||
|
]
|
||||||
|
var seen = Set<String>()
|
||||||
|
return (extras + current).filter { seen.insert($0).inserted }
|
||||||
|
}
|
||||||
|
|
||||||
|
static func findExecutable(named name: String) -> String? {
|
||||||
|
for dir in self.preferredPaths() {
|
||||||
|
let candidate = (dir as NSString).appendingPathComponent(name)
|
||||||
|
if FileManager.default.isExecutableFile(atPath: candidate) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
static func clawdisCommand(subcommand: String, extraArgs: [String] = []) -> [String] {
|
||||||
|
if let clawdisPath = self.findExecutable(named: "clawdis") {
|
||||||
|
return [clawdisPath, subcommand] + extraArgs
|
||||||
|
}
|
||||||
|
if let node = self.findExecutable(named: "node") {
|
||||||
|
let cli = self.projectRoot().appendingPathComponent("bin/clawdis.js").path
|
||||||
|
if FileManager.default.isReadableFile(atPath: cli) {
|
||||||
|
return [node, cli, subcommand] + extraArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let pnpm = self.findExecutable(named: "pnpm") {
|
||||||
|
return [pnpm, "clawdis", subcommand] + extraArgs
|
||||||
|
}
|
||||||
|
return ["clawdis", subcommand] + extraArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func expandPath(_ path: String) -> URL? {
|
||||||
|
var expanded = path
|
||||||
|
if expanded.hasPrefix("~") {
|
||||||
|
let home = FileManager.default.homeDirectoryForCurrentUser.path
|
||||||
|
expanded.replaceSubrange(expanded.startIndex...expanded.startIndex, with: home)
|
||||||
|
}
|
||||||
|
return URL(fileURLWithPath: expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -91,15 +91,21 @@ final class ClawdisXPCService: NSObject, ClawdisXPCProtocol {
|
|||||||
thinking: String?,
|
thinking: String?,
|
||||||
session: String) async -> (ok: Bool, text: String?, error: String?)
|
session: String) async -> (ok: Bool, text: String?, error: String?)
|
||||||
{
|
{
|
||||||
let projectRoot = await MainActor.run { RelayProcessManager.shared.projectRootPath() }
|
let projectRoot = CommandResolver.projectRootPath()
|
||||||
|
var command = CommandResolver.clawdisCommand(subcommand: "agent")
|
||||||
|
command += ["--message", message, "--json"]
|
||||||
|
if !session.isEmpty { command += ["--to", session] }
|
||||||
|
if let thinking { command += ["--thinking", thinking] }
|
||||||
|
|
||||||
let process = Process()
|
let process = Process()
|
||||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
|
process.executableURL = URL(fileURLWithPath: command.first ?? "/usr/bin/env")
|
||||||
var args = ["pnpm", "clawdis", "agent", "--message", message, "--json"]
|
process.arguments = Array(command.dropFirst())
|
||||||
if !session.isEmpty { args += ["--to", session] }
|
|
||||||
if let thinking { args += ["--thinking", thinking] }
|
|
||||||
process.arguments = args
|
|
||||||
process.currentDirectoryURL = URL(fileURLWithPath: projectRoot)
|
process.currentDirectoryURL = URL(fileURLWithPath: projectRoot)
|
||||||
|
|
||||||
|
var env = ProcessInfo.processInfo.environment
|
||||||
|
env["PATH"] = CommandResolver.preferredPaths().joined(separator: ":")
|
||||||
|
process.environment = env
|
||||||
|
|
||||||
let outPipe = Pipe()
|
let outPipe = Pipe()
|
||||||
let errPipe = Pipe()
|
let errPipe = Pipe()
|
||||||
process.standardOutput = outPipe
|
process.standardOutput = outPipe
|
||||||
|
|||||||
Reference in New Issue
Block a user