Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
80fc548
added PLUGIN_ROOT and PLUGIN_DATA expansion to agent plugin
revallion Sep 19, 2026
923c196
only interpolate if it is not agentplugin
revallion Sep 19, 2026
567a847
added expansion in _toServerDefinition
revallion Sep 19, 2026
8cfa3fa
added tests for expansion behaviour
revallion Sep 19, 2026
1bc9a02
only exand fiels allowed by agents-plugin spec and added data path
revallion Sep 19, 2026
8cc751e
added data path in AppData/Roaming/agentPlugins/data/hash
revallion Sep 19, 2026
aa3a0c5
updated tests
revallion Sep 19, 2026
6f852ce
added data folder test
revallion Sep 19, 2026
4548511
cleanup
revallion Sep 19, 2026
390d92c
Merge branch 'main' into fix/335006_pluginRootTokens_empty
revallion Sep 19, 2026
e4cdf1e
Potential fix for pull request finding 'Avoid creating data directori…
revallion Sep 19, 2026
67aa374
refactor: update dataDir type to IObservable and adjust related logic
revallion Sep 19, 2026
7783e49
enhance plugin MCP server definition handling for remote environments
revallion Sep 19, 2026
a08787c
add logging for MCP collection registration errors in PluginMcpDiscovery
revallion Sep 19, 2026
4367adf
enhance PluginMcpDiscovery to handle remote connection state changes …
revallion Sep 19, 2026
aaf5d8b
getAgentPluginDataDirName function for consistent data directory naming
revallion Sep 19, 2026
fa1383a
use getAgentPluginDataDirName for consistent dataDir generation in tests
revallion Sep 19, 2026
17e445b
refactor: update getAgentPluginDataDirName to use identity and enhanc…
revallion Sep 19, 2026
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
7 changes: 7 additions & 0 deletions src/vs/platform/agentPlugins/common/pluginParsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { StringSHA1 } from '../../../base/common/hash.js';
import { parse as parseJSONC } from '../../../base/common/json.js';
import { cloneAndChange, equals as objectEquals } from '../../../base/common/objects.js';
import { isAbsolute } from '../../../base/common/path.js';
Expand All @@ -18,6 +19,12 @@ import { DEFAULT_MCP_APP } from '../../agentHost/common/state/protocol/mcpAppDef
import { customizationId } from '../../agentHost/common/state/sessionState.js';
import { readAgentPluginManifest } from './agentPluginParser.js';

