From 32b97f54e2ec0341c32d665e1f6f136db29cfe79 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:37:48 +0000 Subject: [PATCH] Cap External Agent Sessions to 30 Days (replace `all`, enforce ingest/prune retention) (#331635) * Initial plan * Replace external session All mode with 30-day retention Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> * Limit ESLint worker concurrency Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> * Revert "Limit ESLint worker concurrency" This reverts commit 9190dc515dc124bdb60b19702f5d5bd6d6f6e5a0. Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: benibenj <44439583+benibenj@users.noreply.github.com> --- .../agentHost/common/agentHostSchema.ts | 2 +- .../platform/agentHost/node/agentService.ts | 81 ++++++++- .../agentHost/node/agentSessionRegistry.ts | 5 + .../agentHost/test/node/agentService.test.ts | 155 ++++++++++++------ .../sessionLifecycle.integrationTest.ts | 2 +- src/vs/platform/chat/common/chatSettings.ts | 2 +- .../chat/browser/externalSessionBanner.ts | 14 +- .../browser/externalSessionBanner.test.ts | 6 +- .../externalSessionsFilterMenu.ts | 2 +- .../chat/browser/chat.shared.contribution.ts | 12 +- .../externalSessionsFilterMenu.test.ts | 2 +- 11 files changed, 210 insertions(+), 73 deletions(-) diff --git a/src/vs/platform/agentHost/common/agentHostSchema.ts b/src/vs/platform/agentHost/common/agentHostSchema.ts index 10ec2d438d5..834ed14151c 100644 --- a/src/vs/platform/agentHost/common/agentHostSchema.ts +++ b/src/vs/platform/agentHost/common/agentHostSchema.ts @@ -818,7 +818,7 @@ export const platformRootSchema = createSchema({ type: 'string', title: localize('agentHost.config.showExternalSessions.title', "Show External Agent Sessions"), description: localize('agentHost.config.showExternalSessions.description', "Controls whether sessions created outside the Agent Host are included in the session catalog."), - enum: [ChatExternalSessionsMode.None, ChatExternalSessionsMode.Recent, ChatExternalSessionsMode.Last24Hours, ChatExternalSessionsMode.Last7Days, ChatExternalSessionsMode.All], + enum: [ChatExternalSessionsMode.None, ChatExternalSessionsMode.Recent, ChatExternalSessionsMode.Last24Hours, ChatExternalSessionsMode.Last7Days, ChatExternalSessionsMode.Last30Days], default: ChatExternalSessionsMode.None, }), [AgentHostCopilotMultiRootEnabledConfigKey]: schemaProperty({ diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index 02cc9da6f8d..984c6724e05 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -124,6 +124,8 @@ import { AgentHostCheckpointService } from './agentHostCheckpointService.js'; */ const SESSION_GC_GRACE_MS = 30_000; const DAY_MS = 24 * 60 * 60 * 1000; +const EXTERNAL_SESSION_MAX_AGE_MS = 30 * DAY_MS; +const EXTERNAL_SESSION_PRUNE_DELAY_MS = 60_000; const RECENT_EXTERNAL_SESSION_LIMIT = 2; /** A catalog pass slower than this is logged at info, since it delays every session-list refresh. */ const SLOW_LIST_SESSIONS_THRESHOLD_MS = 1_000; @@ -841,6 +843,7 @@ export class AgentService extends Disposable implements IAgentService { session => this._agentMergeController.getTurnContext(session), ); this._serverToolHost = new AgentServerToolHost(this._stateManager, buildServerToolGroups(this._createSessionServerToolAccessor(), agentMergeTools, this._createArtifactServerToolAccessor())); + this._scheduleExternalSessionPrune(); } /** @@ -861,6 +864,54 @@ export class AgentService extends Disposable implements IAgentService { return this._sideEffects.onDidStartTurn; } + private _scheduleExternalSessionPrune(): void { + this._register(disposableTimeout(() => { + void this._pruneStaleExternalSessions().catch(error => { + this._logService.warn('[AgentService] Failed to prune stale external sessions', error); + }); + }, EXTERNAL_SESSION_PRUNE_DELAY_MS)); + } + + private async _pruneStaleExternalSessions(): Promise { + const now = this._now(); + const registered = await this._listRegisteredSessions(); + const staleExternalSessions: URI[] = []; + for (const entry of registered) { + if (!entry.external) { + continue; + } + const provider = this._providers.get(entry.provider); + if (!provider) { + continue; + } + let metadata: IAgentSessionMetadata | undefined; + try { + metadata = await this._registeredSessionMetadata(provider, entry.session, true); + } catch (error) { + this._logService.warn(`[AgentService] Failed to load metadata while pruning stale external session ${entry.session.toString()}`, error); + continue; + } + if (!metadata) { + continue; + } + if (readSessionEhcliAdoptable(metadata._meta)) { + continue; + } + if (this._isExternalSessionOlderThanMaxAge(metadata.modifiedTime, now)) { + staleExternalSessions.push(entry.session); + } + } + + for (const session of staleExternalSessions) { + await this._sessionRegistry.unregister(session); + } + if (staleExternalSessions.length > 0) { + this._invalidateSessionList(); + this._queueSessionListReconciliation(); + } + this._logService.info(`[AgentService] pruned ${staleExternalSessions.length} stale external session row(s) older than ${EXTERNAL_SESSION_MAX_AGE_MS / DAY_MS} days`); + } + // ---- provider registration ---------------------------------------------- /** @@ -1607,6 +1658,7 @@ export class AgentService extends Disposable implements IAgentService { const existing = new Map((await this._listRegisteredSessions()).map(session => [session.session.toString(), session.external])); const discoveryLimiter = new Limiter(4); let suppressed = 0; + let skippedAsStale = 0; let registeredExternal = false; let alreadyRegistered = 0; let registryChanged = false; @@ -1624,6 +1676,10 @@ export class AgentService extends Disposable implements IAgentService { suppressed++; return false; } + if (external && !readSessionEhcliAdoptable(sessionMetadata._meta) && this._isExternalSessionOlderThanMaxAge(sessionMetadata.modifiedTime, this._now())) { + skippedAsStale++; + return false; + } const identity: IRegisteredSession = { session, provider: provider.id, startTime: metadata.startTime, external, source: external ? 'discovery' : 'restore' }; const registered = await this._retryRegistryMutation( () => this._sessionRegistry.register(session, identity, { checkTombstone: true }), @@ -1656,7 +1712,7 @@ export class AgentService extends Disposable implements IAgentService { if (registeredExternal) { this._queueSessionListReconciliation(); } - 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`); + 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`); return registered > 0; } @@ -1689,10 +1745,13 @@ export class AgentService extends Disposable implements IAgentService { if (!identity) { continue; } + const metadata = sessions[index]; + if (identity.external && !readSessionEhcliAdoptable(metadata._meta) && this._isExternalSessionOlderThanMaxAge(metadata.modifiedTime, this._now())) { + continue; + } const registered = await this._sessionRegistry.register(identity.session, identity, { checkTombstone: true }); if (registered) { this._invalidateSessionList(); - const metadata = sessions[index]; if (identity.external && existing.get(identity.session.toString()) !== true) { await this._initializeExternalSessionReadState(identity.session); } @@ -2101,9 +2160,17 @@ export class AgentService extends Disposable implements IAgentService { } private _getExternalSessionsMode(): AgentHostExternalSessionsMode { + const rootValue = this._configurationService.getRootConfigValues()?.[AgentHostShowExternalSessionsConfigKey]; + if (rootValue === 'all') { + return AgentHostExternalSessionsMode.Last30Days; + } return this._configurationService.getRootValue(platformRootSchema, AgentHostShowExternalSessionsConfigKey) ?? AgentHostExternalSessionsMode.None; } + private _isExternalSessionOlderThanMaxAge(modifiedTime: number, now: number): boolean { + return modifiedTime < now - EXTERNAL_SESSION_MAX_AGE_MS; + } + private _getRecentSessionKeys(sessions: readonly IAgentSessionMetadata[], now: number): ReadonlySet { const recentExternalSessions = sessions .filter(session => readSessionExternal(session._meta) @@ -2139,12 +2206,12 @@ export class AgentService extends Disposable implements IAgentService { case AgentHostExternalSessionsMode.Recent: return session.modifiedTime >= now - 7 * DAY_MS && (recentSessionKeys === undefined || recentSessionKeys.has(session.session.toString())); - case AgentHostExternalSessionsMode.All: - return true; case AgentHostExternalSessionsMode.Last24Hours: return session.modifiedTime >= now - DAY_MS; case AgentHostExternalSessionsMode.Last7Days: return session.modifiedTime >= now - 7 * DAY_MS; + case AgentHostExternalSessionsMode.Last30Days: + return !this._isExternalSessionOlderThanMaxAge(session.modifiedTime, now); case AgentHostExternalSessionsMode.None: return false; } @@ -2276,7 +2343,7 @@ export class AgentService extends Disposable implements IAgentService { previouslyExposed.add(session); } const listed = previousMode !== undefined - ? this._resolveModeChangeVisibility(await this.listSessions(AgentHostExternalSessionsMode.All), previousMode, previouslyExposed) + ? this._resolveModeChangeVisibility(await this.listSessions(AgentHostExternalSessionsMode.Last30Days), previousMode, previouslyExposed) : await this.listSessions(); const visible = new Set(); let published = 0; @@ -2326,7 +2393,7 @@ export class AgentService extends Disposable implements IAgentService { /** * Derives both the previous and current mode's visible sets from one catalog - * pass, since {@link AgentHostExternalSessionsMode.All} is a superset of every + * pass, since {@link AgentHostExternalSessionsMode.Last30Days} is a superset of every * mode and the mode is just a parameter to {@link _shouldIncludeSession}. * Adds what `previousMode` had exposed into `previouslyExposed`. */ @@ -2350,7 +2417,7 @@ export class AgentService extends Disposable implements IAgentService { const mode = this._getExternalSessionsMode(); const recentKeys = recentKeysFor(mode); const visible = superset.filter(session => this._shouldIncludeSession(session, mode, now, recentKeys)); - // The pass ran as `All`, so report the mode actually in effect instead. + // The pass ran as `Last30Days`, so report the mode actually in effect instead. this._logHiddenSessions(superset.length - visible.length, superset.length, mode); return visible; } diff --git a/src/vs/platform/agentHost/node/agentSessionRegistry.ts b/src/vs/platform/agentHost/node/agentSessionRegistry.ts index 15f3a9a19aa..f2dc872ed5e 100644 --- a/src/vs/platform/agentHost/node/agentSessionRegistry.ts +++ b/src/vs/platform/agentHost/node/agentSessionRegistry.ts @@ -69,6 +69,11 @@ export class AgentSessionRegistry extends Disposable { return this._database.registerSession(session.toString(), sessionOptions, registerOptions); } + /** Removes any registry entry for `session` without writing a tombstone. */ + async unregister(session: URI): Promise { + await this._database.unregisterSession(session.toString()); + } + /** * Removes any registry entry for `session` (a true delete) and durably * tombstones it so discovery cannot register it. Used both to delete a diff --git a/src/vs/platform/agentHost/test/node/agentService.test.ts b/src/vs/platform/agentHost/test/node/agentService.test.ts index 9f17a9b43c8..55e878fee42 100644 --- a/src/vs/platform/agentHost/test/node/agentService.test.ts +++ b/src/vs/platform/agentHost/test/node/agentService.test.ts @@ -94,11 +94,11 @@ async function createAgentSession(agent: IAgent, config?: IAgentCreateSessionCon return { session, ...chat, chat }; } -function discoveredChat(session: URI, external = true): IAgentDiscoveredChat { +function discoveredChat(session: URI, external = true, modifiedTime = Date.now()): IAgentDiscoveredChat { return { chat: URI.parse(buildDefaultChatUri(session)), - startTime: 1, - modifiedTime: 1, + startTime: modifiedTime, + modifiedTime, external, }; } @@ -1059,7 +1059,7 @@ suite('AgentService (node dispatcher)', () => { // Reopen: a fresh service on the same DB rediscovers the provider-native // session and must restore the persisted decision into `_meta`. const reopened = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - reopened.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + reopened.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const reopenedAgent = new MockAgent('copilot'); disposables.add(toDisposable(() => reopenedAgent.dispose())); (reopenedAgent as unknown as { _sessions: Map })._sessions.set(AgentSession.id(session), session); @@ -1117,7 +1117,7 @@ suite('AgentService (node dispatcher)', () => { await timeout(0); const reopened = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - reopened.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + reopened.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const reopenedAgent = new MockAgent('copilot'); disposables.add(toDisposable(() => reopenedAgent.dispose())); (reopenedAgent as unknown as { _sessions: Map })._sessions.set(AgentSession.id(session), session); @@ -2934,11 +2934,11 @@ suite('AgentService (node dispatcher)', () => { suite('aggregation', () => { class TimedExternalAgent extends MockAgent { - readonly catalog = new Map(); + readonly catalog = new Map(); - addSession(id: string, modifiedTime: number): URI { + addSession(id: string, modifiedTime: number, _meta?: IAgentSessionMetadata['_meta']): URI { const session = AgentSession.uri(this.id, id); - this.catalog.set(id, { session, modifiedTime }); + this.catalog.set(id, { session, modifiedTime, _meta }); (this as unknown as { _sessions: Map })._sessions.set(id, session); return session; } @@ -2948,13 +2948,14 @@ suite('AgentService (node dispatcher)', () => { chat: URI.parse(buildDefaultChatUri(entry.session)), startTime: entry.modifiedTime, modifiedTime: entry.modifiedTime, + ...(entry._meta ? { _meta: entry._meta } : {}), })); } override async getChatMetadata(chat: URI, context: URI | IAgentChatContext): Promise { const session = resolveAgentChatContext(context, chat).configurationResource; const entry = this.catalog.get(AgentSession.id(session)); - return entry ? { chat, startTime: entry.modifiedTime, modifiedTime: entry.modifiedTime } : undefined; + return entry ? { chat, startTime: entry.modifiedTime, modifiedTime: entry.modifiedTime, ...(entry._meta ? { _meta: entry._meta } : {}) } : undefined; } } @@ -3024,7 +3025,7 @@ suite('AgentService (node dispatcher)', () => { test('listSessions discovers provider-native sessions as external and restore preserves provenance', async () => { const db = new TestSessionDatabase(); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = new MockAgent('copilot'); disposables.add(toDisposable(() => agent.dispose())); @@ -3062,6 +3063,54 @@ suite('AgentService (node dispatcher)', () => { assert.strictEqual(await db.getMetadata(AH_META_IS_READ_DB_KEY), ''); }); + test('discovery does not ingest external sessions older than 30 days', async () => { + const day = 24 * 60 * 60 * 1000; + const now = Date.now(); + const svc = createExternalSessionService(() => now); + const agent = disposables.add(new TimedExternalAgent('copilot')); + const stale = agent.addSession('stale', now - 30 * day - 1); + const fresh = agent.addSession('fresh', now - 30 * day + 60_000); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 1); + await waitForSessionListReconciliation(svc); + svc.registerProvider(agent); + await (svc as unknown as { _registerDiscoveredChats(provider: IAgent, chats: readonly IAgentDiscoveredChat[]): Promise })._registerDiscoveredChats(agent, [ + { chat: URI.parse(buildDefaultChatUri(stale)), startTime: now - 30 * day - 1, modifiedTime: now - 30 * day - 1, external: true }, + { chat: URI.parse(buildDefaultChatUri(fresh)), startTime: now - 30 * day + 60_000, modifiedTime: now - 30 * day + 60_000, external: true }, + ]); + + const listed = (await svc.listSessions()).map(session => AgentSession.id(session.session)).sort(); + const registered = new Set((await svc.getRegisteredSessions()).map(session => session.toString())); + + assert.deepStrictEqual({ + listed, + registered: [...registered].sort(), + }, { + listed: [AgentSession.id(fresh)], + registered: [fresh.toString()], + }); + assert.ok(!registered.has(stale.toString())); + }); + + test('prune removes stale external sessions but keeps adoptable-legacy sessions', async () => { + const day = 24 * 60 * 60 * 1000; + const now = Date.now(); + const svc = createExternalSessionService(() => now); + const agent = disposables.add(new TimedExternalAgent('copilot')); + const stale = agent.addSession('stale-prune', now - 30 * day - 1); + const staleAdoptable = agent.addSession('stale-adoptable', now - 30 * day - 1, withSessionEhcliAdoptable(undefined)); + const fresh = agent.addSession('fresh-prune', now - 30 * day); + svc.registerProvider(agent); + const sessionRegistry = (svc as unknown as { _sessionRegistry: AgentSessionRegistry })._sessionRegistry; + await sessionRegistry.register(stale, { provider: 'copilot', startTime: now - 30 * day - 1, source: 'discovery' }, { checkTombstone: true }); + await sessionRegistry.register(staleAdoptable, { provider: 'copilot', startTime: now - 30 * day - 1, source: 'discovery' }, { checkTombstone: true }); + await sessionRegistry.register(fresh, { provider: 'copilot', startTime: now - 30 * day, source: 'discovery' }, { checkTombstone: true }); + + await (svc as unknown as { _pruneStaleExternalSessions(): Promise })._pruneStaleExternalSessions(); + const registered = (await sessionRegistry.list()).map(entry => entry.session.toString()).sort(); + + assert.deepStrictEqual(registered, [fresh.toString(), staleAdoptable.toString()].sort()); + }); + test('filters external sessions in every mode with inclusive time boundaries', async () => { const day = 24 * 60 * 60 * 1000; const now = Date.now(); @@ -3072,18 +3121,20 @@ suite('AgentService (node dispatcher)', () => { agent.addSession('older-than-24-hours', now - day - 1); agent.addSession('at-7-days', now - 7 * day); agent.addSession('older-than-7-days', now - 7 * day - 1); + agent.addSession('at-30-days', now - 30 * day); + agent.addSession('older-than-30-days', now - 30 * day - 1); svc.registerProvider(agent); const listedByMode: Record = { [AgentHostExternalSessionsMode.Recent]: [], [AgentHostExternalSessionsMode.None]: [], - [AgentHostExternalSessionsMode.All]: [], + [AgentHostExternalSessionsMode.Last30Days]: [], [AgentHostExternalSessionsMode.Last24Hours]: [], [AgentHostExternalSessionsMode.Last7Days]: [], }; const listedByDefault = (await svc.listSessions()).map(session => AgentSession.id(session.session)).sort(); let clientSeq = 1; - for (const mode of [AgentHostExternalSessionsMode.Recent, AgentHostExternalSessionsMode.None, AgentHostExternalSessionsMode.All, AgentHostExternalSessionsMode.Last24Hours, AgentHostExternalSessionsMode.Last7Days]) { + for (const mode of [AgentHostExternalSessionsMode.Recent, AgentHostExternalSessionsMode.None, AgentHostExternalSessionsMode.Last30Days, AgentHostExternalSessionsMode.Last24Hours, AgentHostExternalSessionsMode.Last7Days]) { setExternalSessionsMode(svc, mode, clientSeq++); await waitForSessionListReconciliation(svc); listedByMode[mode] = (await svc.listSessions()).map(session => AgentSession.id(session.session)).sort(); @@ -3094,7 +3145,7 @@ suite('AgentService (node dispatcher)', () => { listedByMode: { [AgentHostExternalSessionsMode.Recent]: ['at-24-hours', 'recent'], [AgentHostExternalSessionsMode.None]: [], - [AgentHostExternalSessionsMode.All]: ['at-24-hours', 'at-7-days', 'older-than-24-hours', 'older-than-7-days', 'recent'], + [AgentHostExternalSessionsMode.Last30Days]: ['at-24-hours', 'at-30-days', 'at-7-days', 'older-than-24-hours', 'older-than-7-days', 'recent'], [AgentHostExternalSessionsMode.Last24Hours]: ['at-24-hours', 'recent'], [AgentHostExternalSessionsMode.Last7Days]: ['at-24-hours', 'at-7-days', 'older-than-24-hours', 'recent'], }, @@ -3109,7 +3160,7 @@ suite('AgentService (node dispatcher)', () => { agent.addSession('external-one', now); agent.addSession('external-two', now); svc.registerProvider(agent); - await svc.listSessions(AgentHostExternalSessionsMode.All); + await svc.listSessions(AgentHostExternalSessionsMode.Last30Days); // A catalog pass otherwise opens every registered session's database, // so a mode that discards the row regardless must not pay for it. @@ -3124,7 +3175,7 @@ suite('AgentService (node dispatcher)', () => { const hidden = (await svc.listSessions(AgentHostExternalSessionsMode.None)).map(session => AgentSession.id(session.session)); const openedWhileHidden = [...new Set(opened)].sort(); opened.length = 0; - const visible = (await svc.listSessions(AgentHostExternalSessionsMode.All)).map(session => AgentSession.id(session.session)).sort(); + const visible = (await svc.listSessions(AgentHostExternalSessionsMode.Last30Days)).map(session => AgentSession.id(session.session)).sort(); assert.deepStrictEqual({ hidden, openedWhileHidden, visible, openedWhileVisible: [...new Set(opened)].sort() }, { hidden: [], @@ -3146,7 +3197,7 @@ suite('AgentService (node dispatcher)', () => { agent.addSession('yesterday', now - day); agent.addSession('last-week', now - 6 * day); svc.registerProvider(agent); - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 1); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 1); await waitForSessionListReconciliation(svc); // Each `listSessions` is one walk over every registered session's @@ -3172,7 +3223,7 @@ suite('AgentService (node dispatcher)', () => { transitionModes, visible: (await svc.listSessions()).map(session => AgentSession.id(session.session)).sort(), }, { - transitionModes: [AgentHostExternalSessionsMode.All], + transitionModes: [AgentHostExternalSessionsMode.Last30Days], visible: ['recent', 'yesterday'], }); }); @@ -3268,7 +3319,7 @@ suite('AgentService (node dispatcher)', () => { test('external discovery reconciles against a mode change that completes while registration is in flight', async () => { const now = Date.now(); const svc = createExternalSessionService(() => now); - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 1); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 1); await waitForSessionListReconciliation(svc); const agent = disposables.add(new TimedExternalAgent('copilot')); svc.registerProvider(agent); @@ -3380,7 +3431,7 @@ suite('AgentService (node dispatcher)', () => { await waitForSessionListReconciliation(svc); notifications.length = 0; - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 2); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 2); await waitForSessionListReconciliation(svc); setExternalSessionsMode(svc, AgentHostExternalSessionsMode.None, 3); await waitForSessionListReconciliation(svc); @@ -3391,7 +3442,7 @@ suite('AgentService (node dispatcher)', () => { test('unpublishes and republishes a restored external session as the configured mode changes', async () => { const now = Date.now(); const svc = createExternalSessionService(() => now); - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 1); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 1); await waitForSessionListReconciliation(svc); const agent = disposables.add(new TimedExternalAgent('copilot')); const session = agent.addSession('restored-external', now); @@ -3411,7 +3462,7 @@ suite('AgentService (node dispatcher)', () => { setExternalSessionsMode(svc, AgentHostExternalSessionsMode.None, 2); await waitForSessionListReconciliation(svc); const hidden = (await svc.listSessions()).map(entry => entry.session.toString()); - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 3); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 3); await waitForSessionListReconciliation(svc); assert.deepStrictEqual({ @@ -3454,7 +3505,7 @@ suite('AgentService (node dispatcher)', () => { await svc.restoreSession(session); notifications.length = 0; - setExternalSessionsMode(svc, AgentHostExternalSessionsMode.All, 2); + setExternalSessionsMode(svc, AgentHostExternalSessionsMode.Last30Days, 2); await waitForSessionListReconciliation(svc); assert.deepStrictEqual({ @@ -3714,12 +3765,12 @@ suite('AgentService (node dispatcher)', () => { override async listExternalChats(): Promise { this.externalCalls++; - return [{ chat: URI.parse(buildDefaultChatUri(external)), startTime: 1, modifiedTime: 1 }]; + return [{ chat: URI.parse(buildDefaultChatUri(external)), startTime: Date.now(), modifiedTime: Date.now() }]; } override async listChatsToMigrate(): Promise { this.legacyCalls++; - return [{ chat: URI.parse(buildDefaultChatUri(legacy)), startTime: 2, modifiedTime: 2 }]; + return [{ chat: URI.parse(buildDefaultChatUri(legacy)), startTime: Date.now(), modifiedTime: Date.now() }]; } override fireDiscoveredChats(chats: readonly IAgentDiscoveredChat[]): void { this._onDidDiscoverChats.fire(chats); } @@ -3811,8 +3862,8 @@ suite('AgentService (node dispatcher)', () => { await (svc as unknown as { _announceSurfacedSession(meta: IAgentSessionMetadata, provider: string): Promise })._announceSurfacedSession({ session, - startTime: 1, - modifiedTime: 1, + startTime: Date.now(), + modifiedTime: Date.now(), }, agent.id); assert.strictEqual(svc.stateManager.getSurfacedSessionSummary(session.toString())?.resource, session.toString()); }); @@ -3821,8 +3872,8 @@ suite('AgentService (node dispatcher)', () => { class MixedMigrationAgent extends MockAgent { override async listChatsToMigrate(): Promise { return [ - { chat: URI.parse(buildDefaultChatUri(restored)), startTime: 1, modifiedTime: 1 }, - { chat: URI.parse(buildDefaultChatUri(external)), startTime: 2, modifiedTime: 2 }, + { chat: URI.parse(buildDefaultChatUri(restored)), startTime: Date.now(), modifiedTime: Date.now() }, + { chat: URI.parse(buildDefaultChatUri(external)), startTime: Date.now(), modifiedTime: Date.now() }, ]; } } @@ -3957,7 +4008,7 @@ suite('AgentService (node dispatcher)', () => { } } const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = disposables.add(new GatedListAgent('copilot')); svc.registerProvider(agent); const legacy = AgentSession.uri('copilot', 'legacy-concurrent'); @@ -4007,7 +4058,7 @@ suite('AgentService (node dispatcher)', () => { } const db = new TestSessionDatabase(); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = disposables.add(new TransientListFailureAgent('copilot')); svc.registerProvider(agent); const legacy = AgentSession.uri('copilot', 'legacy-session'); @@ -4028,7 +4079,7 @@ suite('AgentService (node dispatcher)', () => { test('a late-registered provider gets its own native discovery pass', async () => { const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const early = disposables.add(new MockAgent('copilot')); svc.registerProvider(early); @@ -4166,7 +4217,7 @@ suite('AgentService (node dispatcher)', () => { } } const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const providerA = disposables.add(new CountingAgent('copilot')); const providerB = disposables.add(new FailingThenRecoveringAgent('other')); @@ -4217,7 +4268,7 @@ suite('AgentService (node dispatcher)', () => { } } const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = disposables.add(new NotYetEnumerableAgent('copilot')); const originalListExternalChats = agent.listExternalChats.bind(agent); (agent as unknown as { listExternalChats: () => Promise }).listExternalChats = async () => { @@ -4255,7 +4306,7 @@ suite('AgentService (node dispatcher)', () => { await db.registerSession(existing.toString(), { provider: 'copilot', startTime: 1, source: 'explicit' }, { checkTombstone: false }); const writesBeforeUnavailable = db.registryWriteAttempts; const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, db)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = disposables.add(new NotYetMigratableAgent('copilot')); const legacy = AgentSession.uri('copilot', 'legacy-migration-not-ready'); (agent as unknown as { _sessions: Map })._sessions.set(AgentSession.id(legacy), legacy); @@ -4263,7 +4314,7 @@ suite('AgentService (node dispatcher)', () => { (agent as unknown as { listChatsToMigrate: () => Promise }).listChatsToMigrate = async () => { agent.migrationCalls++; return agent.enumerable - ? [{ chat: URI.parse(buildDefaultChatUri(legacy)), startTime: 1, modifiedTime: 1 }] + ? [{ chat: URI.parse(buildDefaultChatUri(legacy)), startTime: Date.now(), modifiedTime: Date.now() }] : undefined; }; svc.registerProvider(agent); @@ -4321,14 +4372,14 @@ suite('AgentService (node dispatcher)', () => { await timeout(0); } - const all = svc.listSessions(AgentHostExternalSessionsMode.All); + const last30Days = svc.listSessions(AgentHostExternalSessionsMode.Last30Days); const recent = svc.listSessions(AgentHostExternalSessionsMode.Recent); for (let i = 0; i < 20 && agent.catalogCalls < 2; i++) { await timeout(0); } assert.strictEqual(agent.catalogCalls, 2, 'overlapping computations must share the replacement retry'); retryGate.complete(); - await Promise.all([all, recent]); + await Promise.all([last30Days, recent]); assert.strictEqual(agent.catalogCalls, 2, 'a losing caller must await the installed retry instead of queueing another'); }); @@ -4345,7 +4396,8 @@ suite('AgentService (node dispatcher)', () => { } const db = new TransientRegistryWriteDatabase(); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, db)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); + svc.configurationService.updateRootConfig({ [AgentHostMigrateLegacyCopilotCliEnabledConfigKey]: true }); const copilot = disposables.add(new CatalogAgent('copilot')); const claude = disposables.add(new CatalogAgent('claude')); const copilotSession = AgentSession.uri('copilot', 'complete-provider'); @@ -4598,7 +4650,7 @@ suite('AgentService (node dispatcher)', () => { // Simulate an old database whose legacy one-time marker is set. await db.markSessionRegistryBackfilled(); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(), { _serviceBrand: undefined } as IProductService, createNoopGitService(), undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, db)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); const agent = disposables.add(new CountingAgent('copilot')); const legacy = AgentSession.uri('copilot', 'old-db-native-session'); (agent as unknown as { _sessions: Map })._sessions.set(AgentSession.id(legacy), legacy); @@ -4812,7 +4864,7 @@ suite('AgentService (node dispatcher)', () => { (agent as unknown as { _sessions: Map })._sessions.set(sessionId, sessionUri); const svc = disposables.add(new AgentService(new NullLogService(), fileService, sessionDataService, { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4858,7 +4910,7 @@ suite('AgentService (node dispatcher)', () => { (agent as unknown as { _sessions: Map })._sessions.set(sessionId, sessionUri); const svc = disposables.add(new AgentService(new NullLogService(), fileService, sessionDataService, { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4881,7 +4933,7 @@ suite('AgentService (node dispatcher)', () => { }; (agent as unknown as { _sessions: Map })._sessions.set(sessionId, sessionUri); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4902,7 +4954,7 @@ suite('AgentService (node dispatcher)', () => { disposables.add(toDisposable(() => agent.dispose())); (agent as unknown as { _sessions: Map })._sessions.set(sessionId, sessionUri); const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4920,7 +4972,7 @@ suite('AgentService (node dispatcher)', () => { }; (agent as unknown as { _sessions: Map })._sessions.set(sessionId, sessionUri); const svc = disposables.add(new AgentService(new NullLogService(), fileService, nullSessionDataService, { _serviceBrand: undefined } as IProductService, createNoopGitService())); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4946,7 +4998,7 @@ suite('AgentService (node dispatcher)', () => { return []; }; const svc = disposables.add(new AgentService(new NullLogService(), fileService, createSessionDataService(db), { _serviceBrand: undefined } as IProductService, gitService)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.registerProvider(agent); const sessions = await svc.listSessions(); @@ -4981,7 +5033,7 @@ suite('AgentService (node dispatcher)', () => { gitService.getWorktreeRoots = async () => [primaryRoot, linkedCheckout, sessionWorktree]; const sessionDataService = createSessionDataService(db); const svc = disposables.add(new AgentService(new NullLogService(), fileService, sessionDataService, { _serviceBrand: undefined } as IProductService, gitService)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.setWorktreeIsolation(disposables.add(new WorktreeIsolation( { generateBranchName: async () => 'agents/test' }, gitService, @@ -5028,7 +5080,7 @@ suite('AgentService (node dispatcher)', () => { gitService.getDefaultBranch = async () => ({ name: 'main', startPoint: 'main' }); const sessionDataService = createSessionDataService(db); const svc = disposables.add(new AgentService(new NullLogService(), fileService, sessionDataService, { _serviceBrand: undefined } as IProductService, gitService)); - svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }); + svc.configurationService.updateRootConfig({ [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }); svc.setWorktreeIsolation(disposables.add(new WorktreeIsolation( { generateBranchName: async () => 'agents/test' }, gitService, @@ -5189,7 +5241,7 @@ suite('AgentService (node dispatcher)', () => { disposables.add(toDisposable(() => agent.dispose())); agent.resolvedWorkingDirectory = URI.file('/original'); const { session } = await createAgentSession(agent); - setExternalSessionsMode(service, AgentHostExternalSessionsMode.All, 1); + setExternalSessionsMode(service, AgentHostExternalSessionsMode.Last30Days, 1); await waitForSessionListReconciliation(service); service.registerProvider(agent); agent.fireDiscoveredChats([discoveredChat(session)]); @@ -5199,13 +5251,14 @@ suite('AgentService (node dispatcher)', () => { const listing = service.listSessions(); await agent.listStarted.p; + const summaryNow = Date.now(); service.stateManager.restoreSession({ resource: session.toString(), provider: 'copilot', title: 'Materialized', status: SessionStatus.Idle, - createdAt: new Date(1000).toISOString(), - modifiedAt: new Date(2000).toISOString(), + createdAt: new Date(summaryNow - 1_000).toISOString(), + modifiedAt: new Date(summaryNow).toISOString(), project: { uri: URI.file('/project').toString(), displayName: 'project' }, workingDirectories: [URI.file('/worktree').toString()], }, []); @@ -5217,7 +5270,7 @@ suite('AgentService (node dispatcher)', () => { project: listed?.project && { uri: listed.project.uri.path, displayName: listed.project.displayName }, workingDirectory: listed?.workingDirectories?.[0]?.path, }, { - modifiedTime: 2000, + modifiedTime: summaryNow, project: { uri: '/project', displayName: 'project' }, workingDirectory: '/worktree', }); diff --git a/src/vs/platform/agentHost/test/node/protocol/sessionLifecycle.integrationTest.ts b/src/vs/platform/agentHost/test/node/protocol/sessionLifecycle.integrationTest.ts index 074390d8670..601dec1dd23 100644 --- a/src/vs/platform/agentHost/test/node/protocol/sessionLifecycle.integrationTest.ts +++ b/src/vs/platform/agentHost/test/node/protocol/sessionLifecycle.integrationTest.ts @@ -135,7 +135,7 @@ suite('Protocol WebSocket — Session Lifecycle', function () { clientSeq: 1, action: { type: 'root/configChanged', - config: { [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.All }, + config: { [AgentHostShowExternalSessionsConfigKey]: AgentHostExternalSessionsMode.Last30Days }, }, }); await client.call('ping'); diff --git a/src/vs/platform/chat/common/chatSettings.ts b/src/vs/platform/chat/common/chatSettings.ts index 397749566d3..6ed932c63b0 100644 --- a/src/vs/platform/chat/common/chatSettings.ts +++ b/src/vs/platform/chat/common/chatSettings.ts @@ -12,9 +12,9 @@ export type ChatEditAutoApprovePatterns = Readonly>; export const enum ChatExternalSessionsMode { Recent = 'recent', None = 'none', - All = 'all', Last24Hours = 'last24Hours', Last7Days = 'last7Days', + Last30Days = 'last30Days', } /** Edit paths whose executable side effects require confirmation regardless of user configuration. */ diff --git a/src/vs/sessions/contrib/chat/browser/externalSessionBanner.ts b/src/vs/sessions/contrib/chat/browser/externalSessionBanner.ts index ad38790bb80..8280e1178a0 100644 --- a/src/vs/sessions/contrib/chat/browser/externalSessionBanner.ts +++ b/src/vs/sessions/contrib/chat/browser/externalSessionBanner.ts @@ -47,12 +47,12 @@ export function shouldConfirmExternalSessionVisibilityChange(mode: ChatExternalS return true; case ChatExternalSessionsMode.None: return true; - case ChatExternalSessionsMode.All: - return false; case ChatExternalSessionsMode.Last24Hours: return updatedAt.getTime() < now - DAY; case ChatExternalSessionsMode.Last7Days: return updatedAt.getTime() < now - 7 * DAY; + case ChatExternalSessionsMode.Last30Days: + return updatedAt.getTime() < now - 30 * DAY; } } @@ -86,7 +86,9 @@ export function getExternalSessionVisibilityConfirmation(mode: ChatExternalSessi : localize('externalSessionBanner.confirm.daysAgo', "{0} days ago", daysAgo); const detail = mode === ChatExternalSessionsMode.Last24Hours ? localize('externalSessionBanner.confirm.lastDay.detail', "Only external sessions updated in the last day will be shown. This session was last updated {0}. Are you sure you want to save this change?", lastUpdated) - : localize('externalSessionBanner.confirm.last7Days.detail', "Only external sessions updated in the last 7 days will be shown. This session was last updated {0}. Are you sure you want to save this change?", lastUpdated); + : mode === ChatExternalSessionsMode.Last7Days + ? localize('externalSessionBanner.confirm.last7Days.detail', "Only external sessions updated in the last 7 days will be shown. This session was last updated {0}. Are you sure you want to save this change?", lastUpdated) + : localize('externalSessionBanner.confirm.last30Days.detail', "Only external sessions updated in the last 30 days will be shown. This session was last updated {0}. Are you sure you want to save this change?", lastUpdated); return { type: 'warning', message, detail, primaryButton }; } @@ -245,10 +247,10 @@ export class ExternalSessionBanner extends Disposable { }, }, { - mode: ChatExternalSessionsMode.All, + mode: ChatExternalSessionsMode.Last30Days, item: { - text: localize('externalSessionBanner.select.all', "All"), - description: localize('externalSessionBanner.select.all.description', "Show all sessions created in another application."), + text: localize('externalSessionBanner.select.last30Days', "Last 30 Days"), + description: localize('externalSessionBanner.select.last30Days.description', "Show external sessions updated in the last 30 days."), }, }, ]; diff --git a/src/vs/sessions/contrib/chat/test/browser/externalSessionBanner.test.ts b/src/vs/sessions/contrib/chat/test/browser/externalSessionBanner.test.ts index c5841d767e5..10a45a6ea21 100644 --- a/src/vs/sessions/contrib/chat/test/browser/externalSessionBanner.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/externalSessionBanner.test.ts @@ -18,7 +18,8 @@ suite('Sessions - External Session Banner', () => { assert.deepStrictEqual({ recent: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Recent, new Date(now), now), none: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.None, new Date(now), now), - all: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.All, new Date(0), now), + at30Days: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Last30Days, new Date(now - 30 * day), now), + olderThan30Days: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Last30Days, new Date(now - 30 * day - 1), now), at24Hours: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Last24Hours, new Date(now - day), now), olderThan24Hours: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Last24Hours, new Date(now - day - 1), now), at7Days: shouldConfirmExternalSessionVisibilityChange(ChatExternalSessionsMode.Last7Days, new Date(now - 7 * day), now), @@ -26,7 +27,8 @@ suite('Sessions - External Session Banner', () => { }, { recent: true, none: true, - all: false, + at30Days: false, + olderThan30Days: true, at24Hours: false, olderThan24Hours: true, at7Days: false, diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/externalSessionsFilterMenu.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/externalSessionsFilterMenu.ts index 603d6cec716..1f4fbdf8823 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/externalSessionsFilterMenu.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/externalSessionsFilterMenu.ts @@ -17,7 +17,7 @@ const externalSessionOptions = [ { mode: ChatExternalSessionsMode.Recent, title: localize2('agentSessions.filter.external.recent', "Recent") }, { mode: ChatExternalSessionsMode.Last24Hours, title: localize2('agentSessions.filter.external.last24Hours', "Last 24 Hours") }, { mode: ChatExternalSessionsMode.Last7Days, title: localize2('agentSessions.filter.external.last7Days', "Last 7 Days") }, - { mode: ChatExternalSessionsMode.All, title: localize2('agentSessions.filter.external.all', "All") }, + { mode: ChatExternalSessionsMode.Last30Days, title: localize2('agentSessions.filter.external.last30Days', "Last 30 Days") }, ] as const; export function registerExternalSessionsFilterMenu(parentMenuId: MenuId, submenuId: MenuId, group: string): IDisposable { diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts index caf77a01582..50f8e61d9eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts @@ -397,13 +397,13 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.ShowExternalAgentSessions]: { type: 'string', - enum: [AgentHostExternalSessionsMode.None, AgentHostExternalSessionsMode.Recent, AgentHostExternalSessionsMode.Last24Hours, AgentHostExternalSessionsMode.Last7Days, AgentHostExternalSessionsMode.All], + enum: [AgentHostExternalSessionsMode.None, AgentHostExternalSessionsMode.Recent, AgentHostExternalSessionsMode.Last24Hours, AgentHostExternalSessionsMode.Last7Days, AgentHostExternalSessionsMode.Last30Days], enumDescriptions: [ nls.localize('chat.agentSessions.showExternal.none', "Only shows sessions created by the Agent Host."), nls.localize('chat.agentSessions.showExternal.recent', "Shows the 2 most recently updated external sessions from the last 7 days."), nls.localize('chat.agentSessions.showExternal.last24Hours', "Shows external sessions updated in the last 24 hours."), nls.localize('chat.agentSessions.showExternal.last7Days', "Shows external sessions updated in the last 7 days."), - nls.localize('chat.agentSessions.showExternal.all', "Shows all sessions discovered from supported external agent applications."), + nls.localize('chat.agentSessions.showExternal.last30Days', "Shows external sessions updated in the last 30 days."), ], default: AgentHostExternalSessionsMode.None, markdownDescription: nls.localize('chat.agentSessions.showExternal', "Controls which external agent sessions, created outside VS Code's Agent Host, are shown."), @@ -2439,6 +2439,14 @@ Registry.as(Extensions.ConfigurationMigration). return { value }; } }, + { + key: ChatConfiguration.ShowExternalAgentSessions, + migrateFn: (value: unknown) => ({ + value: value === 'all' + ? AgentHostExternalSessionsMode.Last30Days + : value, + }) + }, { key: ChatConfiguration.NotifyWindowOnConfirmation, migrateFn: (value: unknown) => { diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/externalSessionsFilterMenu.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/externalSessionsFilterMenu.test.ts index ea545123c34..80b5849b556 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/externalSessionsFilterMenu.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/externalSessionsFilterMenu.test.ts @@ -59,7 +59,7 @@ suite('External Sessions Filter Menu', () => { { title: 'Recent', checkedForRecent: true }, { title: 'Last 24 Hours', checkedForRecent: false }, { title: 'Last 7 Days', checkedForRecent: false }, - { title: 'All', checkedForRecent: false }, + { title: 'Last 30 Days', checkedForRecent: false }, ], }); });