SPB Git

spb/poche Public

Agent personnel 100 % on-device — SwiftUI + Apple Foundation Models + EventKit + SwiftData. Aucune API, aucun serveur.

Swift 100%
1.9 KB · 63 lines swift
Raw Blame History
1//2//  ConfirmCenter.swift3//  Poche4//5//  Author:  Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//89import Foundation10import Observation1112/// The only gate to side effects. Tools enqueue proposals here; nothing is13/// written until the user confirms on the card. There is no code path that14/// writes without crossing this layer — no expert mode, no preference to15/// disable it, no exception (CLAUDE.md §3).16@MainActor17@Observable18final class ConfirmCenter {19    private(set) var pending: [PendingAction] = []2021    private let executor: ActionExecutor2223    var onExecuted: ((String) -> Void)?24    var onDismissed: ((String) -> Void)?2526    init(executor: ActionExecutor) {27        self.executor = executor28    }2930    /// Called by tools. Registers the proposal; the UI shows the card.31    func propose(_ action: PendingAction) {32        pending.append(action)33    }3435    /// Called by the confirmation card only — this is the user's decision.36    func confirm(_ action: PendingAction) async {37        guard let index = pending.firstIndex(where: { $0.id == action.id }) else { return }38        pending[index].status = .executing39        do {40            let summary = try await executor.execute(action.payload)41            pending.removeAll { $0.id == action.id }42            onExecuted?(summary)43        } catch {44            if let idx = pending.firstIndex(where: { $0.id == action.id }) {45                pending[idx].status = .failed(userMessage(for: error))46            }47        }48    }4950    func dismiss(_ action: PendingAction) {51        guard pending.contains(where: { $0.id == action.id }) else { return }52        pending.removeAll { $0.id == action.id }53        onDismissed?(action.summary)54    }5556    private func userMessage(for error: any Error) -> String {57        if let bridgeError = error as? BridgeError {58            return bridgeError.userMessage59        }60        return "L'action n'a pas pu être exécutée. Réessaie."61    }62}63