export function getAgentPluginDataDirName(identity: string): string {
const sha = new StringSHA1();
sha.update(identity);
return sha.digest();
}

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export interface IAgentPluginAutomation {

export interface IAgentPlugin {
readonly uri: URI;
/** Stable identity used for the persistent plugin data directory when available. */
readonly dataDirId?: string;
/** Persistent data directory used for `${PLUGIN_DATA}` in Agent Plugin MCP configurations. */
readonly dataDir?: IObservable<URI | undefined>;
readonly format: PluginFormat;
/** Human-readable display name for the plugin. */
readonly label: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ import {
readPluginMcpServers,
parseMcpServerDefinitionMap,
detectPluginFormat,
getAgentPluginDataDirName,
type PluginComponent,
type IPluginFormatConfig,
type IParsedHookGroup,
} from '../../../../../platform/agentPlugins/common/pluginParsers.js';
import { Extensions, IExtensionFeaturesRegistry, IExtensionFeatureTableRenderer, IRenderedData, IRowData, ITableData } from '../../../../services/extensionManagement/common/extensionFeatures.js';
import * as extensionsRegistry from '../../../../services/extensions/common/extensionsRegistry.js';
import { IPathService } from '../../../../services/path/common/pathService.js';
import { IUserDataProfileService } from '../../../../services/userDataProfile/common/userDataProfile.js';
import { IUserDataProfile } from '../../../../../platform/userDataProfile/common/userDataProfile.js';
import { ChatConfiguration } from '../constants.js';
import { ContributionEnablementState, EnablementModel, IEnablementModel } from '../enablement.js';
import { AUTOMATION_BLUEPRINT_FILE_SUFFIX, parseAutomationBlueprint } from '../automations/automationBlueprint.js';
Expand Down Expand Up @@ -232,6 +235,8 @@ interface IPluginManifest {
interface IPluginSource {
readonly uri: URI;
readonly fromMarketplace: IMarketplacePlugin | undefined;
/** Stable identity for persistent plugin data. */
readonly dataDirId?: string;
/** Repository root that serves as the boundary for component path resolution. */
readonly repositoryUri?: URI;
/** Called when remove is invoked on the plugin; absent for policy-managed plugins */
Expand All @@ -256,13 +261,19 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements
private _discoverVersion = 0;
protected _enablementModel!: IEnablementModel;

private readonly _currentProfile: IObservable<IUserDataProfile> | undefined;

constructor(
protected readonly _fileService: IFileService,
protected readonly _pathService: IPathService,
protected readonly _logService: ILogService,
protected readonly _workspaceContextService: IWorkspaceContextService,
protected readonly _userDataProfileService: IUserDataProfileService | undefined,
) {
super();
this._currentProfile = this._userDataProfileService
? observableFromEvent(this, this._userDataProfileService.onDidChangeCurrentProfile, () => this._userDataProfileService!.currentProfile)
: undefined;
}

public abstract start(enablementModel: IEnablementModel): void;
Expand Down Expand Up @@ -299,7 +310,7 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements
if (!this._isCurrentRefresh(version)) {
return [];
}
const plugin = await this._toPlugin(source.uri, format, source.fromMarketplace, source.repositoryUri, source.remove, version);
const plugin = await this._toPlugin(source.uri, format, source.fromMarketplace, source.dataDirId, source.repositoryUri, source.remove, version);
seenPluginUris.add(key);
plugins.push(plugin);
} catch (error) {
Expand Down Expand Up @@ -329,7 +340,7 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements
}
}

private async _toPlugin(uri: URI, format: IPluginFormatConfig, fromMarketplace: IMarketplacePlugin | undefined, repositoryUri: URI | undefined, removeCallback: (() => Promise<boolean>) | undefined, version: number): Promise<IAgentPlugin> {
private async _toPlugin(uri: URI, format: IPluginFormatConfig, fromMarketplace: IMarketplacePlugin | undefined, dataDirId: string | undefined, repositoryUri: URI | undefined, removeCallback: (() => Promise<boolean>) | undefined, version: number): Promise<IAgentPlugin> {
const key = uri.toString();
const existing = this._pluginEntries.get(key);
if (existing) {
Expand Down Expand Up @@ -477,8 +488,14 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements
? initialManifest.name.trim()
: undefined;

const dataDir = this._currentProfile
? derived(reader => joinPath(this._currentProfile!.read(reader).globalStorageHome, 'agentPlugins', 'data', getAgentPluginDataDirName(dataDirId ?? uri.toString())))
: undefined;
Comment thread
Copilot marked this conversation as resolved.

const plugin: PluginEntry = {
uri,
dataDirId,
dataDir,
format: format.format,
label: fromMarketplace?.name ?? manifestName ?? basename(uri),
version: pluginVersion,
Expand Down Expand Up @@ -642,8 +659,9 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IPathService pathService: IPathService,
@ILogService logService: ILogService,
@IUserDataProfileService userDataProfileService?: IUserDataProfileService,
) {
super(fileService, pathService, logService, workspaceContextService);
super(fileService, pathService, logService, workspaceContextService, userDataProfileService);
this._pluginLocationsConfig = observableConfigValue<Record<string, boolean>>(ChatConfiguration.PluginLocations, {}, _configurationService);
// Enterprise-managed plugin-ID entries (delivered via the `ChatEnabledPlugins` policy).
// These are plugin IDs in `<plugin>@<marketplace>` form, distinct from filesystem paths.
Expand Down Expand Up @@ -720,9 +738,11 @@ export class ConfiguredAgentPluginDiscovery extends AbstractAgentPluginDiscovery
return;
}

const fromMarketplace = this._pluginMarketplaceService.getMarketplacePluginMetadata(stat.resource);
sources.push({
uri: stat.resource,
fromMarketplace: this._pluginMarketplaceService.getMarketplacePluginMetadata(stat.resource),
fromMarketplace,
dataDirId: fromMarketplace && getMarketplacePluginDataDirId(fromMarketplace),
remove,
});
}
Expand Down Expand Up @@ -816,8 +836,9 @@ export class MarketplaceAgentPluginDiscovery extends AbstractAgentPluginDiscover
@IPathService pathService: IPathService,
@ILogService logService: ILogService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
) {
super(fileService, pathService, logService, workspaceContextService);
super(fileService, pathService, logService, workspaceContextService, userDataProfileService);
}

public override start(enablementModel: IEnablementModel): void {
Expand Down Expand Up @@ -853,6 +874,7 @@ export class MarketplaceAgentPluginDiscovery extends AbstractAgentPluginDiscover
sources.push({
uri: stat.resource,
fromMarketplace: entry.plugin,
dataDirId: getMarketplacePluginDataDirId(entry.plugin),
repositoryUri,
remove: async () => {
this._enablementModel.remove(stat.resource.toString());
Expand Down Expand Up @@ -902,9 +924,10 @@ export class CopilotCliAgentPluginDiscovery extends AbstractAgentPluginDiscovery
@IPathService pathService: IPathService,
@ILogService logService: ILogService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
@IDialogService private readonly _dialogService: IDialogService,
) {
super(fileService, pathService, logService, workspaceContextService);
super(fileService, pathService, logService, workspaceContextService, userDataProfileService);
}

public override start(enablementModel: IEnablementModel): void {
Expand Down Expand Up @@ -1091,7 +1114,7 @@ const epPlugins = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<I

export class ExtensionAgentPluginDiscovery extends AbstractAgentPluginDiscovery {

private readonly _extensionPlugins = new Map<string, { uri: URI; when: ContextKeyExpression | undefined; extensionId: string }>();
private readonly _extensionPlugins = new Map<string, { uri: URI; when: ContextKeyExpression | undefined; extensionId: string; path: string }>();
private readonly _whenKeys = new Set<string>();

constructor(
Expand All @@ -1102,8 +1125,9 @@ export class ExtensionAgentPluginDiscovery extends AbstractAgentPluginDiscovery
@IPathService pathService: IPathService,
@ILogService logService: ILogService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
) {
super(fileService, pathService, logService, workspaceContextService);
super(fileService, pathService, logService, workspaceContextService, userDataProfileService);
}

public override start(enablementModel: IEnablementModel): void {
Expand Down Expand Up @@ -1134,7 +1158,7 @@ export class ExtensionAgentPluginDiscovery extends AbstractAgentPluginDiscovery
continue;
}
}
this._extensionPlugins.set(extensionPluginKey(ext.description.identifier, raw.path), { uri: pluginUri, when: whenExpr, extensionId: ext.description.identifier.value });
this._extensionPlugins.set(extensionPluginKey(ext.description.identifier, raw.path), { uri: pluginUri, when: whenExpr, extensionId: ext.description.identifier.value, path: raw.path });
}
}
for (const ext of delta.removed) {
Expand Down Expand Up @@ -1180,6 +1204,7 @@ export class ExtensionAgentPluginDiscovery extends AbstractAgentPluginDiscovery
sources.push({
uri: stat.resource,
fromMarketplace: undefined,
dataDirId: `extension:${entry.extensionId}/${entry.path}`,
remove: () => this._promptUninstallExtension(entry.extensionId),
});
}
Expand All @@ -1202,6 +1227,10 @@ function extensionPluginKey(extensionId: ExtensionIdentifier, path: string): str
return `${extensionId.value}/${path}`;
}

function getMarketplacePluginDataDirId(plugin: IMarketplacePlugin): string {
return `marketplace:${plugin.marketplaceReference.canonicalId}/${plugin.name}`;
}

class ChatPluginsDataRenderer extends Disposable implements IExtensionFeatureTableRenderer {
readonly type = 'table' as const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TestPluginDiscovery extends AbstractAgentPluginDiscovery {
logService: ILogService,
workspaceContextService: IWorkspaceContextService,
) {
super(fileService, pathService, logService, workspaceContextService);
super(fileService, pathService, logService, workspaceContextService, undefined);
}

start(enablementModel: IEnablementModel): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ suite('ConfiguredAgentPluginDiscovery', () => {
}
},
new NullLogService(),
undefined,
));
}

Expand Down
Loading