@@ -124,6 +124,8 @@ import { AgentHostCheckpointService } from './agentHostCheckpointService.js';
124124 */
125125const SESSION_GC_GRACE_MS = 30_000 ;
126126const DAY_MS = 24 * 60 * 60 * 1000 ;
127+ const EXTERNAL_SESSION_MAX_AGE_MS = 30 * DAY_MS ;
128+ const EXTERNAL_SESSION_PRUNE_DELAY_MS = 60_000 ;
127129const RECENT_EXTERNAL_SESSION_LIMIT = 2 ;
128130/** A catalog pass slower than this is logged at info, since it delays every session-list refresh. */
129131const SLOW_LIST_SESSIONS_THRESHOLD_MS = 1_000 ;
@@ -841,6 +843,7 @@ export class AgentService extends Disposable implements IAgentService {
841843 session => this . _agentMergeController . getTurnContext ( session ) ,
842844 ) ;
843845 this . _serverToolHost = new AgentServerToolHost ( this . _stateManager , buildServerToolGroups ( this . _createSessionServerToolAccessor ( ) , agentMergeTools , this . _createArtifactServerToolAccessor ( ) ) ) ;
846+ this . _scheduleExternalSessionPrune ( ) ;
844847 }
845848
846849 /**
@@ -861,6 +864,54 @@ export class AgentService extends Disposable implements IAgentService {
861864 return this . _sideEffects . onDidStartTurn ;
862865 }
863866
867+ private _scheduleExternalSessionPrune ( ) : void {
868+ this . _register ( disposableTimeout ( ( ) => {
869+ void this . _pruneStaleExternalSessions ( ) . catch ( error => {
870+ this . _logService . warn ( '[AgentService] Failed to prune stale external sessions' , error ) ;
871+ } ) ;
872+ } , EXTERNAL_SESSION_PRUNE_DELAY_MS ) ) ;
873+ }
874+
875+ private async _pruneStaleExternalSessions ( ) : Promise < void > {
876+ const now = this . _now ( ) ;
877+ const registered = await this . _listRegisteredSessions ( ) ;
878+ const staleExternalSessions : URI [ ] = [ ] ;
879+ for ( const entry of registered ) {
880+ if ( ! entry . external ) {
881+ continue ;
882+ }
883+ const provider = this . _providers . get ( entry . provider ) ;
884+ if ( ! provider ) {
885+ continue ;
886+ }
887+ let metadata : IAgentSessionMetadata | undefined ;
888+ try {
889+ metadata = await this . _registeredSessionMetadata ( provider , entry . session , true ) ;
890+ } catch ( error ) {
891+ this . _logService . warn ( `[AgentService] Failed to load metadata while pruning stale external session ${ entry . session . toString ( ) } ` , error ) ;
892+ continue ;
893+ }
894+ if ( ! metadata ) {
895+ continue ;
896+ }
897+ if ( readSessionEhcliAdoptable ( metadata . _meta ) ) {
898+ continue ;
899+ }
900+ if ( this . _isExternalSessionOlderThanMaxAge ( metadata . modifiedTime , now ) ) {
901+ staleExternalSessions . push ( entry . session ) ;
902+ }
903+ }
904+
905+ for ( const session of staleExternalSessions ) {
906+ await this . _sessionRegistry . unregister ( session ) ;
907+ }
908+ if ( staleExternalSessions . length > 0 ) {
909+ this . _invalidateSessionList ( ) ;
910+ this . _queueSessionListReconciliation ( ) ;
911+ }
912+ this . _logService . info ( `[AgentService] pruned ${ staleExternalSessions . length } stale external session row(s) older than ${ EXTERNAL_SESSION_MAX_AGE_MS / DAY_MS } days` ) ;
913+ }
914+
864915 // ---- provider registration ----------------------------------------------
865916
866917 /**
@@ -1607,6 +1658,7 @@ export class AgentService extends Disposable implements IAgentService {
16071658 const existing = new Map ( ( await this . _listRegisteredSessions ( ) ) . map ( session => [ session . session . toString ( ) , session . external ] ) ) ;
16081659 const discoveryLimiter = new Limiter < boolean > ( 4 ) ;
16091660 let suppressed = 0 ;
1661+ let skippedAsStale = 0 ;
16101662 let registeredExternal = false ;
16111663 let alreadyRegistered = 0 ;
16121664 let registryChanged = false ;
@@ -1624,6 +1676,10 @@ export class AgentService extends Disposable implements IAgentService {
16241676 suppressed ++ ;
16251677 return false ;
16261678 }
1679+ if ( external && ! readSessionEhcliAdoptable ( sessionMetadata . _meta ) && this . _isExternalSessionOlderThanMaxAge ( sessionMetadata . modifiedTime , this . _now ( ) ) ) {
1680+ skippedAsStale ++ ;
1681+ return false ;
1682+ }
16271683 const identity : IRegisteredSession = { session, provider : provider . id , startTime : metadata . startTime , external, source : external ? 'discovery' : 'restore' } ;
16281684 const registered = await this . _retryRegistryMutation (
16291685 ( ) => this . _sessionRegistry . register ( session , identity , { checkTombstone : true } ) ,
@@ -1656,7 +1712,7 @@ export class AgentService extends Disposable implements IAgentService {
16561712 if ( registeredExternal ) {
16571713 this . _queueSessionListReconciliation ( ) ;
16581714 }
1659- this . _logService . info ( `[AgentService] discovery for provider ${ provider . id } : ${ chats . length } candidate(s) (${ chats . filter ( chat => chat . external ) . length } external), ${ registered } registered, ${ alreadyRegistered } already registered, ${ suppressed } suppressed as subagent/chat backing` ) ;
1715+ this . _logService . info ( `[AgentService] discovery for provider ${ provider . id } : ${ chats . length } candidate(s) (${ chats . filter ( chat => chat . external ) . length } external), ${ registered } registered, ${ alreadyRegistered } already registered, ${ suppressed } suppressed as subagent/chat backing, ${ skippedAsStale } skipped as older than ${ EXTERNAL_SESSION_MAX_AGE_MS / DAY_MS } days ` ) ;
16601716 return registered > 0 ;
16611717 }
16621718
@@ -1689,10 +1745,13 @@ export class AgentService extends Disposable implements IAgentService {
16891745 if ( ! identity ) {
16901746 continue ;
16911747 }
1748+ const metadata = sessions [ index ] ;
1749+ if ( identity . external && ! readSessionEhcliAdoptable ( metadata . _meta ) && this . _isExternalSessionOlderThanMaxAge ( metadata . modifiedTime , this . _now ( ) ) ) {
1750+ continue ;
1751+ }
16921752 const registered = await this . _sessionRegistry . register ( identity . session , identity , { checkTombstone : true } ) ;
16931753 if ( registered ) {
16941754 this . _invalidateSessionList ( ) ;
1695- const metadata = sessions [ index ] ;
16961755 if ( identity . external && existing . get ( identity . session . toString ( ) ) !== true ) {
16971756 await this . _initializeExternalSessionReadState ( identity . session ) ;
16981757 }
@@ -2101,9 +2160,17 @@ export class AgentService extends Disposable implements IAgentService {
21012160 }
21022161
21032162 private _getExternalSessionsMode ( ) : AgentHostExternalSessionsMode {
2163+ const rootValue = this . _configurationService . getRootConfigValues ( ) ?. [ AgentHostShowExternalSessionsConfigKey ] ;
2164+ if ( rootValue === 'all' ) {
2165+ return AgentHostExternalSessionsMode . Last30Days ;
2166+ }
21042167 return this . _configurationService . getRootValue ( platformRootSchema , AgentHostShowExternalSessionsConfigKey ) ?? AgentHostExternalSessionsMode . None ;
21052168 }
21062169
2170+ private _isExternalSessionOlderThanMaxAge ( modifiedTime : number , now : number ) : boolean {
2171+ return modifiedTime < now - EXTERNAL_SESSION_MAX_AGE_MS ;
2172+ }
2173+
21072174 private _getRecentSessionKeys ( sessions : readonly IAgentSessionMetadata [ ] , now : number ) : ReadonlySet < string > {
21082175 const recentExternalSessions = sessions
21092176 . filter ( session => readSessionExternal ( session . _meta )
@@ -2139,12 +2206,12 @@ export class AgentService extends Disposable implements IAgentService {
21392206 case AgentHostExternalSessionsMode . Recent :
21402207 return session . modifiedTime >= now - 7 * DAY_MS
21412208 && ( recentSessionKeys === undefined || recentSessionKeys . has ( session . session . toString ( ) ) ) ;
2142- case AgentHostExternalSessionsMode . All :
2143- return true ;
21442209 case AgentHostExternalSessionsMode . Last24Hours :
21452210 return session . modifiedTime >= now - DAY_MS ;
21462211 case AgentHostExternalSessionsMode . Last7Days :
21472212 return session . modifiedTime >= now - 7 * DAY_MS ;
2213+ case AgentHostExternalSessionsMode . Last30Days :
2214+ return ! this . _isExternalSessionOlderThanMaxAge ( session . modifiedTime , now ) ;
21482215 case AgentHostExternalSessionsMode . None :
21492216 return false ;
21502217 }
@@ -2276,7 +2343,7 @@ export class AgentService extends Disposable implements IAgentService {
22762343 previouslyExposed . add ( session ) ;
22772344 }
22782345 const listed = previousMode !== undefined
2279- ? this . _resolveModeChangeVisibility ( await this . listSessions ( AgentHostExternalSessionsMode . All ) , previousMode , previouslyExposed )
2346+ ? this . _resolveModeChangeVisibility ( await this . listSessions ( AgentHostExternalSessionsMode . Last30Days ) , previousMode , previouslyExposed )
22802347 : await this . listSessions ( ) ;
22812348 const visible = new Set < string > ( ) ;
22822349 let published = 0 ;
@@ -2326,7 +2393,7 @@ export class AgentService extends Disposable implements IAgentService {
23262393
23272394 /**
23282395 * Derives both the previous and current mode's visible sets from one catalog
2329- * pass, since {@link AgentHostExternalSessionsMode.All } is a superset of every
2396+ * pass, since {@link AgentHostExternalSessionsMode.Last30Days } is a superset of every
23302397 * mode and the mode is just a parameter to {@link _shouldIncludeSession}.
23312398 * Adds what `previousMode` had exposed into `previouslyExposed`.
23322399 */
@@ -2350,7 +2417,7 @@ export class AgentService extends Disposable implements IAgentService {
23502417 const mode = this . _getExternalSessionsMode ( ) ;
23512418 const recentKeys = recentKeysFor ( mode ) ;
23522419 const visible = superset . filter ( session => this . _shouldIncludeSession ( session , mode , now , recentKeys ) ) ;
2353- // The pass ran as `All `, so report the mode actually in effect instead.
2420+ // The pass ran as `Last30Days `, so report the mode actually in effect instead.
23542421 this . _logHiddenSessions ( superset . length - visible . length , superset . length , mode ) ;
23552422 return visible ;
23562423 }
0 commit comments