本文將從架構設計、核心模塊實現等角度,深入解析 Cline 背后的技術原理和實現細節。

概述

Cline 插件采用模塊化設計,主要由以下核心組件構成:

通過這些組件的協同工作,Cline 實現了四個核心目標:

  1. 工具統一
  1. 智能自動化
  1. 上下文保持
  1. 安全可控

整體架構概覽

Cline 的整體架構可以分為四個主要部分:

核心架構圖

插件入口

export function activate(context: vscode.ExtensionContext) {
// 1. 創建輸出通道
outputChannel = vscode.window.createOutputChannel("Cline");

// 2. 初始化日志系統
Logger.initialize(outputChannel);

// 3. 創建側邊欄提供者
const sidebarProvider = new ClineProvider(context, outputChannel);

// 4. 注冊側邊欄視圖
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
ClineProvider.sideBarId,
sidebarProvider,
{
webviewOptions: {
retainContextWhenHidden: true
}
}
)
);
}

// 注冊了多個VS Code命令:
- "cline.plusButtonClicked" // 新建對話
- "cline.mcpButtonClicked" // MCP按鈕
- "cline.popoutButtonClicked" // 彈出到新標簽頁
- "cline.settingsButtonClicked" // 設置
- "cline.historyButtonClicked" // 歷史記錄
- "cline.accountLoginClicked" // 賬號登錄

ClineProvider.ts

cline.ts

提示詞

提示詞分為幾個部分:

  1. 角色定義:定義角色信息
  2. 工具調用:
  1. MCP 服務:MCP 定義,動態注入 MCP 服務器列表,MCP 創建示例和指南
  2. 替換文件和創建文件的區別:兩者的定義和使用場景以及優勢,考慮改寫之后用戶編輯器的自動格式化 (直接輸出最終狀態),工作流程的一些建議
  3. ACT 模式和 PLAN 模式:讓 AI 理解 act 模式和 plan 模式,act 模式可以調用除 plan_mode_response 工具之外的所有工具,而 plan 模式則使用 plan_mode_response
  4. 能力:指定 AI 具有什么能力,比如能調用哪些工具以及這些工具是干什么的
  5. 規則:指定 AI 需要遵守的規則,能做什么,不能做什么
  6. 系統信息:操作系統、默認 shell,home 目錄,當前工作目錄
  7. 目標:宏觀指定 AI 完成任務的步驟
  8. 用戶自定義指南

完整提示詞:https://github.com/cline/cline/blob/main/src/core/prompts/system.ts

核心執行流程

任務執行流程

  1. 任務初始化
  1. 任務執行循環
  1. 任務完成

工具執行流程

核心組件實現

ClineProvider Cline 核心類

class ClineProvider implements vscode.WebviewViewProvider {
private cline?: Cline
private view?: vscode.WebviewView
private context: vscode.ExtensionContext

// 核心管理功能
async initClineWithTask(task?: string)
async handleMessage(message: Message)
async updateState(state: State)

// 定義全局文件名
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json", // API對話歷史
uiMessages: "ui_messages.json", // UI消息
openRouterModels: "openrouter_models.json", // OpenRouter模型
mcpSettings: "cline_mcp_settings.json", // MCP設置
clineRules: ".clinerules", // Cline規則
}
}

3

class ClineProvider {
constructor(context: vscode.ExtensionContext) {
this.context = context
this.cline = undefined // 延遲初始化
}

async initClineWithTask(task?: string) {
await this.clearTask() // 清理現有任務

// 獲取配置
const config = await this.getState()

// 創建新實例
this.cline = new Cline(
this,
config.apiConfiguration,
config.autoApprovalSettings,
config.browserSettings,
config.chatSettings,
config.customInstructions
)
}
}

1

