native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668import Foundationimport GhosttyKitimport ProwlCLISharedimport Testing
@testable import Prowl
@MainActorstruct AgentObservationTests { @Test func liveShellStartsWithAtomicEmptySnapshotAndReplaysLatestSignal() async throws { let fixture = makeFixture() let firstStream = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID) var firstIterator = firstStream.makeAsyncIterator()
let first = try await firstIterator.next() guard case .snapshot(let initial) = first else { Issue.record("Expected snapshot first") return } #expect(initial.agent == nil) #expect(initial.latestSignal == nil) #expect(initial.revision == 0)
let signal = makeSignal() #expect(fixture.manager.recordAgentSignal(signal, surfaceID: fixture.surfaceID)) #expect(try await firstIterator.next() == .signal(signal))
let secondStream = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID) var secondIterator = secondStream.makeAsyncIterator() guard case .snapshot(let replay) = try await secondIterator.next() else { Issue.record("Expected replay snapshot first") return } #expect(replay.agent == nil) #expect(replay.latestSignal == signal) #expect(replay.revision == 1) }
@Test func signalIsMulticastToIndependentSubscribers() async throws { let fixture = makeFixture() var first = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() var second = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() _ = try await first.next() _ = try await second.next()
let signal = makeSignal(detail: "Review complete") #expect(fixture.manager.recordAgentSignal(signal, surfaceID: fixture.surfaceID))
#expect(try await first.next() == .signal(signal)) #expect(try await second.next() == .signal(signal)) }
@Test func publishedAgentRemovalPrecedesSurfaceClosureAndFinishesStream() async throws { let fixture = makeFixture() var iterator = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() _ = try await iterator.next()
fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: PaneAgentState(detectedAgent: .claude, state: .working) ) guard case .changed(let entry) = try await iterator.next() else { Issue.record("Expected changed event") return } #expect(entry.surfaceID == fixture.surfaceID)
#expect(fixture.state.closeSurface(id: fixture.surfaceID, confirmation: .skip)) #expect(try await iterator.next() == .removed) #expect(try await iterator.next() == .surfaceClosed) #expect(try await iterator.next() == nil) }
@Test func shellWithoutPublishedAgentClosesWithoutFalseRemoval() async throws { let fixture = makeFixture() fixture.state.wakeAgentDetection(forSurfaceID: fixture.surfaceID) var iterator = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() _ = try await iterator.next()
#expect(fixture.state.closeSurface(id: fixture.surfaceID, confirmation: .skip)) #expect(try await iterator.next() == .surfaceClosed) #expect(try await iterator.next() == nil) }
@Test func agentRemovalDoesNotCloseObserverAndLaterSignalStillArrives() async throws { let fixture = makeFixture() var iterator = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() _ = try await iterator.next() fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: PaneAgentState(detectedAgent: .claude, state: .working) ) _ = try await iterator.next()
fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: PaneAgentState() ) #expect(try await iterator.next() == .removed)
let signal = makeSignal() #expect(fixture.manager.recordAgentSignal(signal, surfaceID: fixture.surfaceID)) #expect(try await iterator.next() == .signal(signal)) }
@Test func closeAllAndPruneFinishEverySurfaceObserver() async throws { let closeAllFixture = makeFixture() var closeAllIterator = closeAllFixture.manager .observeAgentState(surfaceID: closeAllFixture.surfaceID) .makeAsyncIterator() _ = try await closeAllIterator.next() closeAllFixture.state.emitAgentEntry( surfaceID: closeAllFixture.surfaceID, tabId: closeAllFixture.tabID, state: PaneAgentState(detectedAgent: .claude, state: .working) ) guard case .changed = try await closeAllIterator.next() else { Issue.record("Expected published agent before close-all") return }
closeAllFixture.state.closeAllSurfaces() #expect(try await closeAllIterator.next() == .removed) #expect(try await closeAllIterator.next() == .surfaceClosed) #expect(try await closeAllIterator.next() == nil)
let pruneFixture = makeFixture() var pruneIterator = pruneFixture.manager .observeAgentState(surfaceID: pruneFixture.surfaceID) .makeAsyncIterator() _ = try await pruneIterator.next() pruneFixture.state.emitAgentEntry( surfaceID: pruneFixture.surfaceID, tabId: pruneFixture.tabID, state: PaneAgentState(detectedAgent: .codex, state: .working) ) guard case .changed = try await pruneIterator.next() else { Issue.record("Expected published agent before prune") return }
pruneFixture.manager.prune(keeping: []) #expect(try await pruneIterator.next() == .removed) #expect(try await pruneIterator.next() == .surfaceClosed) #expect(try await pruneIterator.next() == nil) }
@Test func cancellationRemovesOnlyTheCancelledSubscriber() async throws { let fixture = makeFixture() let firstStream = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID) let secondStream = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID) #expect(fixture.manager.agentObservationSubscriberCount(surfaceID: fixture.surfaceID) == 2)
let task = Task { for try await _ in firstStream {} } await Task.yield() task.cancel() _ = await task.result for _ in 0..<10 where fixture.manager.agentObservationSubscriberCount(surfaceID: fixture.surfaceID) == 2 { await Task.yield() }
#expect(fixture.manager.agentObservationSubscriberCount(surfaceID: fixture.surfaceID) == 1) var secondIterator = secondStream.makeAsyncIterator() _ = try await secondIterator.next() let signal = makeSignal() #expect(fixture.manager.recordAgentSignal(signal, surfaceID: fixture.surfaceID)) #expect(try await secondIterator.next() == .signal(signal)) }
@Test func closedSurfaceProducesSnapshotThenSurfaceClosed() async throws { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) var iterator = manager.observeAgentState(surfaceID: UUID()).makeAsyncIterator()
guard case .snapshot(let snapshot) = try await iterator.next() else { Issue.record("Expected snapshot first") return } #expect(snapshot.agent == nil) #expect(snapshot.latestSignal == nil) #expect(try await iterator.next() == .surfaceClosed) #expect(try await iterator.next() == nil) }
@Test func signalFromPaneWithoutDetectedAgentIsRecordedButUnbound() { let fixture = makeFixture() let signal = makeSignal(detail: "from a plain shell") let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [] )
#expect(fixture.manager.recordAgentSignal(signal, caller: caller) == .recorded(binding: .unbound)) #expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == nil) let diagnostics = fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID) #expect(diagnostics.channels.isEmpty) #expect(diagnostics.last?.detail == "from a plain shell") #expect(diagnostics.lastBinding == .unbound) }
@Test func signalForClosedSurfaceReportsPaneGone() { let manager = WorktreeTerminalManager(runtime: GhosttyRuntime()) let caller = CallerPane(worktreeID: "/tmp/agent-observation", surfaceID: UUID(), processAncestry: []) #expect(manager.recordAgentSignal(makeSignal(), caller: caller) == .paneGone) }
@Test func normalizedWorkingActivityInvalidatesPriorTerminalEvidence() throws { let fixture = makeFixture() let processID = getpid() let startedAt = try #require(ProcessDetection.processStartDate(pid: processID)) let session = AgentSession( id: "session-1", transcriptPath: nil, source: .commandLine, confidence: .exact ) let idleState = PaneAgentState( detectedAgent: .pi, agentProcessID: processID, session: session, state: .idle ) fixture.state.surfaceAgentStates[fixture.surfaceID] = idleState fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: idleState ) let signal = makeSignal() let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [ AgentProcessGeneration(pid: processID, startedAt: startedAt) ] )
#expect(fixture.manager.recordAgentSignal(signal, caller: caller) == .recorded(binding: .current)) #expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal)
let workingState = PaneAgentState( detectedAgent: .pi, agentProcessID: processID, session: session, state: .working ) fixture.state.surfaceAgentStates[fixture.surfaceID] = workingState fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: workingState )
#expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == nil) let diagnostics = fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID) #expect(diagnostics.last?.event == .turnEnded) #expect(diagnostics.lastBinding == .current) }
@Test func mediumSessionGuessDoesNotRejectCurrentGenerationSignal() throws { let fixture = makeFixture() let processID = getpid() let startedAt = try #require(ProcessDetection.processStartDate(pid: processID)) let state = PaneAgentState( detectedAgent: .pi, agentProcessID: processID, session: AgentSession( id: "guessed-session", transcriptPath: nil, source: .recentFile, confidence: .medium ), state: .idle ) fixture.state.surfaceAgentStates[fixture.surfaceID] = state fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: state ) let signal = AgentSignal( kind: .turnEnded, source: .hook(runtime: .pi, event: "agent_end"), confidence: .exact, timestamp: Date(timeIntervalSince1970: 1_000), sessionID: "actual-session", detail: nil, claimedOrigin: nil ) let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [ AgentProcessGeneration(pid: processID, startedAt: startedAt) ] )
#expect(fixture.manager.recordAgentSignal(signal, caller: caller) == .recorded(binding: .current)) #expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal) #expect(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).lastBinding == .current) }
@Test func mediumToHighSessionCorrectionKeepsSessionlessSignalCurrent() throws { let fixture = makeFixture() let processID = getpid() let startedAt = try #require(ProcessDetection.processStartDate(pid: processID)) let mediumState = PaneAgentState( detectedAgent: .pi, agentProcessID: processID, session: AgentSession( id: "guessed-session", transcriptPath: nil, source: .recentFile, confidence: .medium ), state: .idle ) fixture.state.surfaceAgentStates[fixture.surfaceID] = mediumState fixture.state.emitAgentEntry( surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: mediumState ) let signal = AgentSignal( kind: .turnEnded, source: .cooperativeCLI, confidence: .exact, timestamp: Date(timeIntervalSince1970: 1_000), sessionID: nil, detail: nil, claimedOrigin: nil ) let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [ AgentProcessGeneration(pid: processID, startedAt: startedAt) ] ) #expect(fixture.manager.recordAgentSignal(signal, caller: caller) == .recorded(binding: .current)) #expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal)
fixture.state.surfaceAgentStates[fixture.surfaceID] = PaneAgentState( detectedAgent: .pi, agentProcessID: processID, session: AgentSession( id: "actual-session", transcriptPath: nil, source: .transcriptMatch, confidence: .high ), state: .idle )
#expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal) #expect(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).lastBinding == .current) }
@Test func boundedOverflowIsExplicitAndRecoverableByResubscription() async throws { let fixture = makeFixture(bufferCapacity: 1) let stream = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID) let signal = makeSignal()
// Leave the initial snapshot buffered so the next critical event overflows. #expect(fixture.manager.recordAgentSignal(signal, surfaceID: fixture.surfaceID))
var iterator = stream.makeAsyncIterator() guard case .snapshot = try await iterator.next() else { Issue.record("Expected the protected initial snapshot") return } do { _ = try await iterator.next() Issue.record("Expected explicit buffer overflow") } catch let error as AgentObservationError { #expect(error == .bufferOverflow) }
var replacement = fixture.manager.observeAgentState(surfaceID: fixture.surfaceID).makeAsyncIterator() guard case .snapshot(let snapshot) = try await replacement.next() else { Issue.record("Expected resubscription snapshot") return } #expect(snapshot.latestSignal == signal) }
@Test func engineChildRebindingKeepsTheLaunchGenerationCurrent() throws { let fixture = makeFixture() let engine = try makeEngineChild() defer { engine.terminate() } let launchPID = getpid() let launchStartedAt = try #require(ProcessDetection.processStartDate(pid: launchPID)) let engineStartedAt = try #require(ProcessDetection.processStartDate(pid: engine.processIdentifier)) let launchOnly = PaneAgentState( detectedAgent: .droid, agentProcessID: launchPID, launchProcessID: launchPID, state: .idle ) fixture.state.surfaceAgentStates[fixture.surfaceID] = launchOnly fixture.state.emitAgentEntry(surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: launchOnly) let signal = makeSignal() let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [ AgentProcessGeneration(pid: engine.processIdentifier, startedAt: engineStartedAt), AgentProcessGeneration(pid: launchPID, startedAt: launchStartedAt), ] ) #expect(fixture.manager.recordAgentSignal(signal, caller: caller) == .recorded(binding: .current)) #expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal)
// The runtime forks an engine child that the classifier now identifies // instead of the launcher (Droid's `droid exec`). Same launch, same // generation: the evidence must survive. let engineBound = PaneAgentState( detectedAgent: .droid, agentProcessID: engine.processIdentifier, launchProcessID: launchPID, state: .idle ) fixture.state.surfaceAgentStates[fixture.surfaceID] = engineBound fixture.state.emitAgentEntry(surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: engineBound)
#expect(fixture.manager.currentEligibleAgentSignal(surfaceID: fixture.surfaceID) == signal) #expect(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).lastBinding == .current) }
@Test func engineChildRebindingKeepsTheManagedHookVerifiable() throws { let fixture = makeFixture() let engine = try makeEngineChild() defer { engine.terminate() } let launchPID = getpid() let launchStartedAt = try #require(ProcessDetection.processStartDate(pid: launchPID)) let engineStartedAt = try #require(ProcessDetection.processStartDate(pid: engine.processIdentifier)) let registration = AgentHookLaunchRegistration( token: "token-123", runtime: .droid, launchCWD: URL(filePath: "/tmp/agent-observation", directoryHint: .isDirectory), nativeEvents: ["SessionStart": .sessionStart, "Stop": .turnEnded], coveredEvents: [.sessionStart, .turnEnded], forwardingRecord: nil ) let plan = AgentProfileLaunchPlan( profileID: UUID(), profileName: "Droid", runtime: .droid, invocation: AgentInvocation(executable: "droid", arguments: []), hookRegistration: registration, commandEnvironmentTokens: [], placement: .tab, splitDirection: .right, surfaceEnvironment: [:], dedicatedHome: nil ) #expect(fixture.state.onAgentProfileSurfacePrepared?(fixture.surfaceID, plan) == true)
for (identified, state) in [(launchPID, AgentRawState.idle), (engine.processIdentifier, .working)] { let paneState = PaneAgentState( detectedAgent: .droid, agentProcessID: identified, launchProcessID: launchPID, state: state ) fixture.state.surfaceAgentStates[fixture.surfaceID] = paneState fixture.state.emitAgentEntry(surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: paneState) }
let input = AgentNativeHookInput( runtime: .droid, token: "token-123", signal: AgentNativeHookSignal( event: .turnEnded, nativeEvent: "Stop", cwd: "/tmp/agent-observation", sessionID: "session-1" ) ) let caller = CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [ AgentProcessGeneration(pid: engine.processIdentifier, startedAt: engineStartedAt), AgentProcessGeneration(pid: launchPID, startedAt: launchStartedAt), ] ) #expect(fixture.manager.recordAgentNativeHook(input, caller: caller)) let channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.source == "hook_droid") #expect(channel.state == .verifiedLive) }
/// Oh My Pi rotates through `session_switch` (no `session_start`); a repeated `session_start` /// for the current id (Pi `reload`) is idempotent (docs-ai 064.010). @Test func ompSessionSwitchRotatesTheManagedSessionAndDelayedStopsAreRejected() throws { let fixture = makeFixture() let table = AgentNativeHookDecoder.nativeEvents(for: .omp) let caller = try registerRelayedRuntime(.omp, table: table, fixture: fixture) func hook(_ nativeEvent: String, session: String) -> AgentNativeHookInput { AgentNativeHookInput( runtime: .omp, token: "token-relayed", signal: AgentNativeHookSignal( event: table[nativeEvent]!, nativeEvent: nativeEvent, cwd: "/tmp/agent-observation", sessionID: session ) ) }
#expect(fixture.manager.recordAgentNativeHook(hook("session_start", session: "S1"), caller: caller)) #expect(fixture.manager.recordAgentNativeHook(hook("session_stop", session: "S1"), caller: caller)) var channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.source == "hook_omp") #expect(channel.state == .verifiedLive) #expect(channel.sessionID == "S1") #expect(channel.events.contains(.turnEnded))
// `/new`: a single `session_switch` carries the new id. #expect(fixture.manager.recordAgentNativeHook(hook("session_switch", session: "S2"), caller: caller)) channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.sessionID == "S2") #expect(channel.state == .verifiedLive) #expect(!fixture.manager.recordAgentNativeHook(hook("session_stop", session: "S1"), caller: caller)) #expect(fixture.manager.recordAgentNativeHook(hook("session_stop", session: "S2"), caller: caller))
// Re-announcing the current id is idempotent, never a rotation. #expect(fixture.manager.recordAgentNativeHook(hook("session_start", session: "S2"), caller: caller)) channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.sessionID == "S2") #expect(channel.state == .verifiedLive) }
/// OpenCode declares no SessionStart (its session is created lazily and `/new` / resume emit /// nothing), so like Codex the first ordinary event verifies and a new id rotates. @Test func opencodeIsNonAnnouncingSoTheFirstIdleVerifiesAndANewSessionRotates() throws { let fixture = makeFixture() let table = AgentNativeHookDecoder.nativeEvents(for: .opencode) let caller = try registerRelayedRuntime(.opencode, table: table, fixture: fixture) func hook(_ nativeEvent: String, session: String) -> AgentNativeHookInput { AgentNativeHookInput( runtime: .opencode, token: "token-relayed", signal: AgentNativeHookSignal( event: table[nativeEvent]!, nativeEvent: nativeEvent, cwd: "/tmp/agent-observation", sessionID: session ) ) }
#expect(fixture.manager.recordAgentNativeHook(hook("session.idle", session: "ses_1"), caller: caller)) var channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.source == "hook_opencode") #expect(channel.state == .verifiedLive) #expect(channel.sessionID == "ses_1") #expect(!channel.events.contains(.sessionStart)) #expect(fixture.manager.recordAgentNativeHook(hook("permission.asked", session: "ses_1"), caller: caller))
#expect(fixture.manager.recordAgentNativeHook(hook("session.idle", session: "ses_2"), caller: caller)) channel = try #require(fixture.manager.agentSignalsPayload(surfaceID: fixture.surfaceID).channels.first) #expect(channel.sessionID == "ses_2") #expect(channel.state == .verifiedLive) #expect(!fixture.manager.recordAgentNativeHook(hook("session.idle", session: "ses_1"), caller: caller)) }
/// Register a single-process relayed runtime (Pi family, OpenCode TUI) on the fixture's pane /// and return the exact caller the bridge would present. private func registerRelayedRuntime( _ runtime: AgentProfileRuntime, table: [String: AgentSignalEvent], fixture: Fixture ) throws -> CallerPane { let launchPID = getpid() let launchStartedAt = try #require(ProcessDetection.processStartDate(pid: launchPID)) let hookRuntime = try #require(AgentNativeHookRuntime(rawValue: runtime.rawValue)) let registration = AgentHookLaunchRegistration( token: "token-relayed", runtime: hookRuntime, launchCWD: URL(filePath: "/tmp/agent-observation", directoryHint: .isDirectory), nativeEvents: table, coveredEvents: Array(Set(table.values)).sorted { $0.rawValue < $1.rawValue }, forwardingRecord: nil ) let plan = AgentProfileLaunchPlan( profileID: UUID(), profileName: runtime.rawValue, runtime: runtime, invocation: AgentInvocation(executable: runtime.rawValue, arguments: []), hookRegistration: registration, commandEnvironmentTokens: [], placement: .tab, splitDirection: .right, surfaceEnvironment: [:], dedicatedHome: nil ) #expect(fixture.state.onAgentProfileSurfacePrepared?(fixture.surfaceID, plan) == true) let paneState = PaneAgentState( detectedAgent: runtime.agent, agentProcessID: launchPID, launchProcessID: launchPID, state: .working ) fixture.state.surfaceAgentStates[fixture.surfaceID] = paneState fixture.state.emitAgentEntry(surfaceID: fixture.surfaceID, tabId: fixture.tabID, state: paneState) return CallerPane( worktreeID: "/tmp/agent-observation", surfaceID: fixture.surfaceID, processAncestry: [AgentProcessGeneration(pid: launchPID, startedAt: launchStartedAt)] ) }
private struct Fixture { let manager: WorktreeTerminalManager let state: WorktreeTerminalState let tabID: TerminalTabID let surfaceID: UUID }
private func makeFixture(bufferCapacity: Int = 64) -> Fixture { let manager = WorktreeTerminalManager( runtime: GhosttyRuntime(), agentObservationBufferCapacity: bufferCapacity ) let worktree = Worktree( id: "/tmp/agent-observation", name: "agent-observation", detail: "", workingDirectory: URL(fileURLWithPath: "/tmp/agent-observation"), repositoryRootURL: URL(fileURLWithPath: "/tmp/agent-observation") ) let state = manager.state(for: worktree) let tabID = state.createTab()! let surfaceID = state.focusedSurfaceId(in: tabID)! return Fixture(manager: manager, state: state, tabID: tabID, surfaceID: surfaceID) }
private func makeSignal(detail: String? = nil) -> AgentSignal { AgentSignal( kind: .turnEnded, source: .cooperativeCLI, confidence: .exact, timestamp: Date(timeIntervalSince1970: 1_000), sessionID: "session-1", detail: detail, claimedOrigin: nil ) }
/// A live child of the test host standing in for an engine process the /// launched agent forked; generation checks need real start dates. private func makeEngineChild() throws -> Process { let process = Process() process.executableURL = URL(filePath: "/bin/sleep") process.arguments = ["30"] try process.run() return process }}