native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108import Foundation
struct TerminalCloseProtectionCandidate: Equatable { let hasAgent: Bool let agentDisplayState: AgentDisplayState? let commandRunningDuration: TimeInterval? var editingAge: TimeInterval? var hasMarkedText = false}
enum TerminalCloseProtectionReason: Equatable, Hashable { case recentInput case agentActive case longRunningCommand}
struct TerminalCloseConfirmationDecision: Equatable { let protectedPaneCount: Int let reasons: Set<TerminalCloseProtectionReason>
var requiresConfirmation: Bool { protectedPaneCount > 0 }}
enum TerminalCloseConfirmationPolicy { static let recentEditingThreshold: TimeInterval = 10 static let longRunningCommandThreshold: TimeInterval = 10
static func decision( for candidates: [TerminalCloseProtectionCandidate], threshold: TimeInterval = Self.longRunningCommandThreshold ) -> TerminalCloseConfirmationDecision { var protectedPaneCount = 0 var reasons: Set<TerminalCloseProtectionReason> = []
for candidate in candidates { var paneReasons: Set<TerminalCloseProtectionReason> = [] if candidate.hasMarkedText || candidate.editingAge.map({ $0 <= recentEditingThreshold }) == true { paneReasons.insert(.recentInput) } if let reason = protectionReason(for: candidate, threshold: threshold) { paneReasons.insert(reason) } guard !paneReasons.isEmpty else { continue } protectedPaneCount += 1 reasons.formUnion(paneReasons) }
return TerminalCloseConfirmationDecision( protectedPaneCount: protectedPaneCount, reasons: reasons ) }
/// Alert body for a close prompt. Always names the worktree: the prompt can /// be triggered from the sidebar against a worktree whose tabs are not /// visible, so the target's identity must be part of the confirmation. static func informativeMessage( for decision: TerminalCloseConfirmationDecision, worktreeName: String ) -> String { let paneText = decision.protectedPaneCount == 1 ? String(localized: "pane") : String(localized: "panes") let reasonText: String if decision.reasons.contains(.recentInput) { var activities = [String(localized: "recent input")] if decision.reasons.contains(.agentActive) { activities.append(String(localized: "active agent work or an unseen agent result")) } if decision.reasons.contains(.longRunningCommand) { activities.append(String(localized: "a command that has been running for at least 10 seconds")) } let activitiesList = activities.joined(separator: ", ") let template = String(localized: "This will close %lld %@ in “%@” with %@. Closing may lose unsubmitted input.") return String(format: template, decision.protectedPaneCount, paneText, worktreeName, activitiesList) } else if decision.reasons == Set([.agentActive]) { reasonText = String(localized: "active agent work or an unseen agent result") } else if decision.reasons == Set([.longRunningCommand]) { reasonText = String(localized: "a command that has been running for at least 10 seconds") } else { reasonText = String(localized: "active agent work, unseen agent results, or long-running commands") } let template = String(localized: "This will close %lld %@ in “%@” with %@.") return String(format: template, decision.protectedPaneCount, paneText, worktreeName, reasonText) } private static func protectionReason( for candidate: TerminalCloseProtectionCandidate, threshold: TimeInterval ) -> TerminalCloseProtectionReason? { if candidate.hasAgent { switch candidate.agentDisplayState { case .working, .blocked, .done: return .agentActive case .idle, .none: return nil } }
guard let duration = candidate.commandRunningDuration, duration >= threshold else { return nil } return .longRunningCommand }}