native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141import ComposableArchitectureimport Foundationimport ProwlCLIShared
struct TerminalClient { var send: @MainActor @Sendable (Command) -> Void /// Creates and selects a tab synchronously so Canvas can target its exact ID. var createTabInDirectory: @MainActor @Sendable (Worktree, URL) -> TerminalTabID? /// Launches a compiled profile request synchronously and returns the exact tab /// and pane identities. This is the CLI/workflow boundary used by workflow starts. var launchAgentProfile: @MainActor @Sendable (Worktree, AgentProfileLaunchRequest) async -> Result<LaunchedSurface, AgentProfileLaunchError> var events: @MainActor @Sendable () -> AsyncStream<Event> /// Per-surface multicast stream. Independent from the single-consumer event stream. var observeAgentState: @MainActor @Sendable (UUID) -> AgentObservationStream var canvasFocusedWorktreeID: @MainActor @Sendable () -> Worktree.ID? /// Active surface in the selected tab. Lets the reducer capture the target /// synchronously before an async dispatch races against AppKit focus reshuffle /// (e.g. when a palette dismisses and the leftmost pane reclaims first responder). var selectedSurfaceID: @MainActor @Sendable (Worktree.ID) -> UUID? /// Captures source-pane session context for workflow native actions. var handoffSessionContextForSurface: @MainActor @Sendable (Worktree.ID, UUID) -> HandoffStore.SessionContext? var latestUnreadNotification: @MainActor @Sendable () -> NotificationLocation? var focusSurface: @MainActor @Sendable (Worktree.ID, UUID) -> Bool var markNotificationRead: @MainActor @Sendable (Worktree.ID, UUID) -> Void var markNotificationsReadForSurface: @MainActor @Sendable (Worktree.ID, UUID) -> Void
enum Command: Equatable { case createTab(Worktree, runSetupScriptIfNew: Bool) case createTabWithInput( Worktree, input: String, workingDirectory: URL? = nil, runSetupScriptIfNew: Bool, autoCloseOnSuccess: Bool, customCommandName: String? = nil, customCommandIcon: String? = nil ) case createSplitWithInput( Worktree, direction: UserCustomSplitDirection, input: String, autoCloseOnSuccess: Bool, customCommandName: String? = nil, customCommandIcon: String? = nil ) case launchAgentProfile(Worktree, plan: AgentProfileLaunchPlan) case createTabInDirectory(Worktree, directory: URL) case focusOrCreateTabInDirectory(Worktree, directory: URL, title: String?) case ensureInitialTab(Worktree, runSetupScriptIfNew: Bool, focusing: Bool) case runScript(Worktree, script: String) case insertText(Worktree, text: String) case stopRunScript(Worktree) case closeFocusedTab(Worktree) case closeFocusedSurface(Worktree) case performBindingAction(Worktree, action: String) case performBindingActionOnSurface(Worktree, surfaceID: UUID, action: String) case startSearch(Worktree) case searchSelection(Worktree) case navigateSearchNext(Worktree) case navigateSearchPrevious(Worktree) case endSearch(Worktree) case focusSelectedTab(Worktree) case prune(Set<Worktree.ID>) case prunePreservingRepositories(keeping: Set<Worktree.ID>, repositoryIDs: Set<Repository.ID>) case setNotificationsEnabled(Bool) case setCommandFinishedNotification(enabled: Bool, threshold: Int) case setCanvasMode(Bool) case setSelectedWorktreeID(Worktree.ID?) case saveLayoutSnapshot case restoreLayoutSnapshot(worktrees: [Worktree]) case presentTabIconPicker(Worktree) }
enum Event: Equatable { case customCommandSucceeded(worktreeID: Worktree.ID, name: String, durationMs: Int) case notificationReceived(worktreeID: Worktree.ID, surfaceID: UUID, title: String, body: String, isViewed: Bool) case notificationIndicatorChanged(count: Int) case tabCreated(worktreeID: Worktree.ID) case tabClosed(worktreeID: Worktree.ID, remainingTabs: Int) /// An undo put a closed tab or pane back. The reducer reveals it: the /// worktree in normal mode, the card in Canvas. case tabRestored(worktreeID: Worktree.ID, tabID: TerminalTabID) case focusChanged(worktreeID: Worktree.ID, surfaceID: UUID) case taskStatusChanged(worktreeID: Worktree.ID, status: WorktreeTaskStatus) case agentEntryChanged(ActiveAgentEntry) case agentEntryRemoved(ActiveAgentEntry.ID) /// A profile launch created its surface. The reducer records the per-repo /// launch memory on this event — not at dispatch — so a failed launch /// never shifts the Recommended resolution (docs-ai 053/005). case agentProfileLaunched(worktreeID: Worktree.ID, profileID: AgentProfile.ID) case agentProfileLaunchWarning(worktreeID: Worktree.ID, profileName: String, message: String) case agentProfileLaunchFailed(worktreeID: Worktree.ID, profileName: String) case runScriptStatusChanged(worktreeID: Worktree.ID, isRunning: Bool) case commandPaletteToggleRequested(worktreeID: Worktree.ID) case setupScriptConsumed(worktreeID: Worktree.ID) case fontSizeChanged(Float32?) case layoutRestored(selectedWorktreeID: Worktree.ID?) case layoutRestoreFailed(message: String) }}
extension TerminalClient: DependencyKey { static let liveValue = TerminalClient( send: { _ in fatalError("TerminalClient.send not configured") }, createTabInDirectory: { _, _ in fatalError("TerminalClient.createTabInDirectory not configured") }, launchAgentProfile: { _, _ in fatalError("TerminalClient.launchAgentProfile not configured") }, events: { fatalError("TerminalClient.events not configured") }, observeAgentState: { _ in fatalError("TerminalClient.observeAgentState not configured") }, canvasFocusedWorktreeID: { nil }, selectedSurfaceID: { _ in nil }, handoffSessionContextForSurface: { _, _ in nil }, latestUnreadNotification: { nil }, focusSurface: { _, _ in false }, markNotificationRead: { _, _ in }, markNotificationsReadForSurface: { _, _ in } )
static let testValue = TerminalClient( send: { _ in }, createTabInDirectory: { _, _ in nil }, launchAgentProfile: { _, _ in .failure(.tabCreationFailed) }, events: { AsyncStream { $0.finish() } }, observeAgentState: { _ in AgentObservationStream { $0.finish() } }, canvasFocusedWorktreeID: { nil }, selectedSurfaceID: { _ in nil }, handoffSessionContextForSurface: { _, _ in nil }, latestUnreadNotification: { nil }, focusSurface: { _, _ in false }, markNotificationRead: { _, _ in }, markNotificationsReadForSurface: { _, _ in } )}
extension DependencyValues { var terminalClient: TerminalClient { get { self[TerminalClient.self] } set { self[TerminalClient.self] = newValue } }}