export class Cline {
// 核心屬性
readonly taskId: string;
api: ApiHandler;
private terminalManager: TerminalManager;
private urlContentFetcher: UrlContentFetcher;
browserSession: BrowserSession;

// 狀態控制
private didEditFile: boolean = false;
private abort: boolean = false;
private consecutiveMistakeCount: number = 0;

// 歷史記錄
apiConversationHistory: Anthropic.MessageParam[] = [];
clineMessages: ClineMessage[] = [];

// 配置系統
autoApprovalSettings: AutoApprovalSettings;
private browserSettings: BrowserSettings;
private chatSettings: ChatSettings;

// 任務執行核心方法
async startTask(task?: string, images?: string[]): Promise<void> {
// 初始化狀態
this.clineMessages = [];
this.apiConversationHistory = [];

// 更新界面
await this.providerRef.deref()?.postStateToWebview();

// 顯示任務
await this.say("text", task, images);

// 標記初始化完成
this.isInitialized = true;

// 構建初始消息
let imageBlocks = formatResponse.imageBlocks(images);

// 啟動任務循環
await this.initiateTaskLoop(
[{
type: "text",
text: <task>\\n${task}\\n</task>, }, ...imageBlocks], true // isNewTask 標記 ); } }

任務循環實現

private async initiateTaskLoop(
userContent: UserContent,
isNewTask: boolean
): Promise<void> {
let nextUserContent = userContent;
let includeFileDetails = true;

while (!this.abort) {
// 核心請求處理
const didEndLoop = await this.recursivelyMakeClineRequests(
nextUserContent,
includeFileDetails,
isNewTask
);

includeFileDetails = false;

if (didEndLoop) {
break;
} else {
// 處理未使用工具的情況
nextUserContent = [{
type: "text",
text: formatResponse.noToolsUsed(),
}];
this.consecutiveMistakeCount++;
}
}
}

任務結束的條件

// 1. this.abort 為 true 的情況:
- 用戶手動中止
- 發生嚴重錯誤
- Cline 實例被銷毀

// 2. didEndLoop 為 true 的情況:
- 任務成功完成(使用 attempt_completion 工具)
- 達到最大錯誤次數
- 任務被標記為完成

// 3. 其他終止條件:
- 連續錯誤次數過多
- API 調用失敗
- 工具執行失敗

請求處理系統

遞歸請求處理

private async recursivelyMakeClineRequests(
userContent: UserContent,
includeFileDetails: boolean,
isNewTask: boolean
): Promise<boolean> {
// 1. 錯誤檢查
if (this.consecutiveMistakeCount >= MAX_CONSECUTIVE_MISTAKES) {
throw new Error("Too many consecutive mistakes")
}

try {
// 2. 準備消息
const messages = this.prepareMessages(userContent, includeFileDetails)

// 3. 發送 API 請求
const response = await this.api.sendRequest(messages)

// 4. 處理響應
if (response.type === "completion") {
return true // 任務完成
} else if (response.type === "tool_use") {
await this.handleToolUse(response.tool)
return false // 繼續執行
}
} catch (error) {
this.handleError(error)
return true
}
}

消息準備

private prepareMessages(
userContent: UserContent,
includeFileDetails: boolean
): MessageParam[] {
// 1. 構建基礎消息
const messages = [...this.apiConversationHistory]

// 2. 添加環境詳情
if (includeFileDetails) {
messages.push({
type: "environment_details",
content: this.getEnvironmentDetails()
})
}

// 3. 添加用戶內容
messages.push({
type: "user_content",
content: userContent
})

return messages
}

API 通信系統

class ApiHandler {
async *attemptApiRequest(previousApiReqIndex: number) {
// 1. 系統提示準備
let systemPrompt = await SYSTEM_PROMPT(
cwd,
this.api.getModel().info.supportsComputerUse,
mcpHub,
this.browserSettings
);

// 2. 上下文預處理
const { truncatedHistory, deletedRange } = this.truncateHistoryToFitContext(
previousApiReqIndex
);

// 3. 流式通信
const stream = this.api.createMessage(systemPrompt, truncatedHistory);
yield* this.handleStreamResponse(stream);
}

private truncateHistoryToFitContext(previousIndex: number) {
const totalTokens = this.calculateTokensUsage();

switch (this.contextWindow) {
case 64_000: // deepseek models
this.maxAllowedSize = this.contextWindow - 27_000;
break;
case 128_000: // most models
this.maxAllowedSize = this.contextWindow - 30_000;
break;
}

if (totalTokens >= this.maxAllowedSize) {
return this.performTruncation();
}

return { truncatedHistory: this.history };
}
}

工具

export const toolUseNames = [
"execute_command",
"read_file",
"write_to_file",
"replace_in_file",
"search_files",
"list_files",
"list_code_definition_names",
"browser_action",
"use_mcp_tool",
"access_mcp_resource",
"ask_followup_question",
"plan_mode_response",
"attempt_completion",
] as const

終端命令執行工具

文件操作工具

class FileOperations implements Tool {
// 文件讀取
async readFile(path: string): Promise<string> {
return fs.readFile(path, 'utf8');
}

// 文件寫入
async writeFile(path: string, content: string): Promise<void> {
await this.ensureDirectoryExists(path);
await fs.writeFile(path, content);
}

// 文件替換
async replaceInFile(path: string, diff: string): Promise<void> {
const content = await this.readFile(path);
const newContent = await constructNewFileContent(diff, content);
await this.writeFile(path, newContent);
}

// 文件列表
async listFiles(path: string, recursive: boolean): Promise<string[]> {
if (recursive) {
return this.recursiveListFiles(path);
}
return fs.readdir(path);
}
}

安全機制實現

文件操作安全

class FileOperationApproval {
async checkFileAccess(operation: FileOperation): Promise<boolean> {
// 1. 路徑安全檢查
if (!this.isPathSafe(operation.path)) {
return false;
}

// 2. 操作權限檢查
if (!this.hasPermission(operation.type)) {
return false;
}

// 3. 內容安全檢查
if (operation.type === 'write' && !this.isContentSafe(operation.content)) {
return false;
}

return true;
}

private isPathSafe(path: string): boolean {
// 檢查路徑是否在工作目錄內
// 防止目錄遍歷攻擊
const normalizedPath = normalizePath(path);
return isWithinWorkspace(normalizedPath);
}
}

命令執行安全

class CommandSecurity {
private readonly restrictedCommands: Set<string> = new Set([
'rm -rf',
'format',
'shutdown',
// ... 其他危險命令
]);

async validateCommand(command: string): Promise<boolean> {
// 1. 檢查是否是受限命令
if (this.isRestrictedCommand(command)) {
return false;
}

// 2. 檢查命令權限
if (!await this.checkCommandPermissions(command)) {
return false;
}

// 3. 檢查資源限制
if (!this.checkResourceLimits(command)) {
return false;
}

return true;
}

private isRestrictedCommand(command: string): boolean {
return Array.from(this.restrictedCommands).some(
restricted => command.includes(restricted)
);
}
}

檢查點系統

三種情況:task (恢復任務消息)、workspace (恢復工作區文件)、taskAndWorkspace (同時恢復任務消息和工作區文件)

核心機制:ShadowGit (主倉庫的變更不會影響到 Shadow 倉庫,shadow 倉庫的變更會形成主倉庫的未提交的更改)

核心流程:

  1. 如果恢復 workspace 或 taskAndWorkspace,則會首先初始化 checkpointTracker,并通過 resetHead 直接將工作區恢復到指定 git commit。
  2. 對于 task 或 taskAndWorkspace,我們還需要恢復 task 歷史消息記錄,同時記錄回滾的 APi 資源消耗
class CheckpointSystem {
private checkpoints: Map<string, Checkpoint> = new Map();

// 創建檢查點
async createCheckpoint(): Promise<string> {
const checkpoint = {
id: generateId(),
timestamp: Date.now(),
state: await this.captureCurrentState(),
files: await this.captureFileState()
};

this.checkpoints.set(checkpoint.id, checkpoint);
return checkpoint.id;
}

// 恢復檢查點
async restoreCheckpoint(id: string): Promise<void> {
const checkpoint = this.checkpoints.get(id);
if (!checkpoint) {
throw new Error('Checkpoint not found');
}

await this.restoreState(checkpoint.state);
await this.restoreFiles(checkpoint.files);
}
}

文件存儲

文件存儲主要分為以下幾類:

Task 對話流,配置文件 (主要是 mcp 服務器信息),mcp 服務端代碼,自定義 prompt (。clinerules)

Task 存儲

存儲位置:~/Library/Application\ Support/Code/User/globalStorage/擴展名/tasks/任務 ID

[
{
"role": "user",
"content": [
{ "type": "text", "text": "<task>\n你給我優化一下代碼\n</task>" },
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\nlib/auth.ts\n\n# VSCode Open Tabs\nlib/auth.ts\n\n# Current Time\n2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)\n\n# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Files\n.env.template\n.gitignore\nauth.config.ts\nauth.ts\ncomponents.json\neslint.config.mjs\nmiddleware.ts\nnext-auth.d.ts\nnext.config.ts\npackage.json\npnpm-lock.yaml\npostcss.config.mjs\nprettier.config.js\nREADME.md\nroutes.ts\ntailwind.config.ts\ntsconfig.json\nactions/\nactions/login.ts\nactions/logout.ts\nactions/new-password.ts\nactions/new-verification.tsx\nactions/register.ts\nactions/reset.ts\nactions/settings.ts\napp/\napp/favicon.ico\napp/globals.css\napp/layout.tsx\napp/page.tsx\napp/(protected)/\napp/api/\napp/api/auth/\napp/api/auth/[...nextauth]/\napp/auth/\napp/auth/layout.tsx\napp/auth/error/\napp/auth/error/page.tsx\napp/auth/login/\napp/auth/login/page.tsx\napp/auth/new-password/\napp/auth/new-password/page.tsx\napp/auth/new-verification/\napp/auth/new-verification/page.tsx\napp/auth/register/\napp/auth/register/page.tsx\napp/auth/reset/\napp/auth/reset/page.tsx\ncomponents/\ncomponents/form-error.tsx\ncomponents/form-success.tsx\ncomponents/user-info.tsx\ncomponents/auth/\ncomponents/auth/back-button.tsx\ncomponents/auth/card-wrapper.tsx\ncomponents/auth/error-card.tsx\ncomponents/auth/header.tsx\ncomponents/auth/login-button.tsx\ncomponents/auth/login-form.tsx\ncomponents/auth/logout-button.tsx\ncomponents/auth/new-password-form.tsx\ncomponents/auth/new-verification-form.tsx\ncomponents/auth/register-form.tsx\ncomponents/auth/reset-form.tsx\ncomponents/auth/role-gate.tsx\ncomponents/auth/social.tsx\ncomponents/auth/user-button.tsx\ncomponents/ui/\ncomponents/ui/avatar.tsx\ncomponents/ui/badge.tsx\ncomponents/ui/button.tsx\ncomponents/ui/card.tsx\ncomponents/ui/dialog.tsx\ncomponents/ui/dropdown-menu.tsx\ncomponents/ui/form.tsx\ncomponents/ui/input.tsx\ncomponents/ui/label.tsx\ncomponents/ui/select.tsx\ncomponents/ui/sonner.tsx\ncomponents/ui/switch.tsx\ndata/\ndata/account.ts\ndata/password-reset-token.ts\ndata/two-factor-confirmation.ts\ndata/two-factor-token.ts\ndata/user.ts\ndata/verification-token.ts\nhooks/\nhooks/use-current-role.ts\nhooks/use-current.user.ts\nlib/\nlib/auth.ts\nlib/db.ts\nlib/mail.ts\nlib/tokens.ts\nlib/utils.ts\nprisma/\nprisma/schema.prisma\nschemas/\nschemas/index.ts\n\n# Current Mode\nACT MODE\n</environment_details>"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>\n1. 需要優化代碼,但用戶沒有指定具體文件。從環境信息中看到當前打開的文件是 lib/auth.ts\n2. 首先需要查看 lib/auth.ts 的內容,了解當前代碼結構,才能進行優化\n3. 使用 read_file 工具讀取 lib/auth.ts 文件內容\n</thinking>\n\n<read_file>\n<path>lib/auth.ts</path>\n</read_file>"
}
]
},
{
"role": "user",
"content": [
{ "type": "text", "text": "[read_file for 'lib/auth.ts'] Result:" },
{
"type": "text",
"text": "import { auth } from '@/auth'\n\nexport const currentUser = async () => {\n const session = await auth()\n return session?.user\n}\n\nexport const currentRole = async () => {\n const session = await auth()\n return session?.user.role\n}\n"
},
{
"type": "text",
"text": "<environment_details>\n# VSCode Visible Files\nlib/auth.ts\n\n# VSCode Open Tabs\nlib/auth.ts\n\n# Current Time\n2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)\n\n# Current Mode\nACT MODE\n</environment_details>"
}
]
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "<thinking>\n1. 當前代碼存在以下可以優化的地方:\n - 兩個函數都調用了 auth(),存在重復代碼\n - 沒有錯誤處理機制\n - 返回類型不明確\n - 可以添加緩存機制提高性能\n\n2. 優化方案:\n - 提取公共的 auth() 調用\n - 添加類型定義\n - 添加錯誤處理\n - 添加簡單的內存緩存\n</thinking>\n\n<replace_in_file>\n<path>lib/auth.ts</path>\n<diff>\n<<<<<<< SEARCH\nimport { auth } from '@/auth'\n\nexport const currentUser = async () => {\n const session = await auth()\n return session?.user\n}\n\nexport const currentRole = async () => {\n const session = await auth()\n return session?.user.role\n}\n=======\nimport { auth } from '@/auth'\nimport { User } from 'next-auth'\n\n// Simple in-memory cache\nlet cachedSession: { user: User } | null = null\nlet lastFetchTime = 0\nconst CACHE_DURATION = 1000 * 60 // 1 minute\n\nasync function getSession() {\n const now = Date.now()\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\n try {\n cachedSession = await auth()\n lastFetchTime = now\n } catch (error) {\n console.error('Failed to fetch session:', error)\n return null\n }\n }\n return cachedSession\n}\n\nexport const currentUser = async (): Promise<User | null> => {\n const session = await getSession()\n return session?.user ?? null\n}\n\nexport const currentRole = async (): Promise<string | null> => {\n const session = await getSession()\n return session?.user?.role ?? null\n}\n>>>>>>> REPLACE\n</diff>\n</replace_in_file>"
}
]
},
...
]
[
{
"ts": 1737537007785,
"type": "say",
"say": "text",
"text": "你給我優化一下代碼",
"images": [],
"conversationHistoryIndex": -1
},
{
"ts": 1737537007797,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"<task>\\n你給我優化一下代碼\\n</task>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:08 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Working Directory (/Users/mlhiter/personal-projects/cherrix) Files\\n.env.template\\n.gitignore\\nauth.config.ts\\nauth.ts\\ncomponents.json\\neslint.config.mjs\\nmiddleware.ts\\nnext-auth.d.ts\\nnext.config.ts\\npackage.json\\npnpm-lock.yaml\\npostcss.config.mjs\\nprettier.config.js\\nREADME.md\\nroutes.ts\\ntailwind.config.ts\\ntsconfig.json\\nactions/\\nactions/login.ts\\nactions/logout.ts\\nactions/new-password.ts\\nactions/new-verification.tsx\\nactions/register.ts\\nactions/reset.ts\\nactions/settings.ts\\napp/\\napp/favicon.ico\\napp/globals.css\\napp/layout.tsx\\napp/page.tsx\\napp/(protected)/\\napp/api/\\napp/api/auth/\\napp/api/auth/[...nextauth]/\\napp/auth/\\napp/auth/layout.tsx\\napp/auth/error/\\napp/auth/error/page.tsx\\napp/auth/login/\\napp/auth/login/page.tsx\\napp/auth/new-password/\\napp/auth/new-password/page.tsx\\napp/auth/new-verification/\\napp/auth/new-verification/page.tsx\\napp/auth/register/\\napp/auth/register/page.tsx\\napp/auth/reset/\\napp/auth/reset/page.tsx\\ncomponents/\\ncomponents/form-error.tsx\\ncomponents/form-success.tsx\\ncomponents/user-info.tsx\\ncomponents/auth/\\ncomponents/auth/back-button.tsx\\ncomponents/auth/card-wrapper.tsx\\ncomponents/auth/error-card.tsx\\ncomponents/auth/header.tsx\\ncomponents/auth/login-button.tsx\\ncomponents/auth/login-form.tsx\\ncomponents/auth/logout-button.tsx\\ncomponents/auth/new-password-form.tsx\\ncomponents/auth/new-verification-form.tsx\\ncomponents/auth/register-form.tsx\\ncomponents/auth/reset-form.tsx\\ncomponents/auth/role-gate.tsx\\ncomponents/auth/social.tsx\\ncomponents/auth/user-button.tsx\\ncomponents/ui/\\ncomponents/ui/avatar.tsx\\ncomponents/ui/badge.tsx\\ncomponents/ui/button.tsx\\ncomponents/ui/card.tsx\\ncomponents/ui/dialog.tsx\\ncomponents/ui/dropdown-menu.tsx\\ncomponents/ui/form.tsx\\ncomponents/ui/input.tsx\\ncomponents/ui/label.tsx\\ncomponents/ui/select.tsx\\ncomponents/ui/sonner.tsx\\ncomponents/ui/switch.tsx\\ndata/\\ndata/account.ts\\ndata/password-reset-token.ts\\ndata/two-factor-confirmation.ts\\ndata/two-factor-token.ts\\ndata/user.ts\\ndata/verification-token.ts\\nhooks/\\nhooks/use-current-role.ts\\nhooks/use-current.user.ts\\nlib/\\nlib/auth.ts\\nlib/db.ts\\nlib/mail.ts\\nlib/tokens.ts\\nlib/utils.ts\\nprisma/\\nprisma/schema.prisma\\nschemas/\\nschemas/index.ts\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":11870,\"tokensOut\":87,\"cacheWrites\":11614,\"cacheReads\":256,\"cost\":0.001653904}",
"conversationHistoryIndex": -1
},
{
"ts": 1737537012356,
"type": "say",
"say": "text",
"text": "1. 需要優化代碼,但用戶沒有指定具體文件。從環境信息中看到當前打開的文件是 lib/auth.ts\n2. 首先需要查看 lib/auth.ts 的內容,了解當前代碼結構,才能進行優化\n3. 使用 read_file 工具讀取 lib/auth.ts 文件內容",
"partial": false,
"conversationHistoryIndex": 0
},
{
"ts": 1737537014438,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"readFile\",\"path\":\"lib/auth.ts\",\"content\":\"/Users/mlhiter/personal-projects/cherrix/lib/auth.ts\"}",
"partial": false,
"conversationHistoryIndex": 0,
"lastCheckpointHash": "a6d8812056f5cf1220148390772dba1a528b95ac"
},
{
"ts": 1737537015217,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[read_file for 'lib/auth.ts'] Result:\\n\\nimport { auth } from '@/auth'\\n\\nexport const currentUser = async () => {\\n const session = await auth()\\n return session?.user\\n}\\n\\nexport const currentRole = async () => {\\n const session = await auth()\\n return session?.user.role\\n}\\n\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:15 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":12095,\"tokensOut\":405,\"cacheWrites\":255,\"cacheReads\":11840,\"cost\":0.00031486}",
"conversationHistoryIndex": 1
},
{
"ts": 1737537017423,
"type": "say",
"say": "text",
"text": "1. 當前代碼存在以下可以優化的地方:\n - 兩個函數都調用了 auth(),存在重復代碼\n - 沒有錯誤處理機制\n - 返回類型不明確\n - 可以添加緩存機制提高性能\n\n2. 優化方案:\n - 提取公共的 auth() 調用\n - 添加類型定義\n - 添加錯誤處理\n - 添加簡單的內存緩存",
"partial": false,
"conversationHistoryIndex": 2
},
{
"ts": 1737537020284,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\n\\nexport const currentUser = async () => {\\n const session = await auth()\\n return session?.user\\n}\\n\\nexport const currentRole = async () => {\\n const session = await auth()\\n return session?.user.role\\n}\\n=======\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 2,
"lastCheckpointHash": "212e268f2f768fce47f65215b40725a967e6c2b4"
},
{
"ts": 1737537031730,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\nNew problems detected after saving the file:\\nlib/auth.ts\\n- [ts Error] Line 30: 類型“User”上不存在屬性“role”。\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:31 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":0,\"tokensOut\":0,\"cacheWrites\":0,\"cacheReads\":0,\"cost\":0}",
"conversationHistoryIndex": 3
},
{
"ts": 1737537034848,
"type": "say",
"say": "text",
"text": "1. 出現類型錯誤,因為 User 類型上缺少 role 屬性\n2. 需要擴展 next-auth 的 User 類型定義\n3. 在 next-auth.d.ts 文件中添加類型擴展",
"partial": false,
"conversationHistoryIndex": 4
},
{
"ts": 1737537036421,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"next-auth.d.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { DefaultSession } from \\\"next-auth\\\";\\n\\ndeclare module \\\"next-auth\\\" {\\n interface Session {\\n user: {\\n id: string;\\n } & DefaultSession[\\\"user\\\"];\\n }\\n}\\n======\"}",
"partial": true,
"conversationHistoryIndex": 4
},
{
"ts": 1737537037472,
"type": "say",
"say": "diff_error",
"text": "next-auth.d.ts",
"conversationHistoryIndex": 4
},
{
"ts": 1737537037561,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'next-auth.d.ts'] Result:\\n\\nThe tool execution failed with the following error:\\n<error>\\nThe SEARCH block:\\nimport { DefaultSession } from \\\"next-auth\\\";\\n\\ndeclare module \\\"next-auth\\\" {\\n interface Session {\\n user: {\\n id: string;\\n } & DefaultSession[\\\"user\\\"];\\n }\\n}\\n...does not match anything in the file.\\n\\nThis is likely because the SEARCH block content doesn't match exactly with what's in the file, or if you used multiple SEARCH/REPLACE blocks they may not have been in the order they appear in the file.\\n\\nThe file was reverted to its original state:\\n\\n<file_content path=\\\"next-auth.d.ts\\\">\\nimport { type DefaultSession } from 'next-auth'\\nimport { UserRole } from '@prisma/client'\\nexport type ExtendedUser = DefaultSession['user'] & {\\n role: UserRole\\n isTwoFactorEnabled: boolean\\n isOAuth: boolean\\n}\\n\\ndeclare module 'next-auth' {\\n interface Session {\\n user: ExtendedUser\\n }\\n}\\n\\n</file_content>\\n\\nTry again with a more precise SEARCH block.\\n(If you keep running into this error, you may use the write_to_file tool as a workaround.)\\n</error>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:37 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":13424,\"tokensOut\":534,\"cacheWrites\":496,\"cacheReads\":12928,\"cost\":0.00039995199999999997}",
"conversationHistoryIndex": 5
},
{
"ts": 1737537039528,
"type": "say",
"say": "text",
"text": "1. 從錯誤信息中看到 next-auth.d.ts 文件已經定義了 ExtendedUser 類型,包含 role 屬性\n2. 需要更新 lib/auth.ts 中的類型定義以使用 ExtendedUser\n3. 使用 replace_in_file 工具更新 lib/auth.ts 的類型定義",
"partial": false,
"conversationHistoryIndex": 6
},
{
"ts": 1737537041539,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\nimport { User } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: User } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<User | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n=======\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 6,
"lastCheckpointHash": "c70f2140b610333799ec281a4004e462e2434b89"
},
{
"ts": 1737537056632,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\nNew problems detected after saving the file:\\nlib/auth.ts\\n- [ts Error] Line 2: 模塊 \\\"\\\"next-auth\\\"\\\" 沒有導出的成員 \\\"ExtendedUser\\\"。你是想改用 \\\"import ExtendedUser from \\\"next-auth\\\"\\\" 嗎?\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:10:56 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":14423,\"tokensOut\":141,\"cacheWrites\":1047,\"cacheReads\":13376,\"cost\":0.000373324}",
"conversationHistoryIndex": 7
},
{
"ts": 1737537058830,
"type": "say",
"say": "text",
"text": "1. 出現新的類型錯誤,因為 ExtendedUser 沒有從 next-auth 模塊正確導出\n2. 需要從 next-auth.d.ts 中導入 ExtendedUser 類型\n3. 使用 replace_in_file 工具更新 lib/auth.ts 的導入語句",
"partial": false,
"conversationHistoryIndex": 8
},
{
"ts": 1737537060971,
"type": "say",
"say": "tool",
"text": "{\"tool\":\"editedExistingFile\",\"path\":\"lib/auth.ts\",\"content\":\"<<<<<<< SEARCH\\nimport { auth } from '@/auth'\\nimport { ExtendedUser } from 'next-auth'\\n=======\\nimport { auth } from '@/auth'\\nimport type { ExtendedUser } from '@/next-auth.d.ts'\\n>>>>>>> REPLACE\"}",
"partial": false,
"conversationHistoryIndex": 8,
"lastCheckpointHash": "1a7d627dcc7386d2185abf42a35c26ca161ab4bb"
},
{
"ts": 1737537067107,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[replace_in_file for 'lib/auth.ts'] Result:\\n\\nThe content was successfully saved to lib/auth.ts.\\n\\nHere is the full, updated content of the file that was saved:\\n\\n<final_file_content path=\\\"lib/auth.ts\\\">\\nimport { auth } from '@/auth'\\nimport type { ExtendedUser } from '@/next-auth.d.ts'\\n\\n// Simple in-memory cache\\nlet cachedSession: { user: ExtendedUser } | null = null\\nlet lastFetchTime = 0\\nconst CACHE_DURATION = 1000 * 60 // 1 minute\\n\\nasync function getSession() {\\n const now = Date.now()\\n if (!cachedSession || now - lastFetchTime > CACHE_DURATION) {\\n try {\\n cachedSession = await auth()\\n lastFetchTime = now\\n } catch (error) {\\n console.error('Failed to fetch session:', error)\\n return null\\n }\\n }\\n return cachedSession\\n}\\n\\nexport const currentUser = async (): Promise<ExtendedUser | null> => {\\n const session = await getSession()\\n return session?.user ?? null\\n}\\n\\nexport const currentRole = async (): Promise<string | null> => {\\n const session = await getSession()\\n return session?.user?.role ?? null\\n}\\n\\n</final_file_content>\\n\\nIMPORTANT: For any future changes to this file, use the final_file_content shown above as your reference. This content reflects the current state of the file, including any auto-formatting (e.g., if you used single quotes but the formatter converted them to double quotes). Always base your SEARCH/REPLACE operations on this final version to ensure accuracy.\\n\\n\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:11:07 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":14981,\"tokensOut\":73,\"cacheWrites\":581,\"cacheReads\":14400,\"cost\":0.00030337999999999996}",
"conversationHistoryIndex": 9
},
{
"ts": 1737537068631,
"type": "say",
"say": "text",
"text": "",
"partial": false,
"conversationHistoryIndex": 10
},
{
"ts": 1737537068761,
"type": "say",
"say": "completion_result",
"text": "已完成代碼優化:\n1. 添加了內存緩存機制,減少重復調用 auth()\n2. 添加了錯誤處理\n3. 完善了類型定義\n4. 解決了類型錯誤問題\n\n優化后的代碼具有更好的性能、更強的健壯性和更好的類型安全性。HAS_CHANGES",
"partial": false,
"conversationHistoryIndex": 10,
"lastCheckpointHash": "ba03330c546aabe2673b238725cd5584813c9101"
},
{
"ts": 1737537071615,
"type": "ask",
"ask": "completion_result",
"text": "",
"conversationHistoryIndex": 11
},
{
"ts": 1737537174467,
"type": "say",
"say": "user_feedback",
"text": "幫我運行起來這個服務器",
"images": [],
"conversationHistoryIndex": 11
},
{
"ts": 1737537174500,
"type": "say",
"say": "api_req_started",
"text": "{\"request\":\"[attempt_completion] Result:\\n\\nThe user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\\n<feedback>\\n幫我運行起來這個服務器\\n</feedback>\\n\\n<environment_details>\\n# VSCode Visible Files\\nlib/auth.ts\\n\\n# VSCode Open Tabs\\nlib/auth.ts\\n\\n# Current Time\\n2025/1/22 下午5:12:54 (Asia/Shanghai, UTC+8:00)\\n\\n# Current Mode\\nACT MODE\\n</environment_details>\",\"tokensIn\":15167,\"tokensOut\":73,\"cacheWrites\":191,\"cacheReads\":14976,\"cost\":0.000256844}",
"conversationHistoryIndex": 11
},
{
"ts": 1737537176056,
"type": "say",
"say": "text",
"text": "1. 這是一個 Next.js 項目,從文件結構可以看出\n2. 需要運行開發服務器來啟動項目\n3. 使用 execute_command 工具運行開發服務器",
"partial": false,
"conversationHistoryIndex": 12
},
{
"ts": 1737537177951,
"type": "say",
"say": "command",
"text": "pnpm dev",
"conversationHistoryIndex": 12
},
{
"ts": 1737537179473,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179473,
"type": "ask",
"ask": "command_output",
"text": "pnpm> pnpm dev",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "pnpm dev\u0007",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "> cherrix@0.1.0 dev /Users/mlhiter/personal-projects/cherrix",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "> next dev --turbopack",
"conversationHistoryIndex": 13
},
{
"ts": 1737537179679,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " ▲ Next.js 15.1.3 (Turbopack)",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Local: http://localhost:3000",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Network: http://192.168.0.123:3000",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " - Environments: .env",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": "",
"conversationHistoryIndex": 13
},
{
"ts": 1737537180524,
"type": "ask",
"ask": "command_output",
"text": " ? Starting...",
"conversationHistoryIndex": 13
},
{
"ts": 1737537181139,
"type": "ask",
"ask": "command_output",
"text": " ? Compiled in 338ms",
"conversationHistoryIndex": 13
},
{
"ts": 1737537181414,
"type": "ask",
"ask": "command_output",
"text": " ? Ready in 1017ms",
"conversationHistoryIndex": 13
}
]

配置信息存儲

存儲位置:~/Library/Application\ Support/Code/User/globalStorage/擴展名/settings

{
"mcpServers": {
"npm-docs": {
"command": "node",
"args": [
"/Users/mlhiter/Documents/Cline/MCP/npm-docs-server/build/index.js"
],
"env": {},
"disabled": false,
"autoApprove": [
"get_docs"
]
}
}
}

mcp 服務端代碼存儲位置

總結

通過對 Cline 開源項目的深入分析,我們可以看到其優秀的架構設計和工程實現為 AI 輔助開發工具樹立了新的標桿。Cline 的模塊化架構設計、基于 ShadowGit 的檢查點系統、多層級文件存儲方案以及安全可控的任務執行機制,為構建智能開發環境提供了重要參考。

文章轉載自: Cursor 開源平替 Cline 到底是如何實現的?本文帶你刨根問底

上一篇:

Cursor開發筆記APP實戰教程

下一篇:

使用Cursor 和 Devbox 一鍵搞定開發環境
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費