Skip to content

Off-by-one in tree-sitter line numbers? #2227

@mrubens

Description

@mrubens

App Version

3.11.3

API Provider

OpenRouter

Model Used

Sonnet 3.7 Thinking

Actual vs. Expected Behavior

When the treesitter map is returned along with a partial file read, the line numbers seem to be 0-indexed instead of the 1-indexed format that the model expects. Here's an example:

Image

I think that this off-by-one can confuse the model.

Detailed Steps to Reproduce

  1. Ask the model a question (my example was "How does tool use work in Cline.ts")
  2. Look at what the API request returns
  3. Compare to the code

Relevant API Request Output

[read_file for 'src/core/Cline.ts'] Result:

  1 | import fs from "fs/promises"
  2 | import * as path from "path"
  3 | import os from "os"
  4 | import crypto from "crypto"
  5 | import EventEmitter from "events"
  6 | 
  7 | import { Anthropic } from "@anthropic-ai/sdk"
  8 | import cloneDeep from "clone-deep"
  9 | import delay from "delay"
 10 | import pWaitFor from "p-wait-for"
 11 | import getFolderSize from "get-folder-size"
 12 | import { serializeError } from "serialize-error"
 13 | import * as vscode from "vscode"
 14 | 
 15 | import { TokenUsage } from "../schemas"
 16 | import { ApiHandler, buildApiHandler } from "../api"
 17 | import { ApiStream } from "../api/transform/stream"
 18 | import { DIFF_VIEW_URI_SCHEME, DiffViewProvider } from "../integrations/editor/DiffViewProvider"
 19 | import {
 20 | 	CheckpointServiceOptions,
 21 | 	RepoPerTaskCheckpointService,
 22 | 	RepoPerWorkspaceCheckpointService,
 23 | } from "../services/checkpoints"
 24 | import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
 25 | import { fetchInstructionsTool } from "./tools/fetchInstructionsTool"
 26 | import { listFilesTool } from "./tools/listFilesTool"
 27 | import { readFileTool } from "./tools/readFileTool"
 28 | import { ExitCodeDetails } from "../integrations/terminal/TerminalProcess"
 29 | import { Terminal } from "../integrations/terminal/Terminal"
 30 | import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
 31 | import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
 32 | import { listFiles } from "../services/glob/list-files"
 33 | import { CheckpointStorage } from "../shared/checkpoints"
 34 | import { ApiConfiguration } from "../shared/api"
 35 | import { findLastIndex } from "../shared/array"
 36 | import { combineApiRequests } from "../shared/combineApiRequests"
 37 | import { combineCommandSequences } from "../shared/combineCommandSequences"
 38 | import {
 39 | 	ClineApiReqCancelReason,
 40 | 	ClineApiReqInfo,
 41 | 	ClineAsk,
 42 | 	ClineMessage,
 43 | 	ClineSay,
 44 | 	ToolProgressStatus,
 45 | } from "../shared/ExtensionMessage"
 46 | import { getApiMetrics } from "../shared/getApiMetrics"
 47 | import { HistoryItem } from "../shared/HistoryItem"
 48 | import { ClineAskResponse } from "../shared/WebviewMessage"
 49 | import { GlobalFileNames } from "../shared/globalFileNames"
 50 | import { defaultModeSlug, getModeBySlug, getFullModeDetails } from "../shared/modes"
 51 | import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments"
 52 | import { calculateApiCostAnthropic } from "../utils/cost"
 53 | import { fileExistsAtPath } from "../utils/fs"
 54 | import { arePathsEqual } from "../utils/path"
 55 | import { parseMentions } from "./mentions"
 56 | import { RooIgnoreController } from "./ignore/RooIgnoreController"
 57 | import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message"
 58 | import { formatResponse } from "./prompts/responses"
 59 | import { SYSTEM_PROMPT } from "./prompts/system"
 60 | import { truncateConversationIfNeeded } from "./sliding-window"
 61 | import { ClineProvider } from "./webview/ClineProvider"
 62 | import { BrowserSession } from "../services/browser/BrowserSession"
 63 | import { formatLanguage } from "../shared/language"
 64 | import { McpHub } from "../services/mcp/McpHub"
 65 | import { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy"
 66 | import { telemetryService } from "../services/telemetry/TelemetryService"
 67 | import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator"
 68 | import { getWorkspacePath } from "../utils/path"
 69 | import { writeToFileTool } from "./tools/writeToFileTool"
 70 | import { applyDiffTool } from "./tools/applyDiffTool"
 71 | import { insertContentTool } from "./tools/insertContentTool"
 72 | import { searchAndReplaceTool } from "./tools/searchAndReplaceTool"
 73 | import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool"
 74 | import { searchFilesTool } from "./tools/searchFilesTool"
 75 | import { browserActionTool } from "./tools/browserActionTool"
 76 | import { executeCommandTool } from "./tools/executeCommandTool"
 77 | import { useMcpToolTool } from "./tools/useMcpToolTool"
 78 | import { accessMcpResourceTool } from "./tools/accessMcpResourceTool"
 79 | import { askFollowupQuestionTool } from "./tools/askFollowupQuestionTool"
 80 | import { switchModeTool } from "./tools/switchModeTool"
 81 | import { attemptCompletionTool } from "./tools/attemptCompletionTool"
 82 | import { newTaskTool } from "./tools/newTaskTool"
 83 | 
 84 | export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
 85 | type UserContent = Array<Anthropic.Messages.ContentBlockParam>
 86 | 
 87 | export type ClineEvents = {
 88 | 	message: [{ action: "created" | "updated"; message: ClineMessage }]
 89 | 	taskStarted: []
 90 | 	taskPaused: []
 91 | 	taskUnpaused: []
 92 | 	taskAskResponded: []
 93 | 	taskAborted: []
 94 | 	taskSpawned: [taskId: string]
 95 | 	taskCompleted: [taskId: string, usage: TokenUsage]
 96 | 	taskTokenUsageUpdated: [taskId: string, usage: TokenUsage]
 97 | }
 98 | 
 99 | export type ClineOptions = {
100 | 	provider: ClineProvider
101 | 	apiConfiguration: ApiConfiguration
102 | 	customInstructions?: string
103 | 	enableDiff?: boolean
104 | 	enableCheckpoints?: boolean
105 | 	checkpointStorage?: CheckpointStorage
106 | 	fuzzyMatchThreshold?: number
107 | 	task?: string
108 | 	images?: string[]
109 | 	historyItem?: HistoryItem
110 | 	experiments?: Record<string, boolean>
111 | 	startTask?: boolean
112 | 	rootTask?: Cline
113 | 	parentTask?: Cline
114 | 	taskNumber?: number
115 | 	onCreated?: (cline: Cline) => void
116 | }
117 | 
118 | export class Cline extends EventEmitter<ClineEvents> {
119 | 	readonly taskId: string
120 | 	readonly instanceId: string
121 | 
122 | 	readonly rootTask: Cline | undefined = undefined
123 | 	readonly parentTask: Cline | undefined = undefined
124 | 	readonly taskNumber: number
125 | 	isPaused: boolean = false
126 | 	pausedModeSlug: string = defaultModeSlug
127 | 	private pauseInterval: NodeJS.Timeout | undefined
128 | 
129 | 	readonly apiConfiguration: ApiConfiguration
130 | 	api: ApiHandler
131 | 	private urlContentFetcher: UrlContentFetcher
132 | 	browserSession: BrowserSession
133 | 	didEditFile: boolean = false
134 | 	customInstructions?: string
135 | 	diffStrategy?: DiffStrategy
136 | 	diffEnabled: boolean = false
137 | 	fuzzyMatchThreshold: number = 1.0
138 | 
139 | 	apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
140 | 	clineMessages: ClineMessage[] = []
141 | 	rooIgnoreController?: RooIgnoreController
142 | 	private askResponse?: ClineAskResponse
143 | 	private askResponseText?: string
144 | 	private askResponseImages?: string[]
145 | 	private lastMessageTs?: number
146 | 	// Not private since it needs to be accessible by tools
147 | 	consecutiveMistakeCount: number = 0
148 | 	consecutiveMistakeCountForApplyDiff: Map<string, number> = new Map()
149 | 	// Not private since it needs to be accessible by tools
150 | 	providerRef: WeakRef<ClineProvider>
151 | 	private abort: boolean = false
152 | 	didFinishAbortingStream = false
153 | 	abandoned = false
154 | 	diffViewProvider: DiffViewProvider
155 | 	private lastApiRequestTime?: number
156 | 	isInitialized = false
157 | 
158 | 	// checkpoints
159 | 	private enableCheckpoints: boolean
160 | 	private checkpointStorage: CheckpointStorage
161 | 	private checkpointService?: RepoPerTaskCheckpointService | RepoPerWorkspaceCheckpointService
162 | 
163 | 	// streaming
164 | 	isWaitingForFirstChunk = false
165 | 	isStreaming = false
166 | 	private currentStreamingContentIndex = 0
167 | 	private assistantMessageContent: AssistantMessageContent[] = []
168 | 	private presentAssistantMessageLocked = false
169 | 	private presentAssistantMessageHasPendingUpdates = false
170 | 	userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = []
171 | 	private userMessageContentReady = false
172 | 	didRejectTool = false
173 | 	private didAlreadyUseTool = false
174 | 	private didCompleteReadingStream = false
175 | 
176 | 	constructor({
177 | 		provider,
178 | 		apiConfiguration,
179 | 		customInstructions,
180 | 		enableDiff,
181 | 		enableCheckpoints = true,
182 | 		checkpointStorage = "task",
183 | 		fuzzyMatchThreshold,
184 | 		task,
185 | 		images,
186 | 		historyItem,
187 | 		experiments,
188 | 		startTask = true,
189 | 		rootTask,
190 | 		parentTask,
191 | 		taskNumber,
192 | 		onCreated,
193 | 	}: ClineOptions) {
194 | 		super()
195 | 
196 | 		if (startTask && !task && !images && !historyItem) {
197 | 			throw new Error("Either historyItem or task/images must be provided")
198 | 		}
199 | 
200 | 		this.rooIgnoreController = new RooIgnoreController(this.cwd)
201 | 		this.rooIgnoreController.initialize().catch((error) => {
202 | 			console.error("Failed to initialize RooIgnoreController:", error)
203 | 		})
204 | 
205 | 		this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
206 | 		this.instanceId = crypto.randomUUID().slice(0, 8)
207 | 		this.taskNumber = -1
208 | 		this.apiConfiguration = apiConfiguration
209 | 		this.api = buildApiHandler(apiConfiguration)
210 | 		this.urlContentFetcher = new UrlContentFetcher(provider.context)
211 | 		this.browserSession = new BrowserSession(provider.context)
212 | 		this.customInstructions = customInstructions
213 | 		this.diffEnabled = enableDiff ?? false
214 | 		this.fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
215 | 		this.providerRef = new WeakRef(provider)
216 | 		this.diffViewProvider = new DiffViewProvider(this.cwd)
217 | 		this.enableCheckpoints = enableCheckpoints
218 | 		this.checkpointStorage = checkpointStorage
219 | 
220 | 		this.rootTask = rootTask
221 | 		this.parentTask = parentTask
222 | 		this.taskNumber = taskNumber ?? -1
223 | 
224 | 		if (historyItem) {
225 | 			telemetryService.captureTaskRestarted(this.taskId)
226 | 		} else {
227 | 			telemetryService.captureTaskCreated(this.taskId)
228 | 		}
229 | 
230 | 		// Initialize diffStrategy based on current state.
231 | 		this.updateDiffStrategy(experiments ?? {})
232 | 
233 | 		onCreated?.(this)
234 | 
235 | 		if (startTask) {
236 | 			if (task || images) {
237 | 				this.startTask(task, images)
238 | 			} else if (historyItem) {
239 | 				this.resumeTaskFromHistory()
240 | 			} else {
241 | 				throw new Error("Either historyItem or task/images must be provided")
242 | 			}
243 | 		}
244 | 	}
245 | 
246 | 	static create(options: ClineOptions): [Cline, Promise<void>] {
247 | 		const instance = new Cline({ ...options, startTask: false })
248 | 		const { images, task, historyItem } = options
249 | 		let promise
250 | 
251 | 		if (images || task) {
252 | 			promise = instance.startTask(task, images)
253 | 		} else if (historyItem) {
254 | 			promise = instance.resumeTaskFromHistory()
255 | 		} else {
256 | 			throw new Error("Either historyItem or task/images must be provided")
257 | 		}
258 | 
259 | 		return [instance, promise]
260 | 	}
261 | 
262 | 	get cwd() {
263 | 		return getWorkspacePath(path.join(os.homedir(), "Desktop"))
264 | 	}
265 | 
266 | 	// Add method to update diffStrategy.
267 | 	async updateDiffStrategy(experiments: Partial<Record<ExperimentId, boolean>>) {
268 | 		this.diffStrategy = getDiffStrategy({
269 | 			model: this.api.getModel().id,
270 | 			experiments,
271 | 			fuzzyMatchThreshold: this.fuzzyMatchThreshold,
272 | 		})
273 | 	}
274 | 
275 | 	// Storing task to disk for history
276 | 
277 | 	private async ensureTaskDirectoryExists(): Promise<string> {
278 | 		const globalStoragePath = this.providerRef.deref()?.context.globalStorageUri.fsPath
279 | 		if (!globalStoragePath) {
280 | 			throw new Error("Global storage uri is invalid")
281 | 		}
282 | 
283 | 		// Use storagePathManager to retrieve the task storage directory
284 | 		const { getTaskDirectoryPath } = await import("../shared/storagePathManager")
285 | 		return getTaskDirectoryPath(globalStoragePath, this.taskId)
286 | 	}
287 | 
288 | 	private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {
289 | 		const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.apiConversationHistory)
290 | 		const fileExists = await fileExistsAtPath(filePath)
291 | 		if (fileExists) {
292 | 			return JSON.parse(await fs.readFile(filePath, "utf8"))
293 | 		}
294 | 		return []
295 | 	}
296 | 
297 | 	private async addToApiConversationHistory(message: Anthropic.MessageParam) {
298 | 		const messageWithTs = { ...message, ts: Date.now() }
299 | 		this.apiConversationHistory.push(messageWithTs)
300 | 		await this.saveApiConversationHistory()
301 | 	}
302 | 
303 | 	async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]) {
304 | 		this.apiConversationHistory = newHistory
305 | 		await this.saveApiConversationHistory()
306 | 	}
307 | 
308 | 	private async saveApiConversationHistory() {
309 | 		try {
310 | 			const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.apiConversationHistory)
311 | 			await fs.writeFile(filePath, JSON.stringify(this.apiConversationHistory))
312 | 		} catch (error) {
313 | 			// in the off chance this fails, we don't want to stop the task
314 | 			console.error("Failed to save API conversation history:", error)
315 | 		}
316 | 	}
317 | 
318 | 	private async getSavedClineMessages(): Promise<ClineMessage[]> {
319 | 		const filePath = path.join(await this.ensureTaskDirectoryExists(), GlobalFileNames.uiMessages)
320 | 
321 | 		if (await fileExistsAtPath(filePath)) {
322 | 			return JSON.parse(await fs.readFile(filePath, "utf8"))
323 | 		} else {
324 | 			// check old location
325 | 			const oldPath = path.join(await this.ensureTaskDirectoryExists(), "claude_messages.json")
326 | 			if (await fileExistsAtPath(oldPath)) {
327 | 				const data = JSON.parse(await fs.readFile(oldPath, "utf8"))
328 | 				await fs.unlink(oldPath) // remove old file
329 | 				return data
330 | 			}
331 | 		}
332 | 		return []
333 | 	}
334 | 
335 | 	private async addToClineMessages(message: ClineMessage) {
336 | 		this.clineMessages.push(message)
337 | 		await this.providerRef.deref()?.postStateToWebview()
338 | 		this.emit("message", { action: "created", message })
339 | 		await this.saveClineMessages()
340 | 	}
341 | 
342 | 	public async overwriteClineMessages(newMessages: ClineMessage[]) {
343 | 		this.clineMessages = newMessages
344 | 		await this.saveClineMessages()
345 | 	}
346 | 
347 | 	private async updateClineMessage(partialMessage: ClineMessage) {
348 | 		await this.providerRef.deref()?.postMessageToWebview({ type: "partialMessage", partialMessage })
349 | 		this.emit("message", { action: "updated", message: partialMessage })
350 | 	}
351 | 
352 | 	getTokenUsage() {
353 | 		const usage = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1))))
354 | 		this.emit("taskTokenUsageUpdated", this.taskId, usage)
355 | 		return usage
356 | 	}
357 | 
358 | 	private async saveClineMessages() {
359 | 		try {
360 | 			const taskDir = await this.ensureTaskDirectoryExists()
361 | 			const filePath = path.join(taskDir, GlobalFileNames.uiMessages)
362 | 			await fs.writeFile(filePath, JSON.stringify(this.clineMessages))
363 | 			// combined as they are in ChatView
364 | 			const apiMetrics = this.getTokenUsage()
365 | 			const taskMessage = this.clineMessages[0] // first message is always the task say
366 | 			const lastRelevantMessage =
367 | 				this.clineMessages[
368 | 					findLastIndex(
369 | 						this.clineMessages,
370 | 						(m) => !(m.ask === "resume_task" || m.ask === "resume_completed_task"),
371 | 					)
372 | 				]
373 | 
374 | 			let taskDirSize = 0
375 | 
376 | 			try {
377 | 				taskDirSize = await getFolderSize.loose(taskDir)
378 | 			} catch (err) {
379 | 				console.error(
380 | 					`[saveClineMessages] failed to get task directory size (${taskDir}): ${err instanceof Error ? err.message : String(err)}`,
381 | 				)
382 | 			}
383 | 
384 | 			await this.providerRef.deref()?.updateTaskHistory({
385 | 				id: this.taskId,
386 | 				number: this.taskNumber,
387 | 				ts: lastRelevantMessage.ts,
388 | 				task: taskMessage.text ?? "",
389 | 				tokensIn: apiMetrics.totalTokensIn,
390 | 				tokensOut: apiMetrics.totalTokensOut,
391 | 				cacheWrites: apiMetrics.totalCacheWrites,
392 | 				cacheReads: apiMetrics.totalCacheReads,
393 | 				totalCost: apiMetrics.totalCost,
394 | 				size: taskDirSize,
395 | 			})
396 | 		} catch (error) {
397 | 			console.error("Failed to save cline messages:", error)
398 | 		}
399 | 	}
400 | 
401 | 	// Communicate with webview
402 | 
403 | 	// partial has three valid states true (partial message), false (completion of partial message), undefined (individual complete message)
404 | 	async ask(
405 | 		type: ClineAsk,
406 | 		text?: string,
407 | 		partial?: boolean,
408 | 		progressStatus?: ToolProgressStatus,
409 | 	): Promise<{ response: ClineAskResponse; text?: string; images?: string[] }> {
410 | 		// If this Cline instance was aborted by the provider, then the only
411 | 		// thing keeping us alive is a promise still running in the background,
412 | 		// in which case we don't want to send its result to the webview as it
413 | 		// is attached to a new instance of Cline now. So we can safely ignore
414 | 		// the result of any active promises, and this class will be
415 | 		// deallocated. (Although we set Cline = undefined in provider, that
416 | 		// simply removes the reference to this instance, but the instance is
417 | 		// still alive until this promise resolves or rejects.)
418 | 		if (this.abort) {
419 | 			throw new Error(`[Cline#ask] task ${this.taskId}.${this.instanceId} aborted`)
420 | 		}
421 | 
422 | 		let askTs: number
423 | 
424 | 		if (partial !== undefined) {
425 | 			const lastMessage = this.clineMessages.at(-1)
426 | 			const isUpdatingPreviousPartial =
427 | 				lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type
428 | 			if (partial) {
429 | 				if (isUpdatingPreviousPartial) {
430 | 					// Existing partial message, so update it.
431 | 					lastMessage.text = text
432 | 					lastMessage.partial = partial
433 | 					lastMessage.progressStatus = progressStatus
434 | 					// TODO: Be more efficient about saving and posting only new
435 | 					// data or one whole message at a time so ignore partial for
436 | 					// saves, and only post parts of partial message instead of
437 | 					// whole array in new listener.
438 | 					this.updateClineMessage(lastMessage)
439 | 					throw new Error("Current ask promise was ignored (#1)")
440 | 				} else {
441 | 					// This is a new partial message, so add it with partial
442 | 					// state.
443 | 					askTs = Date.now()
444 | 					this.lastMessageTs = askTs
445 | 					await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial })
446 | 					throw new Error("Current ask promise was ignored (#2)")
447 | 				}
448 | 			} else {
449 | 				if (isUpdatingPreviousPartial) {
450 | 					// This is the complete version of a previously partial
451 | 					// message, so replace the partial with the complete version.
452 | 					this.askResponse = undefined
453 | 					this.askResponseText = undefined
454 | 					this.askResponseImages = undefined
455 | 
456 | 					/*
457 | 					Bug for the history books:
458 | 					In the webview we use the ts as the chatrow key for the virtuoso list. Since we would update this ts right at the end of streaming, it would cause the view to flicker. The key prop has to be stable otherwise react has trouble reconciling items between renders, causing unmounting and remounting of components (flickering).
459 | 					The lesson here is if you see flickering when rendering lists, it's likely because the key prop is not stable.
460 | 					So in this case we must make sure that the message ts is never altered after first setting it.
461 | 					*/
462 | 					askTs = lastMessage.ts
463 | 					this.lastMessageTs = askTs
464 | 					// lastMessage.ts = askTs
465 | 					lastMessage.text = text
466 | 					lastMessage.partial = false
467 | 					lastMessage.progressStatus = progressStatus
468 | 					await this.saveClineMessages()
469 | 					this.updateClineMessage(lastMessage)
470 | 				} else {
471 | 					// This is a new and complete message, so add it like normal.
472 | 					this.askResponse = undefined
473 | 					this.askResponseText = undefined
474 | 					this.askResponseImages = undefined
475 | 					askTs = Date.now()
476 | 					this.lastMessageTs = askTs
477 | 					await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text })
478 | 				}
479 | 			}
480 | 		} else {
481 | 			// This is a new non-partial message, so add it like normal.
482 | 			this.askResponse = undefined
483 | 			this.askResponseText = undefined
484 | 			this.askResponseImages = undefined
485 | 			askTs = Date.now()
486 | 			this.lastMessageTs = askTs
487 | 			await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text })
488 | 		}
489 | 
490 | 		await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
491 | 
492 | 		if (this.lastMessageTs !== askTs) {
493 | 			// Could happen if we send multiple asks in a row i.e. with
494 | 			// command_output. It's important that when we know an ask could
495 | 			// fail, it is handled gracefully.
496 | 			throw new Error("Current ask promise was ignored")
497 | 		}
498 | 
499 | 		const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages }
500 | 		this.askResponse = undefined

