dispose() {
if (this.requestId) {
}
this.cancellationTokenSource.dispose();
}
Frontier kind: Code frontier
unlabeled · c_a10dde441b65
34 tests · 72005 LOC · 309 files · introduces 0 tests · 274 LOC · 5 files
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.
Every exact file and test below is linked only from the concept that introduces it.
mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/chat/test/common/chatService/chatService.test|title=ChatService untitled session materialization is idempotent/serialized (avoids duplicate sessions) a failed materialization does not poison the latch (retry re-attempts)|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/workbench/contrib/chat/test/common/chatService/chatService.test|title=ChatService untitled session materialization is idempotent/serialized (avoids duplicate sessions) a load failure after alias registration does not poison the late-send re-target|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/lifecycle.test|title=Lifecycle Action bar has broken accessibility #100273|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/lifecycle.test|title=Lifecycle dispose disposable array|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/charCode.test|title=CharCode has good values|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/path.test|title=Paths (Node Implementation) path|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/uri.test|title=URI File paths containing apostrophes break URI parsing and cannot be opened #276075|occurrence=1mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/base/test/common/uri.test|title=URI URI#file, win-speciale|occurrence=1Every collected test enters the hierarchy at exactly one concept.
No tests are introduced at this concept. Its intent tests are introduced by other concepts.
Every collected source range enters the hierarchy at exactly one concept.
5 files ranked by introduced lines: 274 introduced LOC across 48 ranges. Expand a file to inspect source; the > gutter marks introduced lines.
dispose() {
if (this.requestId) {
}
this.cancellationTokenSource.dispose();
}
}
}
// Collect hooks from hook .json files
const collectHooks = async (): Promise<{ hooks: ChatRequestHooks | undefined; hasDisabledClaudeHooks: boolean }> => {
let collectedHooks: ChatRequestHooks | undefined;
let hasDisabledClaudeHooks = false;
try {
const hooksInfo = await this.promptsService.getHooks(token);
if (hooksInfo) {
collectedHooks = hooksInfo.hooks;
hasDisabledClaudeHooks = hooksInfo.hasDisabledClaudeHooks;
}
this.logService.warn('[ChatService] Failed to collect hooks:', error);
}
// Merge hooks from the selected custom agent's frontmatter (if any)
const agentName = options?.modeInfo?.modeInstructions?.name;
if (agentName) {
try {
const agents = await this.promptsService.getCustomAgents(token);
}
}
};
// Collect automatic instructions (.instructions.md, skills, etc.)
const collectInstructions = async (): Promise<IChatRequestVariableEntry[]> => {
const ctx = options?.instructionContext;
if (!ctx) {
return [];
}
// When the extension is responsible for instruction collection, skip the core path entirely.
if (this.configurationService.getValue<boolean>(ChatConfiguration.CollectInstructionsInExtension) === true) {
// resolution can see them. We filter them back out below
// to return only the entries that were newly added.
const computer = this.instantiationService.createInstance(ComputeAutomaticInstructions, ctx.modeKind, ctx.enabledTools, ctx.enabledSubAgents, getChatSessionType(sessionResource));
await computer.collect(variableSet, token);
// Return only the entries that were added by instruction collection
const originalIds = new Set((options?.attachedContext ?? []).map(v => v.id));
chatServiceImpl.ts
return variableSet.asArray().filter(v => !originalIds.has(v.id));
} catch (err) {
this.logService.error('[ChatService] Failed to collect instructions:', err);
return [];
markChat(sessionResource, ChatPerfMark.DidCollectInstructions);
}
const stopWatch = new StopWatch(false);
store.add(token.onCancellationRequested(() => {
this.trace('sendRequest', `Request for session ${model.sessionResource} was cancelled`);
if (!request) {
model.cancelRequest(request);
try {
let rawResult: IChatAgentResult | null | undefined;
let agentOrCommandFollowups: Promise<IChatFollowup[] | undefined> | undefined = undefined;
if (agentPart || (defaultAgent && !commandPart)) {
// --- Step 1: Create the request model immediately (before any awaits) ---
chatServiceImpl.ts
// This fires RequestUiUpdated synchronously so the user sees their message right away.
const initialAgent = agentPart?.agent ?? defaultAgent;
const initialCommand = agentSlashCommandPart?.command;
const initVariableData: IChatRequestVariableData = { variables: [] };
request = model.addRequest(parsedRequest, initVariableData, attempt, options?.modeInfo, initialAgent, initialCommand, options?.confirmation, options?.locationData, options?.attachedContext, undefined, options?.userSelectedModelId, options?.userSelectedTools?.get(), undefined, options?.isSystemInitiated, options?.systemInitiatedLabel, options?.terminalExecutionId, isTerminalCommand);
const thisRequest = request;
completeResponseCreated();
// --- Step 2: Collect hooks + instructions in parallel (after UI is shown) ---
const [hooksResult, instructionEntries] = await Promise.all([
collectHooks(),
collectInstructions(),
]);
const collectedHooks = hooksResult.hooks;
const hasDisabledClaudeHooks = hooksResult.hasDisabledClaudeHooks;
// --- Step 3: Merge instructions and resolved variables into variableData ---
const allContext = this.prepareContext(request.attachedContext);
if (instructionEntries.length > 0) {
allContext.push(...instructionEntries);
}
// Store only non-instruction variables on the model.
// Automatically-added promptText entries (~33 KB each) are
// ephemeral — re-collected every turn, never rendered in
// the UI, and not needed in serialized session history.
const storedVariables = allContext.filter(v => !(isPromptTextVariableEntry(v) && v.automaticallyAdded));
model.updateRequest(request, { variables: storedVariables });
// The full set (including instructions) is passed to the
// agent request only — not stored on the request model.
let variableData: IChatRequestVariableData = { variables: allContext };
// Merge resolved variables (e.g. images from directories) for the
// agent request only - they are not stored on the request model.
if (options?.resolvedVariables?.length) {
variableData = { variables: [...variableData.variables, ...options.resolvedVariables] };
}
const promptTextResult = getPromptText(request.message);
variableData = updateRanges(variableData, promptTextResult.diff); // TODO bit of a hack
const message = promptTextResult.message;
// --- Step 4: Build the agent request object ---
const buildAgentRequest = (agent: IChatAgentData, command?: IChatAgentCommand, enableCommandDetection?: boolean, isParticipantDetected?: boolean): IChatAgentRequest => {
const agentRequest: IChatAgentRequest = {
sessionResource: model.sessionResource,
requestId: thisRequest.id,
agentId: agent.id,
message,
command: command?.name,
variables: variableData,
enableCommandDetection,
isParticipantDetected,
attempt,
location,
locationData: thisRequest.locationData,
acceptedConfirmationData: options?.acceptedConfirmationData,
rejectedConfirmationData: options?.rejectedConfirmationData,
agentHostSessionConfig: options?.agentHostSessionConfig,
userSelectedModelId: options?.userSelectedModelId,
modelConfiguration: options?.userSelectedModelConfiguration ?? (options?.userSelectedModelId ? this.languageModelsService.getModelConfiguration(options.userSelectedModelId) : undefined),
userSelectedTools: options?.userSelectedTools?.get(),
modeInstructions: options?.modeInfo?.modeInstructions,
permissionLevel: options?.modeInfo?.permissionLevel,
editedFileEvents: thisRequest.editedFileEvents,
hooks: collectedHooks,
hasHooksEnabled: !!collectedHooks && Object.values(collectedHooks).some(arr => arr.length > 0),
isSystemInitiated: options?.isSystemInitiated,
workingDirectory: model.workingDirectory,
};
let isInitialTools = true;
store.add(autorun(reader => {
const tools = options?.userSelectedTools?.read(reader);
if (isInitialTools) {
isInitialTools = false;
return;
}
this.chatAgentService.setRequestTools(agent.id, request.id, tools);
// in case the request has not been sent out yet:
agentRequest.userSelectedTools = tools;
}
return agentRequest;
};
// --- Step 5: Participant detection ---
if (
this.configurationService.getValue('chat.detectParticipant.enabled') !== false &&
this.chatAgentService.hasChatParticipantDetectionProviders() &&
!agentPart &&
!commandPart &&
options?.modeInfo?.kind !== ChatModeKind.Edit &&
!options?.agentIdSilent
// We have no agent or command to scope history with, pass the full history to the participant detection provider
const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, location, defaultAgent.id);
}
}
const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!;
const command = detectedCommand ?? agentSlashCommandPart?.command;
await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`);
// Recompute history in case the agent or command changed
const history = this.getHistoryEntriesFromModel(requests, location, agent.id);
const requestProps = buildAgentRequest(agent, command, enableCommandDetection, !!detectedAgent);
this.generateInitialChatTitleIfNeeded(model, requestProps, defaultAgent, token);
const pendingRequest = this._pendingRequests.get(sessionResource);
if (pendingRequest) {
store.add(autorun(reader => {
const yieldRequested = pendingRequest.yieldRequested.read(reader);
if (request) {
this.chatAgentService.setYieldRequested(agent.id, request.id, yieldRequested);
}
}));
pendingRequest.requestId ??= requestProps.requestId;
if (pendingRequest.requestId) {
this.telemetryService.publicLog2<ChatPendingRequestChangeEvent, ChatPendingRequestChangeClassification>(ChatPendingRequestChangeEventName, { action: 'add', source: 'sendRequestId', requestId: pendingRequest.requestId, chatSessionId: chatSessionResourceToId(sessionResource) });
}
}
// Check for disabled Claude Code hooks and notify the user once per workspace.
// Only set the flag when actually showing the hint, so the setup agent flow
// (which may resend requests) doesn't consume the flag before the real request runs.
const disabledClaudeHooksDismissedKey = 'chat.disabledClaudeHooks.notification';
if (hasDisabledClaudeHooks && !this.storageService.getBoolean(disabledClaudeHooksDismissedKey, StorageScope.WORKSPACE)) {
this.storageService.store(disabledClaudeHooksDismissedKey, true, StorageScope.WORKSPACE, StorageTarget.USER);
progressCallback([{ kind: 'disabledClaudeHooks' }]);
}
// MCP autostart: only run for native VS Code sessions (sidebar, new editors) but not for extension contributed sessions that have inputType set.
if (model.canUseTools) {
const autostartResult = new ChatMcpServersStarting(this.mcpService.autostart(token));
if (!autostartResult.isEmpty) {
}
}
const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token);
rawResult = agentResult;
agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken);
request.response?.complete();
}
store.dispose();
}
};
let shouldProcessPending = false;
private generateInitialChatTitleIfNeeded(model: ChatModel, request: IChatAgentRequest, defaultAgent: IChatAgentData, token: CancellationToken): void {
// Generate a title only for the first request, and only via the default agent.
chatServiceImpl.ts
// Use a single-entry history based on the current request (no full chat history).
if (model.getRequests().length !== 1 || model.customTitle) {
return;
}
};
void generate();
private prepareContext(attachedContextVariables: IChatRequestVariableEntry[] | undefined): IChatRequestVariableEntry[] {
// "reverse", high index first so that replacement is simple
attachedContextVariables.sort((a, b) => {
// If either range is undefined, sort it to the back
if (!a.range && !b.range) {
}
return b.range.start - a.range.start;
return attachedContextVariables;
}
private getHistoryEntriesFromModel(requests: IChatRequestModel[], location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] {
const agent = this.chatAgentService.getAgent(forAgentId);
for (const request of requests) {
if (!request.response) {
continue;
history.push({ request: historyRequest, response: toChatHistoryContent(request.response.response.value), result: request.response.result ?? {} });
}
return history;
}
async removeRequest(sessionResource: URI, requestId: string): Promise<void> {
}
const lines = text.split('\n');
const codeBlockLanguages: string[] = [];
let codeBlockState: undefined | { readonly delimiter: string; readonly languageId: string };
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (codeBlockState) {
if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) {
codeBlockLanguages.push(codeBlockState.languageId);
codeBlockState = undefined;
}
const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/);
if (match) {
codeBlockState = { delimiter: match[2], languageId: match[3] };
}
}
return codeBlockLanguages;
}
export class ChatRequestTelemetry {
complete({ timeToFirstProgress, totalTime, result, requestType, request, detectedAgent }: {
totalTime: number | undefined;
result: ChatProviderInvokedEvent['result'];
requestType: ChatProviderInvokedEvent['requestType'];
// Should rearrange so these 2 can be in the constructor
request: ChatRequestModel;
detectedAgent: IChatAgentData | undefined;
}) {
if (this.isComplete) {
return;
}
this.isComplete = true;
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
timeToFirstProgress,
totalTime,
result,
requestType,
requestId: request.id,
agent: detectedAgent?.id ?? this.opts.agent.id,
agentExtensionId: detectedAgent?.extensionId.value ?? this.opts.agent.extensionId.value,
slashCommand: this.opts.agentSlashCommandPart ? this.opts.agentSlashCommandPart.command.name : this.opts.commandPart?.slashCommand.command,
chatSessionId: chatSessionResourceToId(this.opts.sessionResource),
enableCommandDetection: this.opts.enableCommandDetection,
isParticipantDetected: !!detectedAgent,
location: this.opts.location,
citations: request.response?.codeCitations.length ?? 0,
numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length,
attachmentKinds: this.attachmentKindsForTelemetry(request.variableData),
model: this.resolveModelId(this.opts.options?.userSelectedModelId),
permissionLevel: this.opts.options?.modeInfo?.kind === ChatModeKind.Ask ? undefined : this.opts.options?.modeInfo?.permissionLevel,
chatMode: this.opts.options?.modeInfo?.telemetryModeName ?? this.opts.options?.modeInfo?.telemetryModeId,
sessionType: getChatSessionTypeForTelemetry(this.opts.sessionResource),
harness: getHarnessForTelemetry(this.opts.sessionResource),
});
}
private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] {
return variableData.variables.map(v => {
if (v.kind === 'implicit') {
return 'implicit';
}
}
}
private resolveModelId(userSelectedModelId: string | undefined): string | undefined {
return userSelectedModelId && this.languageModelsService.lookupLanguageModel(userSelectedModelId)?.id;
chatServiceTelemetry.ts
}
}
function getChatSessionTypeForTelemetry(sessionResource: URI): string {
chatServiceTelemetry.ts
const sessionType = getChatSessionType(sessionResource);
// Collapse the high-cardinality, host-specific authority into a single
// value (the authority is PII); the harness is reported separately.
return isRemoteAgentHostSessionType(sessionType) ? 'remote-agent-host' : sessionType;
}
/**
* telemetry gap #2 in #8209. Undefined for non-remote sessions.
*/
function getHarnessForTelemetry(sessionResource: URI): string | undefined {
chatServiceTelemetry.ts
return parseRemoteAgentHostHarness(getChatSessionType(sessionResource));
}
public set variableData(v: IChatRequestVariableData) {
this._variableData = v;
}
public get confirmation(): string | undefined {
public get locationData(): IChatLocationData | undefined {
}
public get attachedContext(): IChatRequestVariableEntry[] | undefined {
}
public get editedFileEvents(): IChatAgentEditedFileEvent[] | undefined {
private readonly _canUseTools: boolean = true;
get canUseTools(): boolean {
}
private _disableBackgroundKeepAlive: boolean;
updateRequest(request: ChatRequestModel, variableData: IChatRequestVariableData) {
this._onDidChange.fire({ kind: 'changedRequest', request });
}
adoptRequest(request: ChatRequestModel): void {
export function updateRanges(variableData: IChatRequestVariableData, diff: number): IChatRequestVariableData {
variables: variableData.variables.map(v => ({
...v,
range: v.range && {
endExclusive: v.range.endExclusive - diff
}
};
}
export function canMergeMarkdownStrings(md1: IMarkdownString, md2: IMarkdownString): boolean {
async invokeAgent(id: string, request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
const data = this._agents.get(id);
if (!data?.impl) {
throw new Error(`No activated agent with id "${id}"`);
}
markChat(request.sessionResource, ChatPerfMark.AgentDidInvoke);
return result;
setRequestTools(id: string, requestId: string, tools: UserSelectedTools): void {
setYieldRequested(id: string, requestId: string, value: boolean): void {
if (!data?.impl) {
return;
}
data.impl.setYieldRequested?.(requestId, value);
async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> {
hasChatParticipantDetectionProviders() {
}
async detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined> {
cancelToolCallsForRequest(requestId: string): void {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any