@@ -17,6 +17,7 @@ import {
1717 DEFAULT_COLS ,
1818 DEFAULT_ROWS ,
1919 DEFAULT_SCROLLBACK ,
20+ HUB_TERMINAL_STREAM_CHANNEL ,
2021 PRESETS_STATE_KEY ,
2122 SESSIONS_STATE_KEY ,
2223 TERMINAL_STREAM_CHANNEL ,
@@ -58,13 +59,16 @@ interface HubTerminalEntry {
5859 title : string
5960 description ?: string
6061 status : 'running' | 'stopped' | 'error'
61- icon ?: string
62+ icon ?: string | { light : string , dark : string }
6263}
6364interface HubTerminalsBridge {
64- sessions : Map < string , { id : string } >
65+ sessions : Map < string , HubTerminalEntry >
6566 register : ( session : HubTerminalEntry ) => unknown
6667 update : ( session : HubTerminalEntry ) => void
6768 remove ?: ( session : { id : string } ) => void
69+ events ?: {
70+ on : ( event : 'terminal:session:updated' , cb : ( session : HubTerminalEntry ) => void ) => void
71+ }
6872}
6973
7074/** Map the plugin's session status onto the hub's coarser status set. */
@@ -137,10 +141,52 @@ export class TerminalManager {
137141 icon : p . icon ,
138142 } ) )
139143 } )
144+
145+ // When mounted in a hub, refresh our session list whenever another
146+ // devframe's terminal session (e.g. code-server) changes, so those
147+ // aggregated sessions appear/update/disappear in this plugin's UI. Guarded
148+ // to foreign ids so mirroring our *own* sessions into the hub can't loop.
149+ const hub = this . hubTerminals ( )
150+ hub ?. events ?. on ( 'terminal:session:updated' , ( session ) => {
151+ if ( ! this . sessions . has ( session . id ) )
152+ this . refreshSessionsState ( )
153+ } )
154+ }
155+
156+ /** The hub's terminals subsystem when mounted in a hub, else undefined. */
157+ private hubTerminals ( ) : HubTerminalsBridge | undefined {
158+ return ( this . ctx as { terminals ?: HubTerminalsBridge } ) . terminals
140159 }
141160
142161 list ( ) : TerminalSessionInfo [ ] {
143- return Array . from ( this . sessions . values ( ) ) . map ( s => ( { ...s . info } ) )
162+ const own = Array . from ( this . sessions . values ( ) ) . map ( s => ( { ...s . info } ) )
163+ const hub = this . hubTerminals ( )
164+ if ( ! hub ?. sessions )
165+ return own
166+
167+ // Surface sessions contributed by *other* devframes (aggregated in the
168+ // hub) as read-only entries, reading their output from the hub's channel.
169+ const foreign : TerminalSessionInfo [ ] = [ ]
170+ for ( const session of hub . sessions . values ( ) ) {
171+ if ( this . sessions . has ( session . id ) )
172+ continue
173+ foreign . push ( {
174+ id : session . id ,
175+ title : session . title ,
176+ mode : 'readonly' ,
177+ status : session . status === 'stopped' ? 'exited' : session . status ,
178+ backend : 'pipe' ,
179+ command : '' ,
180+ args : [ ] ,
181+ cwd : '' ,
182+ cols : DEFAULT_COLS ,
183+ rows : DEFAULT_ROWS ,
184+ createdAt : 0 ,
185+ icon : typeof session . icon === 'string' ? session . icon : session . icon ?. light ,
186+ channel : HUB_TERMINAL_STREAM_CHANNEL ,
187+ } )
188+ }
189+ return [ ...own , ...foreign ]
144190 }
145191
146192 getPresets ( ) : TerminalPreset [ ] {
@@ -415,10 +461,20 @@ export class TerminalManager {
415461 }
416462
417463 private publish ( ) : void {
464+ this . refreshSessionsState ( )
465+ this . syncHub ( )
466+ }
467+
468+ /**
469+ * Push the current session list (own + aggregated hub sessions) into shared
470+ * state. Kept separate from {@link publish} so the hub-session listener can
471+ * refresh without re-running {@link syncHub} (which would re-emit hub events
472+ * and loop).
473+ */
474+ private refreshSessionsState ( ) : void {
418475 this . sessionsState ?. mutate ( ( draft ) => {
419476 draft . sessions = this . list ( )
420477 } )
421- this . syncHub ( )
422478 }
423479
424480 /**
0 commit comments