347
}
348
}
350
>
const store = new DisposableStore();
351
>
// Set by the service when enterprise policy blocks this plugin; when set,
352
>
// the plugin is forced disabled regardless of the user's enablement choice.
353
>
const policyBlocked = observableValue<boolean>('policyBlocked', false);
354
>
const enablement = derived(r => policyBlocked.read(r)
355
? ContributionEnablementState.DisabledProfile
357
>
358
>
// Read the manifest up front so its `name` field can be used in the
359
>
// plugin label (for direct installs that have no marketplace metadata).
360
>
// Component directories are tracked via observers downstream and
361
>
// re-read whenever the manifest changes on disk.
362
>
const initialManifest = await readPluginManifest(uri, format, this._fileService);
363
>
const manifest = observableValue<IPluginManifest | undefined>('agentPluginManifest', initialManifest);
364
>
365
>
const observeComponent = <T>(
366
>
prop: PluginComponent,
367
>
doRead: (uris: readonly URI[]) => Promise<readonly T[]>,
368
>
tryReadEmbedded?: (section: unknown) => Promise<T[] | undefined>,
369
>
defaultPath: string = prop,
370
>
): IObservable<readonly T[]> => {
371
>
const secondObs = derivedOpts({ equalsFn: equals }, reader => getPluginManifestComponent(format, prop, manifest.read(reader)));
372
>
373
>
const wrapped = derived(reader => {
374
>
if (format.requiresManifest && !manifest.read(reader)) {
375
return { kind: 'dirs', dirs: [] } as const;
376
}
378
>
if (tryReadEmbedded) {
379
>
if (section && typeof section === 'object' && !Array.isArray(section) && !(hasKey(section, { paths: true }))) {
380
return { kind: 'const', data: new ObservablePromise(tryReadEmbedded(section)) } as const;
381
}
383
>
384
>
const dirs = resolvePluginComponentDirs(uri, format, prop, defaultPath, section, repositoryUri);
385
>
for (const d of dirs) {
386
>
const watcher = this._fileService.createWatcher(d, { recursive: false, excludes: [] });
387
>
reader.store.add(watcher);
388
>
reader.store.add(watcher.onDidChange(() => changeTrigger.trigger(undefined)));
389
>
}
390
>
391
>
return { kind: 'dirs', dirs: dirs } as const;
392
>
});
393
>
394
>
const changeTrigger = observableSignal('fileChange');
395
>
396
>
const promised = derived(reader => {
397
>
const w = wrapped.read(reader);
398
>
if (w.kind === 'const') {
399
return w.data.promiseResult;
401
>
changeTrigger.read(reader); // re-run when a relevant file change occurs
402
>
const promise = new ObservablePromise(doRead(w.dirs));
403
>
return promise.promiseResult;
404
>
}
405
>
});
406
>
407
>
const result = promised.map((w, r) => w.read(r)?.data ?? Iterable.empty());
408
>
409
>
return result.recomputeInitiallyAndOnChange(store);
410
>
};
411
>
412
>
const manifestUri = joinPath(uri, format.manifestPath);
413
>
const commands = observeComponent('commands', d => readMarkdownComponents(d, this._fileService));
414
>
const skills = observeComponent('skills', d => readPluginSkills(uri, d, format, this._fileService));
415
>
const agents = observeComponent('agents', d => readMarkdownComponents(d, this._fileService));
416
>
const instructions = observeComponent('rules', d => this._readRules(d));
417
>
const hooks = observeComponent(
418
>
'hooks',
419
>
paths => this._readHooksFromPaths(uri, paths, format),
420
>
async section => {
421
const userHome = await this._pathService.userHome();
422
const workspaceRoot = resolveWorkspaceRoot(uri, this._workspaceContextService);
423
return toAgentPluginHooks(format.parseHooks(manifestUri, section, uri, workspaceRoot, userHome));
424
},
426
>
);
427
>
428
>
const mcpServerDefinitions = observeComponent(
429
>
'mcpServers',
430
>
paths => readPluginMcpServers(uri, paths, format, this._fileService),
431
>
async section => parseMcpServerDefinitionMap(manifestUri, { mcpServers: section }, uri.fsPath, format),
432
>
'.mcp.json',
433
>
);
434
>
435
>
// Re-read the manifest whenever it changes on disk. The initial value
436
>
// was already populated above before constructing the observable.
437
>
const readManifest = async () => {
438
try {
439
const latestFormat = await detectPluginFormat(uri, this._fileService);