端到端流程:copilot 一次 AI 对话中 Prompt 是怎么注入的
源码版本 : VS Code 1.112.0 + GitHub Copilot Chat 0.40.1分析对象 : extension.js(Copilot 扩展)+ extensionHostProcess.js(VS Code 核心)文档定位 : 专注”一次对话请求从用户输入到 LLM 调用”的 prompt 注入全过程
全局流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 用户在 Chat 面板输入 → 发送消息 │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ VS Code 核心 (extensionHostProcess.js) │ │ │ │ ① Agent 发现 (IPromptsService) │ │ • 启动时/文件变化时,扫描所有注册路径的 .agent.md 文件 │ │ • 解析 YAML frontmatter → description, tools, model 等 │ │ • 注册为 Chat Participant(可通过 @name 调用) │ │ │ │ ② 用户消息路由 │ │ • 判断是否 @agent 调用 → 路由到对应 participant handler │ │ • 默认 → 路由到 Copilot 扩展的 main handler │ │ │ │ ③ Instructions 发现 (IPromptsService) │ │ • 扫描 .github/instructions/, ~/.copilot/instructions/ 等 │ │ • 通过 VS Code API 提供给扩展 │ │ │ │ ④ Skills 发现 │ │ • 扫描 .github/skills/, ~/.copilot/skills/ 等 │ │ • 匹配 SKILL.md 文件 │ └──────────────────────┬───────────────────────────────────────────┘ │ VS Code Extension API ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Copilot 扩展 (extension.js) — 请求处理 │ │ │ │ ⑤ Instructions 收集 (CustomInstructionsService) │ │ • getAgentInstructions() │ │ → stat(.github/copilot-instructions.md) → 读取内容 │ │ • fetchInstructionsFromSetting() │ │ → 读 VS Code settings 中的 instructions 配置 │ │ │ │ ⑥ Prompt 树渲染 (PromptElement 体系) │ │ • SystemMessage: 基础 system prompt │ │ • Instructions 组件 (Fi): 合并所有 instructions │ │ • History 组件: 历史对话 │ │ • UserMessage: 当前用户消息 │ │ • ToolCallRounds: 工具调用结果 │ │ • 附件/代码片段/变量引用等 │ │ │ │ ⑦ Token 预算管理 — 根据模型上限裁剪 prompt 树 │ │ │ │ ⑧ 发送请求到 LLM API (CAPI / Azure / 直连) │ └──────────────────────────────────────────────────────────────────┘
第一阶段:Agent 发现与注册(启动时 + 实时监听) 1.1 发生时机 不是每次对话时才扫描 ,而是在 VS Code 启动时 + 文件变化时:
启动时:IPromptsService 通过 findFiles() glob 模式扫描所有注册路径
运行中:FileSystemWatcher 监听注册路径中的文件增删改,动态更新
1.2 扫描路径(直接来自源码 wj 数组) 1 2 3 4 5 6 7 var wj = [ {path : ".github/agents" , source : "github-workspace" , storage : "local" }, {path : ".claude/agents" , source : "claude-workspace" , storage : "local" }, {path : "~/.claude/agents" , source : "claude-personal" , storage : "user" }, {path : "~/.copilot/agents" , source : "copilot-personal" , storage : "user" }, ];
1.3 文件识别规则(Dj() 函数) VS Code 核心通过 Dj() 函数判断每个发现的文件是什么类型:
1 2 3 4 5 6 7 8 9 10 function Dj (uri ) { let filename = basename (uri.path ); if (filename.endsWith (".agent.md" )) return "agent" ; if (filename.endsWith (".md" ) && filename !== "README.md" && isInAgentsDir (uri)) return "agent" ; }
关键细节:agents 目录中不强制要求 .agent.md 后缀 ,任意 .md 文件(除 README.md)都会被识别为 agent。
1.4 YAML Frontmatter 解析 每个 agent 文件的 --- 包裹的 YAML header 被解析为元数据:
1 2 3 4 5 6 7 --- description: "..." tools: [... ] model: "..." --- (正文内容 → 注入为 system prompt)
1.5 注册为 Chat Participant 发现的 agent 文件通过 $registerAgent() IPC 调用注册到 VS Code 的 Chat Participant 系统中。
注册后:
用户可以通过 @agent-name 在对话中直接调用
出现在 Chat 面板的 agent 选择列表中
Copilot 扩展可以通过 VS Code API 查询可用的 agents
第二阶段:用户发送消息 → 请求路由 2.1 用户消息进入 用户在 Chat 面板输入消息并发送(或通过 inline chat / terminal chat 等入口)。
2.2 路由决策 1 2 3 4 5 6 7 用户消息 "@llama-snapdragon-basic 编译项目" │ ├── 检测到 @agent 前缀 → 路由到该 agent 的 handler │ └── agent .md 正文内容作为 system prompt │ └── 无 @agent → 路由到 Copilot 默认 handler (main agent) └── copilot-instructions.md 作为 instructions 注入
2.3 Subagent 模式(MANDATORY delegation) 当 main agent 收到消息但 copilot-instructions.md 中有 MANDATORY delegation 指令时:
1 2 3 4 5 6 7 8 9 10 11 12 13 用户消息 "编译项目"(无 @ 前缀) │ ▼ Main Agent 收到消息 │ ├── 读取 copilot-instructions.md → 发现 MANDATORY delegation │ "这些任务必须委托给 llama-snapdragon-basic" │ ├── 语义匹配 "编译项目" vs agent description 中的触发词 │ description: "...中文触发词:编译项目、构建项目..." │ └── 调用 runSubagent(agentName: "llama-snapdragon-basic", prompt: "编译项目") └── subagent 接管:以 llama-snapdragon-basic.agent.md 正文为 system prompt
第三阶段:Instructions 收集(Copilot 扩展内部) 这是 Copilot 扩展自己独立实现的逻辑,与 VS Code 核心并行。
3.1 CustomInstructionsService (类名 Nee) 这是 Copilot 内部的 instructions 管理服务,实现了 ICustomInstructionsService 接口。
构造函数中注册的 3 路匹配器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Nee extends Disposable { constructor (configurationService, envService, workspaceService, fileSystemService, promptPathRepresentationService, logService, extensionService, runCommandExecutionService ) { this ._matchInstructionLocationsFromConfig = lazy (() => { }); this ._matchInstructionLocationsFromExtensions = lazy (() => { }); this ._matchInstructionLocationsFromSkills = lazy (() => { }); } }
3.2 getAgentInstructions() — 读取 copilot-instructions.md 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 async getAgentInstructions ( ) { let results = []; if (!this .configurationService .getConfig (UseInstructionFiles )) return results; for (let folder of this .workspaceService .getWorkspaceFolders ()) { try { let uri = joinPath (folder, ".github/copilot-instructions.md" ); let stat = await this .fileSystemService .stat (uri); if (stat.type === FileType .File ) results.push (uri); } catch { } } return results; }
关键行为 :
使用 stat() 而非 findFiles(),不走 glob 不走缓存
每次 AI 请求都会调用,文件增删即时反映
返回的是 URI 列表,实际读取在 Prompt 树渲染阶段
3.3 fetchInstructionsFromSetting() — 读取 settings 中的额外 instructions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 async fetchInstructionsFromSetting (settingKey ) { let results = [], inlineInstructions = []; let config = this .configurationService .inspectConfig (settingKey); await this .collectInstructionsFromSettings ([ config.workspaceFolderValue , config.workspaceValue , config.globalValue , ], usedFiles, inlineInstructions, results); return results; }
3.4 collectInstructionsFromSettings() — 处理 settings 值 settings 支持两种格式的 instructions:
1 2 3 4 5 6 7 8 "github.copilot.chat.codeGeneration.instructions" : [ { "file" : "coding-standards.md" , "language" : "typescript" } , { "text" : "Always use 4 spaces for indentation" } ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 async collectInstructionsFromSettings (levels, usedFiles, inlineResults, fileResults ) { for (let level of levels) { if (Array .isArray (level)) { for (let item of level) { if (isFileInstruction (item) && !usedFiles.has (item.file )) { usedFiles.add (item.file ); await this ._collectInstructionsFromFile (item.file , item.language , fileResults); } if (isTextInstruction (item) && !usedTexts.has (item.text )) { usedTexts.add (item.text ); inlineResults.push ({ instruction : item.text , languageId : item.language }); } } } } }
3.5 readInstructionsFromFile() — 实际读取文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async readInstructionsFromFile (uri, languageId ) { try { let bytes = await this .fileSystemService .readFile (uri); let content = new TextDecoder ().decode (bytes).trim (); if (!content) { this .logService .debug (`Instructions file is empty: ${uri} ` ); return ; } return { kind : 0 , content : [{ instruction : content, languageId : languageId }], reference : uri }; } catch { this .logService .debug (`Instructions file not found: ${uri} ` ); return ; } }
3.6 isExternalInstructionsFile() — URI 来源判断 对于一个给定的文件 URI,判断它是否应该被当作 instructions 处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 async isExternalInstructionsFile (uri ) { if (uri.scheme === "vscode-chat-internal" ) return true ; if (uri.scheme === "vscode-userdata" && uri.path .endsWith (".instructions.md" )) return true ; if (this ._matchInstructionLocationsFromConfig .get ()(uri)) return true ; if (this ._matchInstructionLocationsFromExtensions .get ()(uri)) return true ; if (this ._matchInstructionLocationsFromSkills .get ()(uri)) return true ; return this .isExtensionPromptFile (uri); }
这就是为什么 User/prompts/ 下的 .agent.md 对 Copilot 无效 : 第 2 条规则明确只匹配 .instructions.md 后缀。
第四阶段:Prompt 树构建与渲染 Copilot 使用了一个声明式的 Prompt 树 架构(类似 React 的组件树),每个 prompt 元素是一个 PromptElement 子类。
4.1 Prompt 树的顶层结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 ┌──────────────── Prompt Root ────────────────┐ │ │ │ ┌─ SystemMessage (priority: 最高) ────────┐ │ │ │ "You are an AI programming assistant" │ │ │ │ + 模型/能力相关的基础 system prompt │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌─ Instructions 组件 (Fi) ──────────────────┐ │ │ │ ┌─ copilot-instructions.md 内容 ────┐ │ │ │ │ │ wrapped in <attachment> tag │ │ │ │ │ └───────────────────────────────────┘ │ │ │ │ ┌─ settings instructions ───────────┐ │ │ │ │ │ from CodeGeneration/TestGen/etc │ │ │ │ │ └───────────────────────────────────┘ │ │ │ │ ┌─ chat variable instructions ──────┐ │ │ │ │ │ from #instructions 引用 │ │ │ │ │ └───────────────────────────────────┘ │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌─ Agent/Skills/Memory ─────────────────────┐ │ │ │ • Agent MD 正文 (if @agent 调用) │ │ │ │ • Skill 内容 (if skill 匹配) │ │ │ │ • Repo Memory (if enabled) │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌─ Context 组件 ────────────────────────────┐ │ │ │ • 打开的文件内容/选中的代码 │ │ │ │ • Workspace labels (项目技术栈识别) │ │ │ │ • Notebook 变量 │ │ │ │ • 引用的文件/符号 │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌─ History 组件 (priority: 900) ────────────┐ │ │ │ 历史对话轮次: │ │ │ │ • UserMessage + chatVariables │ │ │ │ • AssistantMessage │ │ │ │ • ToolCallRounds + results │ │ │ └───────────────────────────────────────────┘ │ │ │ │ ┌─ UserMessage (当前轮) ────────────────────┐ │ │ │ 用户本次输入的消息 │ │ │ │ + 附件 (图片/文件/代码等) │ │ │ └───────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────┘
4.2 Instructions 组件 (Fi) 的渲染逻辑 Fi 是 Copilot 扩展中负责收集和渲染所有 instructions 的核心组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class Fi extends PromptElement { async render (state, context ) { let { includeCodeGenerationInstructions, includeTestGenerationInstructions, includeCodeFeedbackInstructions, includeCommitMessageGenerationInstructions, includePullRequestDescriptionGenerationInstructions } = this .props ; let chunks = []; if (includeCodeGenerationInstructions !== false ) { if (this .props .chatVariables ) { for (let variable of this .props .chatVariables ) { if (isInstruction (variable)) { if (isString (variable.value )) { chunks.push (TextChunk (variable.value )); } else if (isUri (variable.value )) { let element = await this .createElementFromURI (variable.value ); if (element && !seen.has (element.content )) chunks.push (element.chuck ); } } } } let agentInstructions = await this .customInstructionsService .getAgentInstructions (); for (let uri of agentInstructions) { if (!seenUris.has (uri)) { seenUris.add (uri); let element = await this .createElementFromURI (uri); if (element && !seen.has (element.content )) chunks.push (element.chuck ); } } } let settingsInstructions = []; if (includeCodeGenerationInstructions !== false ) settingsInstructions.push (...await fetchInstructionsFromSetting (CodeGenerationInstructions )); if (includeTestGenerationInstructions) settingsInstructions.push (...await fetchInstructionsFromSetting (TestGenerationInstructions )); if (includeCodeFeedbackInstructions) settingsInstructions.push (...await fetchInstructionsFromSetting (CodeFeedbackInstructions )); for (let instruction of settingsInstructions) { let element = this .createInstructionElement (instruction); if (element) chunks.push (element); } if (chunks.length === 0 ) return ; return ( <> "When generating code, please follow these user provided coding instructions." {isMultiRoot && " This is a multi-root workspace..."} " You can ignore an instruction if it contradicts a system message." <br /> <tag name ="instructions" > ...chunks </tag > </> ); } }
4.3 createElementFromURI() — 将文件 URI 转为 Prompt 元素 这个函数是所有 instruction 文件从磁盘到 prompt 的桥梁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 async createElementFromURI (uri, toolReferences ) { try { let bytes = await this .fileSystemService .readFile (uri); let content = new TextDecoder ().decode (bytes); if (toolReferences?.length > 0 ) content = await this .promptVariablesService .resolveToolReferencesInPrompt (content, toolReferences); let attrs = { filePath : this .promptPathRepresentationService .getFilePath (uri) }; if (this .workspaceService .getWorkspaceFolders ().length > 1 ) { let folder = this .workspaceService .getWorkspaceFolder (uri); if (folder) attrs.workspaceFolder = this .workspaceService .getWorkspaceFolderName (folder); } return { chuck : ( <tag name ="attachment" attrs ={attrs} > <references value ={[new PromptReference (uri , content )]} /> <TextChunk > {content}</TextChunk > </tag > ), content : content }; } catch { this .logService .debug (`Instruction file not found: ${uri} ` ); return ; } }
4.4 最终注入到 LLM 的 prompt 结构示例 以一次典型对话为例,注入到 LLM API 的 messages 大致如下:
1 2 3 4 5 6 7 8 9 10 [ { "role" : "system" , "content" : "You are an AI programming assistant...\n\n<instructions>\n\nWhen generating code, please follow these user provided coding instructions.\n\n<attachment filePath=\".github/copilot-instructions.md\">\n# Copilot Instructions for llama.cpp\n\n## Subagent Delegation (MANDATORY)\n> IMPORTANT: This repository targets Snapdragon...\n</attachment>\n\n</instructions>" } , { "role" : "user" , "content" : "编译项目" } ]
第五阶段:Token 预算管理与裁剪 5.1 TokenLimit 元素 Prompt 树中的 TokenLimit 元素定义了子树的最大 token 上限:
1 2 3 4 5 <TokenLimit max={32768 }> <PrioritizedList priority ={900} descending ={false} > {历史对话轮次...} </PrioritizedList > </TokenLimit >
5.2 裁剪策略 当总 token 超出模型限制时,按优先级裁剪:
优先保留 : SystemMessage、Instructions、当前 UserMessage
可裁剪 : 历史对话(从最早开始裁剪)、代码上下文(用 summarization 压缩)
智能压缩 : 代码文件用 _computeSummarization() 方法折叠不重要的部分,插入 /* Lines X-Y omitted */ 注释
5.3 代码上下文的 summarization(Qxt 类) 1 2 3 4 5 6 _computeSummarization ( ) { }
第六阶段:Code Review 请求中的 Instructions 注入 Code Review 有一个独立的 instructions 注入路径(函数 ioa),与对话中的 instructions 注入并行但独立:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 async function ioa (customInstructionsService, workspaceService, context, languageMap, startId ) { let guidelines = []; let id = startId; let uris = await customInstructionsService.getAgentInstructions (); for (let uri of uris) { let instruction = await customInstructionsService.fetchInstructionsFromFile (uri); if (instruction) { let relativePath = workspaceService.asRelativePath (uri); for (let content of instruction.content ) { if (content.languageId && !languageMap.has (content.languageId )) continue ; let filePatterns = content.languageId ? Array .from (languageMap.get (content.languageId )) : ["*" ]; guidelines.push ({ type : "github.coding_guideline" , id : String (id), data : { id : id, type : "coding-guideline" , name : `Instruction from ${relativePath} ` , description : content.instruction , filePatterns : filePatterns } }); id++; } } } let settingConfigs = [ { config : CodeGenerationInstructions , name : "Code Generation Instruction" }, ]; for (let { config, name } of settingConfigs) { let instructions = await customInstructionsService.fetchInstructionsFromSetting (config); for (let instruction of instructions) { for (let content of instruction.content ) { guidelines.push ({ type : "github.coding_guideline" , }); id++; } } } return guidelines; }
注入格式:instructions 被包装为 coding_guideline 类型,与对话中的 <instructions> tag 包装不同。
第七阶段:Remote Agent(远程 Agent) Copilot 还支持从 GitHub 服务端加载远程 agent:
1 2 3 4 5 6 7 8 9 10 11 12 let response = await capiClientService.makeRequest ({ method : "GET" , headers : { Authorization : `Bearer ${token} ` } }, { type : RemoteAgent }); let agents = JSON .parse (response).agents ;for (let agent of agents) { _oe.set (agent.slug , this .registerAgent (agent)); }
远程 agent 需要用户授权,授权状态存储在 globalState:
1 2 let key = `copilot.agent.${slug} .authorized` ;
第八阶段:Workspace Labels(工作区技术栈识别) Copilot 会自动检测项目的技术栈,作为上下文信息注入 prompt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class BasicWorkspaceLabels { initIndicators ( ) { this .addIndicator ("package.json" , "javascript" , "npm" ); this .addIndicator ("tsconfig.json" , "typescript" ); this .addIndicator ("CMakeLists.txt" , "c++" , "cmake" ); this .addIndicator ("requirements.txt" ,"python" , "pip" ); this .addIndicator ("Cargo.toml" , "rust" , "cargo" ); this .addIndicator ("go.mod" , "go" , "go.mod" ); } collectCMakeListsTxtIndicators (content ) { } collectPackageJsonIndicators (content ) { } }
第九阶段:Agent Memory(仓库记忆) 如果启用了 Copilot Memory,还会从 GitHub 服务端获取仓库级别的记忆信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 async getRepoMemories (limit = 10 ) { if (!await this .checkMemoryEnabled ()) return ; let nwo = await this .getRepoNwo (); let response = await capiClientService.makeRequest ({ method : "GET" , headers : { Authorization : `Bearer ${token} ` } }, { type : CopilotAgentMemory , repo : nwo, action : "recent" , limit : limit }); let memories = response.json () .filter (isValidMemory) .map (m => ({ subject : m.subject , fact : m.fact , citations : m.citations , reason : m.reason , category : m.category })); return memories; }
Memory 同样会被注入到 prompt 中,帮助 LLM 在后续对话中”记住”仓库的惯例和上下文。
完整调用时序图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 时间线 →→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→ [VS Code 启动] │ ├─ IPromptsService 初始化 │ ├─ findFiles(".github/agents/*.md") → 发现 agent 文件 │ ├─ findFiles("~/.copilot/agents/*.md") → 发现用户级 agent │ ├─ 解析 YAML frontmatter │ └─ $registerAgent() → 注册为 Chat Participant │ ├─ FileSystemWatcher 启动 │ └─ 监听 .github/agents/, ~/.copilot/agents/ 等目录 │ └─ WorkspaceLabels 收集 └─ 扫描 package.json, CMakeLists.txt 等 → 识别技术栈 [用户发送消息] │ ├─ 路由判断: @agent 调用 or 默认 handler │ ├─ Copilot 扩展接管 │ │ │ ├─ CustomInstructionsService.getAgentInstructions() │ │ └─ stat(".github/copilot-instructions.md") → 存在 → 返回 URI │ │ │ ├─ CustomInstructionsService.fetchInstructionsFromSetting() │ │ └─ 读 settings 三个级别: folder → workspace → global │ │ │ ├─ AgentMemoryService.getRepoMemories() (if enabled) │ │ └─ GET /copilot-agent-memory → 获取仓库记忆 │ │ │ └─ Prompt 树渲染 │ ├─ SystemMessage: 基础 system prompt │ ├─ Fi (Instructions): 合并所有 instructions │ │ ├─ readFile(copilot-instructions.md) → 读取内容 │ │ ├─ 包装为 <attachment filePath="..."> tag │ │ └─ settings instructions → 包装为 TextChunk │ ├─ History: 历史对话轮次 │ ├─ UserMessage: 当前消息 │ └─ 代码上下文 / 附件 / 变量引用 │ ├─ Token 预算裁剪 │ └─ 超出限制 → 按优先级裁剪历史 → 压缩代码上下文 │ └─ 发送 HTTP 请求到 LLM API └─ messages: [system, ...history, user]
关键 Settings 开关对注入流程的影响
Setting
默认值
影响
github.copilot.chat.codeGeneration.useInstructionFiles
true
关闭 → getAgentInstructions() 直接返回空,copilot-instructions.md 不被读取
chat.useAgentsMdFile
true
关闭 → .agent.md 文件不被 VS Code 核心发现
chat.useAgentSkills
true
关闭 → Skills 目录不被扫描
chat.agentFilesLocations
(默认 4 路径)
自定义 → 可添加或排除 agent 扫描路径
chat.instructionsFilesLocations
(默认 4 路径)
自定义 → 可添加或排除 instructions 扫描路径
踩过的坑与注意事项 1. stat() vs FileSystemWatcher
copilot-instructions.md:每次请求 stat() 检查,即时生效
.agent.md 文件:FileSystemWatcher 监听,理论上即时生效,但极端情况可能需要几秒延迟
2. 去重逻辑 Instructions 组件 (Fi) 在渲染时做了双重去重:
seenUris(Set):按 URI 去重,同一文件不重复读取
seen(Set):按内容去重,不同路径但相同内容也只注入一次
3. 语言过滤 Code Review 的 instructions 注入(ioa 函数)支持按编程语言过滤:
如果 instruction 指定了 languageId,只对匹配语言的文件生效
如果未指定 languageId,则匹配所有文件(filePatterns: ["*"])
4. 多根工作区 多根工作区时:
getAgentInstructions() 会遍历所有工作区文件夹 ,每个文件夹都独立检查
createElementFromURI() 会在 <attachment> tag 中额外标注 workspaceFolder 属性
Instructions 组件会添加提示:”This is a multi-root workspace. Apply each set of instructions to the folder it belongs to.”
5. 冲突处理 当用户 instructions 与系统消息矛盾时,Fi 组件会默认添加提示:
1 "You can ignore an instruction if it contradicts a system message."
这意味着系统消息的优先级高于用户 instructions 。