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 9190dc515d.

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>
This commit is contained in:
Copilot
2026-08-20 16:37:48 +00:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> benibenj
parent c14e0dc566
commit 32b97f54e2
11 changed files with 210 additions and 73 deletions
@@ -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<boolean>({
+74 -7
View File
@@ -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<void> {
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<boolean>(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<string> {
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<string>();
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;
}
@@ -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<void> {
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
@@ -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<string, URI> })._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<string, URI> })._sessions.set(AgentSession.id(session), session);
@@ -2934,11 +2934,11 @@ suite('AgentService (node dispatcher)', () => {
suite('aggregation', () => {
class TimedExternalAgent extends MockAgent {
readonly catalog = new Map<string, { session: URI; modifiedTime: number }>();
readonly catalog = new Map<string, { session: URI; modifiedTime: number; _meta?: IAgentSessionMetadata['_meta'] }>();
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<string, URI> })._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<IAgentChatMetadata | undefined> {
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<boolean> })._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<void> })._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, string[]> = {
[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<IAgentChatMetadata[]> {
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<IAgentChatMetadata[]> {
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<void> })._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<IAgentChatMetadata[]> {
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<readonly IAgentChatMetadata[] | undefined> }).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<string, URI> })._sessions.set(AgentSession.id(legacy), legacy);
@@ -4263,7 +4314,7 @@ suite('AgentService (node dispatcher)', () => {
(agent as unknown as { listChatsToMigrate: () => Promise<readonly IAgentChatMetadata[] | undefined> }).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<string, URI> })._sessions.set(AgentSession.id(legacy), legacy);
@@ -4812,7 +4864,7 @@ suite('AgentService (node dispatcher)', () => {
(agent as unknown as { _sessions: Map<string, URI> })._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<string, URI> })._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<string, URI> })._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<string, URI> })._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<string, URI> })._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',
});
@@ -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');
+1 -1
View File
@@ -12,9 +12,9 @@ export type ChatEditAutoApprovePatterns = Readonly<Record<string, boolean>>;
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. */
@@ -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."),
},
},
];
@@ -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,
@@ -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 {
@@ -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<IConfigurationMigrationRegistry>(Extensions.ConfigurationMigration).
return { value };
}
},
{
key: ChatConfiguration.ShowExternalAgentSessions,
migrateFn: (value: unknown) => ({
value: value === 'all'
? AgentHostExternalSessionsMode.Last30Days
: value,
})
},
{
key: ChatConfiguration.NotifyWindowOnConfirmation,
migrateFn: (value: unknown) => {
@@ -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 },
],
});
});