[Showing only 500 of 2618 total lines. Use start_line and end_line if you need to read more]

# Cline.ts
86--96 | export type ClineEvents = {
98--115 | export type ClineOptions = {
117--2617 | export class Cline extends EventEmitter<ClineEvents> {
175--243 | 	constructor({
245--259 | 	static create(options: ClineOptions): [Cline, Promise<void>] {
266--272 | 	async updateDiffStrategy(experiments: Partial<Record<ExperimentId, boolean>>) {
276--285 | 	private async ensureTaskDirectoryExists(): Promise<string> {
287--294 | 	private async getSavedApiConversationHistory(): Promise<Anthropic.MessageParam[]> {
296--300 | 	private async addToApiConversationHistory(message: Anthropic.MessageParam) {
302--305 | 	async overwriteApiConversationHistory(newHistory: Anthropic.MessageParam[]) {
307--315 | 	private async saveApiConversationHistory() {
317--332 | 	private async getSavedClineMessages(): Promise<ClineMessage[]> {
334--339 | 	private async addToClineMessages(message: ClineMessage) {
341--344 | 	public async overwriteClineMessages(newMessages: ClineMessage[]) {
346--349 | 	private async updateClineMessage(partialMessage: ClineMessage) {
351--355 | 	getTokenUsage() {
357--398 | 	private async saveClineMessages() {
403--504 | 	async ask(
506--510 | 	async handleWebviewAskResponse(askResponse: ClineAskResponse, text?: string, images?: string[]) {
512--571 | 	async say(
573--581 | 	async sayAndCreateMissingParamError(toolName: ToolUseName, paramName: string, relPath?: string, instructions?: string) {
585--606 | 	private async startTask(task?: string, images?: string[]): Promise<void> {
608--633 | 	async resumePausedTask(lastMessage?: string) {
635--860 | 	private async resumeTaskFromHistory() {
700--729 | 		const conversationWithoutToolBlocks = existingApiConversationHistory.map((message) => {
702--725 | 				const newContent = message.content.map((block) => {
755--759 | 					const toolResponses: Anthropic.ToolResultBlockParam[] = toolUseBlocks.map((block) => ({
791--795 | 							.map((toolUse) => ({
816--834 | 		const agoText = ((): string => {
862--895 | 	private async initiateTaskLoop(userContent: UserContent): Promise<void> {
897--931 | 	async abortTask(isAbandoned = false) {
935--1071 | 	async executeCommandTool(command: string, customCwd?: string): Promise<[boolean, ToolResponse]> {
965--978 | 		const sendCommandOutput = async (line: string): Promise<void> => {
982--988 | 		process.on("line", (line) => {
993--997 | 		process.once("completed", (output?: string) => {
1073--1291 | 	async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream {
1125--1147 | 		const systemPrompt = await (async () => {
1187--1207 | 		const cleanConversationHistory = this.apiConversationHistory.map(({ role, content }) => {
1192--1203 | 					content = content.map((block) => {
1293--1693 | 	async presentAssistantMessage() {
1320--1658 | 		switch (block.type) {
1321--1365 | 			case "text": {
1366--1657 | 			case "tool_use":
1367--1410 | 				const toolDescription = (): string => {
1368--1409 | 					switch (block.name) {
1379--1382 | 						case "search_files":
1403--1408 | 						case "new_task": {
1438--1457 | 				const pushToolResult = (content: ToolResponse) => {
1459--1484 | 				const askApproval = async (
1486--1496 | 				const askFinishSubTaskApproval = async () => {
1498--1510 | 				const handleError = async (action: string, error: Error) => {
1513--1531 | 				const removeClosingTag = (tag: ToolParamName, text?: string): string => {
1559--1655 | 				switch (block.name) {
1569--1578 | 					case "search_and_replace":
1588--1597 | 					case "list_code_definition_names":
1604--1613 | 					case "execute_command":
1617--1626 | 					case "access_mcp_resource":
1627--1636 | 					case "ask_followup_question":
1643--1654 | 					case "attempt_completion":
1699--1709 | 	async waitForResume() {
1700--1708 | 		await new Promise<void>((resolve) => {
1701--1707 | 			this.pauseInterval = setInterval(() => {
1711--2040 | 	async recursivelyMakeClineRequests(
1808--1827 | 			const updateApiReqMsg = (cancelReason?: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
1829--1868 | 			const abortStream = async (cancelReason: ClineApiReqCancelReason, streamingFailedMessage?: string) => {
1897--1920 | 					switch (chunk.type) {
1891--1947 | 				for await (const chunk of stream) {
1898--1901 | 						case "reasoning":
1902--1908 | 						case "usage":
1909--1919 | 						case "text":
2042--2099 | 	async loadContext(userContent: UserContent, includeFileDetails: boolean = false) {
2050--2095 | 				userContent.map(async (block) => {
2073--2085 | 								block.content.map(async (contentBlock) => {
2101--2338 | 	async getEnvironmentDetails(includeFileDetails: boolean = false) {
2206--2209 | 		const terminalsWithOutput = inactiveTerminals.filter((terminal) => {
2342--2442 | 	private getCheckpointService() {
2351--2359 | 		const log = (message: string) => {
2394--2409 | 			service.on("initialize", () => {
2411--2426 | 			service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to }) => {
2415--2418 | 					this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((err) => {
2428--2434 | 			service.initShadowGit().catch((err) => {
2444--2466 | 	private async getInitializedCheckpointService({
2456--2459 | 				() => {
2455--2461 | 			await pWaitFor(
2468--2521 | 	public async checkpointDiff({
2507--2515 | 				changes.map((change) => [
2523--2545 | 	public checkpointSave() {
2541--2544 | 		service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`).catch((err) => {
2547--2616 | 	public async checkpointRestore({

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions