首页
/ Cline VSCode 扩展调试台:用 HTTP API 远程操控、断点调试与自动化 OAuth 测试

Cline VSCode 扩展调试台:用 HTTP API 远程操控、断点调试与自动化 OAuth 测试

2026-09-06 09:07:20作者:吴年前Myrtle

Cline 仓库内置了一个面向 VSCode 扩展的 HTTP 控制式调试台(Debug Harness),它把「启动 VSCode 实例 → CDP 断点调试 → Playwright UI 自动化 → OAuth 流程模拟」全部收敛到一个 curl 即可调用的 HTTP 服务上。本文以仓库中的调试台文档 debug-harness.md 为主体,结合 server.tsenv.tsextension.ts 的源码实现,完整讲解其启动方式、数据隔离机制、浏览器 URL 捕获与 OAuth 回调模拟、视图导航命令以及完整的 API 方法集。读完本文,你可以用 curl 命令以编程方式驱动 Cline 扩展实例进行断点调试、UI 自动化和端到端认证测试。

快速开始:构建、启动与首次调用

调试台的入口是 server.ts,它启动 VSCode 并暴露一个 HTTP API。首次使用需要构建扩展(protos + esbuild):

# 构建扩展(protos + esbuild)
bun run protos && IS_DEV=true bun esbuild.mjs

# 启动(已构建过则加 --skip-build)。必须用 node 而不是 bun 运行,
# 因为 Playwright 的 Electron 启动在 bun 下会超时:
node src/dev/debug-harness/server.ts --skip-build --auto-launch

# 另开终端,验证服务状态
curl localhost:19229/api -d '{"method":"status"}'

server.ts 的注释明确解释了 node/bun 的选择原因:Playwright 的 _electron.launch() 在 bun 下能启动 Electron 进程但 attach 会一直等待直到超时,同样的启动流程在 node 下正常(Node >= 22.6 可直接以 type stripping 方式执行该文件)。

服务支持以下命令行选项(来自 server.ts 头部注释):

