Something went wrong. Try again.
A fully native SwiftUI ADE Proof of Concept that leverages macOS on-device intelligence to create a fully featured coding agent experience
Something went wrong. Try again.
8.2 kB · 207 lines
Swift
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208import Darwinimport Foundation
public struct ProcessResult: Sendable { public let output: String public let error: String public let status: Int32 public let truncated: Bool}
public enum ProcessRunnerError: Error, LocalizedError { case timedOut public var errorDescription: String? { "The command exceeded its time limit." }}
/// Captures both pipes concurrently and continues draining after the capture limit.public enum ProcessRunner { /// GUI launches can inherit a developer-directory override from the launching /// shell long after that Xcode installation has been renamed or removed. static func childEnvironment(_ inherited: [String: String], developerCandidates: [String]? = nil) -> [String: String] { var result = inherited if let override = result["DEVELOPER_DIR"], !isDirectory(override) { let installed = developerCandidates ?? installedDeveloperDirectories() if let replacement = installed.first(where: isDirectory) { result["DEVELOPER_DIR"] = replacement } else { result.removeValue(forKey: "DEVELOPER_DIR") } } result["GIT_TERMINAL_PROMPT"] = "0" result["GIT_EDITOR"] = "/usr/bin/true" return result }
private static func isDirectory(_ path: String) -> Bool { var directory: ObjCBool = false return FileManager.default.fileExists(atPath: path, isDirectory: &directory) && directory.boolValue }
private static func installedDeveloperDirectories() -> [String] { let applications = (try? FileManager.default.contentsOfDirectory(atPath: "/Applications")) ?? [] let xcodes = applications.filter { $0.hasPrefix("Xcode") && $0.hasSuffix(".app") }.sorted() .map { "/Applications/\($0)/Contents/Developer" } return xcodes + ["/Library/Developer/CommandLineTools"] }
static func childExecutable(_ requested: URL, environment: [String: String]) -> URL { guard requested.path == "/usr/bin/git", let developer = environment["DEVELOPER_DIR"] else { return requested } let git = URL(fileURLWithPath: developer).appendingPathComponent("usr/bin/git") return FileManager.default.isExecutableFile(atPath: git.path) ? git : requested }
public static func run( executable: URL, arguments: [String], directory: URL? = nil, timeout: TimeInterval = 120, outputLimit: Int = 4_194_304, input: Data? = nil ) async throws -> ProcessResult { let execution = Execution() return try await withTaskCancellationHandler { try Task.checkCancellation() return try await withCheckedThrowingContinuation { continuation in DispatchQueue.global(qos: .userInitiated).async { do { continuation.resume( returning: try execution.run( executable, arguments, directory, timeout: timeout, limit: max(0, outputLimit), input: input)) } catch { continuation.resume(throwing: error) } } } } onCancel: { execution.cancel() } }}
private final class Execution: @unchecked Sendable { private let lock = NSLock() private var process: Process? private var cancelled = false private var timedOut = false private var finished = false private var hasFinished: Bool { lock.lock() defer { lock.unlock() } return finished }
func cancel(timeout: Bool = false) { lock.lock() if timeout { timedOut = true } else { cancelled = true } let running = process if let running, running.isRunning { let pid = running.processIdentifier // Foundation launches macOS subprocesses in their own process group. Kill // that group only after verifying it, so shell children stop with the run. let ownsGroup = Darwin.getpgid(pid) == pid if ownsGroup { Darwin.kill(-pid, SIGTERM) } else { running.terminate() } DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [self] in lock.lock() defer { lock.unlock() } if ownsGroup { Darwin.kill(-pid, SIGKILL) } else if process?.isRunning == true { Darwin.kill(pid, SIGKILL) } } } lock.unlock() }
func run( _ executable: URL, _ arguments: [String], _ directory: URL?, timeout: TimeInterval, limit: Int, input: Data? ) throws -> ProcessResult { let child = Process() let environment = ProcessRunner.childEnvironment(ProcessInfo.processInfo.environment) child.executableURL = ProcessRunner.childExecutable(executable, environment: environment) child.arguments = arguments child.currentDirectoryURL = directory child.environment = environment let stdout = Pipe() let stderr = Pipe() let stdin = Pipe() child.standardOutput = stdout child.standardError = stderr child.standardInput = input == nil ? FileHandle.nullDevice : stdin lock.lock() if cancelled { lock.unlock() throw CancellationError() } do { try child.run() process = child lock.unlock() } catch { lock.unlock() throw error } let group = DispatchGroup() let output = Capture() let error = Capture() for (pipe, capture) in [(stdout, output), (stderr, error)] { group.enter() DispatchQueue.global().async { defer { group.leave() } let fd = pipe.fileHandleForReading.fileDescriptor var descriptor = pollfd(fd: fd, events: Int16(POLLIN | POLLHUP), revents: 0) while true { let ready = Darwin.poll(&descriptor, 1, 100) if ready > 0 { guard let data = try? pipe.fileHandleForReading.read(upToCount: 65_536), !data.isEmpty else { break } capture.append(data, limit: limit) } else if self.hasFinished || ready < 0 { break } } try? pipe.fileHandleForReading.close() } } if let input { group.enter() DispatchQueue.global().async { defer { try? stdin.fileHandleForWriting.close() group.leave() } try? stdin.fileHandleForWriting.write(contentsOf: input) } } let timer = DispatchWorkItem { [weak self] in self?.cancel(timeout: true) } DispatchQueue.global().asyncAfter(deadline: .now() + max(0.01, timeout), execute: timer) child.waitUntilExit() timer.cancel() lock.lock() finished = true process = nil let wasCancelled = cancelled let expired = timedOut lock.unlock() // Closing the parent's write ends lets readers finish even for empty output. try? stdout.fileHandleForWriting.close() try? stderr.fileHandleForWriting.close() group.wait() if wasCancelled { throw CancellationError() } if expired { throw ProcessRunnerError.timedOut } return ProcessResult( output: String(decoding: output.data, as: UTF8.self), error: String(decoding: error.data, as: UTF8.self), status: child.terminationStatus, truncated: output.truncated || error.truncated) }}
private final class Capture: @unchecked Sendable { var data = Data() var truncated = false func append(_ chunk: Data, limit: Int) { let remaining = max(0, limit - data.count) data.append(chunk.prefix(remaining)) if chunk.count > remaining { truncated = true } }}