擴充入口: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 的對話 context 值得這點開銷。 - 命令分兩層註冊: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。