diff --git a/supacode/App/supacodeApp.swift b/supacode/App/supacodeApp.swift index 5b47acd0..b3544456 100644 --- a/supacode/App/supacodeApp.swift +++ b/supacode/App/supacodeApp.swift @@ -541,12 +541,19 @@ struct SupacodeApp: App { ) } let agentsHandler = AgentsCommandHandler { - AgentsRuntimeSnapshot( + var rawStatesBySurfaceID: [UUID: AgentRawState] = [:] + for terminalState in terminalManager.activeWorktreeStates { + for (surfaceID, agentState) in terminalState.surfaceAgentStates { + rawStatesBySurfaceID[surfaceID] = agentState.fallbackState + } + } + return AgentsRuntimeSnapshot( repositoriesState: appStore.state.repositories, listSnapshot: ListRuntimeSnapshotBuilder.makeSnapshot( repositoriesState: appStore.state.repositories, terminalManager: terminalManager - ) + ), + rawStatesBySurfaceID: rawStatesBySurfaceID ) } let sendHandler = SendCommandHandler( diff --git a/supacode/CLIService/AgentsCommandHandler.swift b/supacode/CLIService/AgentsCommandHandler.swift index 4290233c..0ce2e2d3 100644 --- a/supacode/CLIService/AgentsCommandHandler.swift +++ b/supacode/CLIService/AgentsCommandHandler.swift @@ -3,6 +3,10 @@ import Foundation struct AgentsRuntimeSnapshot { let repositoriesState: RepositoriesFeature.State let listSnapshot: ListRuntimeSnapshot + /// Live detector output keyed by pane. Reducer entries intentionally skip + /// raw-state-only changes to avoid invalidating the sidebar, so CLI snapshots + /// must source this field from terminal state instead. + let rawStatesBySurfaceID: [UUID: AgentRawState] } final class AgentsCommandHandler: CommandHandler { @@ -83,7 +87,7 @@ final class AgentsCommandHandler: CommandHandler { type: entry.agent.rawValue, name: entry.displayName, status: AgentsCommandStatus(rawValue: entry.displayState.rawValue) ?? .idle, - rawState: entry.rawState.rawValue, + rawState: (snapshot.rawStatesBySurfaceID[entry.surfaceID] ?? entry.rawState).rawValue, lastChangedAt: dateFormatter.string(from: entry.lastChangedAt), project: AgentsCommandProject( name: display.repositoryName, diff --git a/supacode/Features/Repositories/Views/SidebarListView.swift b/supacode/Features/Repositories/Views/SidebarListView.swift index c68d2bc7..d5aa04bd 100644 --- a/supacode/Features/Repositories/Views/SidebarListView.swift +++ b/supacode/Features/Repositories/Views/SidebarListView.swift @@ -62,7 +62,6 @@ struct SidebarListView: View { @State private var isAddChoicePresented = false @Namespace private var topSegmentNamespace @Environment(\.resolvedKeybindings) private var resolvedKeybindings - @Shared(.repositoryAppearances) private var repositoryAppearances var body: some View { let state = store.state diff --git a/supacodeTests/AgentEntryEmissionDedupTests.swift b/supacodeTests/AgentEntryEmissionDedupTests.swift index 86c951a8..d974a4b0 100644 --- a/supacodeTests/AgentEntryEmissionDedupTests.swift +++ b/supacodeTests/AgentEntryEmissionDedupTests.swift @@ -90,6 +90,7 @@ struct AgentEntryEmissionDedupTests { ("paneIndex", Self.entry(paneIndex: 2)), ("iconLookupToken", Self.entry(iconLookupToken: "codex")), ("agent", Self.entry(agent: .codex)), + ("launchProfileName", Self.entry(launchProfileName: "Review Profile")), // Carries the resume target `prowl agents` reports; a suppressed change // would keep pointing at the previous session's transcript. ("session", Self.entry(session: Self.otherSession)), @@ -141,6 +142,7 @@ struct AgentEntryEmissionDedupTests { paneIndex: Int = 1, iconLookupToken: String = "claude", agent: DetectedAgent = .claude, + launchProfileName: String? = nil, session: AgentSession? = baseSession, rawState: AgentRawState = .working, displayState: AgentDisplayState = .working, @@ -160,7 +162,8 @@ struct AgentEntryEmissionDedupTests { session: session, rawState: rawState, displayState: displayState, - lastChangedAt: lastChangedAt + lastChangedAt: lastChangedAt, + launchProfileName: launchProfileName ) } diff --git a/supacodeTests/CLIAgentsCommandHandlerTests.swift b/supacodeTests/CLIAgentsCommandHandlerTests.swift index d3cb80a5..1483a984 100644 --- a/supacodeTests/CLIAgentsCommandHandlerTests.swift +++ b/supacodeTests/CLIAgentsCommandHandlerTests.swift @@ -29,7 +29,10 @@ struct CLIAgentsCommandHandlerTests { #expect(agent.type == "pi") #expect(agent.name == "omp") #expect(agent.status == .blocked) - #expect(agent.rawState == "blocked") + // The reducer entry may intentionally lag raw-state-only terminal polls so + // the sidebar does not re-render. CLI snapshots must use the live terminal + // raw state instead of that UI-deduplicated value. + #expect(agent.rawState == "working") #expect(agent.lastChangedAt == "2026-09-21T14:00:00Z") #expect(agent.project.name == "Prowl") #expect(agent.project.branch == "feature/agents") @@ -179,7 +182,8 @@ struct CLIAgentsCommandHandlerTests { otherPaneID: otherPaneID, tabID: tabID, tabWorktree: tabWorktree - ) + ), + rawStatesBySurfaceID: [tabPaneID: .working] ) ) }