VS Code 本地 Mock Copilot Policy Server 实战:模拟、代理接入与失败闭环测试企业 Copilot 策略端点
这篇技术指南围绕 VS Code 仓库中的开发调试工具 mock-policy-server(位于 scripts/mock-policy-server)展开,讲解如何在本机启动一个零运行时依赖的 Node 服务与 Web GUI,对 VS Code 账号体系 DefaultAccountService 依赖的四条 Copilot 企业策略端点做「模拟(mock)」或「透传(passthrough)」分流,并系统覆盖 GUI 接入、HTTP 控制 API、文件式策略部署与 fail-closed 刷新测试等完整工作流。读完本文,你将掌握如何在本地复现、观测并人为制造 Copilot 策略下发过程中的各种成功与故障场景,而无需真实企业服务器环境。
它解决的问题与适用边界
VS Code 从服务端拉取企业级 Copilot 策略(Managed Settings、Entitlements、Token 携带的策略标志、MCP Registry)时,访问的是 DefaultAccountService 定义的一组固定端点。真实环境里这些端点在 https://api.github.com 上,由企业配置驱动,难以在本地随意「制造」返回 404、500、畸形 JSON 甚至直接断开连接的响应来测试客户端行为。
mock-policy-server 正是一个仅供本地开发与联调使用的工具:它在 127.0.0.1:3000 启动一个本地 Node 服务并附赠 Web 控制台,把选定端点「留在本地伪造」,其余请求则原样转发给真实上游 API。它没有运行时依赖,也不随 VS Code 产品发布——根据 package.json 中定义的脚本即可确认它的启动入口:
"mock-policy-server": "node --experimental-strip-types scripts/mock-policy-server/server.ts"
它被挂在仓库的 scripts/ 开发目录下,服务端与浏览器端通过 Node 内置的 module.stripTypeScriptTypes() 直接消费 .ts 源码,全程无构建步骤。
四个被模拟的策略端点
端点定义集中在单一事实来源 scripts/mock-policy-server/endpoints.ts,该文件同时被 Node 服务(作为路由表与默认状态)和浏览器 GUI(作为标签页与预设下拉)以 UMD 风格导出共享。四个端点如下:
| 端点 ID | GUI 标签 | URL 路径 | product.json 键 |
默认行为 |
|---|---|---|---|---|
managedSettings |
Managed Settings | /copilot_internal/managed_settings |
managedSettingsUrl |
默认 mock(mockedByDefault: true) |
entitlements |
Entitlements | /copilot_internal/user |
entitlementUrl |
默认透传 |
token |
Token | /copilot_internal/v2/token |
tokenEntitlementUrl |
默认透传 |
mcpRegistry |
MCP Registry | /copilot/mcp_registry |
mcpRegistryDataUrl |
默认透传 |
端点 URL 前缀与实际地址来自 product.json 中 defaultChatAgent 段的真实配置,例如 "managedSettingsUrl": "https://api.github.com/copilot_internal/managed_settings"。这些路径之所以能被本工具接管,是因为它们处在一条系统代理规则之下,使 Code OSS、Stable/Insiders、Copilot CLI 与 SDK 客户端走同一条策略下发通道。非 mockedByDefault 的端点启动时处于透传状态,被转发到真实 API,从而保证「一条全量代理规则」是安全的——只有你主动开启的端点才会被伪造。
Managed Settings 的 13 个内置预设
managedSettings 是四个端点中预设最丰富、也是企业策略管控最核心的一个,其完整预设表定义在 endpoints.ts。GUI 的预设下拉与 HTTP API 的 preset 字段均直接引用这些 ID:
| 预设 ID | 语义要点 | 状态码 |
|---|---|---|
empty |
空对象 = 「无企业策略文件」的成功响应 | 200 |
disable-bypass-permissions |
封锁所有升级到 bypass-permissions(allow-all/yolo)的能力,含自动批准 | 200 |
deny-dangerous-commands |
按 managed 权限语法拒绝特定 Shell 命令、工作区级文件写入与域名(单个前导 / 表示工作区根) |
200 |
workspace-scoped-paths |
演示相对工作区根的路径:/src/**、/test/** 只匹配工作区内,/package.json 精确命中工作区文件 |
200 |
ask-before-publish |
发布/部署与用户主目录内任意写入需人工批准 | 200 |
lockdown-allowlist |
只允许批准集合(与其它 managed 白名单求交集),配合 deny/ask 纵深防御 | 200 |
sandbox-no-internet |
开启运行时沙箱、允许 bypass,但关闭出站网络 | 200 |
model-auto |
将托管模型设为 auto |
200 |
extra-known-marketplaces |
追加带托管自动更新配置的 marketplace | 200 |
customization-lockdown |
只允许托管插件/MCP 服务器/hooks,并强制远程刷新 | 200 |
not-configured |
无服务端托管策略 | 404 |
update-required |
客户端无法执行有效托管设置而被拒绝 | 466 |
server-error |
HTTP 500,用于走 fail-closed 的 HTTP 错误路径 | 500 |
预设中的响应体都是可直接复制的 JSON 样例,例如 customization-lockdown 的关键策略体为:
{
"strictPluginOnlyCustomization": true,
"allowManagedMcpServersOnly": true,
"allowManagedHooksOnly": true,
"forceRemoteSettingsRefresh": true
}
deny-dangerous-commands 则演示了托管权限语法(Shell、Write、Domain 动作 + 工作区根路径语义):
{
"permissions": {
"deny": [
"Shell(rm -rf *)",
"Shell(curl *)",
"Write(/.github/workflows/**)",
"Domain(evil.example.com)"
]
}
}
其余三个端点各带一个基线预设:entitlements 的 enterprise-enabled(返回 chat_enabled: true 与 copilot_plan: 'enterprise',是网关端点——只有这里 chat_enabled 为真,后续 token 与 managed settings 才会被拉取);token 的 all-enabled(token 串内以分号分隔 agent_mode=1;editor_preview_features=1;mcp=1 等策略标志);mcpRegistry 的 registry-only(仅允许企业 MCP registry 的 registry_access)。
快速开始
npm run mock-policy-server
然后打开 http://127.0.0.1:3000。默认情况下 Managed Settings 已被 mock;在 GUI 中每个端点标签页旁都有一个开关,用于在 mock / passthrough 之间切换。预设立即生效;响应行为(mode)、状态码与 JSON 编辑内容都会自动保存。
GUI 打开后位于 Policies 工作区,顶部有全局连接指示灯与操作入口:
- 选择 header 中的 Setup 打开设置对话框,引导你完成下列连接方式中的任意一种;
- 主区域(Live Requests)展示本服务处理过的滚动请求日志,选择一条命中四端点之一的请求即可打开其响应编辑器,非策略端点请求保持只读(响应数据由 server.ts 中以「最新在前」方式维护、上限 200 条的请求日志驱动)。
三种连接方式与文件式部署
方式一:系统代理(推荐)
系统代理对以下客户端全部生效:Code OSS、Stable、Insiders、Copilot CLI,以及 SDK/运行时客户端。任何能改写 HTTPS 请求的 HTTP 调试代理都可行,README 在 macOS 与 Windows 上推荐 Proxyman:
- 在 Setup 页拿到面向代理的 Map Remote 规则(把
api.github.com的这四个策略路径映射到本地 mock server); - 使用页面提供的按平台切换开关,让系统流量经由 Proxyman(macOS 是 Tools > macOS Proxy,Windows 是 Tools > Override Windows Proxy);
- VS Code 系列客户端还需要在
settings.json中加入页面展示的http.proxy属性——注意「复制」动作只复制该属性本身,不带外层对象花括号,便于直接追加到既有 JSON 中。
方式二:基于文件的设置(免代理)
完全跳过代理:把企业 managed-settings.json 写到客户端设备上。客户端在启动时、登录前、零网络往返地直接从磁盘读取该文件,因此这些请求根本不会到达本服务。它适合:
- 想省去代理配置的情况;
- 想验证「文件式设置 vs 服务端托管设置」的优先级关系。
该方式仅限本地客户端。文件式设置的部署位置与官方 Enterprise Managed Settings 的 "Deploying file-based settings" 说明一致,各平台路径由 server.ts 的 getFileDeployment() 常量定义:
| 操作系统 | managed-settings.json 位置 |
|---|---|
| macOS | /Library/Application Support/GitHubCopilot/managed-settings.json |
| Windows | %ProgramFiles%\GitHubCopilot\managed-settings.json |
| Linux | /etc/github-copilot/managed-settings.json |
文件部署与解除
选用文件式方案时无需「连接」,直接在 Policies 页右侧边栏使用 File Deployment 即可。它提供按平台的命令(macOS、Windows PowerShell、Linux),把当前响应体写入上述 managed-settings.json;复制后在客户端设备上执行,然后重启客户端。macOS 与 Linux 命令带 sudo,因为 Copilot CLI 要求该文件是root 所有、不可写的普通文件。每个平台段落都额外提供一条删除命令用于撤销文件式策略。
连上后(文件式则直接跑 VS Code 命令),打开 VS Code 命令面板执行 > Developer: Sync Account Policy 以刷新策略;若需同步 Local Agent Host 使用的策略,还需再执行 > Developer: Restart Local Agent Host。
清除 SDK 策略缓存(Troubleshooting)
如果在 Live Requests 中看不到真实请求,打开右侧边栏的 Troubleshooting,在 Clear SDK policy cache 下展开客户端平台并执行复制的命令。Copilot SDK 把该缓存维护在 VS Code 之外的磁盘上;未清除时,一个新鲜的缓存条目会让客户端在最长一小时内都不发起新请求。缓存位置逻辑(server.ts 的注释与 managedSettingsCacheDirs())与 copilot-agent-runtime 的 path_helpers::copilot_cache_home + managed_settings_cache::CACHE_SUBDIR 对齐:COPILOT_CACHE_HOME 优先,否则按平台缓存基目录(macOS ~/Library/Caches、Windows %LOCALAPPDATA%、Linux ${XDG_CACHE_HOME:-~/.cache})拼上 copilot/managed-settings。
macOS 清缓存命令:
rm -rf -- "${COPILOT_CACHE_HOME:-$HOME/Library/Caches/copilot}/managed-settings"
Windows PowerShell 清缓存命令(等价逻辑的完整实现见 README):
$root = if ($env:COPILOT_CACHE_HOME) { $env:COPILOT_CACHE_HOME } elseif ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA 'copilot' } else { Join-Path $HOME '.cache\copilot' }; $path = Join-Path $root 'managed-settings'; if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Recurse -Force }
清完缓存再按上文重新执行 Sync Account Policy。
隔离运行:共享缓存与专用缓存目录
由于其它 Copilot 客户端共享该缓存,如需完全隔离,可以让 server 与 Code OSS 使用同一个临时缓存目录启动:
COPILOT_CACHE_HOME="$PWD/.build/mock-policy-cache" npm run mock-policy-server
COPILOT_CACHE_HOME="$PWD/.build/mock-policy-cache" ./scripts/code.sh
状态持久化:双副本与原子写
README 明确了两处冗余存储:
- 浏览器侧:GUI 把每个端点的响应体草稿保存在
localStorage(键前缀见 app.ts); - 服务端侧:每次更新后 server 把完整状态原子写入
~/.mock-policy-server/state.json(默认路径常量见 server.ts)。
两份拷贝在服务重启后都能存活。实现上 persistEndpointState()(server.ts)先以 0o600 权限写入 state.json.<pid>.tmp,再 renameSync 换名落盘——这正是「原子更新」的语义来源;启动时若发现该文件不可读或校验失败,服务会打印 Ignoring invalid persisted state ... 并继续用默认状态运行。无效 JSON 会一直留在浏览器存储中(server 无法为其服务),直到你在编辑器里改正。
用 --state-file 或环境变量 MOCK_POLICY_STATE_FILE 可以选择不同的服务端状态文件。
自动连接探测机制
Setup 对话框每五秒通过系统代理发送一次无凭据的探测请求,形状为:
GET <configured upstream>/copilot_internal/managed_settings?mockPolicySetupProbe=<random UUID>
默认 upstream 是 https://api.github.com。要点在于:
- 探测必须与真实策略流量走同一条 Map Remote 映射,页面只在被映射的响应携带本 mock server 的标识头
X-Mock-Policy-Server(常量见 server.ts)时才判定连接成功; - 它不检查 Proxyman 或操作系统的代理配置本身,只验证端到端映射是否生效;
- 顶部全局指示器据此显示绿色/红色连接状态。
为了让这些自动探测不污染 Proxyman 的流量列表,可在其显示过滤器中用正则 ^(?!.*mockPolicySetupProbe).*managed_settings.* 过滤展示;但请不要把探测从 Map Remote 规则中移除,否则连接检查无法到达本服务。在服务端,mock server 会识别该探测查询参数,并把这类请求从 Live Requests 请求日志中剔除(相关处理见 server.ts:带 mockPolicySetupProbe 的请求直接回 204 且不记日志)。
HTTP 控制 API:无需 GUI 的完整配置
控制 API 是纯 JSON 的,机器可读、可脚本化。先读机器可读的索引与当前状态:
BASE=http://127.0.0.1:3000
curl "$BASE/api"
curl "$BASE/api/state"
GET /api/state返回端点 ID、预设、当前响应体、状态码与 mock/passthrough 状态;GET /api面向人类与 Agent 双端,文档化更新字段类型、合法响应模式、原子更新语义、路由副作用与统一 JSON 错误形状。
服务端对「已知路径 + 不支持的方法」返回 405 并带 Allow 头;未知路径回 404 且响应带 discovery: "/api" 引导回索引(见 server.ts)。
应用预设 / 自定义响应 / 原子批量更新
应用一个已知预设:
curl -X POST "$BASE/api/state" \
-H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","preset":"not-configured"}'
设置自定义响应(显式覆盖 status/body/active,优先级高于预设):
curl -X POST "$BASE/api/state" \
-H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","active":true,"status":200,"body":{}}'
一次原子地配置多个端点(先整体校验、后统一应用,任一无效则全部不生效,从源码看更新会被构造为 nextState 副本后整体替换):
curl -X POST "$BASE/api/state" \
-H 'Content-Type: application/json' \
-d '{"endpoints":[
{"endpoint":"managedSettings","preset":"empty"},
{"endpoint":"entitlements","active":false},
{"endpoint":"token","active":false},
{"endpoint":"mcpRegistry","active":false}
]}'
更新字段与合法取值(与 server.ts 的 EndpointUpdate 及 applyEndpointUpdates() 校验逻辑一一对应):
| 字段 | 类型/取值 | 说明 |
|---|---|---|
endpoint |
string(必填) | 必须是 GET /api/state 返回的端点 ID |
preset |
string | 目标端点支持的预设 ID;会设置 status/body 并开启 mock |
status |
integer 200–599 | 显式值覆盖预设的 status |
body |
任意 JSON | 显式值覆盖预设的 body |
mode |
json / malformed-json / disconnect / timeout |
独立于预设的响应行为 |
active |
boolean | true mock,false 透传到上游 |
批量更新先整体校验、后原子应用;无效请求在任何端点变更发生前即被拒绝(400),未知字段、重复 endpoint、未知预设等都会被逐条报告。服务端在每次成功更新后原子持久化。
文件式部署命令生成与其它控制操作
基于当前 managedSettings 响应体生成三平台的文件式安装/删除命令:
curl "$BASE/api/file-deployment"
响应含目标路径以及 macOS/Windows/Linux 的安装与删除命令。server 不会代跑这些命令——请在客户端机器上执行后重启客户端;撤销文件式 Managed Settings 则执行对应的删除命令(或直接删除 managed-settings.json)。
其余控制操作一览:
curl "$BASE/api/schema"
curl "$BASE/api/log"
curl -X DELETE "$BASE/api/log"
curl -X DELETE "$BASE/api/cache"
curl -X POST "$BASE/api/reset"
注意:DELETE /api/cache 会修改运行 server 那台机器上的文件(删除所有平台候选缓存目录内容)。调用任何带副作用的路由前,建议先查 GET /api 中每条路由的 sideEffects 字段——控制路由表原样定义了 server-state / filesystem / none 三种取值(见 server.ts)。
路由总表
| Method | Route | Purpose |
|---|---|---|
GET |
/api |
发现请求形状与路由 |
GET |
/api/state |
读取定义、预设与当前状态 |
POST |
/api/state |
应用并持久化单次更新或原子端点数组 |
POST |
/api/reset |
恢复并持久化默认端点状态 |
GET |
/api/schema |
读取 managed-settings schema |
POST |
/api/schema |
为当前 server 进程更换并重载 schema 源(仅限 loopback URL) |
GET |
/api/file-deployment |
生成文件安装与删除命令 |
GET, DELETE |
/api/log |
读取或清空请求日志 |
DELETE |
/api/cache |
清空 managed-settings 磁盘缓存 |
实战:测 fail-closed 的 Managed Settings 刷新
README 提供了一套演练 fail-closed(关闭即失败)刷新语义的完整脚本。先下发一个「开启强制刷新要求」的成功策略并同步进 VS Code,再配置 HTTP 错误预设或失败响应行为并再次同步——先种下强制刷新要求,是在镜像真实部署中「缓存的管控策略在故障期间自我延续」的场景:
curl -X POST "$BASE/api/state" \
-H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","preset":"customization-lockdown"}'
# 在 VS Code 里运行 "Developer: Sync Account Policy",然后任选其一:
curl -X POST "$BASE/api/state" -H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","preset":"server-error"}'
curl -X POST "$BASE/api/state" -H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","mode":"malformed-json","status":200}'
curl -X POST "$BASE/api/state" -H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","mode":"disconnect"}'
curl -X POST "$BASE/api/state" -H 'Content-Type: application/json' \
-d '{"endpoint":"managedSettings","mode":"timeout"}'
这四类配置分别覆盖 HTTP 错误、畸形响应、即时网络失败与客户端超时路径。若 Live Requests 中看不到请求,先按上文清策略缓存再重试。
从源码看,四种响应模式的实际行为定义在 sendMockedResponse()(server.ts):
json:按entry.status与entry.body返回 JSON;malformed-json:同样写指定状态码,但响应体是残缺的{"unterminated":;disconnect:记录status: 0后直接销毁连接;timeout:挂起 10 秒后销毁连接(timer.unref()避免阻塞进程退出)。
请求日志会以 mocked / passthrough / upstream-error 三种 outcome 区分每条请求的处置方式(server.ts)。
Schema 来源与启动选项
服务启动时自动探测主 VS Code 检出目录旁的 copilot-agent-runtime/schema/managed-settings-schema.json,并且从 Git worktree 也能找到:resolveDefaultSchemaSource()(server.ts)会通过读取 .git 文件(gitdir: 记录)回溯到主检出路径,因此在嵌套 worktree 中运行也能定位仓库(如 /Users/name/git/copilot-agent-runtime)。
替换 schema 源有三种方式:启动参数 --schema、环境变量 MANAGED_SETTINGS_SCHEMA,或在 GUI 中编辑 Schema source 并点击 Load Schema。GUI 的改动只作用于当前 server 进程,server 重启后回到启动时源。由于 schema 源可能是本地文件路径或远端 URL,更换 schema 源被限制为仅接受 loopback 来源的请求(isLoopbackRequest() 同时校验远端地址与 Host 均为回环地址,见 server.ts)。schema 源支持三类形态:http(s):// URL、file:// URI、或相对 cwd 的普通路径(相对路径含 .. 会被拒绝)。
带参启动示例(注意 npm 传参需 --):
npm run mock-policy-server -- --upstream https://api.ghe.example.com
npm run mock-policy-server -- --schema /path/to/managed-settings-schema.json
npm run mock-policy-server -- --port 3001
npm run mock-policy-server -- --state-file /path/to/mock-policy-state.json
npm run mock-policy-server -- --help
完整选项表(命令行参数由 parseArgs() 手工解析,--port 必须是 1–65535 的整数,见 server.ts):
| Flag | 环境变量 | 默认值 |
|---|---|---|
--host |
— | 127.0.0.1 |
--port |
— | 3000 |
--upstream |
MOCK_POLICY_UPSTREAM |
https://api.github.com |
--schema |
MANAGED_SETTINGS_SCHEMA |
自动探测的兄弟检出 |
--state-file |
MOCK_POLICY_STATE_FILE |
~/.mock-policy-server/state.json |
源码级安全与分流细节
从 server.ts 的主请求处理流程可以提炼出几条值得注意的工程约束:
- 控制 API 同源限制:
/api/*不设 CORS,且isAllowedControlOrigin()要求带Origin的请求必须与Host同源,否则返回 403——防止任意无关网页驱动本机的文件系统类控制路由; - 被 mock 的端点才开放宽松 CORS:只有四端点命中且
active为真时才带上Access-Control-Allow-Origin: *,让 Code OSS 的 Web(浏览器)构建能跨源调用; - GUI 静态资源白名单:服务仅用显式 Map 服务
/、/index.html、/style.css、/app.js(即 public/app.ts,type-strip 后按 JS 提供)、/endpoints.js等固定资源,避免静态路由遮蔽需要透传的 API 路径; - favicon 就地消化:页面自动触发的
/favicon.ico请求直接回204,不进代理也不污染请求日志; - 透传保真:
passthrough()改写Host、剥掉 hop-by-hop 头、禁用 gzip 以便代理 UI 中可读原始字节,同时原样转发客户端的Authorization,从而让未被 mock 的端点返回真实结果(server.ts)。
相关实现索引
继续深入阅读当前仓库时,可按下面顺序追踪:
- 工具本体:scripts/mock-policy-server/server.ts(HTTP 服务、控制 API、持久化、透传与缓存清理)、scripts/mock-policy-server/endpoints.ts(端点定义与预设)、scripts/mock-policy-server/public/app.ts 与 scripts/mock-policy-server/public/index.html(GUI);
- 启动脚本与端点 URL:package.json、product.json;
- 消费这些端点的账号服务:src/vs/workbench/services/accounts/browser/defaultAccount.ts(其中对 managed-settings 拉取、新鲜度(freshness)与缓存的判断逻辑,解释了为什么本地调试必须重视 SDK 磁盘缓存与
forceRemoteSettingsRefresh语义); - 原版操作手册(本文所有工作流与命令的出处):scripts/mock-policy-server/README.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00