@@ -88,6 +88,11 @@ export class WebUiBackend implements UiBackend {
8888 tools : ToolCallEntry [ ] ;
8989 } | null = null ;
9090
91+ // Context window broadcast throttling
92+ private contextWindowThrottleTimer : ReturnType < typeof setTimeout > | null = null ;
93+ private contextWindowThrottlePending : boolean = false ;
94+ private isAssistantTurn : boolean = false ;
95+
9196 constructor ( options : WebUiOptions ) {
9297 this . port = options . port ;
9398 this . harness = options . harness ;
@@ -128,16 +133,19 @@ export class WebUiBackend implements UiBackend {
128133 this . currentAssistant . tools . push ( { name : e . name , args : "" , result : rs , isError : e . isError , images : imgs } ) ;
129134 }
130135 this . broadcast ( { type : "tool_end" , name : e . name , result : rs , isError : e . isError , images : imgs } ) ;
131- } ) ;
136+ this . broadcastContextWindow ( false ) ; } ) ;
132137 h . events . on ( "turn:streaming:start" , ( ) => {
133138 this . currentAssistant = { thinking : "" , text : "" , tools : [ ] } ;
134139 this . broadcast ( { type : "assistant_start" } ) ;
135140 this . startSessionTimeBroadcast ( ) ;
136- } ) ;
141+ this . isAssistantTurn = true ; } ) ;
137142 h . events . on ( "turn:end" , ( ) => {
138143 this . broadcastSessionTime ( ) ;
144+ const toolsForBroadcast = this . currentAssistant ?. tools ?? [ ] ;
139145 this . currentAssistant = null ;
140146 this . broadcast ( { type : "assistant_end" } ) ;
147+ this . isAssistantTurn = false ;
148+ this . broadcastContextWindow ( true , toolsForBroadcast ) ;
141149 this . pushSessionListToAll ( ) ;
142150 } ) ;
143151 h . events . on ( "turn:abort" , ( ) => { this . stopSessionTimeBroadcast ( ) ; this . broadcast ( { type : "loader" , state : "hide" } ) ; } ) ;
@@ -149,7 +157,7 @@ export class WebUiBackend implements UiBackend {
149157 h . events . on ( "ui:error" , ( e ) => { this . broadcast ( { type : "error" , text : e . text } ) ; } ) ;
150158 h . events . on ( "ui:warning" , ( e ) => { this . broadcast ( { type : "warning" , text : e . text } ) ; } ) ;
151159 h . events . on ( "ui:image:pending" , ( e ) => { this . pendingImages . push ( e . image ) ; } ) ;
152- h . events . on ( "ui:conversation:clear" , ( ) => { this . currentAssistant = null ; this . pendingImages = [ ] ; this . broadcast ( { type : "clear_conversation" } ) ; } ) ;
160+ h . events . on ( "ui:conversation:clear" , ( ) => { this . currentAssistant = null ; this . pendingImages = [ ] ; this . broadcast ( { type : "clear_conversation" } ) ; this . broadcastContextWindow ( true ) ; } ) ;
153161 h . events . on ( "config:change" , ( e ) => { this . broadcast ( { type : "config" , data : e . data } ) ; } ) ;
154162 h . events . on ( "mcp:state" , ( e ) => { this . broadcast ( { type : "mcp_state" , servers : e . servers } ) ; } ) ;
155163 h . events . on ( "mcp:browser:open" , ( ) => { this . pushMcpState ( ) ; this . broadcast ( { type : "mcp_open_browser" } ) ; } ) ;
@@ -248,12 +256,15 @@ export class WebUiBackend implements UiBackend {
248256 } ) ;
249257 }
250258 this . broadcast ( { type : "tool_end" , name, result : resultStr , isError, images } ) ;
259+ this . broadcastContextWindow ( false ) ;
251260 }
252261 finishAssistantMessage ( ) : void {
253262 this . broadcastSessionTime ( ) ;
254263 this . stopSessionTimeBroadcast ( ) ;
264+ const toolsForBroadcast2 = this . currentAssistant ?. tools ?? [ ] ;
255265 this . currentAssistant = null ;
256266 this . broadcast ( { type : "assistant_end" } ) ;
267+ this . broadcastContextWindow ( true , toolsForBroadcast2 ) ;
257268
258269 const sm2 = this . harness . sessionManager ;
259270 if ( sm2 ) {
@@ -376,9 +387,11 @@ export class WebUiBackend implements UiBackend {
376387 }
377388
378389 clearConversationView ( ) : void {
390+ const toolsForBroadcast3 = this . currentAssistant ?. tools ?? [ ] ;
379391 this . currentAssistant = null ;
380392 this . pendingImages = [ ] ;
381393 this . broadcast ( { type : "clear_conversation" } ) ;
394+ this . broadcastContextWindow ( true , toolsForBroadcast3 ) ;
382395 }
383396
384397 // ── UiBackend Processing ──
@@ -435,7 +448,7 @@ export class WebUiBackend implements UiBackend {
435448 config : configData ,
436449 messages,
437450 } ) ;
438-
451+ this . broadcastContextWindow ( true ) ;
439452 if ( this . mcpManager ) {
440453 this . pushMcpState ( ) ;
441454 }
@@ -1156,6 +1169,51 @@ export class WebUiBackend implements UiBackend {
11561169 return rebuildDisplayMessages ( messages , vms ) as any ;
11571170 }
11581171
1172+
1173+ private getSkillToolNames ( ) : Set < string > {
1174+ const names = new Set < string > ( ) ;
1175+ const sm = this . harness . skillManager ;
1176+ if ( sm ) {
1177+ for ( const name of sm . listAllSkillNames ( ) ) {
1178+ names . add ( name ) ;
1179+ }
1180+ }
1181+ return names ;
1182+ }
1183+
1184+ private broadcastContextWindow ( bypassThrottle : boolean , toolsOverride ?: { name : string ; result : string } [ ] ) : void {
1185+ const cm = this . harness . contextManager ;
1186+ const cw = cm . getContextWindow ( ) ;
1187+ if ( cw <= 0 ) return ;
1188+
1189+ if ( ! bypassThrottle ) {
1190+ if ( this . contextWindowThrottleTimer ) {
1191+ this . contextWindowThrottlePending = true ;
1192+ return ;
1193+ }
1194+ this . contextWindowThrottleTimer = setTimeout ( ( ) => {
1195+ this . contextWindowThrottleTimer = null ;
1196+ if ( this . contextWindowThrottlePending ) {
1197+ this . contextWindowThrottlePending = false ;
1198+ this . broadcastContextWindow ( true , toolsOverride ) ;
1199+ }
1200+ } , 500 ) ;
1201+ }
1202+
1203+ const messages = this . harness . agent . state . messages as unknown [ ] ;
1204+ const tools = toolsOverride ?? this . currentAssistant ?. tools ?? [ ] ;
1205+ const breakdown = cm . getCategoryBreakdown ( messages , tools , this . getSkillToolNames ( ) ) ;
1206+
1207+ this . broadcast ( {
1208+ type : "context_window" ,
1209+ total : breakdown . total ,
1210+ used : breakdown . used ,
1211+ free : breakdown . free ,
1212+ categories : breakdown . categories ,
1213+ } ) ;
1214+ }
1215+
1216+
11591217 private broadcast ( event : ServerEvent ) : void {
11601218 this . wsServer . broadcast ( event ) ;
11611219 }
0 commit comments