Cline VSCode 扩展调试台:用 HTTP API 远程操控、断点调试与自动化 OAuth 测试
Cline 仓库内置了一个面向 VSCode 扩展的 HTTP 控制式调试台(Debug Harness),它把「启动 VSCode 实例 → CDP 断点调试 → Playwright UI 自动化 → OAuth 流程模拟」全部收敛到一个 curl 即可调用的 HTTP 服务上。本文以仓库中的调试台文档 debug-harness.md 为主体,结合 server.ts、env.ts 与 extension.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() 做两件事,与文档描述一一对应:
- 写盘:以
{timestamp, url}结构追加到$CLINE_DIR/data/debug-captured-urls.jsonl(JSONL 格式); - 实时上报:若环境变量
CLINE_DEBUG_HARNESS_PORT已设置(调试台启动时固定注入19229),则 POST 到服务端的/captured-url端点,从而可通过oauth.captured_urlsAPI 实时查询。
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 响应头中的真实 code 和 state → 通过 __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_raw 加 urlRegex。
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 窗口),而应直接读取返回的 path;ui.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 实现可以梳理出完整链路:
- 构建:esbuild 将
src/extension.ts打包为dist/extension.js(不压缩、带 sourcemap);Vite 构建webview-ui/(不压缩、内联 sourcemap); - 启动:用
@vscode/test-electron下载 VSCode,再用 Playwright 的_electron.launch()以--inspect-extensions=9230和--extensionDevelopmentPath启动; - 数据隔离:在被测环境注入
CLINE_DIR=~/.cline2,createStorageContext()据此决定 globalState.json、secrets.json、任务历史等的存放位置; - 浏览器捕获:注入
CLINE_CAPTURE_BROWSER=1与CLINE_DEBUG_HARNESS_PORT=19229,openExternal()转而写 JSONL 并 POST 上报; - 扩展 CDP:经 WebSocket 连上 9230 端口的 V8 inspector,启用
Debugger与Runtime域,跟踪scriptParsed与 paused/resumed 状态; - Sourcemap 解析:内置 VLQ 解码,把源码位置映射为打包文件位置;
- Webview CDP:sidebar 加载后为 webview frame 建立 CDP session,失败则回退
frame.evaluate(); - 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.ts、extension.ts 与 server.ts 中都有清晰源码佐证。对需要自动化验证扩展认证流程、回归 UI 状态或深入排查扩展宿主行为的开发者,这套 curl 可驱动的 API 集合(完整参考见 debug-harness README)是目前仓库内唯一的端到端自动化手段。适用前提:macOS 环境、Node >= 22.6、端口 19229/9230 空闲。
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 StartedRust0624
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