Skip to content

让 Copilot Chat 控制 Cursor Max Mode:跨进程配置传递实战

Cursor CLI 支持 Max Mode——扩展上下文窗口和工具调用限制,代价是更高的 token 消耗。在 Cursor IDE 里这是一个 toggle,但在 CLI 中只能通过 cli-config.jsonmaxMode 字段控制,且 CLI 读取后会自动重置为 false(一次性消费)。

我们的 VSCode 扩展 cursor-copilot-bridge 将 Cursor CLI 封装为 Copilot Chat 的 Language Model Provider。本文记录如何为它添加 Max Mode 开关。

架构:Preflight 脚本

VSCode 扩展宿主进程和 Cursor CLI 是完全隔离的——扩展无法直接修改 CLI 的运行时状态。可选方案:

方案问题
CLI 命令行参数 --max-mode不存在,CLI 不支持
环境变量 CURSOR_MAX_MODECLI 不读这个变量
直接在扩展中写 config扩展是 JS,CLI 是独立 node 进程,config 路径解析取决于运行时环境变量
Preflight 脚本用 CLI 自带的 node.exe 执行,继承相同环境变量,路径解析天然一致 ✓

最终方案:在 spawn(CLI) 前,用 execFileSync 同步执行一个轻量脚本,写入 maxMode: trueexecFileSync 阻塞直到完成,不存在时序问题。

execFileSync(node.exe, [preflight.js])  // 同步,阻塞
    ↓  写入 cli-config.json: maxMode=true
spawn(node.exe, [index.js, ...args])    // preflight 完成后才执行
    ↓  CLI 读取 maxMode=true → 发送请求 → 重置为 false

关键:镜像 CLI 的 Config 路径解析

Preflight 的 config 路径解析必须与 CLI 完全一致,否则两边读写的是不同文件。

从 CLI 的 minified 源码(index.js)中提取出 config 目录解析函数:

javascript
// CLI 源码(反混淆后)
function getConfigDir() {
  const e = process.env.CURSOR_CONFIG_DIR;
  if (e?.trim()) return e;
  const t = process.env.XDG_CONFIG_HOME;
  return t?.trim() ? path.join(t, "cursor") : path.join(os.homedir(), ".cursor");
}

function getCliConfigPath() {
  return path.join(getConfigDir(), "cli-config.json");
}

三级优先级:CURSOR_CONFIG_DIRXDG_CONFIG_HOME/cursor~/.cursor

Preflight 1:1 复刻:

typescript
// max-mode-preflight.ts
function getConfigDir(): string {
  const envDir = process.env.CURSOR_CONFIG_DIR;
  if (envDir?.trim()) return envDir;
  const xdg = process.env.XDG_CONFIG_HOME;
  if (xdg?.trim()) return path.join(xdg, "cursor");
  return path.join(os.homedir(), ".cursor");
}

const configPath = path.join(getConfigDir(), "cli-config.json");

读取并写入 maxMode: true。Windows 下部分编辑器会给 JSON 加 UTF-8 BOM,需要容错:

typescript
const raw = JSON.parse(rawStr.replace(/^\uFEFF/, ""));
raw.maxMode = true;
fs.writeFileSync(configPath, JSON.stringify(raw, null, 2), "utf-8");

Preflight 和 CLI 都是扩展宿主的子进程,继承相同环境变量,用相同的三级逻辑,天然指向同一个文件。spawnAgent 无需做任何额外处理。

Protobuf 抓包验证

CLI 真的把 maxMode=true 发给了服务器吗?通过 NODE_OPTIONS=--require 注入 HTTP/2 hook,拦截 CLI 发往 agent.v1.AgentService/Run 的 Connect Protocol 请求体(protobuf 二进制)。对比 RequestedModel 消息:

maxMode=true  → 4a21 0a1d "claude-4.6-opus-high-thinking" 1001
                                                           ^^^^
                                              protobuf field#2 = 1 (true)

maxMode=false → 4a1f 0a1d "claude-4.6-opus-high-thinking"
                                                          (field#2 缺失,默认 false)

帧大小差 2 字节,与 10 01 完全对应。cursor.com/usage 页面对 maxMode=true 的请求标注 MAX 徽章。

排查技巧:利用 CLI 的自动重置行为

CLI 读取 maxMode: true 后会自动重置为 false。这个行为是天然的路径一致性探针:

请求后 config 状态含义
maxMode: falseCLI 读取并重置 ✓ preflight 和 CLI 读写同一个文件
maxMode: trueCLI 根本没读这个文件 ✗ 路径不一致

如果发现 config 没被重置,说明 preflight 和 CLI 解析到了不同的文件。此时应该对照 CLI 源码检查路径解析逻辑是否完全一致。

完整数据流

用户在 VSCode 设置中勾选 cursorBridge.maxMode

provider.ts 读取 config.maxMode = true

process.ts → spawnAgent:
  ① resolveAgent("agent") → PATH 查找 → agent.cmd → node.exe + index.js
  ② execFileSync(node.exe, [preflight.js])  // 同步阻塞
     → getConfigDir() 解析路径(与 CLI 相同的三级逻辑)
     → 写入 cli-config.json: maxMode = true
  ③ spawn(node.exe, [index.js, ...args])

CLI 内部调用同样的 getConfigDir() → 读取同一个 cli-config.json → maxMode = true

构建 RequestedModel protobuf (field#2 = 1) → 发送到 Cursor 服务器

CLI 自动重置 maxMode = false

cursor.com/usage 显示 MAX 标记 ✓

收获

  1. 照抄源码,不要猜路径——config 路径解析直接从 CLI 的 minified 源码中提取,1:1 复刻。两个进程用完全相同的逻辑,就不会出现路径分歧。

  2. 子进程天然共享环境——preflight 和 CLI 都是扩展宿主的子进程,继承相同的环境变量。只要路径解析逻辑一致,不需要手动注入环境变量或添加额外配置项。

  3. Protobuf 抓包比推理有效——与其反复推理 minified 代码的行为,不如直接看请求的二进制差异。NODE_OPTIONS=--require + HTTP/2 stream hook 在不需要代理的情况下就能完成。

  4. 善用消费方的副作用做探针——CLI 读取 maxMode 后自动重置为 false,这个副作用可以直接判断文件是否被正确读取,比添加 debug 日志更快。

最后更新于:

Hosted by GitHub Pages