claudeSessionCustomizationDiscovery.ts ×9

Frontier kind: Code frontier

unlabeled · c_fc1b7432edaa

225 tests · 18951 LOC · 70 files · introduces 0 tests · 171 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
9 ranges171 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1847 ranges18951 lines · 70 files · Browse complete extent
All tests (intent)
225 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: 171 introduced LOC across 9 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/claude/customizations/claudeSessionCustomizationDiscovery.ts 171 introduced LOC · 9 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- claudeSessionCustomizationDiscovery.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { URI } from '../../../../../base/common/uri.js';
7 > import { isEqualOrParent } from '../../../../../base/common/resources.js';
8 > import { Event } from '../../../../../base/common/event.js';
9 > import { Disposable } from '../../../../../base/common/lifecycle.js';
10 > import { IFileService } from '../../../../files/common/files.js';
11 > import { ILogService } from '../../../../log/common/log.js';
12 > import { makeMcpServerCustomization, parseAgentFile, toParsedAgent, type IParsedAgent, type IParsedRule, type IParsedSkill } from '../../../../agentPlugins/common/pluginParsers.js';
13 > import { CustomizationType, type AgentSelection, type McpServerCustomization } from '../../../common/state/protocol/channels-session/state.js';
14 > import { CustomizationLoadStatus, customizationId, type AgentCustomization, type ChildCustomization, type Customization, type DirectoryCustomization, type HookCustomization, type PluginCustomization, type RuleCustomization, type SkillCustomization } from '../../../common/state/sessionState.js';
15 > import type { ISdkResolvedCustomizations } from '../claudeSdkPipeline.js';
16 > import { deriveMcpState } from './scan/claudeMcpScan.js';
17 > import { claudeMemoryFiles } from './scan/claudeRuleScan.js';
18 > import type { IResolvedNativePlugin } from './scan/claudeNativePluginScan.js';
19 > import { CLAUDE_BUILTIN_AGENTS, buildClaudeBuiltinSkillsContainer, buildSdkBuiltinSkillsContainer } from './claudeBuiltinCommands.js';
20 >
21 > /**
22 > * The Claude SDK's built-in default agent. Hidden from the picker:
23 > * selecting it would be equivalent to "no selection" since the SDK
24 > * uses it as the fallback when `Options.agent` is omitted.
25 > */
26 > export const CLAUDE_SDK_DEFAULT_AGENT_NAME = 'general-purpose';
27 >
28 > /**
29 > * Scheme for synthetic, non-openable URIs that mark SDK-only customizations
30 > * the disk scan couldn't locate (Decision D2). It has no file provider, so
31 > * the workbench renders such entries read-only. The writer ({@link nonEditableUri})
32 > * and reader ({@link resolveClaudeAgentName}) share this constant so the two
33 > * never drift.
34 > */
35 > const CLAUDE_INTERNAL_SCHEME = 'claude-internal';
36 >
37 function makeDirectory(base: URI, sub: string, contents: CustomizationType.Agent | CustomizationType.Skill | CustomizationType.Rule | CustomizationType.Hook, children: readonly (AgentCustomization | SkillCustomization | RuleCustomization | HookCustomization)[]): DirectoryCustomization {
38 const uri = URI.joinPath(base, '.claude', sub).toString();
49 };
50 }
52 > /**
53 > * Projects a resolved Claude-native plugin into a top-level
54 > * {@link PluginCustomization} (its own protocol container type — *not* a
55 > * per-scope {@link DirectoryCustomization}, mirroring how MCP servers are
56 > * top-level). The container `uri` is the real plugin root directory; its
57 > * `name` is the `enabledPlugins` id (the manifest carries no display name
58 > * through {@link IResolvedNativePlugin}). Children are the plugin's bundled
59 > * components, deduped by id (a plugin's hooks share one settings-file
60 > * customization, so the groups would otherwise repeat).
61 > */
62 function makePlugin(plugin: IResolvedNativePlugin): PluginCustomization {
63 const uri = plugin.root.toString();
85 };
86 }
88 > /**
89 > * The scope a discovered customization belongs to, derived from which
90 > * `.claude/` tree contains its source file.
91 > */
92 > const enum ClaudeCustomizationScope {
93 > Workspace = 'workspace',
94 > User = 'user',
95 > }
96 >
97 > /**
98 > * Attributes a discovered file to the scope whose `.claude/` directory
99 > * contains it. SDK-only (`claude-internal:`) and any out-of-tree URIs fall
100 > * back to the user scope. Drives per-scope grouping so the workbench can
101 > * label containers "Workspace" vs "User".
102 > */
103 function scopeOf(uri: URI, workingDirectory: URI | undefined): ClaudeCustomizationScope {
104 return workingDirectory && uri.scheme === workingDirectory.scheme && isEqualOrParent(uri, workingDirectory)
106 : ClaudeCustomizationScope.User;
107 }
109 > /**
110 > * Maps the disk-discovered customizations into the protocol
111 > * {@link Customization} surface. Agents, skills and rules are wrapped in
112 > * {@link DirectoryCustomization} containers (the protocol's `Customization`
113 > * union has no bare agent/skill/rule member), one container per (scope, kind):
114 > * the container `uri` is the real `<scope>/.claude/<sub>` directory so the
115 > * workbench derives the "Workspace" vs "User" label from it (mirroring
116 > * CopilotAgent). Each child carries its real source-file `uri` so the
117 > * workbench can open it for editing. MCP servers are top-level entries.
118 > */
119 > export function mapDiscoveredCustomizations(
120 discovered: readonly (IParsedAgent | IParsedSkill | IParsedRule)[],
121 mcpServers: readonly McpServerCustomization[],
181 return result;
182 }
184 > /**
185 > * A synthetic, non-openable URI that marks an SDK-only customization the
186 > * disk scan couldn't locate. The `claude-internal:` scheme has no file
187 > * provider, so the workbench renders the entry read-only (Decision D2).
188 > */
189 function nonEditableUri(kind: string, name: string): URI {
190 return URI.from({ scheme: CLAUDE_INTERNAL_SCHEME, path: `/${kind}/${encodeURIComponent(name)}` });
191 }
193 > /**
194 > * Resolves an {@link AgentSelection} URI to the SDK agent name the SDK
195 > * expects on `Options.agent`. {@link AgentSelection} carries only a `uri`,
196 > * so the name is recovered from the source:
197 > *
198 > * - A `claude-internal:` URI — an SDK-only agent the disk scan couldn't
199 > * locate (Decision D2); the name is the path segment encoded by
200 > * {@link nonEditableUri} (this is its inverse).
201 > * - A real `file:` agent — the SDK keys agents by their frontmatter
202 > * `name`, which may differ from the filename, so it is parsed (falling
203 > * back to the basename when the file can't be read).
204 > *
205 > * Returns `undefined` when no agent is selected (or the name can't be
206 > * recovered) so the SDK falls back to its default (no `--agent` flag).
207 > */
208 export async function resolveClaudeAgentName(
209 agent: AgentSelection | undefined,
248 return name;
249 }
251 > /**
252 > * Builds the discovered-customization projection for a session, applying
253 > * the live SDK snapshot as a post-materialize filter.
254 > *
255 > * - `sdk === undefined` (provisional): the full disk-discovered set is
256 > * returned unfiltered — no live session yet to say what's active.
257 > * - `sdk` present (materialized): disk entries are kept only when the live
258 > * session knows them (matched by name, per type — agents against the SDK
259 > * agent set; skills against the SDK command set; MCP against the SDK
260 > * server set, enriched with live state). SDK-known AGENTS and MCP servers
261 > * with no matching disk file are surfaced as NON-EDITABLE entries
262 > * (`claude-internal:` — Decision D2): a non-editable agent is still
263 > * selectable and a non-editable MCP server still shows status. SDK-only
264 > * SKILLS (Claude's built-in slash commands like `/init`) are NOT mixed in
265 > * among the editable disk skills — instead they appear, read-only, in the
266 > * separate "Built-in" skills container this function appends. The SDK's
267 > * built-in default agent is hidden. Rules (CLAUDE.md + `.claude/rules`)
268 > * have no SDK counterpart and are always kept.
269 > *
270 > * The "Built-in" surfacing for BOTH agents and skills is decided here (the
271 > * single place that has the disk set and the optional `sdk` snapshot): built-in
272 > * agents merge into the agent set (selectable, `claude-internal:`); built-in
273 > * skills are a separate read-only container appended to the result.
274 > */
275 > export function buildDiscoveredCustomizations(
276 discovered: readonly (IParsedAgent | IParsedSkill | IParsedRule)[],
277 mcpServers: readonly McpServerCustomization[],
419 return withBuiltinSkills(mapDiscoveredCustomizations(entries, servers, hooks, visiblePlugins, workingDirectory, userHome));
420 }
422 > /**
423 > * The customization-source subpaths under a `.claude` directory. Only edits
424 > * to these should force a re-scan. Everything else under `.claude` is Claude
425 > * SDK runtime churn — `history.jsonl`, `projects/` (per-message transcripts),
426 > * `tasks/`, `file-history/`, `sessions/`, `shell-snapshots/`, `backups/`,
427 > * `session-env/`, `statsig`, and assorted `*-cache.json` files — all of which
428 > * the SDK rewrites constantly during a turn. Triggering on those produced a
429 > * storm of `SessionCustomizationsChanged` envelopes (thousands per session),
430 > * so the watcher deliberately triggers on this allowlist only.
431 > */
432 > const CLAUDE_CUSTOMIZATION_SUBPATHS: readonly string[] = Object.freeze([
433 > 'agents',
434 > 'skills',
435 > 'commands',
436 > 'rules',
437 > 'plugins',
438 > 'CLAUDE.md',
439 > 'settings.json',
440 > 'settings.local.json',
441 > ]);
442 >
443 > /**
444 > * Watches a session's on-disk Claude customization sources and fires
445 > * {@link onDidChange} (debounced) whenever any of them is created, edited,
446 > * or removed, so the workbench re-fetches `getSessionCustomizations`.
447 > *
448 > * Watched roots:
449 > * - `<cwd>/.claude` and `<userHome>/.claude` (recursive) — cover the
450 > * agents / skills / commands trees, the `.claude/rules` + `.claude/CLAUDE.md`
451 > * instruction sources, plus the inline `settings.json` MCP config.
452 > * - `<cwd>` (non-recursive) — watched to catch the sibling `.mcp.json` and
453 > * the root `CLAUDE.md` / `CLAUDE.local.md` memory files.
454 > *
455 > * The recursive `.claude` watches keep OS-level watcher count low, but the
456 > * change *triggers* are narrowed to {@link CLAUDE_CUSTOMIZATION_SUBPATHS} (and
457 > * the specific memory / `.mcp.json` files) so the SDK's high-frequency runtime
458 > * writes elsewhere under `.claude` (and unrelated edits in the workspace root)
459 > * don't force a re-scan.
460 > */
461 > export class ClaudeCustomizationWatcher extends Disposable {
462 >
463 > private static readonly DEBOUNCE_MS = 300;
464 >
465 > readonly onDidChange: Event<void>;
466 >
467 > constructor(
468 workingDirectory: URI | undefined,
469 userHome: URI,