@@ -10,6 +10,10 @@ import { buildRepoIconUrl, pathBasename, resolvePaneRepoCwd } from '@/lib/repo-i
1010import { hueFromString } from '@/components/icons/RepoIcon'
1111import { makeFreshAgentSessionKey } from '@shared/fresh-agent'
1212import type { DeckTileStyle } from '@shared/settings'
13+ import { isNonShellMode } from '@/lib/coding-cli-utils'
14+
15+ export type TilePaneTint = 'blue' | 'green' | 'amber' | 'red' | 'mutedDim' | 'muted'
16+ export type TilePaneIcon = { provider : string ; tint : TilePaneTint }
1317
1418export type DeckTab = {
1519 id : string
@@ -22,6 +26,7 @@ export type DeckTab = {
2226 dot : TileDot
2327 priority : number
2428 repoIcons : TileRepoIcon [ ]
29+ paneIcons : TilePaneIcon [ ]
2530}
2631export type DeckModel = { tabs : DeckTab [ ] ; activeTabId : string | null ; tileStyle : DeckTileStyle }
2732
@@ -122,6 +127,48 @@ export function getTabRepoIcons(state: RootState, tab: Tab): TileRepoIcon[] {
122127 return icons
123128}
124129
130+ /** Mirrors getTerminalStatusIconClassName (src/lib/terminal-status-indicator.ts). */
131+ function paneStatusTint ( status : string ) : TilePaneTint {
132+ switch ( status ) {
133+ case 'running' : return 'green' // text-success
134+ case 'recovering' : return 'amber' // text-warning
135+ case 'exited' : return 'mutedDim' // text-muted-foreground/40
136+ case 'error' : return 'red' // text-destructive
137+ default : return 'muted' // creating etc. -> text-muted-foreground
138+ }
139+ }
140+
141+ /**
142+ * Agent pane icons for a tab, in layout order, UNCAPPED (the renderer caps
143+ * drawn icons and folds the rest into a +N badge — TabItem's MAX_PANE_ICONS
144+ * overflow rule adapted to key size). Agent panes are non-shell terminals
145+ * (provider = mode) and fresh-agent panes (provider = sessionType);
146+ * shell/browser/editor/picker/extension panes draw no agent icon on a key
147+ * this small. Tint mirrors TabItem.tsx renderIcons: busy -> blue (wins),
148+ * else the pane's effective status (non-terminal kinds count as 'running').
149+ */
150+ export function getTabPaneIcons ( state : RootState , tab : Tab ) : TilePaneIcon [ ] {
151+ const busyIds = getBusyPaneIdsForTab ( {
152+ tab,
153+ paneLayouts : state . panes . layouts as Record < string , PaneNode | undefined > ,
154+ ...activityInputs ( state ) ,
155+ } )
156+ const icons : TilePaneIcon [ ] = [ ]
157+ for ( const { paneId, content } of panesForTab ( state , tab ) ) {
158+ let provider : string | null = null
159+ let status = 'running'
160+ if ( content . kind === 'terminal' && isNonShellMode ( content . mode ) ) {
161+ provider = content . mode
162+ status = content . status
163+ } else if ( content . kind === 'fresh-agent' ) {
164+ provider = content . sessionType
165+ }
166+ if ( ! provider ) continue
167+ icons . push ( { provider, tint : busyIds . includes ( paneId ) ? 'blue' : paneStatusTint ( status ) } )
168+ }
169+ return icons
170+ }
171+
125172/**
126173 * Per-tab status flags, derived from the SAME conditions the tab bar uses:
127174 * - busy: any pane busy (getBusyPaneIdsForTab, TabBar.tsx:329-338)
@@ -167,6 +214,7 @@ export function selectDeckModel(state: RootState): DeckModel {
167214 dot : tileDot ( flags ) ,
168215 priority : tilePriority ( active , flags ) ,
169216 repoIcons : getTabRepoIcons ( state , tab ) ,
217+ paneIcons : getTabPaneIcons ( state , tab ) ,
170218 }
171219 } )
172220 if ( tileStyle === 'status-icons' ) {
0 commit comments