选项 说明
--skip-build 跳过扩展/webview 构建,直接使用现有 dist/
--auto-launch 服务启动后自动启动 VSCode
--workspace PATH 要打开的工作区目录(默认 /tmp/cline-debug-workspace
--port PORT 服务端口(默认 19229
--launch-timeout MS Playwright 启动超时(默认 120000
--no-browser-capture openExternal() 打开真实浏览器(用于交互式 OAuth)
--cline-dir PATH 覆盖被测实例的 CLINE_DIR(默认 ~/.cline2

另外,环境变量 VSCODE_TEST_VERSION 可指定被测 VSCode 版本,默认 1.103.0——从源码结构看,这是因为捆绑的 Playwright 版本无法驱动最新版 stable 的 Electron。

所有命令都通过 POST localhost:19229/api 发送,请求体格式为 {"method":"...", "params":{...}},成功返回 {"result": {...}},失败返回 {"error": "..."}。另有便捷端点:GET /health(健康检查)、GET /status(完整状态)、POST /captured-url(被测实例回传捕获 URL 的内部端点)。

数据隔离:为什么被测实例要用 ~/.cline2

调试台启动的 VSCode(文档中称为 "debugee",被测方)默认以 CLINE_DIR=~/.cline2 运行,与开发者真实使用的 ~/.cline 完全隔离。这一设计的目的是避免两个实例互相干扰:

  • 被测实例退出登录不会把调试器(以及你自己的 IDE 扩展)一起登出,反之亦然;
  • 任务历史、API 密钥、设置不会在两个实例间泄漏;
  • 共享的 secrets.json 不会造成状态损坏。

从源码实现看,隔离逻辑在 server.ts 中定义为常量 DEFAULT_CLINE_DIR = ~/.cline2,可通过 --cline-dir /tmp/test-dir 覆盖;隔离后的目录会出现在 status()launch() 的响应里,可用 status() 返回的 clineDir 字段确认。如果需要基于真实数据测试,文档建议 cp -r ~/.cline ~/.cline2,但要意识到这样做会使 API 密钥和认证令牌在两个实例间共享。

浏览器捕获与 OAuth 测试

这是调试台最有价值的能力之一。被测实例启动时,调试台会设置 CLINE_CAPTURE_BROWSER=1,它会拦截 env.ts 中的 openExternal() 调用。查看源码可以看到门控逻辑非常直接:

// apps/vscode/src/utils/env.ts (openExternal)
export async function openExternal(url: string): Promise<void> {
	// Debug harness mode: capture URL instead of opening browser
	if (process.env.CLINE_CAPTURE_BROWSER === "1" || process.env.CLINE_CAPTURE_BROWSER === "true") {
		await captureBrowserUrl(url)
		return
	}
	// ... 正常路径:走 HostProvider.env.openExternal RPC
}

captureBrowserUrl() 做两件事,与文档描述一一对应:

  1. 写盘:以 {timestamp, url} 结构追加到 $CLINE_DIR/data/debug-captured-urls.jsonl(JSONL 格式);
  2. 实时上报:若环境变量 CLINE_DEBUG_HARNESS_PORT 已设置(调试台启动时固定注入 19229),则 POST 到服务端的 /captured-url 端点,从而可通过 oauth.captured_urls API 实时查询。

OAuth API 方法集

方法 参数 说明
oauth.captured_urls {clear?} 获取被测实例尝试打开的 URL(被浏览器拦截捕获),clear 为 true 时取完即清空
oauth.read_stored_token 读取被测实例 secrets.json 中认证令牌的存放状态
oauth.simulate_callback {path, code?, state?, provider?, token?} 构建 vscode:// 回调 URI(仅构建,不投递)
oauth.read_captured_urls_file 读取磁盘上的 JSONL 捕获文件

Cline OAuth(SDK 本地回调)测试流程

Cline 登录走 SDK 的本地回调服务器模式:点击 "Login" 后 SDK 在随机端口起一个本地 HTTP 服务,调用 openExternal(authorizationUrl)(被捕获),用户在浏览器完成认证后重定向回本地回调服务携带 ?code=...,SDK 捕获 code 并换取令牌。自动化测试时的完成方式二选一:

# 1. 打开侧边栏并点击登录(先关闭弹窗,见下文)
curl localhost:19229/api -d '{"method":"ui.open_sidebar"}'
curl localhost:19229/api -d '{"method":"ui.locator","params":{"text":"Login to Cline","frame":"sidebar","action":"click"}}'

# 2. 查看捕获到的授权 URL
curl localhost:19229/api -d '{"method":"oauth.captured_urls"}'
# → { "urls": [{ "url": "https://api.cline.bot/auth/authorize?callback_url=http://127.0.0.1:PORT/..." }] }

# 3a. 方式一:在真实浏览器中打开捕获的 URL(会自动重定向回 SDK 本地回调服务器)
# 3b. 方式二:提取回调端口后直接 curl 模拟重定向:
curl "http://127.0.0.1:PORT/callback?code=..." 2>/dev/null

# 4. 验证令牌已存储
curl localhost:19229/api -d '{"method":"oauth.read_stored_token"}'
# → { "found": true, "hasAccountId": true, "keys": ["cline:clineAccountId"] }

# 5. 截图确认 UI 呈现已认证状态
curl localhost:19229/api -d '{"method":"ui.screenshot"}'

MCP / Provider OAuth(vscode:// URI)投递

MCP 服务器和部分 Provider(如 OpenRouter)的 OAuth 走 vscode:// URI 回调,由扩展注册的 URI handler 处理。这里有一个关键限制:oauth.simulate_callback 只会构建 URI,不会投递——VSCode 只把真实的 vscode:// URI 路由给已注册的 handler,调试台无法合成这种系统级 URI,且扩展宿主是 ESM,不能 require() 到 handler 模块。

仓库的解法是在 extension.ts 中挂了一个仅调试期存在的 hook:

// apps/vscode/src/extension.ts(仅当 CLINE_CAPTURE_BROWSER 设置时注册)
if (process.env.CLINE_CAPTURE_BROWSER === "1" || process.env.CLINE_CAPTURE_BROWSER === "true") {
	;(globalThis as Record<string, unknown>).__clineHandleUri = (url: string) => SharedUriHandler.handleUri(url)
}

源码注释明确说明:该 hook 调用的是与 VSCode 真实 registerUriHandler 相同的 SharedUriHandler.handleUri(url),并且以 CLINE_CAPTURE_BROWSER 为门控条件,绝不会进入生产构建。实际投递回调时通过 ext.evaluate 调用(记得加 awaitPromise: true):

curl localhost:19229/api -d '{
  "method": "ext.evaluate",
  "params": {
    "awaitPromise": true,
    "expression": "globalThis.__clineHandleUri(\"vscode://saoudrizwan.claude-dev/mcp-auth/callback/HASH?code=REAL_CODE&state=SAVED_STATE\")"
  }
}'

要做端到端的 MCP OAuth 测试,需要一个能签发真实 code 的本地认证服务器,仓库提供了现成脚本:bun run dev:mcp-oauth-test-server(见 mcp-oauth-test-server/README.md,该脚本已在 package.json 中注册)。典型流程:触发 MCP OAuth → oauth.captured_urls 拿到 authorize URL(其中包含 redirect_uri=vscode://saoudrizwan.claude-dev/mcp-auth/callback/HASH)→ 用 curl -s -D - 跟随该 URL 从测试服务器拿到 302 响应头中的真实 codestate → 通过 __clineHandleUri 投递 → oauth.read_stored_token 验证令牌落盘。

视图导航:用命令而不是点击

侧边栏上的小图标很难被自动化点击。调试台文档给出最佳实践:通过 VSCode 命令面板(ui.command_palette 方法)执行命令。这些命令在 registry.ts 中注册:

命令 打开的视图
cline.accountButtonClicked 账户 / 登录
cline.historyButtonClicked 任务历史
cline.settingsButtonClicked 设置
cline.mcpButtonClicked MCP 服务器
cline.plusButtonClicked 新任务(聊天)
cline.worktreesButtonClicked Worktrees

源码印证了这些命令名的来源——registry.ts 中以 prefix + ".xxxButtonClicked" 的形式统一定义(源码中还额外有 marketplaceButtonClicked 等未在文档表格中列出的命令)。调用示例:

curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'

关键 API 命令全览

按功能域分组的完整方法表(综合 README.md 的 API 参考),全部经 POST localhost:19229/api 调用:

生命周期

方法 参数 说明
launch {workspace?, skipBuild?} 构建并启动 VSCode
shutdown 关闭 VSCode 及所有 CDP 连接
status 查询各组件当前状态(含 clineDir、浏览器捕获配置等)
connect_webview 在侧边栏打开后连接 webview 的 CDP(断点/单步才需要)

扩展宿主调试(Node.js,经 9230 端口 V8 inspector)

方法 参数 说明
ext.set_breakpoint {file, line, column?, condition?} 按源码文件+行号设断点(sourcemap 解析)
ext.set_breakpoint_raw {url?, urlRegex?, scriptId?, lineNumber, columnNumber?, condition?} 用原始 CDP 参数设断点
ext.remove_breakpoint {breakpointId} 移除断点
ext.evaluate {expression, callFrameId?} 在断点处或全局作用域求值表达式
ext.pause / ext.resume 暂停 / 恢复
ext.step_over / ext.step_into / ext.step_out 单步执行
ext.call_stack 暂停时查看调用栈
ext.scripts {filter?} 列出已加载脚本
ext.source_files 列出 sourcemap 中的源文件(断点解析失败时用于排查路径)
ext.get_properties {objectId} 获取对象属性
ext.get_script_source {scriptId} 获取脚本源码文本

其中"按源码文件设断点"背后的实现值得了解:server.ts 内置了一个 VLQ 解码器,读取 dist/extension.js.map,将原始文件+行号解析为打包后文件中的行列位置——因为 esbuild 输出的是相对路径(如 ../src/extension.ts),解析器会先精确匹配再做后缀匹配。如果解析失败,用 ext.source_files 查看 sourcemap 实际包含的路径,改用 ext.set_breakpoint_rawurlRegex

Webview 调试(Chrome/CDP)

方法 参数 说明
web.set_breakpoint {url, line, column?, condition?} 按 URL 模式设断点(需先 connect_webview
web.remove_breakpoint {breakpointId} 移除断点
web.evaluate {expression, callFrameId?} 在 sidebar 中求值(Playwright frame.evaluate())或在断点处求值(CDP)
web.post_message {message} 通过暴露的 vsCodeApi 向扩展宿主发 postMessage
web.pause / web.resume / web.step_over/into/out 暂停、恢复与单步

注意:connect_webview 可能因 Electron 版本原因失败;即使 CDP 未连接,web.evaluate 仍可通过 Playwright 的 frame.evaluate() 回退路径工作。

UI 自动化(Playwright)

方法 参数 说明
ui.screenshot {fullPage?} 截图到 /tmp/cline-debug/,返回 {path}
ui.sidebar_screenshot 聚焦 sidebar 的截图
ui.click {selector, frame?, delay?} 点击元素(webview 用 frame: "sidebar"
ui.fill {selector, text, frame?} 填写输入框
ui.press {key} 按键(如 "Enter""Meta+Shift+p"
ui.type {text, delay?} 输入文本
ui.open_sidebar 打开 Cline 侧边栏
ui.frames 列出所有 frame
ui.wait_for_selector {selector, frame?, timeout?} 等待元素出现
ui.command_palette {command} 打开命令面板并执行命令
ui.get_text {selector, frame?} 读取元素文本
ui.locator {role?, name?, testId?, text?, frame?, action?, value?} 富 Playwright 定位器(sidebar frame 失效时自动刷新重试)
ui.react_input {text, selector?, clear?, submit?} 通过 execCommand('insertText') 设置 React 受控 textarea 的值,多个任务场景下依然可靠
ui.send_message {text, images?, files?, responseType?} 完全绕过 textarea 直接发送聊天消息(经 gRPC postMessage)

文档特别强调两点自动化细节:截图不要open 打开文件(macOS 上 Preview.app 会盖住 VSCode 窗口),而应直接读取返回的 pathui.react_input 之所以用 execCommand('insertText'),是为了正确触发 React 受控组件的状态更新。

组合方法

方法 参数 说明
wait_for_pause {timeout?} 阻塞直到任一调试目标命中断点

典型调试会话

把上述能力串起来,一次完整的调试会话如下(直接取自文档的 Typical Session 流程):

# 1. 启动(若未使用 --auto-launch)
curl localhost:19229/api -d '{"method":"launch","params":{"skipBuild":true}}'

# 2. 打开侧边栏并关闭遮罩弹窗(务必最先做)
curl localhost:19229/api -d '{"method":"ui.open_sidebar"}'
curl localhost:19229/api -d '{"method":"web.evaluate","params":{"expression":"document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'

# 3. 导航到目标视图
curl localhost:19229/api -d '{"method":"ui.command_palette","params":{"command":"cline.accountButtonClicked"}}'

# 4. 若测认证流程,查看捕获到的 OAuth URL
curl localhost:19229/api -d '{"method":"oauth.captured_urls"}'

# 5. 截图验证
curl localhost:19229/api -d '{"method":"ui.screenshot"}'

# 6. 结束时关闭
curl localhost:19229/api -d '{"method":"shutdown"}'

一个断点调试的典型组合:

curl localhost:19229/api -d '{"method":"ext.set_breakpoint","params":{"file":"src/extension.ts","line":25}}'
curl localhost:19229/api -d '{"method":"ui.open_sidebar"}'
curl localhost:19229/api -d '{"method":"wait_for_pause","params":{"timeout":10000}}'
curl localhost:19229/api -d '{"method":"ext.call_stack"}'
curl localhost:19229/api -d '{"method":"ext.resume"}'

内部工作原理

README.md 的 "How It Works" 与 server.ts 实现可以梳理出完整链路:

  1. 构建:esbuild 将 src/extension.ts 打包为 dist/extension.js(不压缩、带 sourcemap);Vite 构建 webview-ui/(不压缩、内联 sourcemap);
  2. 启动:用 @vscode/test-electron 下载 VSCode,再用 Playwright 的 _electron.launch()--inspect-extensions=9230--extensionDevelopmentPath 启动;
  3. 数据隔离:在被测环境注入 CLINE_DIR=~/.cline2createStorageContext() 据此决定 globalState.json、secrets.json、任务历史等的存放位置;
  4. 浏览器捕获:注入 CLINE_CAPTURE_BROWSER=1CLINE_DEBUG_HARNESS_PORT=19229openExternal() 转而写 JSONL 并 POST 上报;
  5. 扩展 CDP:经 WebSocket 连上 9230 端口的 V8 inspector,启用 DebuggerRuntime 域,跟踪 scriptParsed 与 paused/resumed 状态;
  6. Sourcemap 解析:内置 VLQ 解码,把源码位置映射为打包文件位置;
  7. Webview CDP:sidebar 加载后为 webview frame 建立 CDP session,失败则回退 frame.evaluate()
  8. UI 自动化:sidebar webview 作为 VSCode 窗口内的 Frame 由 Playwright Page/Frame API 驱动。

常见陷阱与排错

文档的 Caveats 部分浓缩了大量实战经验,按重要性排列:

  • 先关促销弹窗:全新启动时全屏促销遮罩会挡住 sidebar 一切交互。ui.open_sidebar 之后、做任何其他操作之前,立即执行关闭 .sr-only 弹窗的 web.evaluate(可能需运行两次)。
  • 截图查看方式ui.screenshot 返回的 PNG 保存在 /tmp/cline-debug/(可用 SCREENSHOT_DIR 配置),用读取文件的方式查看,不要用 open
  • scripts 计数为 0:CDP 在扩展宿主启动之后才连接,启动期间解析的脚本不会被跟踪;断点本身通过 sourcemap 解析仍然可用。
  • 端口 9230 冲突:扩展宿主 inspector 使用固定端口 9230。若其他 VSCode 实例占用了该端口,harness 将连接失败——先杀掉其他调试实例。
  • 平台限制:目前仅支持 macOS(Playwright Electron 启动行为所致)。
  • Webview CDP 不可靠connect_webview 可能因 Electron 版本失败;web.evaluate 有 Playwright 回退,不受影响。
  • Sourcemap 路径:esbuild 输出相对路径(如 ../src/extension.ts),解析器已处理;文件找不到时用 ext.source_files 查看确切路径。
  • 假 code 的 OAuth:浏览器捕获只拦截 URL,不提供有效认证码。用假 code 模拟回调时,令牌交换必然失败(认证服务器不认识该 code);要么在浏览器完成真实流程拿真 code,要么在单元测试中 mock 令牌交换端点。

排错速查(来自 README 的 Troubleshooting 小节):

现象 原因与处理
"Inspector not available on port 9230" 扩展宿主尚未启动,等待更久或检查扩展是否构建成功
"Sidebar frame not found" Cline 侧边栏未打开,先 ui.open_sidebar
"Webview CDP not connected" 侧边栏打开后再 connect_webview;失败则断点不可用但 web.evaluate 仍可用
Sourcemap 解析失败 ext.source_files 查看实际路径,改用 ext.set_breakpoint_raw + urlRegex
被测实例仍在用 ~/.cline 检查 status() 响应中是否有 clineDir;没有说明实例在 harness 设置环境变量前启动了,shutdown 后重启

小结

Cline 的 Debug Harness 是一个"为 Agent 循环设计"的调试基础设施:一个 node 进程拉起隔离数据的 VSCode 实例,通过 POST /api 的统一 JSON 协议暴露生命周期、双端 CDP 调试(扩展宿主 9230 端口 + webview)、Playwright UI 自动化、sourcemap 断点解析和 OAuth 全链路模拟。其设计亮点——CLINE_DIR 数据隔离、CLINE_CAPTURE_BROWSER 门控的 URL 捕获、以及永不进入生产构建的 __clineHandleUri 调试 hook——在 env.tsextension.tsserver.ts 中都有清晰源码佐证。对需要自动化验证扩展认证流程、回归 UI 状态或深入排查扩展宿主行为的开发者,这套 curl 可驱动的 API 集合(完整参考见 debug-harness README)是目前仓库内唯一的端到端自动化手段。适用前提:macOS 环境、Node >= 22.6、端口 19229/9230 空闲。

登录后查看全文
热门项目推荐
相关项目推荐