SPB Git

spb/zyquo-agent Public MIT

The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.

Swift 94.7% Shell 4.1% Python 0.7% Makefile 0.5%
2.7 KB · 76 lines swift
Raw Blame History
1//2//  UIApprovalPresenter.swift3//  Zyquo Agent4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  Bridges the PolicyEngine's ApprovalPresenting protocol to the UI: when the9//  gate holds an action, the presenter parks the engine on a10//  CheckedContinuation and publishes a PendingApproval to the RunController;11//  the inline approval card resolves it (Approve / Approve & remember / Edit /12//  Deny). Resolution is single-shot — a second resolve is ignored.13//1415import Foundation1617/// One approval request surfaced to the UI, carrying its resolution hook.18struct PendingApproval: Identifiable, Sendable {19    let id = UUID()20    let action: ActionRequest21    let risk: RiskAssessment22    private let resolver: ApprovalResolver2324    init(action: ActionRequest, risk: RiskAssessment, continuation: CheckedContinuation<ApprovalResolution, Never>) {25        self.action = action26        self.risk = risk27        self.resolver = ApprovalResolver(continuation: continuation)28    }2930    /// Resolves the request exactly once; later calls are no-ops.31    func resolve(_ resolution: ApprovalResolution) {32        resolver.resolve(resolution)33    }34}3536/// Single-shot continuation wrapper (thread-safe).37private final class ApprovalResolver: @unchecked Sendable {38    private let lock = NSLock()39    private var continuation: CheckedContinuation<ApprovalResolution, Never>?4041    init(continuation: CheckedContinuation<ApprovalResolution, Never>) {42        self.continuation = continuation43    }4445    func resolve(_ resolution: ApprovalResolution) {46        lock.lock()47        let continuation = self.continuation48        self.continuation = nil49        lock.unlock()50        continuation?.resume(returning: resolution)51    }52}5354/// The ApprovalPresenting implementation the UI wires into the PolicyEngine.55/// `onRequest` is installed by the RunController and hops to the MainActor.56final class UIApprovalPresenter: ApprovalPresenting, @unchecked Sendable {57    private let lock = NSLock()58    private var _onRequest: (@Sendable (PendingApproval) -> Void)?5960    /// Installed by the RunController before the run starts.61    var onRequest: (@Sendable (PendingApproval) -> Void)? {62        get { lock.lock(); defer { lock.unlock() }; return _onRequest }63        set { lock.lock(); defer { lock.unlock() }; _onRequest = newValue }64    }6566    func requestApproval(for action: ActionRequest, risk: RiskAssessment) async -> ApprovalResolution {67        guard let onRequest else {68            // No UI attached (should not happen in app runs): fail closed.69            return .deny70        }71        return await withCheckedContinuation { continuation in72            onRequest(PendingApproval(action: action, risk: risk, continuation: continuation))73        }74    }75}76