Cline Desktop Sidecar 架构解析:一个 Bun 进程如何把桌面 UI 适配到共享 Cline Hub
【免费下载链接】clineAutonomous coding agent as an SDK, IDE extension, or CLI assistant.项目地址: https://gitcode.com/GitHub_Trending/cl/cline
Cline 桌面应用(@cline/code)的核心枢纽是sidecar/目录下的 sidecar 进程:它作为一个独立的 Bun 进程,负责把桌面 Webview UI 和操作系统级原生操作适配到 Cline 的共享 Hub(Cline Hub)之上。读完本文,你会理解 sidecar 的目录组织、HTTP + WebSocket 传输协议、ClineCore的 Hub 客户端接入方式、工具审批(tool approval)的客户端 Promise 决议机制、完整的命令映射表,以及如何在本地启动 sidecar 进行开发调试。
一、Sidecar 的定位与总体设计
sidecar 是一个Bun 进程,它的职责可以概括为三件事:
- 导入
@cline/core,发现或启动规范化的共享 Hub(canonical shared Hub); - 作为 Hub 客户端(client)注册到该 Hub;
- 通过 HTTP + WebSocket 向前端(Next.js Webview)提供服务。
一个关键的设计约束是:sidecar 不拥有私有的 agent 运行时 Hub。它复用由 CLI 发现的同一个兼容 Hub;如果桌面端是第一个客户端,则由 Core 启动规范化的 detached Hub。这保证了同一个工作区内的多个 Cline 客户端(CLI、桌面端等)收敛到同一个 Hub 进程上。
sidecar 的入口文件是 index.ts,从源码可以看到启动流程的几个关键节点:
runEntrypoint()先检查--telemetry-selfcheck参数(用于 CI 校验构建期内联的遥测配置),再检查claimHubDaemonProcess():如果本进程被选为 Hub daemon 宿主,则直接转入@cline/core/hub/daemon-entry,让 sidecar 二进制承担共享 Hub 守护进程的启动职责;- 否则进入
main():解析工作区根目录、设置 home 目录、启动可观测性、创建SidecarContext、预热工作区元数据、initializeSessionManager()接入 Hub,最后调用startServer()启动 HTTP + WS 服务; - 就绪后向 stdout 输出一行 JSON(
type: "ready"),携带endpoint、wsEndpoint(含approval_token查询参数)、pid、mode,宿主应用据此把前端指向 sidecar; - 进程通过
SIGINT/SIGTERM/beforeExit统一走shutdown(),并带 5 秒超时保护;watchManagedHubBuildMismatch()会在其他 Cline 安装(如更新后的 CLI)替换共享 Hub 时向前端广播hub_build_mismatch事件,提示用户更新重启。
二、目录结构与职责划分
ARCHITECTURE.md 中给出的核心目录结构如下:
sidecar/ ├── index.ts # Entry point: starts HTTP+WS server ├── server.ts # Bun HTTP server + WebSocket handlers ├── context.ts # SidecarContext type and factory ├── commands.ts # Command router ├── chat-session.ts # Shared-Hub chat session adapter ├── session-data/ # Shared discovery, messages, artifacts, search helpers ├── paths.ts # Path resolution ├── types.ts # Shared types └── ARCHITECTURE.md # This file结合当前仓库的实际目录(sidecar/),围绕上述核心还扩展了attachments.ts(附件物化)、mcp.ts/mcp-oauth.ts(MCP 配置与 OAuth)、marketplace.ts(插件市场目录)、observability.ts(日志与遥测)、shell-path.ts(登录 shell PATH 修复)、telemetry-selfcheck.ts等模块,每个模块都配有同名.test.ts测试文件。
几个核心文件的实际职责(以源码为准):
- types.ts:定义
SidecarContext(含liveSessions、pendingApprovals、pendingQuestions、wsClients、sessionManager、hubClient等字段),并导出环境配置常量:SIDECAR_PORT = Number(process.env.CLINE_SIDECAR_PORT) || 3126,SIDECAR_HOST = process.env.CLINE_SIDECAR_HOST?.trim() || "127.0.0.1"(默认仅监听回环地址,注释说明可通过CLINE_SIDECAR_HOST=0.0.0.0开放给容器端口映射等场景)。 - context.ts:
SidecarContext工厂与ClineCore初始化、Hub 客户端管理、事件路由的所在地。 - commands.ts:命令路由器
handleCommand(ctx, command, args, options),按命令名分发到各子模块。 - paths.ts:路径解析。
resolveWorkspaceRoot()用git rev-parse --show-toplevel定位工作区根;sharedSessionDataDir()优先读CLINE_SESSION_DATA_DIR,否则回退到resolveSessionDataDir();MCP 设置路径默认为~/.cline/data/settings/cline_mcp_settings.json(可被CLINE_MCP_SETTINGS_PATH覆盖);会话日志写到~/.cline/apps/kanban/sessions/<sessionId>.jsonl(可被CLINE_KANBAN_DATA_DIR覆盖)。
三、传输协议:Request / Response / Event 三件套
前端与 sidecar 之间走 WebSocket,消息格式固定为三种(文档原文,协议保持不变):
Request: { "type": "command", "id": string, "command": string, "args"?: object } Response: { "type": "response", "id": string, "ok": boolean, "result"?: unknown, "error"?: string } Event: { "type": "event", "event": { "name": string, "payload": unknown } }在 server.ts 中可以确认这条协议的落地细节:
createWebSocketHandler()的message()对每条入站消息做JSON.parse,成功后调用handleCommand(ctx, request.command, request.args, { connection: ws }),用jsonResponse(id, true, result)或jsonResponse(id, false, undefined, message)回包;- 事件编码统一由
encodeSidecarEvent(name, payload)(位于 context.ts)完成,即{"type":"event","event":{"name":...,"payload":...}}。
除/transportWebSocket 端点外,HTTP fetch 处理器还暴露了若干端点:
| 端点 | 作用 |
|---|---|
GET /health | 返回{ ok: true, mode: "sidecar", pid },用于存活探测 |
POST /shutdown | 仅信任 origin 可调用,异步触发onShutdown后process.exit(0) |
GET /api/marketplace/catalog | 返回插件市场目录,失败时返回带error字段的空目录 |
POST /telemetry/error | 接收 Webview 的前端错误上报,字段截断后写入 SDK 遥测 |
安全边界:Origin 校验与 approval token
/transport的 WebSocket 升级有两层门禁:
- Origin 白名单:
TRUSTED_BROWSER_ORIGINS默认包含tauri://localhost、http://tauri.localhost、https://tauri.localhost、http://localhost:3125、http://127.0.0.1:3125,可通过CLINE_SIDECAR_TRUSTED_ORIGINS(逗号分隔)追加; - approval token:
startServer()生成(或读取CLINE_SIDECAR_APPROVAL_TOKEN)一个 token,升级请求必须携带?approval_token=...且通过timingSafeEqual恒定时间比较,才会给该连接标记canApproveTools: true。源码注释明确说明:无 origin 的本地客户端仍然可以连接,但只有携带了有效 token 的浏览器托管桌面 UI 才能接收和决议审批。审批类命令(poll_tool_approvals、hub_upgrade等)在 commands.ts 中都会检查options.connection.data.canApproveTools,不满足则直接抛错。
另外,startServer()采用“首选端口 + OS 分配端口(0)”双候选策略:若3126被占用则自动回退,避免端口冲突直接启动失败。
四、核心设计决策
1. 聊天会话 —— 共享 Hub 客户端
sidecar 通过ClineCore以 Hub 模式接入,且不指定显式 endpoint。ClineCore因此复用 CLI 发现的同一个兼容 Hub;当桌面端是第一个客户端时,则启动规范化的 detached Hub。context.ts 中initializeSessionManager()的实际实现与文档示例一致,并额外注入了能力、日志、遥测与特性开关:
const sessionManager = await ClineCore.create({ clientName: "cline-code", backendMode: "hub", capabilities: createSidecarRuntimeCapabilities(ctx), logger: ctx.logger, telemetry: ctx.telemetry, featureFlags: getDesktopFeatureFlagsService({ ... }), hub: { strategy: "require-hub", workspaceRoot: ctx.workspaceRoot, cwd: ctx.workspaceRoot, clientType: "code-sidecar", displayName: "Cline Desktop sidecar", }, });随后:
sessionManager.subscribe((event) => handleCoreSessionEvent(ctx, event))订阅全部会话事件并转发给 WS 客户端;ensureSharedHubClient(ctx, sessionManager.runtimeAddress)再创建第二个NodeHubClient(clientType: "code-sidecar-observer"),用ensureCompatibleLocalHubUrl({ strategy: "require-hub", ... })解析 Hub URL 后connect()并subscribe(handleHubLiveEvent)。这个观察客户端用于接收 Hub 级事件(如 Agenda 任务审批、task.*生命周期事件、经 Hub 附加的会话流)。
启动发现与加锁机制确保并发客户端收敛到同一个 Hub;编译后的 sidecar 还识别 Core 的 Hub daemon 启动模式(即上文claimHubDaemonProcess()分支),让桌面端在尚无 CLI 进程时也能拉起同一个 detached Hub。
会话事件如何变成前端 chunk?handleAgentEvent()将AgentEvent映射为chat_text、chat_reasoning、chat_tool_call_start/update/end、chat_media、chat_core_log、chat_usage、chat_done等流,统一经emitChunk()广播并同步追加到会话日志文件(每 session 串行化写入,避免同步写阻塞事件循环)。handleHubLiveEvent()则处理观察客户端路径:把assistant.delta、reasoning.delta、tool.started/updated/finished、run.started/completed/failed/aborted等 Hub 事件翻译为同样的chat_*chunk,供经 Hub 附加的会话(attachedViaHub)使用。
chat_session_command支持的动作集合在 types.ts 的ChatSessionCommandRequest中定义:start、attach、send、stop、abort、fork、reset、restore_checkpoint、pending_prompts、steer_prompt、update_pending_prompt、remove_pending_prompt。
2. 工具审批 —— 客户端持有 Promise 决议
共享 Hub 把审批请求路由回创建该会话的客户端。桌面端在 Webview 在线期间用内存 Promise Map 处理审批,context.ts 中的实现(requestSidecarToolApproval)要点:
- 从
ctx.wsClients中找到第一个canApproveTools === true的连接作为 owner;若没有,直接拒绝并附原因"No trusted desktop approval surface is connected"; - 将
{ item, owner, resolve }存入ctx.pendingApprovals,再向 owner 推送tool_approval_state事件(携带该会话全部待审批项); - 前端响应
respond_tool_approval命令时 resolve 对应 Promise;owner 断连时cancelSidecarToolApprovalsForOwner()会把它名下的所有 pending 以{ approved: false, reason: "Desktop approval surface disconnected" }决议,防止审批永远挂起; - 连接集合变化(开关 WS)时,
syncSidecarApprovalReadiness()会向 Hub 更新客户端能力:只要有能审批的 webview 在线,就声明HUB_CLIENT_TOOL_APPROVAL_CAPABILITY("Cline Code has a live user surface for tool review."); - 对于经 Hub 触发的任务审批(
approval.requested事件),handleHubApprovalRequest()先走同一套桌面审批 Promise,再把结果通过hubClient.command("approval.respond", { approvalId, approved, reason }, sessionId)回传 Hub。
与审批同构的还有ask_question机制:requestSidecarAskQuestion()把问题推送给前端并挂起 Promise,带ASK_QUESTION_TIMEOUT_MS = 5 分钟超时,超时以ask_question_cancelled事件通知前端并 reject。
3. 供应商管理 —— 直接使用 ProviderSettingsManager
import { ProviderSettingsManager, listLocalProviders, ... } from "@cline/core"; const manager = new ProviderSettingsManager();供应商相关命令(list_provider_catalog、list_provider_models、save_provider_settings、add_provider、run_provider_oauth_login)都直接调用@cline/core的本地供应商 API,不经过 Hub。
4. 会话存储 —— 直接使用 SqliteSessionStore
import { SqliteSessionStore, resolveSessionBackend } from "@cline/core"; const store = new SqliteSessionStore();list_chat_sessions组合SqliteSessionStore与文件发现;update_chat_session_title走resolveSessionBackend().updateSession;delete_chat_session执行SqliteSessionStore.delete并清理文件产物。
5. 例行计划(Routine Schedules)—— 直连 Hub 命令
例行操作复用与聊天会话观察相同的已连接 Hub 客户端,绝不启动第二个进程内 Hub:
await ctx.hubClient.command("schedule.list", { limit: 200 });6. 原生命令
pick_workspace_directory—— macOS 用osascript、Linux 用zenity弹出目录选择器;open_mcp_settings_file—— 用open/xdg-open打开文件。
7. 前端连接
前端 desktop-client.ts 直接连接 sidecar 的 WebSocket:
- 优先从
window.__SIDECAR_WS_ENDPOINT__发现端点(由 sidecar 的 HTML scaffold 注入); - 未注入时回退到默认值
ws://127.0.0.1:3126/transport(与 types.ts 中的默认端口一致); - 不依赖 Tauri,保持相同的
invoke()/subscribe()API。
五、命令映射表(Command Map)
以下命令映射表完整继承自 ARCHITECTURE.md,并可与 commands.ts 中的handleCommand实现一一对应:
| Command | 实现 |
|---|---|
chat_session_command | 经ClineCore走共享 Hub |
list_provider_catalog | ProviderSettingsManager+listLocalProviders |
list_provider_models | getLocalProviderModels |
save_voice_input_settings | 校验并持久化所选转写供应商/模型 |
create_streaming_transcription_session | 签发短时效、绑定转写用途的浏览器 token,不暴露供应商凭据 |
transcribe_audio | 已配置的语音输入选择 + 供应商凭据 |
save_provider_settings | saveLocalProviderSettings |
add_provider | addLocalProvider |
run_provider_oauth_login | loginLocalProvider |
list_chat_sessions | SqliteSessionStore+ 文件发现 |
list_discovered_sessions | 合并发现(merged discovery) |
read_session_messages | 会话数据读取器(commands.ts中默认maxMessages: 800) |
read_session_hooks | 会话数据读取器(默认limit: 300) |
delete_chat_session | SqliteSessionStore.delete+ 文件清理 |
update_chat_session_title | resolveSessionBackend().updateSession |
list_mcp_servers | 直接文件 I/O |
authorize_mcp_server_oauth | 显式 Connect 动作 → 可取消的authorizeMcpServerOAuth+ 系统浏览器 |
cancel_mcp_server_oauth | 取消挂起的 MCP OAuth 回调等待 |
upsert_mcp_server | 直接文件 I/O |
delete_mcp_server | 直接文件 I/O |
get_git_branch | 异步execFile("git", ...) |
list_git_branches | 异步execFile("git", ...) |
checkout_git_branch | 异步execFile("git", ...) |
search_workspace_files | getFileIndex |
get_process_context | 内存上下文(返回 workspaceRoot、平台、app 版本、运行中会话数、Hub 连接状态等) |
poll_tool_approvals | 内存 pending map(需可信连接) |
respond_tool_approval | 内存 Promise 决议 |
poll_ask_questions | 内存 pending map |
respond_ask_question | 内存 Promise 决议 |
list_routine_schedules | 共享 Hub schedule 命令 |
list_user_instruction_configs | 直接 core API |
pick_workspace_directory | OS 原生对话框 |
open_mcp_settings_file | OSopen命令 |
从源码结构看,handleCommand还包含文档表格未逐一列出的命令,例如proceed_while_running(转发run.proceed_while_runningHub 命令)、list_session_agents、hub_upgrade(强制升级托管 Hub,同样要求可信桌面连接)等,阅读 commands.ts 可获得完整清单。
六、开发工作流
文档给出的开发命令(可在 package.json 中核对对应脚本):
bun run dev:headless # Start sidecar and Next.js with a fresh shared approval credential bun run dev:sidecar # Start only the sidecar (no browser approval surface) bun run dev:web # Start only Next.js (no authenticated approval connection) bun run dev # Both concurrently脚本实际定义:dev:sidecar执行bun run sidecar/index.ts;dev:web执行next dev webview -p 3125 --turbo(Next.js 跑在 3125 端口,正好命中 sidecar 的默认信任 origin 列表);dev:headless走scripts/dev-headless.ts以全新共享审批凭据同时拉起 sidecar 与 Next.js;dev则是tauri dev完整桌面壳。
七、关键环境变量速查
综合 types.ts、server.ts 与 paths.ts 的解析逻辑:
| 环境变量 | 默认值 | 作用 |
|---|---|---|
CLINE_SIDECAR_PORT | 3126 | sidecar 首选监听端口;被占用时自动回退到 OS 分配端口 |
CLINE_SIDECAR_HOST | 127.0.0.1 | 监听地址;设为0.0.0.0可接受外部连接(如 Docker 端口发布),但对外通告的 dial 地址仍为回环 |
CLINE_SIDECAR_TRUSTED_ORIGINS | 空 | 逗号分隔的额外信任 origin(容器内非标准端口的 dev server 等场景) |
CLINE_SIDECAR_APPROVAL_TOKEN | 运行时随机 UUID | 审批 token;前端 WS 连接需携带才能标记canApproveTools |
CLINE_HUB_PORT | 空 | 显式钉住 Hub 端点;设置后跳过启动期的 build mismatch 检测(该宿主仅保持协议兼容) |
CLINE_SESSION_DATA_DIR | resolveSessionDataDir() | 共享会话数据目录 |
CLINE_MCP_SETTINGS_PATH | ~/.cline/data/settings/cline_mcp_settings.json | MCP 设置文件路径 |
CLINE_TOOL_APPROVAL_DIR | <会话数据目录>/tool-approvals | 工具审批目录(保留用于文件清理兼容) |
CLINE_KANBAN_DATA_DIR | ~/.cline/apps/kanban | 会话日志(.jsonl)根目录 |
八、小结
sidecar 的设计可以浓缩为三条主线:传输层保持极简的三消息协议(command/response/event)并叠加 origin 白名单 + approval token 双门禁;运行时层坚持“共享 Hub、不私起 Hub”,通过ClineCore(require-hub策略)与NodeHubClient观察客户端两条通道接入,让桌面端与 CLI 等客户端共享同一 Hub 实例;交互层把审批、提问等跨端等待统一实现为“推送事件 + 内存 Promise 决议 + 断连自动取消/拒绝”的模式,保证了 UI 离线时 agent 流程不会被永久阻塞。理解了 ARCHITECTURE.md 描述的协议骨架,再对照 index.ts、server.ts、context.ts 与 commands.ts 的实现,即可完整掌握 Cline Desktop sidecar 从进程启动到事件回传的全链路。
【免费下载链接】clineAutonomous coding agent as an SDK, IDE extension, or CLI assistant.项目地址: https://gitcode.com/GitHub_Trending/cl/cline
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考