扩展入口:activate 启动流程
职责
activate() 是 VSCode 扩展在用户首次触发扩展功能时被调用的入口,写在 apps/vscode/src/extension.ts。Cline 把整个启动过程在这里拆成五步:先把平台相关的 HostProvider 装好,然后清理上一版本遗留的存储格式,再把 VSCode 自己的存储导出到共享文件存储,接着跑一遍跨平台初始化 (initialize),最后才注册 VSCode 专属的命令、视图、URI 处理器和 CodeAction Provider。整个函数以 createClineAPI 收尾,返回外部使用者能直接调用的极简 API 对象。
它存在的意义是把「扩展怎么被 VSCode 拉起来」这件平台相关的事,和「Cline 自己怎么初始化」这件跨平台的事隔开。common.ts 里的 initialize() 同时被 VSCode、CLI、JetBrains 三个 host 共用 (initialize:34),extension.ts 只负责把 VSCode 特有的 context、命令、URI 等接进来。所以你在读 extension.ts 时,看到的几乎都是 vscode.commands.registerCommand(...) 这种注册动作,真正的服务装配都在 initialize() 和 Controller 构造函数里。
设计动机
- 平台抽象先于一切:
setupHostProvider必须先跑 (setupHostProvider:73),因为后续所有服务都通过HostProvider.get()拿 webview、终端、回调 URL。VSCode 特有的实现 (VscodeWebviewProvider、VscodeTerminalManager 等) 在这里被注入。 - 迁移先于导出:
cleanupLegacyVSCodeStorage在exportVSCodeStorageToSharedFiles之前跑 (cleanupLegacyVSCodeStorage:78),先把 workspace→global、task history→文件、custom instructions→rules 这几类历史脏数据清掉,再导出干净的 state 到~/.cline/data/,让其它平台能读到一致的状态。 - 共享初始化抽到 common.ts:跨平台服务 (StateManager、ErrorService、PostHog、ClineTempManager、FileContextTracker 等) 都在
initialize()里拉起 (external services:56),VSCode 不重复造一遍。这样 CLI 和 JetBrains 复用同一套启动逻辑。 - webview 用 retainContextWhenHidden:
registerWebviewViewProvider传retainContextWhenHidden: true(webview retain:124),让 sidebar 关闭时 React 状态不丢,下次打开不用重渲染。代价是内存常驻,但 Cline 的对话上下文值得这点开销。 - 命令分两层注册:VSCode 命令先从
ExtensionRegistryInfo.commands拿 ID 常量 (ExtensionRegistryInfo:130),保证 package.json 和代码里的 command ID 一致。Dev 模式额外加载dev/commands/tasks里的调试命令 (dev commands:193)。 - URI handler 兜底重试:
handleUri处理深链时,如果 sidebar 还没初始化好,先调openClineSidebarForTaskUri再处理一次 (task uri retry:178),防止首次安装时 sidebar 初始化和深链到达之间的竞态。
关键文件
activate:68— 整个扩展入口,五步启动流程都在这里。setupHostProvider call:73— 第一步,装配 VSCode 版 HostProvider (webview、diff、comment review、terminal、callback URL)。cleanupLegacyVSCodeStorage:78— 第二步,清理 workspace→global、task history→file、custom instructions→rules 等历史格式。exportVSCodeStorageToSharedFiles:84— 第三步,把 VSCode 原生存储导出到~/.cline/data/共享文件存储,供其它平台读取。initialize call:88— 第四步,跑common.ts的跨平台初始化,返回VscodeWebviewProvider。registerWebviewViewProvider:124— 把 sidebar webview 注册到 VSCode,启用retainContextWhenHidden。registerUriHandler:187— 注册 URI handler,处理vscode://extension.id/cline/task/...这种深链。registerCodeActionsProvider:248— 注册 CodeAction Provider,在编辑器错误灯泡上挂「Add to Cline / Explain / Improve / Fix with Cline」四个动作。setupHostProvider:602— 装配 HostProvider 的实际函数,把 VscodeWebviewProvider、VscodeDiffViewProvider、VscodeTerminalManager 等工厂函数传进去。createClineAPI:553— 返回外部 API,提供startNewTask、sendMessage、pressPrimaryButton、pressSecondaryButton四个方法。initialize:34— 跨平台初始化主体,StateManager、PostHog、ErrorService、syncWorker 都在这里拉起。deactivate:699— 反激活时调tearDown()+ dispose comment review controller。
数据流
启动顺序在 activate 顶部一眼可见,核心是「先迁移后初始化,先共享后平台」:
// apps/vscode/src/extension.ts
// 1. Set up HostProvider for VSCode
setupHostProvider(context)
// 2. Clean up legacy data patterns within VSCode's native storage.
await cleanupLegacyVSCodeStorage(context)
// 3. One-time export of VSCode's native storage to shared file-backed stores.
const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
const storageContext = createStorageContext({ workspacePath })
await exportVSCodeStorageToSharedFiles(context, storageContext)
// 4. Register services and perform common initialization
const webview = (await initialize(storageContext)) as VscodeWebviewProvider这段在 activation sequence:73 附近。initialize() 内部 (createWebviewProvider:63) 调用 HostProvider.get().createWebviewProvider(),工厂方法返回一个 VscodeWebviewProvider 实例,这个实例在构造时同步 new Controller(context),Controller 构造函数里又拉起 StateManager、AuthService、McpHub、BannerService 等一长串依赖。所以 initialize() 返回时,Controller 已经准备好接 webview 消息了。回到 activate() 后面那段才注册命令、URI handler、CodeAction Provider,这些注册动作最终都把请求转给 webview.controller 或 WebviewProvider.getInstance()。
边界与失败
- StateManager 初始化失败不致命:
initialize()用 try/catch 包住StateManager.initialize,失败时弹 ERROR 消息但继续往下走 (stateManager try:45),因为存储在内存里仍然可用,只是没法持久化。 - task URI 与 sidebar 初始化竞态:URI handler 先尝试处理 task 深链,失败时先
openClineSidebarForTaskUri等 sidebar 可见再重试一次 (task uri retry:178),最多等 3 秒。 - dev 模式额外命令异步加载:
IS_DEV分支里用动态import("./dev/commands/tasks")加载调试命令 (dev import:193),失败只记日志,不影响主流程。 - secrets 跨窗口同步:监听
storageContext.secrets.onDidChange,当cline:clineAccountId变化时,新值代表另一窗口登录、空值代表另一窗口登出 (secrets listener:533),让多窗口登录状态保持一致。 - ripgrep 路径探测:VSCode 1.122 把 ripgrep 从
@vscode/ripgrep迁到@vscode/ripgrep-universal,getBinaryLocation按新布局先探,失败再回退到旧路径 (ripgrep path probe:685)。 - deactivate 调 tearDown:
deactivate()调common.ts的tearDown()(tearDown:153),按顺序 dispose PostHog、telemetry、ErrorService、featureFlags、WebviewProvider、syncWorker、HookProcessRegistry、ClineTempManager,避免留下僵尸进程。
小结
activate() 把 VSCode 平台特性和 Cline 业务逻辑拆得很干净:VSCode 这边只管注册命令、视图、URI handler、CodeAction,真正的服务装配都委托给 common.ts 的 initialize() 和 Controller 构造函数。这种分层让 CLI 和 JetBrains 能复用同一套初始化代码。要继续看初始化后 Controller 怎么装配依赖、怎么把 webview 和 Task 串起来,转 /startup/controller;看 webview 和扩展之间怎么用 gRPC-over-postMessage 通信,转 /startup/webview-bridge。