native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119import Foundationimport Testing
@testable import Prowl
struct PaneAgentStateTests { @Test func displayStateDerivesDoneFromUnseenIdle() { var state = PaneAgentState( detectedAgent: .codex, fallbackState: .idle, state: .idle, seen: false, lastChangedAt: Date(timeIntervalSince1970: 0) )
#expect(state.displayState == .done) state.seen = true #expect(state.displayState == .idle) }
@Test func presenceRequiresSixMissesBeforeRelease() { var presence = AgentDetectionPresence(currentAgent: .codex)
for _ in 0..<5 { #expect(presence.update(detectedAgent: nil) == .codex) } #expect(presence.update(detectedAgent: nil) == nil) #expect(presence.currentAgent == nil) }
@Test func isBusyReflectsWorkingAndBlockedDetectedAgents() { #expect(PaneAgentState(detectedAgent: .claude, state: .working).isBusy) #expect(PaneAgentState(detectedAgent: .claude, state: .blocked).isBusy) #expect(!PaneAgentState(detectedAgent: .claude, state: .idle).isBusy) // Unseen idle surfaces display as `.done`, which must not count as busy. #expect(!PaneAgentState(detectedAgent: .claude, state: .idle, seen: false).isBusy) // A plain shell (no detected agent) is never busy, even mid-output. #expect(!PaneAgentState(detectedAgent: nil, state: .working).isBusy) }
@Test func launchObservationRetainsOnlyForSameProcess() { let observation = AgentLaunchObservation(model: "gpt-5.4", executionMode: .unrestricted) let previous = PaneAgentState(agentProcessID: 42, launchObservation: observation)
#expect( PaneAgentState.retainedLaunchObservation( observed: nil, previous: previous, identifiedPID: 42 ) == observation ) #expect( PaneAgentState.retainedLaunchObservation( observed: nil, previous: previous, identifiedPID: 43 ) == nil ) }
@Test func launchProcessStaysStickyWhenASampleTransientlyDropsIt() { // A complete sample bound the launcher (100). If the next `proc_listpids` transiently // omits 100, `launchProcessID(of:)` returns the engine (101); keeping the previous launch // process — still a live ancestor of 101 — prevents a spurious process-replacement. let previous = PaneAgentState(agentProcessID: 101, launchProcessID: 100) #expect( PaneAgentState.retainedLaunchProcessID( identifiedLaunchProcessID: 101, identifiedProcessID: 101, previous: previous, isLiveAncestor: { ancestor, descendant in ancestor == 100 && descendant == 101 } ) == 100) }
@Test func launchProcessMovesWhenThePreviousLauncherIsGone() { // A genuine relaunch: the old launcher is no longer an ancestor of the identified process. let previous = PaneAgentState(agentProcessID: 201, launchProcessID: 100) #expect( PaneAgentState.retainedLaunchProcessID( identifiedLaunchProcessID: 200, identifiedProcessID: 201, previous: previous, isLiveAncestor: { _, _ in false } ) == 200) }
@Test func launchProcessUsesTheIdentifiedRootWhenUnchangedOrFirstSeen() { // Steady state (engine child rebinding keeps the same launch root) and the first bind. #expect( PaneAgentState.retainedLaunchProcessID( identifiedLaunchProcessID: 100, identifiedProcessID: 102, previous: PaneAgentState(agentProcessID: 101, launchProcessID: 100), isLiveAncestor: { _, _ in Issue.record("must not probe when the root is unchanged") return false } ) == 100) #expect( PaneAgentState.retainedLaunchProcessID( identifiedLaunchProcessID: 100, identifiedProcessID: 100, previous: PaneAgentState(), isLiveAncestor: { _, _ in false } ) == 100) }
@Test func launchProcessHoldsAcrossAFullProbeGap() { // No agent identified this poll: keep the last known launch process untouched. #expect( PaneAgentState.retainedLaunchProcessID( identifiedLaunchProcessID: nil, identifiedProcessID: nil, previous: PaneAgentState(agentProcessID: 101, launchProcessID: 100), isLiveAncestor: { _, _ in false } ) == 100) }}