diff --git a/docs/components/active-agents.md b/docs/components/active-agents.md index c72ab17c..f8efec90 100644 --- a/docs/components/active-agents.md +++ b/docs/components/active-agents.md @@ -24,7 +24,9 @@ Command Palette → "Toggle Active Agents Panel". tab title or branch (secondary) ``` -- **Icon** — the detected agent's icon (falls back to a sparkle). +- **Icon** — the detected command/agent icon (for example `omp` keeps the OMP + icon even though it reports as the Pi agent; unknown wrappers fall back to the + agent icon, then a sparkle). - **Title** — agent name + repository (repo color-coded). - **Subtitle** — the tab title (if `showActiveAgentTabTitles`) or branch name. - **Status pill** — one of: diff --git a/supacode/Domain/AgentDetection/PaneAgentState.swift b/supacode/Domain/AgentDetection/PaneAgentState.swift index e336f0c1..68440989 100644 --- a/supacode/Domain/AgentDetection/PaneAgentState.swift +++ b/supacode/Domain/AgentDetection/PaneAgentState.swift @@ -2,6 +2,7 @@ import Foundation struct PaneAgentState: Equatable, Sendable { var detectedAgent: DetectedAgent? + var iconLookupToken: String? var fallbackState: AgentRawState var state: AgentRawState var seen: Bool @@ -9,12 +10,14 @@ struct PaneAgentState: Equatable, Sendable { init( detectedAgent: DetectedAgent? = nil, + iconLookupToken: String? = nil, fallbackState: AgentRawState = .unknown, state: AgentRawState = .unknown, seen: Bool = true, lastChangedAt: Date = Date() ) { self.detectedAgent = detectedAgent + self.iconLookupToken = iconLookupToken self.fallbackState = fallbackState self.state = state self.seen = seen diff --git a/supacode/Features/ActiveAgents/Models/ActiveAgentEntry.swift b/supacode/Features/ActiveAgents/Models/ActiveAgentEntry.swift index bd6b7aa0..29e3b9ef 100644 --- a/supacode/Features/ActiveAgents/Models/ActiveAgentEntry.swift +++ b/supacode/Features/ActiveAgents/Models/ActiveAgentEntry.swift @@ -16,8 +16,15 @@ struct ActiveAgentEntry: Identifiable, Equatable, Sendable { let tabTitle: String let surfaceID: UUID let paneIndex: Int + /// Command/process token used for row icon lookup. This can be more specific than + /// `agent` for aliases that share one semantic agent, e.g. `omp` vs `pi`. + let iconLookupToken: String let agent: DetectedAgent let rawState: AgentRawState let displayState: AgentDisplayState let lastChangedAt: Date + + var iconSource: TabIconSource? { + CommandIconMap.iconForFirstToken(iconLookupToken) ?? CommandIconMap.iconForFirstToken(agent.iconLookupToken) + } } diff --git a/supacode/Features/ActiveAgents/Views/ActiveAgentRow.swift b/supacode/Features/ActiveAgents/Views/ActiveAgentRow.swift index b2776621..61c06af0 100644 --- a/supacode/Features/ActiveAgents/Views/ActiveAgentRow.swift +++ b/supacode/Features/ActiveAgents/Views/ActiveAgentRow.swift @@ -46,7 +46,7 @@ struct ActiveAgentRow: View { private var agentIcon: some View { Group { - if let icon = CommandIconMap.iconForFirstToken(entry.agent.iconLookupToken) { + if let icon = entry.iconSource { TabIconImage(rawName: icon.storageString, pointSize: 16) } else { Image(systemName: "sparkle") diff --git a/supacode/Features/Terminal/Models/WorktreeTerminalState+AgentDetection.swift b/supacode/Features/Terminal/Models/WorktreeTerminalState+AgentDetection.swift index 2e8c35cd..b79d8658 100644 --- a/supacode/Features/Terminal/Models/WorktreeTerminalState+AgentDetection.swift +++ b/supacode/Features/Terminal/Models/WorktreeTerminalState+AgentDetection.swift @@ -100,9 +100,11 @@ extension WorktreeTerminalState { } else { seen = previous.seen } + let iconLookupToken = identified?.name ?? previous.iconLookupToken ?? agent.iconLookupToken let lastChangedAt = (previous.detectedAgent != agent || previous.state != stabilized) ? now : previous.lastChangedAt let next = PaneAgentState( detectedAgent: agent, + iconLookupToken: iconLookupToken, fallbackState: raw, state: stabilized, seen: seen, @@ -191,6 +193,7 @@ extension WorktreeTerminalState { tabTitle: tabTitle, surfaceID: surfaceID, paneIndex: paneIndex, + iconLookupToken: state.iconLookupToken ?? agent.iconLookupToken, agent: agent, rawState: state.fallbackState, displayState: state.displayState, diff --git a/supacodeTests/ActiveAgentsFeatureTests.swift b/supacodeTests/ActiveAgentsFeatureTests.swift index a5f0553f..241ae379 100644 --- a/supacodeTests/ActiveAgentsFeatureTests.swift +++ b/supacodeTests/ActiveAgentsFeatureTests.swift @@ -100,6 +100,26 @@ struct ActiveAgentsFeatureTests { ) } + @Test func rowIconUsesDetectedCommandTokenBeforeAgentFallback() { + let ompEntry = entry( + id: UUID(0), + state: .idle, + changedAt: Date(timeIntervalSince1970: 10), + agent: .pi, + iconLookupToken: "omp" + ) + #expect(ompEntry.iconSource?.assetName == "OMP") + + let fallbackEntry = entry( + id: UUID(1), + state: .idle, + changedAt: Date(timeIntervalSince1970: 10), + agent: .pi, + iconLookupToken: "unknown-wrapper" + ) + #expect(fallbackEntry.iconSource?.assetName == "Pi") + } + @Test func navigationReturnsNilForEmptyList() { let entries: IdentifiedArrayOf = [] #expect(ActiveAgentsFeature.entryID(navigatingFrom: nil, direction: .next, in: entries) == nil) @@ -228,7 +248,9 @@ struct ActiveAgentsFeatureTests { id: UUID, tabTitle: String = "1", state: AgentDisplayState, - changedAt: Date + changedAt: Date, + agent: DetectedAgent = .codex, + iconLookupToken: String? = nil ) -> ActiveAgentEntry { ActiveAgentEntry( id: id, @@ -239,7 +261,8 @@ struct ActiveAgentsFeatureTests { tabTitle: tabTitle, surfaceID: id, paneIndex: 1, - agent: .codex, + iconLookupToken: iconLookupToken ?? agent.iconLookupToken, + agent: agent, rawState: state == .blocked ? .blocked : state == .working ? .working : .idle, displayState: state, lastChangedAt: changedAt diff --git a/supacodeTests/AppFeatureSettingsChangedTests.swift b/supacodeTests/AppFeatureSettingsChangedTests.swift index bd6a31af..86fb0de6 100644 --- a/supacodeTests/AppFeatureSettingsChangedTests.swift +++ b/supacodeTests/AppFeatureSettingsChangedTests.swift @@ -246,6 +246,7 @@ struct AppFeatureSettingsChangedTests { tabTitle: "codex", surfaceID: fixedUUID(0), paneIndex: 1, + iconLookupToken: DetectedAgent.codex.iconLookupToken, agent: .codex, rawState: .working, displayState: .working, diff --git a/supacodeTests/RepositoriesFeatureTests.swift b/supacodeTests/RepositoriesFeatureTests.swift index 33532f5c..c8d4f90f 100644 --- a/supacodeTests/RepositoriesFeatureTests.swift +++ b/supacodeTests/RepositoriesFeatureTests.swift @@ -1025,6 +1025,7 @@ struct RepositoriesFeatureTests { tabTitle: "agent", surfaceID: surfaceID, paneIndex: 0, + iconLookupToken: DetectedAgent.codex.iconLookupToken, agent: .codex, rawState: .working, displayState: .working, @@ -1079,6 +1080,7 @@ struct RepositoriesFeatureTests { tabTitle: "agent", surfaceID: surfaceID, paneIndex: 0, + iconLookupToken: DetectedAgent.codex.iconLookupToken, agent: .codex, rawState: .working, displayState: .working, @@ -1276,6 +1278,7 @@ struct RepositoriesFeatureTests { tabTitle: "agent", surfaceID: surfaceID, paneIndex: 0, + iconLookupToken: DetectedAgent.codex.iconLookupToken, agent: .codex, rawState: .working, displayState: .working, diff --git a/supacodeTests/RepositorySectionViewTests.swift b/supacodeTests/RepositorySectionViewTests.swift index b9c75d73..0d247608 100644 --- a/supacodeTests/RepositorySectionViewTests.swift +++ b/supacodeTests/RepositorySectionViewTests.swift @@ -305,6 +305,7 @@ struct RepositorySectionViewTests { tabTitle: "agent", surfaceID: UUID(), paneIndex: 1, + iconLookupToken: DetectedAgent.codex.iconLookupToken, agent: .codex, rawState: .working, displayState: .working,