Collapses the early SDK-migration history (through 2026-05-20) into a single commit. Later commits are preserved individually.
SDK Migration — Entry Point
You are working on migrating the Cline VSCode extension from its
classic core to the Cline SDK (@cline/core). This document is
your primary reference. Read it in full before starting any step.
Document Map
| Document | Purpose | When to Read |
|---|---|---|
| This file | Entry point, plan, operational procedure | Always, first |
| ARCHITECTURE.md | Features, design decisions, SDK capabilities | Before Step 1; refer back as needed |
| SDK-REFERENCE/OAUTH.md | How the SDK handles OAuth and credentials | When working on auth (Steps 4, 5) |
| SDK-REFERENCE/MCP.md | How the SDK handles MCP server management | When working on MCP (Step 5) |
| PROBLEMS.md | Known issues, verification status | Before each verification gate |
| ../src/dev/debug-harness/README.md | Debug harness API reference | When using the debug harness |
Docs from previous attempts that are not carried forward:
- CAVEATS.md, FIXED.md, FEATURE-REMOVAL-CLEANUP-PLAN.md, DEBUG-HARNESS.md (root level), FEEDBACK.md — these degraded badly. Lessons are incorporated into this plan.
References
Code Repositories
| Repo | Path | kb_search name |
|---|---|---|
| Cline (this repo) | ~/clients/cline/cline |
cline |
| Cline SDK | ~/clients/cline/sdk-wip |
sdk |
| JetBrains Plugin | ~/clients/cline/intellij-plugin |
plugin |
| VSCode | ~/clients/cline/vscode |
vscode |
How to Research the SDK
Always use kb_search with the sdk repo when you need to
understand how the SDK supports a feature. Do not guess at APIs,
URLs, or data formats. The SDK is the source of truth.
Example: Before implementing OAuth, search:
kb_search(name="sdk", query="OAuth login flow callback")
You can also compare before/after states using commit-based search:
kb_search(name="cline", query="accountLoginClicked", commit="origin/main")
kb_search(name="cline", query="accountLoginClicked", commit="HEAD")
Core Principles
These principles are derived from hard-won experience on two previous attempts. Violating them leads to broken products and wasted time.
1. Thunk, Don't Replace
The webview speaks gRPC-over-postMessage today. We will not replace that with a new message protocol in this migration. Instead, we build a thunking layer that sits between the SDK and the existing gRPC interface. The webview continues to send gRPC-shaped messages; the thunking layer translates between those and SDK calls.
This means:
- The webview code is largely untouched
- gRPC proto files stay in place until the final cleanup step
- Each SDK feature is wired up by implementing its gRPC handler
2. Verify Before You Proceed
Every step has a verification gate. You must demonstrate the feature works before moving on. Verification means:
- Unit tests that test real behavior, not just that functions exist
- Debug harness smoke tests for UI-facing features
- Manual confirmation when automated tests can't cover it
Mark things as "awaiting verification" not "fixed". Only mark "verified" after you have evidence (test output, screenshot, etc.).
3. Delete and Document
When replacing a classic module with its SDK equivalent, delete the classic code immediately and document where to find it. Dead code in the tree creates confusion about what is active vs. vestigial.
The classic implementation is always accessible via:
kb_search(name="cline", query="...", commit="origin/main")— search the classic codebase at the pre-migration commitgit show origin/main:path/to/file.ts— view any filegit diff origin/main..HEAD -- path/— see what changed
When deleting a module, add a comment in the replacement file:
// Replaces classic src/core/task/ (see origin/main)
This way there is never any ambiguity about what code is running.
4. Use the Debug Harness
The debug harness at src/dev/debug-harness/ is your primary
integration testing tool. Use it to:
- Verify UI renders correctly after changes
- Test user flows (login, chat, settings, history)
- Catch regressions that unit tests miss
Always dismiss promotional overlays first. There may be one or two:
- "Introducing Cline Kanban" overlay
- "New in v3.78.0" announcement overlay
Both follow the same sr-only pattern and can be dismissed with:
curl localhost:19229/api -d '{"method": "web.evaluate", "params": {"expression": "document.querySelectorAll(\".sr-only\").forEach(el => el.parentElement?.click())"}}'
You may need to run this twice if both overlays are present.
Use VSCode command palette actions to navigate between tabs.
5. SDK "Default" Implementations Are References, Not Products
The SDK's DefaultSessionBuilder, DefaultRuntimeBuilder, etc. are
designed for simple use cases. As an IDE, we need more:
- Custom MCP manager (file watching, SSE/streamableHTTP support)
- Custom session persistence (read existing task history format)
- Custom tool approval (integrate with webview approval UI)
Use the defaults as references, but implement what the product needs.
6. Avoid as Casts and Type Confusion
A recurring bug source was confusion between SDK types and gRPC/proto
types. For example, SDK returns accountId but gRPC expects
workos:accountId. Use explicit conversion functions with tests, and
never use as to paper over type mismatches.
Migration Steps
This plan is ordered by dependency: each step builds on the previous. Do not skip steps. Each step ends with a verification gate.
Step 1: Foundation & Cutover
Goal: SDK dependencies installed, test infrastructure ready, and the extension's entry point switched to the SDK adapter. There is one entry point, not two.
Tasks:
- Add
@cline/core,@cline/llms,@cline/shared,@cline/agentsas dependencies (vianpm linkfrom local SDK) - Add
vitest.config.sdk.tsfor SDK adapter tests - Create
src/sdk/directory withindex.tsbarrel export - Modify
src/extension.tsto use the SDK adapter as its activation path (replacing the classicControllerimport) - Delete
src/core/controller/— the classic controller is replaced bysrc/sdk/SdkController.ts(to be implemented in Step 4). Add comment:// Replaces classic src/core/controller/ (see origin/main) - Update
esbuild.mjsif needed for the new import structure - Verify:
npm run compilesucceeds, extension loads in VSCode (sidebar may show errors since handlers aren't implemented yet, but the extension process itself starts)
Why one entry point: Attempt 2 used CLINE_SDK=1 to switch
between two entry points. This caused constant confusion about which
codepath was running. With a single entry point, there is never any
doubt. The classic code is always accessible via origin/main.
Verification gate: Extension compiles and loads. The SDK adapter is the only codepath. (It won't do much yet — that's Step 4.)
Step 2: Legacy State Reader
Goal: Read all existing on-disk state from the SDK adapter layer.
Tasks:
- Implement
src/sdk/legacy-state-reader.ts:- Read
globalState.json(provider settings, model selections, dismissed banners, etc.) - Read
secrets.json(API keys, Cline auth tokens) - Read
taskHistory.json(task list for history view) - Read per-task directories (
api_conversation_history.json,ui_messages.json) - Read
cline_mcp_settings.json(MCP server configs)
- Read
- Write tests against fixture data (copy real
~/.cline/data/samples, redact secrets) - Verify: All reads produce correct typed results, error handling for missing/corrupt files
Verification gate: Unit tests pass; reader correctly parses
real ~/.cline/data/ contents (spot-check manually).
Step 3: Provider Migration
Goal: Existing provider credentials survive the transition.
Tasks:
- Implement
src/sdk/provider-migration.ts:- Use SDK's
migrateLegacyProviderSettings()as reference - Map classic
globalState.json+secrets.jsonentries to SDKproviders.jsonformat - Never overwrite existing entries
- Tag migrated entries with
tokenSource: "migration" - Write a migration sentinel to prevent re-migration
- Use SDK's
- Test with fixtures covering all 30+ providers
- Verify: After migration, SDK can create handler for each provider; existing API keys still work
Critical: This is the highest-risk step. Getting it wrong means users get logged out. Test exhaustively.
Verification gate: All provider credential tests pass. Manual test: set up providers in classic extension, switch to SDK branch, verify inference still works for Anthropic, OpenAI, OpenRouter, Ollama, and the Cline provider.
Step 4: Session Lifecycle (No UI Yet) — ✅ Completed
Goal: Create and manage SDK sessions from the adapter layer.
Tasks:
- Implement
src/sdk/cline-session-factory.ts:- Custom session persistence adapter reading
~/.cline/data/tasks/ - Map
HistoryItem↔ session fields - Implement
ClineCore.create()with proper config - Build
CoreSessionConfigfrom legacy state viaProviderSettingsManager - Build
StartSessionInputand resume input helpers
- Custom session persistence adapter reading
- Implement
src/sdk/SdkController.ts:initTask(prompt)— create session, start inferenceaskResponse(message)— continue conversation (sends to existing session)cancelTask()— abort running sessionclearTask()— reset for new taskshowTaskWithId(id)— load task from historyreinitExistingTaskFromId(id)— resume task from history- Subscribe to SDK events, translate to internal message format
- Session event listener system for downstream consumers
- Implement
src/sdk/message-translator.ts:- SDK
CoreSessionEvent→ClineMessage[]for webview consumption - Handle all event types: chunk, agent_event (content_start/update/end, done, error, notice, iteration_start/end, usage), ended, hook, status
- Streaming state tracking (partial message dedup)
- Tool text formatting helpers
- HistoryItem ↔ session field mapping
- SDK
- Test all paths — 91 unit tests pass across 4 test files
Verification gate: ✅ Unit tests pass (91/91). TypeScript compiles
with 0 errors in src/sdk/. Session lifecycle methods work through
the adapter layer without any UI. See PROBLEMS.md for known minor issues.
Step 5: gRPC Thunking Layer — ✅ Completed
Goal: Wire SDK adapter to the existing webview via gRPC handlers.
This is the critical insight from attempt 2: the webview speaks gRPC. We translate at the boundary. The webview stays untouched.
Tasks:
- Implement
src/sdk/task-proxy.ts:TaskProxyprovides a classic Task-compatible interface that delegates to SDK session methodshandleWebviewAskResponse()→ SdkController.askResponse()abortTask()→ SdkController.cancelTask()MessageStateHandlerextends EventEmitter for CLI compatibilityTaskProxyStatemirrors classic TaskState subset- Stub properties for removed features (browser, checkpoints)
- Implement
src/sdk/webview-grpc-bridge.ts:- Bridges SDK session events to webview gRPC streams
- Translates ClineMessages to proto format via
convertClineMessageToProto() - Pushes through
sendPartialMessageEvent()for streaming - Pushes through
sendStateUpdate()on significant events - Error handling — never blocks the event stream
- Wire SdkController to use TaskProxy + WebviewGrpcBridge:
- Session events → message translation → gRPC bridge → webview
handleSessionEvent()translates and emits to all listeners- Messages accumulated in
messageStateHandlerfor state building - State updates pushed on turn complete / session ended
- Reuse existing
getStateToPostToWebview()for state building- Classic implementation reads from StateManager
- TaskProxy provides
messageStateHandler.getClineMessages() - Will be gradually replaced with SDK-sourced state in later steps
Verification gate: ✅ 114 unit tests pass across 6 test files. TypeScript compiles with 0 new errors (3 pre-existing in unrelated files). The gRPC thunking layer is complete — session events flow from SDK through message translation to webview gRPC streams. See PROBLEMS.md for known minor issues.
Step 6: Auth & Account Flows — ✅ Implementation Complete, 🔵 Awaiting E2E Verification
Goal: Full OAuth login/logout, credit display, org switching work.
This was the most broken area in attempt 2. Be especially careful.
Tasks:
- Implement Cline OAuth using SDK's
loginClineOAuth():- SDK spawns local callback server and provides the auth URL
- Our code opens the browser via
openExternal() - SDK handles token exchange
- We persist tokens to
secrets.jsonundercline:clineAccountId
- Implement
subscribeToAuthStatusUpdatestreaming:- Read credentials from disk on subscription
- Push initial auth state immediately (prevents race condition)
- Cross-window sync via secrets change listener
- Implement
getUserCredits/getOrganizationCredits:- Fetch from Cline API using stored auth token via
ClineAccountService - Use
{apiBaseUrl}not hardcodedapp.cline.bot
- Fetch from Cline API using stored auth token via
- Implement
accountLogoutClicked:- Clear credentials from disk
- Push unauthenticated state to webview
- Implement
setUserOrganization:- Update active org via API call
- Refresh auth info after switching
- Implement OpenAI Codex OAuth via SDK's
loginOpenAICodex() - Implement OCA OAuth via SDK's
loginOcaOAuth() - Implement token refresh using SDK's
refreshClineToken() - Write unit tests — 20 tests in
src/sdk/auth-service.test.ts
Key pitfalls from attempt 2 (all addressed):
workos:prefix on account IDs —getAuthToken()always returnsworkos:-prefixed token{appBaseUrl}vs hardcoded URLs — usesClineEnv.config().apiBaseUrlandappBaseUrl- Race condition: webview subscribes to auth state before the
bridge pushes it —
subscribeToAuthStatusUpdatepushes initial state immediately - Token field name mismatches between SDK and classic storage —
explicit conversion in
credentialsToAuthInfo()(ms→seconds for expiresAt)
Files created/modified:
src/sdk/auth-service.ts— SDK-backed AuthService (replacessrc/services/auth/AuthService.ts)src/sdk/account-service.ts— SDK-backed ClineAccountService (replacessrc/services/account/ClineAccountService.ts)src/sdk/auth-service.test.ts— 20 unit testssrc/sdk/SdkController.ts— Wired auth/account services in constructorsrc/sdk/index.ts— Added barrel exportssrc/core/controller/account/accountLoginClicked.ts— Import from@/sdk/auth-servicesrc/core/controller/account/accountLogoutClicked.ts— Delegates to SdkControllersrc/core/controller/account/subscribeToAuthStatusUpdate.ts— Import from@/sdk/auth-servicesrc/core/controller/account/openAiCodexSignIn.ts— Uses SDK-backed AuthServicesrc/core/controller/account/openAiCodexSignOut.ts— Uses SDK-backed AuthServicesrc/extension.ts— Import from@/sdk/auth-service
Verification gate: 🔵 Unit tests pass (20/20). TypeScript compiles with 0 new errors. End-to-end verification with debug harness pending — need to test: login flow, profile display, credits, org switching, logout.
Step 7: MCP Integration — ✅ Classic McpHub Wired (SDK Manager Deferred)
Goal: MCP servers load, tools appear in agent, server management UI works.
Following the "Thunk, Don't Replace" principle, we wire the classic
McpHub into the SdkController instead of building a custom SDK MCP
manager. The classic McpHub already supports all three transports
(stdio, SSE, streamableHTTP), file watching, and all gRPC handlers.
The SDK's InMemoryMcpManager will replace it in Step 10 (Cleanup).
Tasks:
- Wire classic
McpHubintoSdkController.mcpHub- Same constructor args as classic Controller
- Existing gRPC handlers (
subscribeToMcpServers,restartMcpServer,deleteMcpServer,toggleMcpServer, etc.) work without modification - They all delegate to
controller.mcpHubwhich is now a real instance
- Update
SdkController.mcpHubtype fromanytoMcpHub - Implement MCP marketplace (cache + refresh from API) — deferred
- Replace classic McpHub with SDK's InMemoryMcpManager — deferred to Step 10
Reference: See SDK-REFERENCE/MCP.md for how the SDK's MCP
manager works and what gaps exist.
Verification gate: 🔵 Classic McpHub wired in. Existing gRPC handlers should work. Full E2E verification pending debug harness test with real MCP servers configured.
Step 8: Settings & Features — ✅ Core Settings Working
Goal: All settings UI works, feature toggles persist.
Following the "Thunk, Don't Replace" principle, the existing
updateSettings gRPC handler already works — it calls
controller.stateManager.setGlobalState() which is available.
We just needed to ensure TaskProxy properties don't crash it.
Tasks:
- Wire all
updateSettingskeys to persist toglobalState.json— already works via StateManager - TaskProxy.api is settable (updateSettings replaces it on model switch)
- TaskProxy.terminalManager safely no-ops (settings compatibility)
- Implement
togglePlanActMode()— saves mode, cancels active task - Implement
toggleActModeForYoloMode()— switches to act mode - Implement
getAvailableTerminalProfiles(simplified — only background terminal) — deferred - Simplify terminal settings UI (remove IDE terminal options) — deferred
- Remove workflows tab from Cline Rules modal — deferred
- Remove focus chain / deep planning / memory bank UI remnants — deferred
- Verify model picker works for all providers — needs E2E test
- Verify Plan/Act mode toggle works with separate model configs — needs E2E test
Verification gate: 🔵 Core settings work (updateSettings, mode toggle). Full E2E verification pending debug harness test with real credentials. UI cleanup items deferred to post-Step-9 polish.
Step 9: Full Integration Verification
Goal: The SDK-backed extension is functionally equivalent to the classic extension for all core features.
Tasks:
- Write QA test scripts covering:
- Fresh install flow (no saved state)
- Upgrade flow (existing state from classic)
- Login → inference → logout → login
- Multiple providers (Cline, Anthropic, OpenAI, Ollama)
- Task history: create, view, resume, delete, favorite
- Settings: change model, change provider, toggle features
- MCP: add server, use tool, remove server
- Plan/Act mode switching
- @ mentions and file attachments
- Cancel task mid-execution, start new task
- Run each test with the debug harness
- Document any known issues in PROBLEMS.md with reproduction steps
Verification gate: All QA scripts pass. Any failures are documented and triaged.
Step 10: Cleanup (Only After Step 9 Passes)
Goal: Remove classic core code that is no longer used.
Do NOT start this step until Step 9 is fully verified.
Tasks:
- Delete
src/core/task/(replaced by@cline/agents) - Delete
src/core/controller/(replaced by SDK adapter) - Delete
src/core/api/(replaced by@cline/llms) - Delete
src/core/prompts/system-prompt/(replaced by SDK prompts) - Delete
src/services/mcp/McpHub.ts(replaced by SDK MCP) - Delete
src/standalone/(not needed for VSCode) - Remove deprecated feature code (browser automation, shadow git, memory bank, focus chain, deep planning, workflows)
- Remove proto files for webview messages (keep proto for any persisted state that still uses them)
- Remove proto build steps from
package.json - Remove
src/shared/proto-conversions/,src/generated/ - Clean up imports, fix TypeScript errors
- Run full test suite
Verification gate: Extension compiles and loads. All QA scripts
from Step 9 still pass. npm run compile produces no errors.
Future Steps (Not In Scope)
- Step 11: JetBrains sidecar (JSON-RPC over stdio)
- Step 12: Enterprise features (remote config, SSO, team controls)
- Step 13: Improved checkpoints (kanban-style git refs)
- Step 14: MCP Marketplace improvements
- Step 15: Remove gRPC thunking layer, switch webview to typed JSON messages (optional — only if the thunking layer is a maintenance burden)
Operational Procedure
How to Work on a Step
- Read the step description in full
- Check PROBLEMS.md for any known issues in this area
- Research the SDK using
kb_search(name="sdk", query="...")before implementing anything - Implement the minimum needed to make the step's verification gate pass
- Write tests that verify real behavior
- Verify using the debug harness for UI-facing features
- Update PROBLEMS.md with any issues found, marked as "awaiting verification"
- Commit with a descriptive message referencing the step number
How to Use the Debug Harness
# Build and launch
npx tsx src/dev/debug-harness/server.ts --skip-build --auto-launch
# Dismiss promotional overlays FIRST (may need to run twice)
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())"}}'
# Navigate using command palette, NOT by clicking tabs
curl localhost:19229/api -d '{"method": "ui.command_palette", "params": {"command": "cline.accountLogin"}}'
# Take screenshots (read the file, don't open it!)
curl localhost:19229/api -d '{"method": "ui.screenshot"}'
# Returns {"result": {"path": "/tmp/cline-debug/screenshot-0001.png"}}
# Use read_file on that path to examine it
How to Report Problems
When you find a bug, add it to PROBLEMS.md with:
- ID: Sequential number
- Status: 🔴 Blocker / 🟡 Minor / 🟢 Verified Fixed
- Description: What's wrong, where, how to reproduce
- Root cause: If known
- Fix: If attempted, with file references
- Verification: How to verify it's fixed (test name, harness command, etc.)
Never mark a problem 🟢 without evidence. Write the test first, then mark it fixed.
How to Handle "SDK Doesn't Support X"
If the SDK is missing a feature you need:
- Document the gap in PROBLEMS.md
- Search the SDK codebase (
kb_search name="sdk") for any workaround or extension point - If no workaround exists, implement a minimal version in the adapter layer
- File an issue / PR to the SDK repo for the proper fix
- Use
npm linkfor quick iteration on SDK changes
What Changed From Previous Attempts
Attempt 1 (sdk-migration-port-check)
Deleted ~138K lines of classic core before having a working replacement. Created stub webview components. Result: 595 TypeScript errors, non-functional extension.
Lesson: Delete and document — but only as you replace, not
before. The classic code is always accessible via origin/main.
Attempt 2 (sdk-migration-v2, 90 commits)
Built an SDK adapter layer with tests. Got inference working.
But documentation degraded, bugs were marked fixed without
verification, auth flows were broken, gRPC "mode" vs SDK "mode"
caused confusion, feature removals were incomplete, and the
agent kept confusing SDK types with gRPC types. The dual entry
point (CLINE_SDK=1) was a constant source of confusion.
Lessons applied in this plan:
- Single entry point — no
CLINE_SDKflag, no dual codepaths - Delete and document — dead code creates confusion; use
origin/mainandkb_searchto reference the classic impl - Thunk at the gRPC boundary, don't create a "mode" system
- Verify before proceeding, don't mark things fixed prematurely
- Use kb_search to research the SDK, don't guess at APIs
- Don't hardcode URLs, use
{appBaseUrl} - SDK "Default" implementations are references, not products
- Keep docs focused and reliable; use PROBLEMS.md for tracking </task_progress> </write_to_file>