sessionCustomizationDiscovery.ts ×37

Frontier kind: Code frontier

unlabeled · c_6d4e6b175dda

7 tests · 51133 LOC · 299 files · introduces 0 tests · 293 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
37 ranges293 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
4850 ranges51133 lines · 299 files · Browse complete extent
All tests (intent)
7 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 293 introduced LOC across 37 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts 293 introduced LOC · 37 ranges

Open complete file

94 }
95
96 > function compareDirectoryCustomization(a: DirectoryCustomization, b: DirectoryCustomization): number { sessionCustomizationDiscovery.ts
97 > const byUri = compareStrings(a.uri, b.uri);
98 > if (byUri !== 0) {
99 > return byUri;
100 > }
101 return compareStrings(a.contents, b.contents);
102 }
269
270 private async getDiscoveredDirectories(client: CopilotClient, token: CancellationToken): Promise<readonly IDiscoveredDirectory[]> {
271 > throwIfCancelled(token); sessionCustomizationDiscovery.ts
272 >
273 > const p: AgentsDiscoverRequest = { projectPaths: [this._workingDirectory.fsPath] };
274 > const result = this.getHooksDiscoveryPaths();
275 > const workspaceAgentInstructionFiles: IDiscoveredFile[] = [];
276 > const userAgentInstructionFiles: IDiscoveredFile[] = [];
277 >
278 > try {
279 > const [agentDiscovery, instructionDiscovery, skillDiscovery] = await Promise.all([
280 > raceCancellationError(client.rpc.agents.getDiscoveryPaths(p), token),
281 > raceCancellationError(client.rpc.instructions.getDiscoveryPaths(p), token),
282 > raceCancellationError(client.rpc.skills.getDiscoveryPaths(p), token)
283 > ]);
284
285 // Process agent discovery paths
286 > for (const agentPath of agentDiscovery?.paths ?? []) { sessionCustomizationDiscovery.ts
287 throwIfCancelled(token);
288 result.push({
296
297 // Process instruction discovery paths
298 > for (const instructionPath of instructionDiscovery?.paths ?? []) { sessionCustomizationDiscovery.ts
299 throwIfCancelled(token);
300 if (instructionPath.kind === 'file') {
337
338 // Process skill discovery paths
339 > for (const skillPath of skillDiscovery?.paths ?? []) { sessionCustomizationDiscovery.ts
340 throwIfCancelled(token);
341 result.push({
354 this._logService.debug(`[SessionCustomizationDiscovery] Error getting discovery paths: ${err instanceof Error ? err.message : String(err)}`);
355 }
357 > return result.sort(compareDiscoveredDirectory);
358 > }
359
360 private getHooksDiscoveryPaths(): IDiscoveredDirectory[] {
361 > const byUri = new ResourceMap<IDiscoveredDirectory>(); sessionCustomizationDiscovery.ts
362 > const add = (uri: URI, name: string): void => {
363 > if (!byUri.has(uri)) {
364 > byUri.set(uri, { uri, type: DiscoveredType.Hook, files: [], name, writable: true });
365 > }
366 > };
367 >
368 > for (const root of searchRoots.workspace) {
369 > if (root.type === DiscoveredType.Hook) {
370 > add(joinPath(this._workingDirectory, ...root.path), root.name);
371 > }
372 > }
373 > for (const root of searchRoots.user) {
374 > if (root.type === DiscoveredType.Hook) {
375 > add(joinPath(this._userHome, ...root.path), root.name);
376 > }
377 > }
378 > for (const root of fixedDiscoveryFiles.workspace) {
379 > if (root.type === DiscoveredType.Hook) {
380 > add(joinPath(this._workingDirectory, ...root.path), basename(joinPath(this._workingDirectory, ...root.path).path));
381 > }
382 > }
383 > for (const root of fixedDiscoveryFiles.user) {
384 > if (root.type === DiscoveredType.Hook) {
385 add(joinPath(this._userHome, ...root.path), basename(joinPath(this._userHome, ...root.path).path));
386 }
388 > return [...byUri.values()];
389 > }
390
391 private async _updateWatchers(discoveredDirectories: readonly IDiscoveredDirectory[], token: CancellationToken): Promise<void> {
392 > const nextWatchRootUris = new ResourceMap<IWatchSpec>(); sessionCustomizationDiscovery.ts
393 > const toResolve = new ResourceSet();
394 > const recursiveByDirectory = new ResourceMap<boolean>();
395 >
396 > for (const discoveredDir of discoveredDirectories) {
397 > throwIfCancelled(token);
398 >
399 > const dirUri = discoveredDir.uri;
400 > const recursive = discoveredDir.type === DiscoveredType.Skill ||
401 > discoveredDir.type === DiscoveredType.Instruction ||
402 > discoveredDir.type === DiscoveredType.Hook;
403 > recursiveByDirectory.set(dirUri, recursive);
404 > toResolve.add(dirUri);
405 >
406 > let current = dirUri;
407 > while (!extUriBiasedIgnorePathCase.isEqual(current, this._workingDirectory) && !extUriBiasedIgnorePathCase.isEqual(current, this._userHome)) {
408 > const parent = uriDirname(current);
409 > if (extUriBiasedIgnorePathCase.isEqual(parent, current)) {
410 break;
411 }
412 > toResolve.add(parent); sessionCustomizationDiscovery.ts
413 > current = parent;
414 > }
415 >
416 > for (const file of discoveredDir.files) {
417 throwIfCancelled(token);
418
427 }
428 }
430 >
431 > throwIfCancelled(token);
432 >
433 > const toResolveArray = [...toResolve];
434 > const statResults = await this._fileService.resolveAll(toResolveArray.map(resource => ({ resource })));
435 > const existingDirectories = new ResourceSet();
436 > for (let i = 0; i < statResults.length; i++) {
437 > const result = statResults[i];
438 > if (result.success && result.stat?.isDirectory) {
439 existingDirectories.add(toResolveArray[i]);
440 }
442 >
443 > for (const discoveredDir of discoveredDirectories) {
444 > throwIfCancelled(token);
445 >
446 > const dirUri = discoveredDir.uri;
447 > const recursive = recursiveByDirectory.get(dirUri) ?? false;
448 > if (existingDirectories.has(dirUri)) {
449 addWatch(nextWatchRootUris, dirUri, recursive, dirUri);
450 }
452 > let current = dirUri;
453 > while (!extUriBiasedIgnorePathCase.isEqual(current, this._workingDirectory) && !extUriBiasedIgnorePathCase.isEqual(current, this._userHome)) {
454 > const parent = uriDirname(current);
455 > if (extUriBiasedIgnorePathCase.isEqual(parent, current)) {
456 break;
457 }
458 > if (existingDirectories.has(parent)) { sessionCustomizationDiscovery.ts
459 addWatch(nextWatchRootUris, parent, false, current);
460 }
461 > current = parent; sessionCustomizationDiscovery.ts
462 > }
463 >
464 > for (const file of discoveredDir.files) {
465 throwIfCancelled(token);
466
477 }
478 }
480 >
481 > this._reconcileWatchers(nextWatchRootUris);
482 > }
483
484
485 public async discover(client: CopilotClient, token: CancellationToken): Promise<readonly DirectoryCustomization[]> {
486 > await this.writeCustomizationDiscoveryDebugLog({ sessionCustomizationDiscovery.ts
487 > method: 'discover',
488 > workingDirectory: this._workingDirectory.toString(),
489 > userHome: this._userHome.toString(),
490 > });
491 > if (!this._discoveredDirectories) {
492 > this._discoveredDirectories = await this.getDiscoveredDirectories(client, token);
493 > }
494 >
495 > throwIfCancelled(token);
496 >
497 > const p: AgentsDiscoverRequest = { projectPaths: [this._workingDirectory.fsPath] };
498 >
499 > try {
500 > const [agents, rules, skills, hooks] = await Promise.all([
501 > this.discoverAgents(p, client, token),
502 > this.discoverRules(p, client, token),
503 > this.discoverSkills(p, client, token),
504 > this.discoverHooks(token),
505 > this._updateWatchers(this._discoveredDirectories, token)
506 > ]);
507 > throwIfCancelled(token);
508 > const result: DirectoryCustomization[] = [];
509 > await this.toDirectoryCustomizations(CustomizationType.Agent, agents, this._discoveredDirectories, result);
510 > await this.toDirectoryCustomizations(CustomizationType.Rule, rules, this._discoveredDirectories, result);
511 > await this.toDirectoryCustomizations(CustomizationType.Skill, skills, this._discoveredDirectories, result);
512 > await this.toDirectoryCustomizations(CustomizationType.Hook, hooks, this._discoveredDirectories, result);
513 > const sortedResult = result.sort(compareDirectoryCustomization);
514 > await this.writeCustomizationDiscoveryDebugLog({
515 > method: 'discover',
516 > result: sortedResult.map(customization => ({
517 > contents: customization.contents,
518 > uri: customization.uri,
519 > children: (customization.children ?? []).map(child => ({ type: child.type, uri: child.uri, name: child.name })),
520 > })),
521 > });
522 > return sortedResult;
523 > } catch (err) {
524 this._logService.error(`[SessionCustomizationDiscovery] Error during discovery: ${err instanceof Error ? err.message : String(err)}`);
525 return [];
526 }
528
529 private async discoverAgents(discoveryRequest: AgentsDiscoverRequest, client: CopilotClient, token: CancellationToken): Promise<AgentCustomization[]> {
530 > const agents: AgentCustomization[] = []; sessionCustomizationDiscovery.ts
531 >
532 > const agentDiscovery = await raceCancellationError(client.rpc.agents.discover(discoveryRequest), token);
533 > for (const agent of agentDiscovery.agents) {
534 if (agent.path) {
535 const uri = this._pathToUri(agent.path);
537 }
538 }
539 > return agents; sessionCustomizationDiscovery.ts
540 > }
541
542 private async discoverRules(discoveryRequest: AgentsDiscoverRequest, client: CopilotClient, token: CancellationToken): Promise<RuleCustomization[]> {
543 > const rules: RuleCustomization[] = []; sessionCustomizationDiscovery.ts
544 > const seenRuleUris = new Set<string>();
545 >
546 > const instructionDiscovery = await raceCancellationError(client.rpc.instructions.discover(discoveryRequest), token);
547 > await this.writeCustomizationDiscoveryDebugLog({
548 > method: 'discoverRules.instructions.discover',
549 > sources: instructionDiscovery.sources.map(source => ({
550 id: source.id,
551 label: source.label,
553 applyTo: source.applyTo,
554 type: source.type,
556 > });
557 >
558 > for (const instruction of instructionDiscovery.sources) {
559 let uri: URI;
560 if (isAbsolute(instruction.sourcePath)) {
575 seenRuleUris.add(uriString);
576 }
578 > for (const directory of this._discoveredDirectories ?? []) {
579 > if (directory.type !== DiscoveredType.AgentInstruction) {
580 > continue;
581 > }
582
583 for (const file of directory.files) {
597 }
598 }
600 > return rules;
601 > }
602
603 private _isAgentInstructionSource(instruction: InstructionSource): boolean {
611
612 private async discoverSkills(discoveryRequest: AgentsDiscoverRequest, client: CopilotClient, token: CancellationToken): Promise<SkillCustomization[]> {
613 > const skills: SkillCustomization[] = []; sessionCustomizationDiscovery.ts
614 >
615 > const skillDiscovery = await raceCancellationError(client.rpc.skills.discover(discoveryRequest), token);
616 > for (const skill of skillDiscovery.skills) {
617 if (skill.path) {
618 const uri = this._pathToUri(skill.path);
620 }
621 }
622 > return skills; sessionCustomizationDiscovery.ts
623 > }
624
625 private async discoverHooks(token: CancellationToken): Promise<HookCustomization[]> {
626 > const seen = new ResourceSet(); sessionCustomizationDiscovery.ts
627 > const discoveredDirectories: IDiscoveredDirectory[] = [];
628 >
629 > const hookRootsWorkspace = searchRoots.workspace.filter(root => root.type === DiscoveredType.Hook);
630 > const hookRootsUser = searchRoots.user.filter(root => root.type === DiscoveredType.Hook);
631 > const fixedHookFilesWorkspace = fixedDiscoveryFiles.workspace.filter(root => root.type === DiscoveredType.Hook);
632 > const fixedHookFilesUser = fixedDiscoveryFiles.user.filter(root => root.type === DiscoveredType.Hook);
633 >
634 > await Promise.all([
635 > ...hookRootsWorkspace.map(root => this._discoverHookRoot(this._workingDirectory, root, seen, discoveredDirectories, token)),
636 > ...hookRootsUser.map(root => this._discoverHookRoot(this._userHome, root, seen, discoveredDirectories, token)),
637 > this._discoverFixedHookFiles(this._workingDirectory, fixedHookFilesWorkspace, seen, discoveredDirectories, token),
638 > this._discoverFixedHookFiles(this._userHome, fixedHookFilesUser, seen, discoveredDirectories, token),
639 > ]);
640 >
641 > const hooks: HookCustomization[] = [];
642 > for (const directory of discoveredDirectories) {
643 > for (const file of directory.files) {
644 const uri = file.uri.toString();
645 hooks.push({
650 });
651 }
653 > hooks.sort((a, b) => compareStrings(a.uri, b.uri));
654 > return hooks;
655 > }
656
657 private async _discoverHookRoot(base: URI, root: ISearchRoot, seen: ResourceSet, result: IDiscoveredDirectory[], token: CancellationToken): Promise<void> {
658 > const rootUri = joinPath(base, ...root.path); sessionCustomizationDiscovery.ts
659 > let stat: IFileStatWithMetadata | undefined = undefined;
660 > try {
661 > stat = await this._fileService.resolve(rootUri, { resolveMetadata: true });
662 > } catch {
663 > // Root does not exist (or is unreadable) — still discover as an empty source folder.
664 > }
665 > await this._scanForHooks(root, rootUri, stat, seen, result, token);
666 > }
667
668 private async _discoverFixedHookFiles(base: URI, roots: readonly IFixedDiscoveryFile[], seen: ResourceSet, result: IDiscoveredDirectory[], token: CancellationToken): Promise<void> {
669 > for (const root of roots) { sessionCustomizationDiscovery.ts
670 > throwIfCancelled(token);
671 >
672 > const rootUri = joinPath(base, ...root.path);
673 > const files: IDiscoveredFile[] = [];
674 > let stat: IFileStatWithMetadata | undefined = undefined;
675 > try {
676 > stat = await this._fileService.resolve(rootUri, { resolveMetadata: true });
677 > } catch {
678 > // Root does not exist (or is unreadable) — still discover as an empty source folder.
679 > }
680 >
681 > for (const child of stat?.children ?? []) {
682 throwIfCancelled(token);
683
689 }
690 }
691 > if (files.length > 0) { sessionCustomizationDiscovery.ts
692 result.push({ uri: rootUri, type: DiscoveredType.Hook, files: files.sort(compareDiscoveredFile), name: basename(rootUri.path), writable: true });
693 }
695 > }
696
697 private async toDirectoryCustomizations(type: ChildCustomizationType, customizations: readonly ChildCustomization[], allDiscoveredDirectories: readonly IDiscoveredDirectory[], result: DirectoryCustomization[]): Promise<void> {
698 > const discoveredDirectories = allDiscoveredDirectories.filter(d => { sessionCustomizationDiscovery.ts
699 > if (type === CustomizationType.Agent) {
700 > return d.type === DiscoveredType.Agent;
701 > }
702 > if (type === CustomizationType.Rule) {
703 > return d.type === DiscoveredType.Instruction || d.type === DiscoveredType.AgentInstruction;
704 > }
705 > if (type === CustomizationType.Hook) {
706 > return d.type === DiscoveredType.Hook;
707 > }
708 > return d.type === DiscoveredType.Skill;
709 > });
710 > const candidateOutputDirectories = type === CustomizationType.Rule
711 > ? discoveredDirectories.filter(d => d.type !== DiscoveredType.AgentInstruction || extUriBiasedIgnorePathCase.isEqual(d.uri, this._workingDirectory) || extUriBiasedIgnorePathCase.isEqual(d.uri, this._userHome))
712 > : discoveredDirectories;
713 > const outputDirectories = type === CustomizationType.Skill
714 > ? candidateOutputDirectories.filter(directory => !candidateOutputDirectories.some(candidate =>
715 !extUriBiasedIgnorePathCase.isEqual(directory.uri, candidate.uri)
716 && extUriBiasedIgnorePathCase.isEqualOrParent(directory.uri, candidate.uri)
718 > : candidateOutputDirectories;
719 > const byParent = new ResourceMap<{ readonly uri: URI; readonly name: string; readonly writable: boolean; readonly children: ChildCustomization[] }>();
720 > for (const discoveredDirectory of outputDirectories) {
721 > byParent.set(discoveredDirectory.uri, {
722 > uri: discoveredDirectory.uri,
723 > name: discoveredDirectory.name || basename(discoveredDirectory.uri.path),
724 > writable: discoveredDirectory.writable,
725 > children: []
726 > });
727 > }
728 >
729 > const fixedHookDirectoryUris = type === CustomizationType.Hook
730 > ? new ResourceSet([
731 > ...fixedDiscoveryFiles.workspace
732 > .filter(root => root.type === DiscoveredType.Hook)
733 > .map(root => joinPath(this._workingDirectory, ...root.path)),
734 > ...fixedDiscoveryFiles.user
735 > .filter(root => root.type === DiscoveredType.Hook)
736 > .map(root => joinPath(this._userHome, ...root.path)),
737 > ])
738 > : undefined;
739 >
740 > const agentInstructionDirectoryUris = new ResourceSet(
741 > outputDirectories
742 > .filter(directory => directory.type === DiscoveredType.AgentInstruction)
743 > .map(directory => directory.uri)
744 > );
745 >
746 > for (const customization of customizations) {
747 if (customization.type !== type) {
748 continue;
778 entry.children.push(customization);
779 }
781 > for (const { uri, name, writable, children } of byParent.values()) {
782 > if (type === CustomizationType.Hook && fixedHookDirectoryUris?.has(uri) && children.length === 0) {
783 > continue;
784 > }
785 >
786 > if (type === CustomizationType.Rule && agentInstructionDirectoryUris.has(uri)) {
787 const existingChildren: ChildCustomization[] = [];
788 for (const child of children) {
803 children.push(...existingChildren);
804 }
806 > children.sort((a, b) => compareStrings(a.uri, b.uri));
807 > result.push({
808 > type: CustomizationType.Directory,
809 > id: customizationId(uri.toString()),
810 > uri: uri.toString(),
811 > name,
812 > enabled: true,
813 > contents: type,
814 > writable,
815 > load: { kind: CustomizationLoadStatus.Loaded },
816 > children,
817 > });
818 > }
819 > }
820
821