spb/poche Public
Agent personnel 100 % on-device — SwiftUI + Apple Foundation Models + EventKit + SwiftData. Aucune API, aucun serveur.
Swift 100%
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