Browse Source

Rename `Cline` to `Task` (#3352)

Chris Estreich 8 months ago
parent
commit
1a78be3bfa

+ 2 - 2
src/core/assistant-message/presentAssistantMessage.ts

@@ -31,7 +31,7 @@ import { checkpointSave } from "../checkpoints"
 
 import { formatResponse } from "../prompts/responses"
 import { validateToolUse } from "../tools/validateToolUse"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 
 /**
  * Processes and presents assistant message content to the user interface.
@@ -50,7 +50,7 @@ import { Cline } from "../Cline"
  * as it becomes available.
  */
 
-export async function presentAssistantMessage(cline: Cline) {
+export async function presentAssistantMessage(cline: Task) {
 	if (cline.abort) {
 		throw new Error(`[Cline#presentAssistantMessage] task ${cline.taskId}.${cline.instanceId} aborted`)
 	}

+ 6 - 9
src/core/checkpoints/index.ts

@@ -1,7 +1,7 @@
 import pWaitFor from "p-wait-for"
 import * as vscode from "vscode"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 
 import { getWorkspacePath } from "../../utils/path"
 
@@ -13,7 +13,7 @@ import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider
 import { telemetryService } from "../../services/telemetry/TelemetryService"
 import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../../services/checkpoints"
 
-export function getCheckpointService(cline: Cline) {
+export function getCheckpointService(cline: Task) {
 	if (!cline.enableCheckpoints) {
 		return undefined
 	}
@@ -124,7 +124,7 @@ export function getCheckpointService(cline: Cline) {
 }
 
 async function getInitializedCheckpointService(
-	cline: Cline,
+	cline: Task,
 	{ interval = 250, timeout = 15_000 }: { interval?: number; timeout?: number } = {},
 ) {
 	const service = getCheckpointService(cline)
@@ -148,7 +148,7 @@ async function getInitializedCheckpointService(
 	}
 }
 
-export async function checkpointSave(cline: Cline) {
+export async function checkpointSave(cline: Task) {
 	const service = getCheckpointService(cline)
 
 	if (!service) {
@@ -177,7 +177,7 @@ export type CheckpointRestoreOptions = {
 	mode: "preview" | "restore"
 }
 
-export async function checkpointRestore(cline: Cline, { ts, commitHash, mode }: CheckpointRestoreOptions) {
+export async function checkpointRestore(cline: Task, { ts, commitHash, mode }: CheckpointRestoreOptions) {
 	const service = await getInitializedCheckpointService(cline)
 
 	if (!service) {
@@ -245,10 +245,7 @@ export type CheckpointDiffOptions = {
 	mode: "full" | "checkpoint"
 }
 
-export async function checkpointDiff(
-	cline: Cline,
-	{ ts, previousCommitHash, commitHash, mode }: CheckpointDiffOptions,
-) {
+export async function checkpointDiff(cline: Task, { ts, previousCommitHash, commitHash, mode }: CheckpointDiffOptions) {
 	const service = await getInitializedCheckpointService(cline)
 
 	if (!service) {

+ 15 - 15
src/core/environment/__tests__/getEnvironmentDetails.test.ts

@@ -16,7 +16,7 @@ import { ApiHandler } from "../../../api/index"
 import { ClineProvider } from "../../webview/ClineProvider"
 import { RooIgnoreController } from "../../ignore/RooIgnoreController"
 import { formatResponse } from "../../prompts/responses"
-import { Cline } from "../../Cline"
+import { Task } from "../../task/Task"
 
 jest.mock("vscode", () => ({
 	window: {
@@ -56,7 +56,7 @@ describe("getEnvironmentDetails", () => {
 		cleanCompletedProcessQueue?: jest.Mock
 	}
 
-	let mockCline: Partial<Cline>
+	let mockCline: Partial<Task>
 	let mockProvider: any
 	let mockState: any
 
@@ -134,7 +134,7 @@ describe("getEnvironmentDetails", () => {
 	})
 
 	it("should return basic environment details", async () => {
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("<environment_details>")
 		expect(result).toContain("</environment_details>")
@@ -157,7 +157,7 @@ describe("getEnvironmentDetails", () => {
 	})
 
 	it("should include file details when includeFileDetails is true", async () => {
-		const result = await getEnvironmentDetails(mockCline as Cline, true)
+		const result = await getEnvironmentDetails(mockCline as Task, true)
 		expect(result).toContain("# Current Workspace Directory")
 		expect(result).toContain("Files")
 
@@ -173,14 +173,14 @@ describe("getEnvironmentDetails", () => {
 	})
 
 	it("should not include file details when includeFileDetails is false", async () => {
-		await getEnvironmentDetails(mockCline as Cline, false)
+		await getEnvironmentDetails(mockCline as Task, false)
 		expect(listFiles).not.toHaveBeenCalled()
 		expect(formatResponse.formatFilesList).not.toHaveBeenCalled()
 	})
 
 	it("should handle desktop directory specially", async () => {
 		;(arePathsEqual as jest.Mock).mockReturnValue(true)
-		const result = await getEnvironmentDetails(mockCline as Cline, true)
+		const result = await getEnvironmentDetails(mockCline as Task, true)
 		expect(result).toContain("Desktop files not shown automatically")
 		expect(listFiles).not.toHaveBeenCalled()
 	})
@@ -191,7 +191,7 @@ describe("getEnvironmentDetails", () => {
 			"modified2.ts",
 		])
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("# Recently Modified Files")
 		expect(result).toContain("modified1.ts")
@@ -208,14 +208,14 @@ describe("getEnvironmentDetails", () => {
 		;(TerminalRegistry.getTerminals as jest.Mock).mockReturnValue([mockActiveTerminal])
 		;(TerminalRegistry.getUnretrievedOutput as jest.Mock).mockReturnValue("Test output")
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("# Actively Running Terminals")
 		expect(result).toContain("Original command: `npm test`")
 		expect(result).toContain("Test output")
 
 		mockCline.didEditFile = true
-		await getEnvironmentDetails(mockCline as Cline)
+		await getEnvironmentDetails(mockCline as Task)
 		expect(delay).toHaveBeenCalledWith(300)
 
 		expect(pWaitFor).toHaveBeenCalled()
@@ -237,7 +237,7 @@ describe("getEnvironmentDetails", () => {
 			active ? [] : [mockInactiveTerminal],
 		)
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("# Inactive Terminals with Completed Process Output")
 		expect(result).toContain("Terminal terminal-2")
@@ -261,7 +261,7 @@ describe("getEnvironmentDetails", () => {
 			return null
 		})
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("NOTE: You are currently in '💻 Code' mode, which does not allow write operations")
 	})
@@ -270,7 +270,7 @@ describe("getEnvironmentDetails", () => {
 		mockState.experiments = { [EXPERIMENT_IDS.POWER_STEERING]: true }
 		;(experiments.isEnabled as jest.Mock).mockReturnValue(true)
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		expect(result).toContain("<role>You are a code assistant</role>")
 		expect(result).toContain("<custom_instructions>Custom instructions</custom_instructions>")
@@ -280,7 +280,7 @@ describe("getEnvironmentDetails", () => {
 		// Mock provider to return null.
 		mockCline.providerRef!.deref = jest.fn().mockReturnValue(null)
 
-		const result = await getEnvironmentDetails(mockCline as Cline)
+		const result = await getEnvironmentDetails(mockCline as Task)
 
 		// Verify the function still returns a result.
 		expect(result).toContain("<environment_details>")
@@ -291,7 +291,7 @@ describe("getEnvironmentDetails", () => {
 			getState: jest.fn().mockResolvedValue(null),
 		})
 
-		const result2 = await getEnvironmentDetails(mockCline as Cline)
+		const result2 = await getEnvironmentDetails(mockCline as Task)
 
 		// Verify the function still returns a result.
 		expect(result2).toContain("<environment_details>")
@@ -311,6 +311,6 @@ describe("getEnvironmentDetails", () => {
 		;(TerminalRegistry.getBackgroundTerminals as jest.Mock).mockReturnValue([])
 		;(mockCline.fileContextTracker!.getAndClearRecentlyModifiedFiles as jest.Mock).mockReturnValue([])
 
-		await expect(getEnvironmentDetails(mockCline as Cline)).resolves.not.toThrow()
+		await expect(getEnvironmentDetails(mockCline as Task)).resolves.not.toThrow()
 	})
 })

+ 2 - 2
src/core/environment/getEnvironmentDetails.ts

@@ -15,9 +15,9 @@ import { Terminal } from "../../integrations/terminal/Terminal"
 import { arePathsEqual } from "../../utils/path"
 import { formatResponse } from "../prompts/responses"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 
-export async function getEnvironmentDetails(cline: Cline, includeFileDetails: boolean = false) {
+export async function getEnvironmentDetails(cline: Task, includeFileDetails: boolean = false) {
 	let details = ""
 
 	const clineProvider = cline.providerRef.deref()

+ 48 - 48
src/core/Cline.ts → src/core/task/Task.ts

@@ -9,17 +9,17 @@ import pWaitFor from "p-wait-for"
 import { serializeError } from "serialize-error"
 
 // schemas
-import { TokenUsage, ToolUsage, ToolName } from "../schemas"
+import { TokenUsage, ToolUsage, ToolName } from "../../schemas"
 
 // api
-import { ApiHandler, buildApiHandler } from "../api"
-import { ApiStream } from "../api/transform/stream"
+import { ApiHandler, buildApiHandler } from "../../api"
+import { ApiStream } from "../../api/transform/stream"
 
 // shared
-import { ApiConfiguration } from "../shared/api"
-import { findLastIndex } from "../shared/array"
-import { combineApiRequests } from "../shared/combineApiRequests"
-import { combineCommandSequences } from "../shared/combineCommandSequences"
+import { ApiConfiguration } from "../../shared/api"
+import { findLastIndex } from "../../shared/array"
+import { combineApiRequests } from "../../shared/combineApiRequests"
+import { combineCommandSequences } from "../../shared/combineCommandSequences"
 import {
 	ClineApiReqCancelReason,
 	ClineApiReqInfo,
@@ -27,45 +27,45 @@ import {
 	ClineMessage,
 	ClineSay,
 	ToolProgressStatus,
-} from "../shared/ExtensionMessage"
-import { getApiMetrics } from "../shared/getApiMetrics"
-import { HistoryItem } from "../shared/HistoryItem"
-import { ClineAskResponse } from "../shared/WebviewMessage"
-import { defaultModeSlug } from "../shared/modes"
-import { DiffStrategy } from "../shared/tools"
+} from "../../shared/ExtensionMessage"
+import { getApiMetrics } from "../../shared/getApiMetrics"
+import { HistoryItem } from "../../shared/HistoryItem"
+import { ClineAskResponse } from "../../shared/WebviewMessage"
+import { defaultModeSlug } from "../../shared/modes"
+import { DiffStrategy } from "../../shared/tools"
 
 // services
-import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
-import { BrowserSession } from "../services/browser/BrowserSession"
-import { McpHub } from "../services/mcp/McpHub"
-import { McpServerManager } from "../services/mcp/McpServerManager"
-import { telemetryService } from "../services/telemetry/TelemetryService"
-import { RepoPerTaskCheckpointService } from "../services/checkpoints"
+import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher"
+import { BrowserSession } from "../../services/browser/BrowserSession"
+import { McpHub } from "../../services/mcp/McpHub"
+import { McpServerManager } from "../../services/mcp/McpServerManager"
+import { telemetryService } from "../../services/telemetry/TelemetryService"
+import { RepoPerTaskCheckpointService } from "../../services/checkpoints"
 
 // integrations
-import { DiffViewProvider } from "../integrations/editor/DiffViewProvider"
-import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
-import { RooTerminalProcess } from "../integrations/terminal/types"
-import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry"
+import { DiffViewProvider } from "../../integrations/editor/DiffViewProvider"
+import { findToolName, formatContentBlockToMarkdown } from "../../integrations/misc/export-markdown"
+import { RooTerminalProcess } from "../../integrations/terminal/types"
+import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
 
 // utils
-import { calculateApiCostAnthropic } from "../utils/cost"
-import { getWorkspacePath } from "../utils/path"
+import { calculateApiCostAnthropic } from "../../utils/cost"
+import { getWorkspacePath } from "../../utils/path"
 
 // prompts
-import { formatResponse } from "./prompts/responses"
-import { SYSTEM_PROMPT } from "./prompts/system"
+import { formatResponse } from "../prompts/responses"
+import { SYSTEM_PROMPT } from "../prompts/system"
 
 // core modules
-import { ToolRepetitionDetector } from "./tools/ToolRepetitionDetector"
-import { FileContextTracker } from "./context-tracking/FileContextTracker"
-import { RooIgnoreController } from "./ignore/RooIgnoreController"
-import { type AssistantMessageContent, parseAssistantMessage, presentAssistantMessage } from "./assistant-message"
-import { truncateConversationIfNeeded } from "./sliding-window"
-import { ClineProvider } from "./webview/ClineProvider"
-import { MultiSearchReplaceDiffStrategy } from "./diff/strategies/multi-search-replace"
-import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "./task-persistence"
-import { getEnvironmentDetails } from "./environment/getEnvironmentDetails"
+import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector"
+import { FileContextTracker } from "../context-tracking/FileContextTracker"
+import { RooIgnoreController } from "../ignore/RooIgnoreController"
+import { type AssistantMessageContent, parseAssistantMessage, presentAssistantMessage } from "../assistant-message"
+import { truncateConversationIfNeeded } from "../sliding-window"
+import { ClineProvider } from "../webview/ClineProvider"
+import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace"
+import { readApiMessages, saveApiMessages, readTaskMessages, saveTaskMessages, taskMetadata } from "../task-persistence"
+import { getEnvironmentDetails } from "../environment/getEnvironmentDetails"
 import {
 	type CheckpointDiffOptions,
 	type CheckpointRestoreOptions,
@@ -73,8 +73,8 @@ import {
 	checkpointSave,
 	checkpointRestore,
 	checkpointDiff,
-} from "./checkpoints"
-import { processUserContentMentions } from "./mentions/processUserContentMentions"
+} from "../checkpoints"
+import { processUserContentMentions } from "../mentions/processUserContentMentions"
 
 export type ClineEvents = {
 	message: [{ action: "created" | "updated"; message: ClineMessage }]
@@ -90,7 +90,7 @@ export type ClineEvents = {
 	taskToolFailed: [taskId: string, tool: ToolName, error: string]
 }
 
-export type ClineOptions = {
+export type TaskOptions = {
 	provider: ClineProvider
 	apiConfiguration: ApiConfiguration
 	customInstructions?: string
@@ -103,18 +103,18 @@ export type ClineOptions = {
 	historyItem?: HistoryItem
 	experiments?: Record<string, boolean>
 	startTask?: boolean
-	rootTask?: Cline
-	parentTask?: Cline
+	rootTask?: Task
+	parentTask?: Task
 	taskNumber?: number
-	onCreated?: (cline: Cline) => void
+	onCreated?: (cline: Task) => void
 }
 
-export class Cline extends EventEmitter<ClineEvents> {
+export class Task extends EventEmitter<ClineEvents> {
 	readonly taskId: string
 	readonly instanceId: string
 
-	readonly rootTask: Cline | undefined = undefined
-	readonly parentTask: Cline | undefined = undefined
+	readonly rootTask: Task | undefined = undefined
+	readonly parentTask: Task | undefined = undefined
 	readonly taskNumber: number
 	readonly workspacePath: string
 
@@ -201,7 +201,7 @@ export class Cline extends EventEmitter<ClineEvents> {
 		parentTask,
 		taskNumber = -1,
 		onCreated,
-	}: ClineOptions) {
+	}: TaskOptions) {
 		super()
 
 		if (startTask && !task && !images && !historyItem) {
@@ -264,8 +264,8 @@ export class Cline extends EventEmitter<ClineEvents> {
 		}
 	}
 
-	static create(options: ClineOptions): [Cline, Promise<void>] {
-		const instance = new Cline({ ...options, startTask: false })
+	static create(options: TaskOptions): [Task, Promise<void>] {
+		const instance = new Task({ ...options, startTask: false })
 		const { images, task, historyItem } = options
 		let promise
 

+ 72 - 97
src/core/__tests__/Cline.test.ts → src/core/task/__tests__/Task.test.ts

@@ -1,4 +1,4 @@
-// npx jest src/core/__tests__/Cline.test.ts
+// npx jest src/core/task/__tests__/Task.test.ts
 
 import * as os from "os"
 import * as path from "path"
@@ -6,64 +6,18 @@ import * as path from "path"
 import * as vscode from "vscode"
 import { Anthropic } from "@anthropic-ai/sdk"
 
-import { GlobalState } from "../../schemas"
-import { Cline } from "../Cline"
-import { ClineProvider } from "../webview/ClineProvider"
-import { ApiConfiguration, ModelInfo } from "../../shared/api"
-import { ApiStreamChunk } from "../../api/transform/stream"
-import { ContextProxy } from "../config/ContextProxy"
-import { processUserContentMentions } from "../mentions/processUserContentMentions"
-
-jest.mock("../mentions", () => ({
-	parseMentions: jest.fn().mockImplementation((text) => {
-		return Promise.resolve(`processed: ${text}`)
-	}),
-	openMention: jest.fn(),
-	getLatestTerminalOutput: jest.fn(),
-}))
-
-jest.mock("../../integrations/misc/extract-text", () => ({
-	extractTextFromFile: jest.fn().mockResolvedValue("Mock file content"),
-}))
-
-jest.mock("../environment/getEnvironmentDetails", () => ({
-	getEnvironmentDetails: jest.fn().mockResolvedValue(""),
-}))
+import { GlobalState } from "../../../schemas"
+import { Task } from "../Task"
+import { ClineProvider } from "../../webview/ClineProvider"
+import { ApiConfiguration, ModelInfo } from "../../../shared/api"
+import { ApiStreamChunk } from "../../../api/transform/stream"
+import { ContextProxy } from "../../config/ContextProxy"
+import { processUserContentMentions } from "../../mentions/processUserContentMentions"
 
 jest.mock("execa", () => ({
 	execa: jest.fn(),
 }))
 
-// Mock RooIgnoreController
-jest.mock("../ignore/RooIgnoreController")
-
-// Mock storagePathManager to prevent dynamic import issues
-jest.mock("../../shared/storagePathManager", () => ({
-	getTaskDirectoryPath: jest
-		.fn()
-		.mockImplementation((globalStoragePath, taskId) => Promise.resolve(`${globalStoragePath}/tasks/${taskId}`)),
-	getSettingsDirectoryPath: jest
-		.fn()
-		.mockImplementation((globalStoragePath) => Promise.resolve(`${globalStoragePath}/settings`)),
-}))
-
-// Mock fileExistsAtPath
-jest.mock("../../utils/fs", () => ({
-	fileExistsAtPath: jest.fn().mockImplementation((filePath) => {
-		return filePath.includes("ui_messages.json") || filePath.includes("api_conversation_history.json")
-	}),
-}))
-
-// Mock fs/promises
-const mockMessages = [
-	{
-		ts: Date.now(),
-		type: "say",
-		say: "text",
-		text: "historical task",
-	},
-]
-
 jest.mock("fs/promises", () => ({
 	mkdir: jest.fn().mockResolvedValue(undefined),
 	writeFile: jest.fn().mockResolvedValue(undefined),
@@ -93,35 +47,18 @@ jest.mock("fs/promises", () => ({
 	rmdir: jest.fn().mockResolvedValue(undefined),
 }))
 
-// Mock dependencies
+jest.mock("p-wait-for", () => ({
+	__esModule: true,
+	default: jest.fn().mockImplementation(async () => Promise.resolve()),
+}))
+
 jest.mock("vscode", () => {
 	const mockDisposable = { dispose: jest.fn() }
-	const mockEventEmitter = {
-		event: jest.fn(),
-		fire: jest.fn(),
-	}
-
-	const mockTextDocument = {
-		uri: {
-			fsPath: "/mock/workspace/path/file.ts",
-		},
-	}
-
-	const mockTextEditor = {
-		document: mockTextDocument,
-	}
-
-	const mockTab = {
-		input: {
-			uri: {
-				fsPath: "/mock/workspace/path/file.ts",
-			},
-		},
-	}
-
-	const mockTabGroup = {
-		tabs: [mockTab],
-	}
+	const mockEventEmitter = { event: jest.fn(), fire: jest.fn() }
+	const mockTextDocument = { uri: { fsPath: "/mock/workspace/path/file.ts" } }
+	const mockTextEditor = { document: mockTextDocument }
+	const mockTab = { input: { uri: { fsPath: "/mock/workspace/path/file.ts" } } }
+	const mockTabGroup = { tabs: [mockTab] }
 
 	return {
 		CodeActionKind: {
@@ -142,9 +79,7 @@ jest.mock("vscode", () => {
 		workspace: {
 			workspaceFolders: [
 				{
-					uri: {
-						fsPath: "/mock/workspace/path",
-					},
+					uri: { fsPath: "/mock/workspace/path" },
 					name: "mock-workspace",
 					index: 0,
 				},
@@ -173,12 +108,52 @@ jest.mock("vscode", () => {
 	}
 })
 
-// Mock p-wait-for to resolve immediately
-jest.mock("p-wait-for", () => ({
-	__esModule: true,
-	default: jest.fn().mockImplementation(async () => Promise.resolve()),
+jest.mock("../../mentions", () => ({
+	parseMentions: jest.fn().mockImplementation((text) => {
+		return Promise.resolve(`processed: ${text}`)
+	}),
+	openMention: jest.fn(),
+	getLatestTerminalOutput: jest.fn(),
+}))
+
+jest.mock("../../../integrations/misc/extract-text", () => ({
+	extractTextFromFile: jest.fn().mockResolvedValue("Mock file content"),
 }))
 
+jest.mock("../../environment/getEnvironmentDetails", () => ({
+	getEnvironmentDetails: jest.fn().mockResolvedValue(""),
+}))
+
+// Mock RooIgnoreController
+jest.mock("../../ignore/RooIgnoreController")
+
+// Mock storagePathManager to prevent dynamic import issues
+jest.mock("../../../shared/storagePathManager", () => ({
+	getTaskDirectoryPath: jest
+		.fn()
+		.mockImplementation((globalStoragePath, taskId) => Promise.resolve(`${globalStoragePath}/tasks/${taskId}`)),
+	getSettingsDirectoryPath: jest
+		.fn()
+		.mockImplementation((globalStoragePath) => Promise.resolve(`${globalStoragePath}/settings`)),
+}))
+
+// Mock fileExistsAtPath
+jest.mock("../../../utils/fs", () => ({
+	fileExistsAtPath: jest.fn().mockImplementation((filePath) => {
+		return filePath.includes("ui_messages.json") || filePath.includes("api_conversation_history.json")
+	}),
+}))
+
+// Mock fs/promises
+const mockMessages = [
+	{
+		ts: Date.now(),
+		type: "say",
+		say: "text",
+		text: "historical task",
+	},
+]
+
 describe("Cline", () => {
 	let mockProvider: jest.Mocked<ClineProvider>
 	let mockApiConfig: ApiConfiguration
@@ -295,7 +270,7 @@ describe("Cline", () => {
 
 	describe("constructor", () => {
 		it("should respect provided settings", async () => {
-			const cline = new Cline({
+			const cline = new Task({
 				provider: mockProvider,
 				apiConfiguration: mockApiConfig,
 				customInstructions: "custom instructions",
@@ -309,7 +284,7 @@ describe("Cline", () => {
 		})
 
 		it("should use default fuzzy match threshold when not provided", async () => {
-			const cline = new Cline({
+			const cline = new Task({
 				provider: mockProvider,
 				apiConfiguration: mockApiConfig,
 				customInstructions: "custom instructions",
@@ -327,7 +302,7 @@ describe("Cline", () => {
 
 		it("should require either task or historyItem", () => {
 			expect(() => {
-				new Cline({ provider: mockProvider, apiConfiguration: mockApiConfig })
+				new Task({ provider: mockProvider, apiConfiguration: mockApiConfig })
 			}).toThrow("Either historyItem or task/images must be provided")
 		})
 	})
@@ -336,7 +311,7 @@ describe("Cline", () => {
 		describe("API conversation handling", () => {
 			it("should clean conversation history before sending to API", async () => {
 				// Cline.create will now use our mocked getEnvironmentDetails
-				const [cline, task] = Cline.create({
+				const [cline, task] = Task.create({
 					provider: mockProvider,
 					apiConfiguration: mockApiConfig,
 					task: "test task",
@@ -444,7 +419,7 @@ describe("Cline", () => {
 				]
 
 				// Test with model that supports images
-				const [clineWithImages, taskWithImages] = Cline.create({
+				const [clineWithImages, taskWithImages] = Task.create({
 					provider: mockProvider,
 					apiConfiguration: configWithImages,
 					task: "test task",
@@ -467,7 +442,7 @@ describe("Cline", () => {
 				clineWithImages.apiConversationHistory = conversationHistory
 
 				// Test with model that doesn't support images
-				const [clineWithoutImages, taskWithoutImages] = Cline.create({
+				const [clineWithoutImages, taskWithoutImages] = Task.create({
 					provider: mockProvider,
 					apiConfiguration: configWithoutImages,
 					task: "test task",
@@ -558,7 +533,7 @@ describe("Cline", () => {
 			})
 
 			it.skip("should handle API retry with countdown", async () => {
-				const [cline, task] = Cline.create({
+				const [cline, task] = Task.create({
 					provider: mockProvider,
 					apiConfiguration: mockApiConfig,
 					task: "test task",
@@ -682,7 +657,7 @@ describe("Cline", () => {
 			})
 
 			it.skip("should not apply retry delay twice", async () => {
-				const [cline, task] = Cline.create({
+				const [cline, task] = Task.create({
 					provider: mockProvider,
 					apiConfiguration: mockApiConfig,
 					task: "test task",
@@ -806,7 +781,7 @@ describe("Cline", () => {
 
 			describe("processUserContentMentions", () => {
 				it("should process mentions in task and feedback tags", async () => {
-					const [cline, task] = Cline.create({
+					const [cline, task] = Task.create({
 						provider: mockProvider,
 						apiConfiguration: mockApiConfig,
 						task: "test task",

+ 8 - 8
src/core/tools/__tests__/executeCommandTool.test.ts

@@ -2,7 +2,7 @@
 
 import { describe, expect, it, jest, beforeEach } from "@jest/globals"
 
-import { Cline } from "../../Cline"
+import { Task } from "../../task/Task"
 import { formatResponse } from "../../prompts/responses"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../../shared/tools"
 import { ToolUsage } from "../../../schemas"
@@ -13,7 +13,7 @@ jest.mock("execa", () => ({
 	execa: jest.fn(),
 }))
 
-jest.mock("../../Cline")
+jest.mock("../../task/Task")
 jest.mock("../../prompts/responses")
 
 // Create a mock for the executeCommand function
@@ -74,7 +74,7 @@ beforeEach(() => {
 
 describe("executeCommandTool", () => {
 	// Setup common test variables
-	let mockCline: jest.Mocked<Partial<Cline>> & { consecutiveMistakeCount: number; didRejectTool: boolean }
+	let mockCline: jest.Mocked<Partial<Task>> & { consecutiveMistakeCount: number; didRejectTool: boolean }
 	let mockAskApproval: jest.Mock
 	let mockHandleError: jest.Mock
 	let mockPushToolResult: jest.Mock
@@ -160,7 +160,7 @@ describe("executeCommandTool", () => {
 
 			// Execute
 			await executeCommandTool(
-				mockCline as unknown as Cline,
+				mockCline as unknown as Task,
 				mockToolUse,
 				mockAskApproval as unknown as AskApproval,
 				mockHandleError as unknown as HandleError,
@@ -181,7 +181,7 @@ describe("executeCommandTool", () => {
 
 			// Execute
 			await executeCommandTool(
-				mockCline as unknown as Cline,
+				mockCline as unknown as Task,
 				mockToolUse,
 				mockAskApproval as unknown as AskApproval,
 				mockHandleError as unknown as HandleError,
@@ -204,7 +204,7 @@ describe("executeCommandTool", () => {
 
 			// Execute
 			await executeCommandTool(
-				mockCline as unknown as Cline,
+				mockCline as unknown as Task,
 				mockToolUse,
 				mockAskApproval as unknown as AskApproval,
 				mockHandleError as unknown as HandleError,
@@ -228,7 +228,7 @@ describe("executeCommandTool", () => {
 
 			// Execute
 			await executeCommandTool(
-				mockCline as unknown as Cline,
+				mockCline as unknown as Task,
 				mockToolUse,
 				mockAskApproval as unknown as AskApproval,
 				mockHandleError as unknown as HandleError,
@@ -258,7 +258,7 @@ describe("executeCommandTool", () => {
 
 			// Execute
 			await executeCommandTool(
-				mockCline as unknown as Cline,
+				mockCline as unknown as Task,
 				mockToolUse,
 				mockAskApproval as unknown as AskApproval,
 				mockHandleError as unknown as HandleError,

+ 2 - 2
src/core/tools/accessMcpResourceTool.ts

@@ -1,10 +1,10 @@
 import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
 import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { formatResponse } from "../prompts/responses"
 
 export async function accessMcpResourceTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/applyDiffTool.ts

@@ -3,7 +3,7 @@ import fs from "fs/promises"
 
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { getReadablePath } from "../../utils/path"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { AskApproval, HandleError, PushToolResult } from "../../shared/tools"
@@ -14,7 +14,7 @@ import { telemetryService } from "../../services/telemetry/TelemetryService"
 import { unescapeHtmlEntities } from "../../utils/text-normalization"
 
 export async function applyDiffTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/askFollowupQuestionTool.ts

@@ -1,10 +1,10 @@
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { parseXml } from "../../utils/xml"
 
 export async function askFollowupQuestionTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/attemptCompletionTool.ts

@@ -1,6 +1,6 @@
 import Anthropic from "@anthropic-ai/sdk"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import {
 	ToolResponse,
 	ToolUse,
@@ -16,7 +16,7 @@ import { telemetryService } from "../../services/telemetry/TelemetryService"
 import { type ExecuteCommandOptions, executeCommand } from "./executeCommandTool"
 
 export async function attemptCompletionTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/browserActionTool.ts

@@ -1,4 +1,4 @@
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import {
 	BrowserAction,
@@ -9,7 +9,7 @@ import {
 import { formatResponse } from "../prompts/responses"
 
 export async function browserActionTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 3 - 3
src/core/tools/executeCommandTool.ts

@@ -3,7 +3,7 @@ import * as path from "path"
 
 import delay from "delay"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { CommandExecutionStatus } from "../../schemas"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolResponse } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
@@ -16,7 +16,7 @@ import { Terminal } from "../../integrations/terminal/Terminal"
 class ShellIntegrationError extends Error {}
 
 export async function executeCommandTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,
@@ -114,7 +114,7 @@ export type ExecuteCommandOptions = {
 }
 
 export async function executeCommand(
-	cline: Cline,
+	cline: Task,
 	{
 		executionId,
 		command,

+ 2 - 2
src/core/tools/fetchInstructionsTool.ts

@@ -1,11 +1,11 @@
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { fetchInstructions } from "../prompts/instructions/instructions"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { formatResponse } from "../prompts/responses"
 import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools"
 
 export async function fetchInstructionsTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/insertContentTool.ts

@@ -3,7 +3,7 @@ import fs from "fs/promises"
 import path from "path"
 
 import { getReadablePath } from "../../utils/path"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
@@ -12,7 +12,7 @@ import { fileExistsAtPath } from "../../utils/fs"
 import { insertGroups } from "../diff/insert-groups"
 
 export async function insertContentTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/listCodeDefinitionNamesTool.ts

@@ -2,14 +2,14 @@ import path from "path"
 import fs from "fs/promises"
 
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { getReadablePath } from "../../utils/path"
 import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
 import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
 
 export async function listCodeDefinitionNamesTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/listFilesTool.ts

@@ -1,6 +1,6 @@
 import * as path from "path"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { formatResponse } from "../prompts/responses"
 import { listFiles } from "../../services/glob/list-files"
@@ -23,7 +23,7 @@ import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } f
  */
 
 export async function listFilesTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/newTaskTool.ts

@@ -1,12 +1,12 @@
 import delay from "delay"
 
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
 import { formatResponse } from "../prompts/responses"
 
 export async function newTaskTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/readFileTool.ts

@@ -1,7 +1,7 @@
 import path from "path"
 import { isBinaryFile } from "isbinaryfile"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { formatResponse } from "../prompts/responses"
 import { t } from "../../i18n"
@@ -15,7 +15,7 @@ import { extractTextFromFile, addLineNumbers } from "../../integrations/misc/ext
 import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
 
 export async function readFileTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 3 - 3
src/core/tools/searchAndReplaceTool.ts

@@ -4,7 +4,7 @@ import fs from "fs/promises"
 import delay from "delay"
 
 // Internal imports
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { AskApproval, HandleError, PushToolResult, RemoveClosingTag, ToolUse } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
@@ -21,7 +21,7 @@ import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
  * Validates required parameters for search and replace operation
  */
 async function validateParams(
-	cline: Cline,
+	cline: Task,
 	relPath: string | undefined,
 	search: string | undefined,
 	replace: string | undefined,
@@ -61,7 +61,7 @@ async function validateParams(
  * @param removeClosingTag - Function to remove closing tags
  */
 export async function searchAndReplaceTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/searchFilesTool.ts

@@ -1,13 +1,13 @@
 import path from "path"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { getReadablePath } from "../../utils/path"
 import { regexSearchFiles } from "../../services/ripgrep"
 
 export async function searchFilesTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/switchModeTool.ts

@@ -1,12 +1,12 @@
 import delay from "delay"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
 
 export async function switchModeTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/useMcpToolTool.ts

@@ -1,10 +1,10 @@
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
 import { formatResponse } from "../prompts/responses"
 import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage"
 
 export async function useMcpToolTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 2 - 2
src/core/tools/writeToFileTool.ts

@@ -2,7 +2,7 @@ import path from "path"
 import delay from "delay"
 import * as vscode from "vscode"
 
-import { Cline } from "../Cline"
+import { Task } from "../task/Task"
 import { ClineSayTool } from "../../shared/ExtensionMessage"
 import { formatResponse } from "../prompts/responses"
 import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
@@ -15,7 +15,7 @@ import { detectCodeOmission } from "../../integrations/editor/detect-omission"
 import { unescapeHtmlEntities } from "../../utils/text-normalization"
 
 export async function writeToFileTool(
-	cline: Cline,
+	cline: Task,
 	block: ToolUse,
 	askApproval: AskApproval,
 	handleError: HandleError,

+ 11 - 11
src/core/webview/ClineProvider.ts

@@ -42,7 +42,7 @@ import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
 import { CustomModesManager } from "../config/CustomModesManager"
 import { buildApiHandler } from "../../api"
 import { CodeActionName } from "../../activate/CodeActionProvider"
-import { Cline, ClineOptions } from "../Cline"
+import { Task, TaskOptions } from "../task/Task"
 import { getNonce } from "./getNonce"
 import { getUri } from "./getUri"
 import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
@@ -57,7 +57,7 @@ import { WebviewMessage } from "../../shared/WebviewMessage"
  */
 
 export type ClineProviderEvents = {
-	clineCreated: [cline: Cline]
+	clineCreated: [cline: Task]
 }
 
 export class ClineProvider extends EventEmitter<ClineProviderEvents> implements vscode.WebviewViewProvider {
@@ -66,7 +66,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 	private static activeInstances: Set<ClineProvider> = new Set()
 	private disposables: vscode.Disposable[] = []
 	private view?: vscode.WebviewView | vscode.WebviewPanel
-	private clineStack: Cline[] = []
+	private clineStack: Task[] = []
 	private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
 	public get workspaceTracker(): WorkspaceTracker | undefined {
 		return this._workspaceTracker
@@ -116,7 +116,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 	// Adds a new Cline instance to clineStack, marking the start of a new task.
 	// The instance is pushed to the top of the stack (LIFO order).
 	// When the task is completed, the top instance is removed, reactivating the previous task.
-	async addClineToStack(cline: Cline) {
+	async addClineToStack(cline: Task) {
 		console.log(`[subtasks] adding task ${cline.taskId}.${cline.instanceId} to stack`)
 
 		// Add this cline instance into the stack that represents the order of all the called tasks.
@@ -161,7 +161,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 
 	// returns the current cline object in the stack (the top one)
 	// if the stack is empty, returns undefined
-	getCurrentCline(): Cline | undefined {
+	getCurrentCline(): Task | undefined {
 		if (this.clineStack.length === 0) {
 			return undefined
 		}
@@ -438,7 +438,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 		this.log("Webview view resolved")
 	}
 
-	public async initClineWithSubTask(parent: Cline, task?: string, images?: string[]) {
+	public async initClineWithSubTask(parent: Task, task?: string, images?: string[]) {
 		return this.initClineWithTask(task, images, parent)
 	}
 
@@ -451,10 +451,10 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 	public async initClineWithTask(
 		task?: string,
 		images?: string[],
-		parentTask?: Cline,
+		parentTask?: Task,
 		options: Partial<
 			Pick<
-				ClineOptions,
+				TaskOptions,
 				| "customInstructions"
 				| "enableDiff"
 				| "enableCheckpoints"
@@ -478,7 +478,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 		const modePrompt = customModePrompts?.[mode] as PromptComponent
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 
-		const cline = new Cline({
+		const cline = new Task({
 			provider: this,
 			apiConfiguration,
 			customInstructions: effectiveInstructions,
@@ -504,7 +504,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 		return cline
 	}
 
-	public async initClineWithHistoryItem(historyItem: HistoryItem & { rootTask?: Cline; parentTask?: Cline }) {
+	public async initClineWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
 		await this.removeClineFromStack()
 
 		const {
@@ -521,7 +521,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
 		const modePrompt = customModePrompts?.[mode] as PromptComponent
 		const effectiveInstructions = [globalInstructions, modePrompt?.customInstructions].filter(Boolean).join("\n\n")
 
-		const cline = new Cline({
+		const cline = new Task({
 			provider: this,
 			apiConfiguration,
 			customInstructions: effectiveInstructions,

+ 107 - 95
src/core/webview/__tests__/ClineProvider.test.ts

@@ -1,52 +1,41 @@
 // npx jest src/core/webview/__tests__/ClineProvider.test.ts
 
+import Anthropic from "@anthropic-ai/sdk"
 import * as vscode from "vscode"
 import axios from "axios"
 
 import { ClineProvider } from "../ClineProvider"
-import { ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
+import { ClineMessage, ExtensionMessage, ExtensionState } from "../../../shared/ExtensionMessage"
 import { setSoundEnabled } from "../../../utils/sound"
 import { setTtsEnabled } from "../../../utils/tts"
 import { defaultModeSlug } from "../../../shared/modes"
 import { experimentDefault } from "../../../shared/experiments"
 import { ContextProxy } from "../../config/ContextProxy"
+import { Task, TaskOptions } from "../../task/Task"
 
 // Mock setup must come before imports
 jest.mock("../../prompts/sections/custom-instructions")
 
-// Mock dependencies
 jest.mock("vscode")
+
 jest.mock("delay")
 
-// Mock BrowserSession
-jest.mock("../../../services/browser/BrowserSession", () => ({
-	BrowserSession: jest.fn().mockImplementation(() => ({
-		testConnection: jest.fn().mockImplementation(async (url) => {
-			if (url === "http://localhost:9222") {
-				return {
-					success: true,
-					message: "Successfully connected to Chrome",
-					endpoint: "ws://localhost:9222/devtools/browser/123",
-				}
-			} else {
-				return {
-					success: false,
-					message: "Failed to connect to Chrome",
-					endpoint: undefined,
-				}
-			}
-		}),
-	})),
+jest.mock("p-wait-for", () => ({
+	__esModule: true,
+	default: jest.fn().mockResolvedValue(undefined),
 }))
 
-// Mock browserDiscovery
-jest.mock("../../../services/browser/browserDiscovery", () => ({
-	discoverChromeHostUrl: jest.fn().mockImplementation(async () => {
-		return "http://localhost:9222"
-	}),
-	tryChromeHostUrl: jest.fn().mockImplementation(async (url) => {
-		return url === "http://localhost:9222"
-	}),
+jest.mock("fs/promises", () => ({
+	mkdir: jest.fn(),
+	writeFile: jest.fn(),
+	readFile: jest.fn(),
+	unlink: jest.fn(),
+	rmdir: jest.fn(),
+}))
+
+jest.mock("axios", () => ({
+	get: jest.fn().mockResolvedValue({ data: { data: [] } }),
+	post: jest.fn(),
 }))
 
 jest.mock(
@@ -74,6 +63,35 @@ jest.mock(
 	{ virtual: true },
 )
 
+jest.mock("../../../services/browser/BrowserSession", () => ({
+	BrowserSession: jest.fn().mockImplementation(() => ({
+		testConnection: jest.fn().mockImplementation(async (url) => {
+			if (url === "http://localhost:9222") {
+				return {
+					success: true,
+					message: "Successfully connected to Chrome",
+					endpoint: "ws://localhost:9222/devtools/browser/123",
+				}
+			} else {
+				return {
+					success: false,
+					message: "Failed to connect to Chrome",
+					endpoint: undefined,
+				}
+			}
+		}),
+	})),
+}))
+
+jest.mock("../../../services/browser/browserDiscovery", () => ({
+	discoverChromeHostUrl: jest.fn().mockImplementation(async () => {
+		return "http://localhost:9222"
+	}),
+	tryChromeHostUrl: jest.fn().mockImplementation(async (url) => {
+		return url === "http://localhost:9222"
+	}),
+}))
+
 // Initialize mocks
 const mockAddCustomInstructions = jest.fn().mockResolvedValue("Combined instructions")
 
@@ -115,7 +133,6 @@ jest.mock(
 	{ virtual: true },
 )
 
-// Mock dependencies
 jest.mock("vscode", () => ({
 	ExtensionContext: jest.fn(),
 	OutputChannel: jest.fn(),
@@ -156,50 +173,24 @@ jest.mock("vscode", () => ({
 	},
 }))
 
-// Mock sound utility
 jest.mock("../../../utils/sound", () => ({
 	setSoundEnabled: jest.fn(),
 }))
 
-// Mock tts utility
 jest.mock("../../../utils/tts", () => ({
 	setTtsEnabled: jest.fn(),
 	setTtsSpeed: jest.fn(),
 }))
 
-// Mock ESM modules
-jest.mock("p-wait-for", () => ({
-	__esModule: true,
-	default: jest.fn().mockResolvedValue(undefined),
-}))
-
-// Mock fs/promises
-jest.mock("fs/promises", () => ({
-	mkdir: jest.fn(),
-	writeFile: jest.fn(),
-	readFile: jest.fn(),
-	unlink: jest.fn(),
-	rmdir: jest.fn(),
-}))
-
-// Mock axios
-jest.mock("axios", () => ({
-	get: jest.fn().mockResolvedValue({ data: { data: [] } }),
-	post: jest.fn(),
-}))
-
-// Mock buildApiHandler
 jest.mock("../../../api", () => ({
 	buildApiHandler: jest.fn(),
 }))
 
-// Mock system prompt
 jest.mock("../../prompts/system", () => ({
 	SYSTEM_PROMPT: jest.fn().mockImplementation(async () => "mocked system prompt"),
 	codeMode: "code",
 }))
 
-// Mock WorkspaceTracker
 jest.mock("../../../integrations/workspace/WorkspaceTracker", () => {
 	return jest.fn().mockImplementation(() => ({
 		initializeFilePaths: jest.fn(),
@@ -207,9 +198,8 @@ jest.mock("../../../integrations/workspace/WorkspaceTracker", () => {
 	}))
 })
 
-// Mock Cline
-jest.mock("../../Cline", () => ({
-	Cline: jest
+jest.mock("../../task/Task", () => ({
+	Task: jest
 		.fn()
 		.mockImplementation(
 			(_provider, _apiConfiguration, _customInstructions, _diffEnabled, _fuzzyMatchThreshold, _task, taskId) => ({
@@ -229,7 +219,6 @@ jest.mock("../../Cline", () => ({
 		),
 }))
 
-// Mock extract-text
 jest.mock("../../../integrations/misc/extract-text", () => ({
 	extractTextFromFile: jest.fn().mockImplementation(async (_filePath: string) => {
 		const content = "const x = 1;\nconst y = 2;\nconst z = 3;"
@@ -249,6 +238,8 @@ afterAll(() => {
 })
 
 describe("ClineProvider", () => {
+	let defaultTaskOptions: TaskOptions
+
 	let provider: ClineProvider
 	let mockContext: vscode.ExtensionContext
 	let mockOutputChannel: vscode.OutputChannel
@@ -327,6 +318,13 @@ describe("ClineProvider", () => {
 
 		provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
 
+		defaultTaskOptions = {
+			provider,
+			apiConfiguration: {
+				apiProvider: "openrouter",
+			},
+		}
+
 		// @ts-ignore - Access private property for testing
 		updateGlobalStateSpy = jest.spyOn(provider.contextProxy, "setValue")
 
@@ -451,8 +449,7 @@ describe("ClineProvider", () => {
 
 	test("clearTask aborts current task", async () => {
 		// Setup Cline instance with auto-mock from the top of the file
-		const { Cline } = require("../../Cline") // Get the mocked class
-		const mockCline = new Cline() // Create a new mocked instance
+		const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
 
 		// add the mock object to the stack
 		await provider.addClineToStack(mockCline)
@@ -475,9 +472,8 @@ describe("ClineProvider", () => {
 
 	test("addClineToStack adds multiple Cline instances to the stack", async () => {
 		// Setup Cline instance with auto-mock from the top of the file
-		const { Cline } = require("../../Cline") // Get the mocked class
-		const mockCline1 = new Cline() // Create a new mocked instance
-		const mockCline2 = new Cline() // Create a new mocked instance
+		const mockCline1 = new Task(defaultTaskOptions) // Create a new mocked instance
+		const mockCline2 = new Task(defaultTaskOptions) // Create a new mocked instance
 		Object.defineProperty(mockCline1, "taskId", { value: "test-task-id-1", writable: true })
 		Object.defineProperty(mockCline2, "taskId", { value: "test-task-id-2", writable: true })
 
@@ -833,15 +829,11 @@ describe("ClineProvider", () => {
 			experiments: experimentDefault,
 		} as any)
 
-		// Reset Cline mock
-		const { Cline } = require("../../Cline")
-		;(Cline as jest.Mock).mockClear()
-
 		// Initialize Cline with a task
 		await provider.initClineWithTask("Test task")
 
 		// Verify Cline was initialized with mode-specific instructions
-		expect(Cline).toHaveBeenCalledWith({
+		expect(Task).toHaveBeenCalledWith({
 			provider,
 			apiConfiguration: mockApiConfig,
 			customInstructions: modeCustomInstructions,
@@ -958,13 +950,19 @@ describe("ClineProvider", () => {
 				{ ts: 4000, type: "say", say: "browser_action" }, // Response to delete
 				{ ts: 5000, type: "say", say: "user_feedback" }, // Next user message
 				{ ts: 6000, type: "say", say: "user_feedback" }, // Final message
-			]
-
-			const mockApiHistory = [{ ts: 1000 }, { ts: 2000 }, { ts: 3000 }, { ts: 4000 }, { ts: 5000 }, { ts: 6000 }]
-
-			// Setup Cline instance with auto-mock from the top of the file
-			const { Cline } = require("../../Cline") // Get the mocked class
-			const mockCline = new Cline() // Create a new mocked instance
+			] as ClineMessage[]
+
+			const mockApiHistory = [
+				{ ts: 1000 },
+				{ ts: 2000 },
+				{ ts: 3000 },
+				{ ts: 4000 },
+				{ ts: 5000 },
+				{ ts: 6000 },
+			] as (Anthropic.MessageParam & { ts?: number })[]
+
+			// Setup Task instance with auto-mock from the top of the file
+			const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
 			mockCline.clineMessages = mockMessages // Set test-specific messages
 			mockCline.apiConversationHistory = mockApiHistory // Set API history
 			await provider.addClineToStack(mockCline) // Add the mocked instance to the stack
@@ -1005,13 +1003,19 @@ describe("ClineProvider", () => {
 				{ ts: 2000, type: "say", say: "text", value: 3000 }, // Message to delete
 				{ ts: 3000, type: "say", say: "user_feedback" },
 				{ ts: 4000, type: "say", say: "user_feedback" },
-			]
+			] as ClineMessage[]
 
-			const mockApiHistory = [{ ts: 1000 }, { ts: 2000 }, { ts: 3000 }, { ts: 4000 }]
+			const mockApiHistory = [
+				{ ts: 1000 },
+				{ ts: 2000 },
+				{ ts: 3000 },
+				{ ts: 4000 },
+			] as (Anthropic.MessageParam & {
+				ts?: number
+			})[]
 
 			// Setup Cline instance with auto-mock from the top of the file
-			const { Cline } = require("../../Cline") // Get the mocked class
-			const mockCline = new Cline() // Create a new mocked instance
+			const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
 			mockCline.clineMessages = mockMessages
 			mockCline.apiConversationHistory = mockApiHistory
 			await provider.addClineToStack(mockCline)
@@ -1037,10 +1041,11 @@ describe("ClineProvider", () => {
 			;(vscode.window.showInformationMessage as jest.Mock).mockResolvedValue("Cancel")
 
 			// Setup Cline instance with auto-mock from the top of the file
-			const { Cline } = require("../../Cline") // Get the mocked class
-			const mockCline = new Cline() // Create a new mocked instance
-			mockCline.clineMessages = [{ ts: 1000 }, { ts: 2000 }]
-			mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }]
+			const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
+			mockCline.clineMessages = [{ ts: 1000 }, { ts: 2000 }] as ClineMessage[]
+			mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }] as (Anthropic.MessageParam & {
+				ts?: number
+			})[]
 			await provider.addClineToStack(mockCline)
 
 			// Trigger message deletion
@@ -1212,15 +1217,16 @@ describe("ClineProvider", () => {
 		})
 
 		test("passes diffEnabled: false to SYSTEM_PROMPT when diff is disabled", async () => {
-			// Setup Cline instance with mocked api.getModel()
-			const { Cline } = require("../../Cline")
-			const mockCline = new Cline()
+			// Setup Task instance with mocked api.getModel()
+			const mockCline = new Task(defaultTaskOptions)
+
 			mockCline.api = {
 				getModel: jest.fn().mockReturnValue({
 					id: "claude-3-sonnet",
 					info: { supportsComputerUse: true },
 				}),
-			}
+			} as any
+
 			await provider.addClineToStack(mockCline)
 
 			// Mock getState to return diffEnabled: false
@@ -1742,9 +1748,8 @@ describe("ClineProvider", () => {
 					.mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]),
 			} as any
 
-			// Setup Cline instance with auto-mock from the top of the file
-			const { Cline } = require("../../Cline") // Get the mocked class
-			const mockCline = new Cline() // Create a new mocked instance
+			// Setup Task instance with auto-mock from the top of the file
+			const mockCline = new Task(defaultTaskOptions) // Create a new mocked instance
 			await provider.addClineToStack(mockCline)
 
 			const testApiConfig = {
@@ -2084,6 +2089,7 @@ describe.skip("ContextProxy integration", () => {
 })
 
 describe("getTelemetryProperties", () => {
+	let defaultTaskOptions: TaskOptions
 	let provider: ClineProvider
 	let mockContext: vscode.ExtensionContext
 	let mockOutputChannel: vscode.OutputChannel
@@ -2113,9 +2119,15 @@ describe("getTelemetryProperties", () => {
 		mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel
 		provider = new ClineProvider(mockContext, mockOutputChannel, "sidebar", new ContextProxy(mockContext))
 
-		// Setup Cline instance with mocked getModel method
-		const { Cline } = require("../../Cline")
-		mockCline = new Cline()
+		defaultTaskOptions = {
+			provider,
+			apiConfiguration: {
+				apiProvider: "openrouter",
+			},
+		}
+
+		// Setup Task instance with mocked getModel method
+		mockCline = new Task(defaultTaskOptions)
 		mockCline.api = {
 			getModel: jest.fn().mockReturnValue({
 				id: "claude-3-7-sonnet-20250219",