Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/vs/platform/agentHost/common/agentHostSubscriptionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { URI } from '../../../base/common/uri.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { parseAnnotationsUri } from './annotationsUri.js';
import { parseChangesetUri } from './changesetUri.js';
import { parseDefaultChatUri, parseSubagentSessionUri } from './state/sessionState.js';

export const IAgentHostSubscriptionService = createDecorator<IAgentHostSubscriptionService>('agentHostSubscriptionService');

/**
* Authoritative registry of logical protocol clients observing Agent Host state.
*/
export interface IAgentHostSubscriptionService {
readonly _serviceBrand: undefined;

readonly subscribedResources: Iterable<URI>;

addSubscriber(resource: URI, clientId: string): boolean;
removeSubscriber(resource: URI, clientId: string): boolean;
hasSubscribers(resource: URI): boolean;
hasSessionSubscribers(resource: URI): boolean;
}

export function resolveAgentHostSession(resource: URI): URI {
const resourceString = resource.toString();
const changesetSession = parseChangesetUri(resourceString)?.sessionUri;
const annotationsSession = parseAnnotationsUri(resourceString)?.sessionUri;
const chatSession = parseDefaultChatUri(resourceString);
let session = URI.parse(changesetSession ?? annotationsSession ?? chatSession ?? resourceString);
let subagent;
while ((subagent = parseSubagentSessionUri(session))) {
session = subagent.parentSession;
}
return session;
}
12 changes: 5 additions & 7 deletions src/vs/platform/agentHost/common/agentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,11 @@ export const AgentHostClaudeAgentEnabledEnvVar = 'VSCODE_AGENT_HOST_CLAUDE_AGENT
*/
export const AgentHostCodexAgentEnabledEnvVar = 'VSCODE_AGENT_HOST_CODEX_AGENT_ENABLED';

/**
* Overrides the grace period (in milliseconds) before an idle, fully
* unsubscribed session is released from memory. Defaults to 30_000. Primarily a
* test hook so real-SDK integration tests can force a prompt release without
* waiting the full production grace; production does not set it.
*/
export const AgentHostSessionReleaseGraceMsEnvVar = 'VSCODE_AGENT_HOST_SESSION_RELEASE_GRACE_MS';
/** Overrides the soft cap on resident session roots. Primarily used by integration tests. */
export const AgentHostSessionResidencyLimitEnvVar = 'VSCODE_AGENT_HOST_SESSION_RESIDENCY_LIMIT';

/** Overrides the retry delay after a provider temporarily vetoes session release. Primarily used by integration tests. */
export const AgentHostSessionReleaseRetryMsEnvVar = 'VSCODE_AGENT_HOST_SESSION_RELEASE_RETRY_MS';

/**
* Resolves the effective enable state for a Claude/Codex provider from the
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/agentHost/node/agentHostServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IAgentHostChatContributions } from '../common/agentHostChatContribution
import { IAgentHostCheckpointService } from '../common/agentHostCheckpointService.js';
import { IAgentHostGitStateService } from '../common/agentHostGitStateService.js';
import { IAgentHostReviewService } from '../common/agentHostReviewService.js';
import { IAgentHostSubscriptionService } from '../common/agentHostSubscriptionService.js';
import { CopilotApiService, ICopilotApiService } from './shared/copilotApiService.js';
import { AgentHostFileMonitorService, IAgentHostFileMonitorService } from './agentHostFileMonitorService.js';
import { AgentHostGitService } from './agentHostGitService.js';
Expand All @@ -48,6 +49,7 @@ import { AgentHostGitStateService } from './agentHostGitStateService.js';
import { AgentHostManagedSettingsService, IAgentHostManagedSettingsService } from './agentHostManagedSettingsService.js';
import { AgentHostPromptCache, IAgentHostPromptCache } from './agentHostPromptCache.js';
import { AgentHostReviewService } from './agentHostReviewService.js';
import { AgentHostSubscriptionService } from './agentHostSubscriptionService.js';
import { AgentHostSessionTitleSignal, IAgentHostSessionTitleSignal } from './agentHostSessionTitleSignal.js';
import { AgentHostStorageService, IAgentHostStorageService } from './agentHostStorageService.js';
import { AgentHostTerminalManager, IAgentHostTerminalManager } from './agentHostTerminalManager.js';
Expand Down Expand Up @@ -96,6 +98,7 @@ export function registerAgentHostCoreServices(services: ServiceCollection, input
registerService(services, IAgentHostPromptCache, new SyncDescriptor(AgentHostPromptCache));
registerService(services, IAgentHostSessionTitleSignal, new SyncDescriptor(AgentHostSessionTitleSignal));
registerService(services, IAgentHostChangesetSubscriptionService, new SyncDescriptor(AgentHostChangesetSubscriptionService));
registerService(services, IAgentHostSubscriptionService, new SyncDescriptor(AgentHostSubscriptionService));
registerService(services, IAgentHostChangesetOperationService, new SyncDescriptor(AgentHostChangesetOperationService));
registerService(services, IAgentHostReviewService, new SyncDescriptor(AgentHostReviewService));
registerService(services, IAgentHostChangesetService, new SyncDescriptor(AgentHostChangesetService));
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/agentHost/node/agentHostStateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1239,8 +1239,8 @@ export class AgentHostStateManager extends Disposable {
* be emitted as a side-effect of this call.
*
* Per-session changesets are intentionally NOT torn down here: this method
* is also used as an idle-eviction (LRU) hook (see
* `AgentService._maybeEvictIdleSession`) and the session list view keeps a
* is also used by `AgentSessionResidency` for residency eviction, and
* the session list view keeps a
* changeset subscription open per visible row to render the diff chip.
* Tearing down on eviction would clear the chip on the list while the row
* is still on screen. Permanent-delete paths (`deleteSession`,
Expand Down
56 changes: 56 additions & 0 deletions src/vs/platform/agentHost/node/agentHostSubscriptionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ResourceMap } from '../../../base/common/map.js';
import { URI } from '../../../base/common/uri.js';
import { IAgentHostSubscriptionService, resolveAgentHostSession } from '../common/agentHostSubscriptionService.js';

export class AgentHostSubscriptionService implements IAgentHostSubscriptionService {
declare readonly _serviceBrand: undefined;

private readonly _subscribers = new ResourceMap<Set<string>>();

get subscribedResources(): Iterable<URI> {
return this._subscribers.keys();
}

addSubscriber(resource: URI, clientId: string): boolean {
let subscribers = this._subscribers.get(resource);
const firstForResource = !subscribers || subscribers.size === 0;
if (!subscribers) {
subscribers = new Set();
this._subscribers.set(resource, subscribers);
}
subscribers.add(clientId);
return firstForResource;
}

removeSubscriber(resource: URI, clientId: string): boolean {
const subscribers = this._subscribers.get(resource);
if (!subscribers) {
return false;
}
subscribers.delete(clientId);
if (subscribers.size > 0) {
return false;
}
this._subscribers.delete(resource);
return true;
}

hasSubscribers(resource: URI): boolean {
return this._subscribers.has(resource);
}

hasSessionSubscribers(resource: URI): boolean {
const sessionKey = resolveAgentHostSession(resource).toString();
for (const subscribedResource of this._subscribers.keys()) {
if (resolveAgentHostSession(subscribedResource).toString() === sessionKey) {
return true;
}
}
return false;
}
}
Loading
Loading