Browse Source

Add support for task page event population (#7117)

Chris Estreich 6 months ago
parent
commit
44086e4a86

+ 1 - 1
packages/types/npm/package.metadata.json

@@ -1,6 +1,6 @@
 {
 	"name": "@roo-code/types",
-	"version": "1.48.0",
+	"version": "1.49.0",
 	"description": "TypeScript type definitions for Roo Code.",
 	"publishConfig": {
 		"access": "public",

+ 9 - 12
packages/types/src/task.ts

@@ -3,6 +3,7 @@ import { z } from "zod"
 import { RooCodeEventName } from "./events.js"
 import { type ClineMessage, type BlockingAsk, type TokenUsage } from "./message.js"
 import { type ToolUsage, type ToolName } from "./tool.js"
+import type { StaticAppProperties, GitProperties, TelemetryProperties } from "./telemetry.js"
 
 /**
  * TaskProviderLike
@@ -14,19 +15,23 @@ export interface TaskProviderState {
 
 export interface TaskProviderLike {
 	readonly cwd: string
+	readonly appProperties: StaticAppProperties
+	readonly gitProperties: GitProperties | undefined
 
-	getCurrentCline(): TaskLike | undefined
+	getCurrentTask(): TaskLike | undefined
 	getCurrentTaskStack(): string[]
+	getRecentTasks(): string[]
 
-	initClineWithTask(text?: string, images?: string[], parentTask?: TaskLike): Promise<TaskLike>
+	createTask(text?: string, images?: string[], parentTask?: TaskLike): Promise<TaskLike>
 	cancelTask(): Promise<void>
 	clearTask(): Promise<void>
-	postStateToWebview(): Promise<void>
 
 	getState(): Promise<TaskProviderState>
-
+	postStateToWebview(): Promise<void>
 	postMessageToWebview(message: unknown): Promise<void>
 
+	getTelemetryProperties(): Promise<TelemetryProperties>
+
 	on<K extends keyof TaskProviderEvents>(
 		event: K,
 		listener: (...args: TaskProviderEvents[K]) => void | Promise<void>,
@@ -36,14 +41,6 @@ export interface TaskProviderLike {
 		event: K,
 		listener: (...args: TaskProviderEvents[K]) => void | Promise<void>,
 	): this
-
-	context: {
-		extension?: {
-			packageJSON?: {
-				version?: string
-			}
-		}
-	}
 }
 
 export type TaskProviderEvents = {

+ 25 - 2
packages/types/src/telemetry.ts

@@ -72,17 +72,37 @@ export enum TelemetryEventName {
  * TelemetryProperties
  */
 
-export const appPropertiesSchema = z.object({
+export const staticAppPropertiesSchema = z.object({
 	appName: z.string(),
 	appVersion: z.string(),
 	vscodeVersion: z.string(),
 	platform: z.string(),
 	editorName: z.string(),
+})
+
+export type StaticAppProperties = z.infer<typeof staticAppPropertiesSchema>
+
+export const dynamicAppPropertiesSchema = z.object({
 	language: z.string(),
 	mode: z.string(),
+})
+
+export type DynamicAppProperties = z.infer<typeof dynamicAppPropertiesSchema>
+
+export const cloudAppPropertiesSchema = z.object({
 	cloudIsAuthenticated: z.boolean().optional(),
 })
 
+export type CloudAppProperties = z.infer<typeof cloudAppPropertiesSchema>
+
+export const appPropertiesSchema = z.object({
+	...staticAppPropertiesSchema.shape,
+	...dynamicAppPropertiesSchema.shape,
+	...cloudAppPropertiesSchema.shape,
+})
+
+export type AppProperties = z.infer<typeof appPropertiesSchema>
+
 export const taskPropertiesSchema = z.object({
 	taskId: z.string().optional(),
 	apiProvider: z.enum(providerNames).optional(),
@@ -99,12 +119,16 @@ export const taskPropertiesSchema = z.object({
 		.optional(),
 })
 
+export type TaskProperties = z.infer<typeof taskPropertiesSchema>
+
 export const gitPropertiesSchema = z.object({
 	repositoryUrl: z.string().optional(),
 	repositoryName: z.string().optional(),
 	defaultBranch: z.string().optional(),
 })
 
+export type GitProperties = z.infer<typeof gitPropertiesSchema>
+
 export const telemetryPropertiesSchema = z.object({
 	...appPropertiesSchema.shape,
 	...taskPropertiesSchema.shape,
@@ -112,7 +136,6 @@ export const telemetryPropertiesSchema = z.object({
 })
 
 export type TelemetryProperties = z.infer<typeof telemetryPropertiesSchema>
-export type GitProperties = z.infer<typeof gitPropertiesSchema>
 
 /**
  * TelemetryEvent

+ 9 - 9
pnpm-lock.yaml

@@ -584,8 +584,8 @@ importers:
         specifier: ^1.14.0
         version: 1.14.0([email protected])
       '@roo-code/cloud':
-        specifier: ^0.14.0
-        version: 0.14.0
+        specifier: ^0.15.0
+        version: 0.15.0
       '@roo-code/ipc':
         specifier: workspace:^
         version: link:../packages/ipc
@@ -3103,11 +3103,11 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@roo-code/[email protected]4.0':
-    resolution: {integrity: sha512-EKFw7sLSJcOpXQDjCMFL4EnE7OH3RhO00joteaysNYNpNe+js6kMFrBZY61rj1tZAXV/943RWeQptG4sSAfPFQ==}
+  '@roo-code/[email protected]5.0':
+    resolution: {integrity: sha512-0DivOP5uUS9U6UKSxzoxZ4NxMCYUxA7wG72y3PBP91JhGzHaTqxi9WrvF61bo134dCnCktU9oTKIAD9AvFigzg==}
 
-  '@roo-code/[email protected]8.0':
-    resolution: {integrity: sha512-DJkA/310sLcs4vM4YLhJhc5A1JFYmdcOt+hRQMUPq+wF02yW9rc7ZZzHNDOOUZL+kk5naUd2nBRxLiJoh6o0Ng==}
+  '@roo-code/[email protected]9.0':
+    resolution: {integrity: sha512-h7gbjfIxBN+fgFecQiZs3W+vjdUhZOvtjh4OqcpPGG1w8B1DlXPDV4L/+ARUeNzZxSOK5S5rNPy57jC35EaD/w==}
 
   '@sec-ant/[email protected]':
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -12305,9 +12305,9 @@ snapshots:
   '@rollup/[email protected]':
     optional: true
 
-  '@roo-code/[email protected]4.0':
+  '@roo-code/[email protected]5.0':
     dependencies:
-      '@roo-code/types': 1.48.0
+      '@roo-code/types': 1.49.0
       ioredis: 5.6.1
       p-wait-for: 5.0.2
       socket.io-client: 4.8.1
@@ -12317,7 +12317,7 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  '@roo-code/[email protected]8.0':
+  '@roo-code/[email protected]9.0':
     dependencies:
       zod: 3.25.76
 

+ 2 - 2
scripts/link-packages.ts

@@ -212,7 +212,7 @@ function startWatch(pkg: PackageConfig): WatcherResult {
 		throw new Error(`Invalid watch command for ${pkg.name}`)
 	}
 
-	console.log(`Watching for changes to ${pkg.sourcePath} with ${cmd} ${args.join(" ")}`)
+	console.log(`👀 Watching for changes to ${pkg.sourcePath} with ${cmd} ${args.join(" ")}`)
 
 	const child = spawn(cmd, args, {
 		cwd: path.resolve(__dirname, "..", pkg.sourcePath),
@@ -251,7 +251,7 @@ function startWatch(pkg: PackageConfig): WatcherResult {
 				debounceTimer = setTimeout(() => {
 					linkPackage(pkg)
 
-					console.log(`📋 Copied ${pkg.name} to ${pkg.targetPaths.length} paths\n`)
+					console.log(`♻️ Copied ${pkg.name} to ${pkg.targetPaths.length} paths\n`)
 
 					debounceTimer = null
 				}, DEBOUNCE_DELAY)

+ 1 - 1
src/core/task/Task.ts

@@ -1951,7 +1951,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
 						const history = await provider?.getTaskWithId(this.taskId)
 
 						if (history) {
-							await provider?.initClineWithHistoryItem(history.historyItem)
+							await provider?.createTaskWithHistoryItem(history.historyItem)
 						}
 					}
 				} finally {

+ 7 - 7
src/core/tools/__tests__/newTaskTool.spec.ts

@@ -22,7 +22,7 @@ const mockAskApproval = vi.fn<AskApproval>()
 const mockHandleError = vi.fn<HandleError>()
 const mockPushToolResult = vi.fn()
 const mockRemoveClosingTag = vi.fn((_name: string, value: string | undefined) => value ?? "")
-const mockInitClineWithTask = vi.fn<() => Promise<MockClineInstance>>().mockResolvedValue({ taskId: "mock-subtask-id" })
+const mockCreateTask = vi.fn<() => Promise<MockClineInstance>>().mockResolvedValue({ taskId: "mock-subtask-id" })
 const mockEmit = vi.fn()
 const mockRecordToolError = vi.fn()
 const mockSayAndCreateMissingParamError = vi.fn()
@@ -40,7 +40,7 @@ const mockCline = {
 		deref: vi.fn(() => ({
 			getState: vi.fn(() => ({ customModes: [], mode: "ask" })),
 			handleModeSwitch: vi.fn(),
-			initClineWithTask: mockInitClineWithTask,
+			createTask: mockCreateTask,
 		})),
 	},
 }
@@ -88,8 +88,8 @@ describe("newTaskTool", () => {
 		// Verify askApproval was called
 		expect(mockAskApproval).toHaveBeenCalled()
 
-		// Verify the message passed to initClineWithTask reflects the code's behavior in unit tests
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		// Verify the message passed to createTask reflects the code's behavior in unit tests
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"Review this: \\@file1.txt and also \\\\\\@file2.txt", // Unit Test Expectation: \\@ -> \@, \\\\@ -> \\\\@
 			undefined,
 			mockCline,
@@ -122,7 +122,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 		)
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
 			undefined,
 			mockCline,
@@ -149,7 +149,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 		)
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"A normal mention @file1.txt", // Expected: @ remains @
 			undefined,
 			mockCline,
@@ -176,7 +176,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 		)
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
 			undefined,
 			mockCline,

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

@@ -83,7 +83,8 @@ export async function newTaskTool(
 			cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug
 
 			// Create new task instance first (this preserves parent's current mode in its history)
-			const newCline = await provider.initClineWithTask(unescapedMessage, undefined, cline)
+			const newCline = await provider.createTask(unescapedMessage, undefined, cline)
+
 			if (!newCline) {
 				pushToolResult(t("tools:newTask.errors.policy_restriction"))
 				return

+ 141 - 58
src/core/webview/ClineProvider.ts

@@ -17,6 +17,11 @@ import {
 	type ProviderSettings,
 	type RooCodeSettings,
 	type ProviderSettingsEntry,
+	type StaticAppProperties,
+	type DynamicAppProperties,
+	type CloudAppProperties,
+	type TaskProperties,
+	type GitProperties,
 	type TelemetryProperties,
 	type TelemetryPropertiesProvider,
 	type CodeActionId,
@@ -104,15 +109,14 @@ export class ClineProvider
 	private codeIndexStatusSubscription?: vscode.Disposable
 	private currentWorkspaceManager?: CodeIndexManager
 	private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
-	public get workspaceTracker(): WorkspaceTracker | undefined {
-		return this._workspaceTracker
-	}
 	protected mcpHub?: McpHub // Change from private to protected
 	private marketplaceManager: MarketplaceManager
 	private mdmService?: MdmService
 	private taskCreationCallback: (task: Task) => void
 	private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
 
+	private recentTasksCache?: string[]
+
 	public isViewLaunched = false
 	public settingsImportedAt?: number
 	public readonly latestAnnouncementId = "jul-29-2025-3-25-0" // Update for v3.25.0 announcement
@@ -366,7 +370,7 @@ export class ClineProvider
 
 	// returns the current cline object in the stack (the top one)
 	// if the stack is empty, returns undefined
-	getCurrentCline(): Task | undefined {
+	getCurrentTask(): Task | undefined {
 		if (this.clineStack.length === 0) {
 			return undefined
 		}
@@ -374,7 +378,7 @@ export class ClineProvider
 	}
 
 	// returns the current clineStack length (how many cline objects are in the stack)
-	getClineStackSize(): number {
+	getTaskStackSize(): number {
 		return this.clineStack.length
 	}
 
@@ -390,7 +394,7 @@ export class ClineProvider
 		// remove the last cline instance from the stack (this is the finished sub task)
 		await this.removeClineFromStack()
 		// resume the last cline instance in the stack (if it exists - this is the 'parent' calling task)
-		await this.getCurrentCline()?.resumePausedTask(lastMessage)
+		await this.getCurrentTask()?.resumePausedTask(lastMessage)
 	}
 
 	// Clear the current task without treating it as a subtask
@@ -399,6 +403,51 @@ export class ClineProvider
 		await this.removeClineFromStack()
 	}
 
+	getRecentTasks(): string[] {
+		if (this.recentTasksCache) {
+			return this.recentTasksCache
+		}
+
+		const history = this.getGlobalState("taskHistory") ?? []
+		const workspaceTasks: HistoryItem[] = []
+
+		for (const item of history) {
+			if (!item.ts || !item.task || item.workspace !== this.cwd) {
+				continue
+			}
+
+			workspaceTasks.push(item)
+		}
+
+		if (workspaceTasks.length === 0) {
+			this.recentTasksCache = []
+			return this.recentTasksCache
+		}
+
+		workspaceTasks.sort((a, b) => b.ts - a.ts)
+		let recentTaskIds: string[] = []
+
+		if (workspaceTasks.length >= 100) {
+			// If we have at least 100 tasks, return tasks from the last 7 days.
+			const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
+
+			for (const item of workspaceTasks) {
+				// Stop when we hit tasks older than 7 days.
+				if (item.ts < sevenDaysAgo) {
+					break
+				}
+
+				recentTaskIds.push(item.id)
+			}
+		} else {
+			// Otherwise, return the most recent 100 tasks (or all if less than 100).
+			recentTaskIds = workspaceTasks.slice(0, Math.min(100, workspaceTasks.length)).map((item) => item.id)
+		}
+
+		this.recentTasksCache = recentTaskIds
+		return this.recentTasksCache
+	}
+
 	/*
 	VSCode extensions use the disposable pattern to clean up resources when the sidebar/editor tab is closed by the user or system. This applies to event listening, commands, interacting with the UI, etc.
 	- https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/
@@ -489,7 +538,7 @@ export class ClineProvider
 		}
 
 		// Check if there is a cline instance in the stack (if this provider has an active task)
-		if (visibleProvider.getCurrentCline()) {
+		if (visibleProvider.getCurrentTask()) {
 			return true
 		}
 
@@ -520,7 +569,7 @@ export class ClineProvider
 			return
 		}
 
-		await visibleProvider.initClineWithTask(prompt)
+		await visibleProvider.createTask(prompt)
 	}
 
 	public static async handleTerminalAction(
@@ -545,7 +594,7 @@ export class ClineProvider
 		}
 
 		try {
-			await visibleProvider.initClineWithTask(prompt)
+			await visibleProvider.createTask(prompt)
 		} catch (error) {
 			if (error instanceof OrganizationAllowListViolationError) {
 				// Errors from terminal commands seem to get swallowed / ignored.
@@ -681,17 +730,13 @@ export class ClineProvider
 		this.log("Webview view resolved")
 	}
 
-	public async initClineWithSubTask(parent: Task, task?: string, images?: string[]) {
-		return this.initClineWithTask(task, images, parent)
-	}
-
 	// When initializing a new task, (not from history but from a tool command
 	// new_task) there is no need to remove the previous task since the new
 	// task is a subtask of the previous one, and when it finishes it is removed
 	// from the stack and the caller is resumed in this way we can have a chain
 	// of tasks, each one being a sub task of the previous one until the main
 	// task is finished.
-	public async initClineWithTask(
+	public async createTask(
 		text?: string,
 		images?: string[],
 		parentTask?: Task,
@@ -744,7 +789,7 @@ export class ClineProvider
 		return task
 	}
 
-	public async initClineWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
+	public async createTaskWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
 		await this.removeClineFromStack()
 
 		// If the history item has a saved mode, restore it and its associated API configuration
@@ -1020,7 +1065,7 @@ export class ClineProvider
 	 * @param newMode The mode to switch to
 	 */
 	public async handleModeSwitch(newMode: Mode) {
-		const cline = this.getCurrentCline()
+		const cline = this.getCurrentTask()
 
 		if (cline) {
 			TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
@@ -1133,7 +1178,7 @@ export class ClineProvider
 
 				// Change the provider for the current task.
 				// TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
-				const task = this.getCurrentCline()
+				const task = this.getCurrentTask()
 
 				if (task) {
 					task.api = buildApiHandler(providerSettings)
@@ -1194,7 +1239,7 @@ export class ClineProvider
 		}
 
 		// Change the provider for the current task.
-		const task = this.getCurrentCline()
+		const task = this.getCurrentTask()
 
 		if (task) {
 			task.api = buildApiHandler(providerSettings)
@@ -1206,7 +1251,7 @@ export class ClineProvider
 	// Task Management
 
 	async cancelTask() {
-		const cline = this.getCurrentCline()
+		const cline = this.getCurrentTask()
 
 		if (!cline) {
 			return
@@ -1223,13 +1268,13 @@ export class ClineProvider
 
 		await pWaitFor(
 			() =>
-				this.getCurrentCline()! === undefined ||
-				this.getCurrentCline()!.isStreaming === false ||
-				this.getCurrentCline()!.didFinishAbortingStream ||
+				this.getCurrentTask()! === undefined ||
+				this.getCurrentTask()!.isStreaming === false ||
+				this.getCurrentTask()!.didFinishAbortingStream ||
 				// If only the first chunk is processed, then there's no
 				// need to wait for graceful abort (closes edits, browser,
 				// etc).
-				this.getCurrentCline()!.isWaitingForFirstChunk,
+				this.getCurrentTask()!.isWaitingForFirstChunk,
 			{
 				timeout: 3_000,
 			},
@@ -1237,15 +1282,15 @@ export class ClineProvider
 			console.error("Failed to abort task")
 		})
 
-		if (this.getCurrentCline()) {
+		if (this.getCurrentTask()) {
 			// 'abandoned' will prevent this Cline instance from affecting
 			// future Cline instances. This may happen if its hanging on a
 			// streaming request.
-			this.getCurrentCline()!.abandoned = true
+			this.getCurrentTask()!.abandoned = true
 		}
 
 		// Clears task again, so we need to abortTask manually above.
-		await this.initClineWithHistoryItem({ ...historyItem, rootTask, parentTask })
+		await this.createTaskWithHistoryItem({ ...historyItem, rootTask, parentTask })
 	}
 
 	async updateCustomInstructions(instructions?: string) {
@@ -1403,10 +1448,10 @@ export class ClineProvider
 	}
 
 	async showTaskWithId(id: string) {
-		if (id !== this.getCurrentCline()?.taskId) {
+		if (id !== this.getCurrentTask()?.taskId) {
 			// Non-current task.
 			const { historyItem } = await this.getTaskWithId(id)
-			await this.initClineWithHistoryItem(historyItem) // Clears existing task.
+			await this.createTaskWithHistoryItem(historyItem) // Clears existing task.
 		}
 
 		await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
@@ -1440,7 +1485,7 @@ export class ClineProvider
 			const { taskDirPath } = await this.getTaskWithId(id)
 
 			// remove task from stack if it's the current task
-			if (id === this.getCurrentCline()?.taskId) {
+			if (id === this.getCurrentTask()?.taskId) {
 				// if we found the taskid to delete - call finish to abort this task and allow a new task to be started,
 				// if we are deleting a subtask and parent task is still waiting for subtask to finish - it allows the parent to resume (this case should neve exist)
 				await this.finishSubTask(t("common:tasks.deleted"))
@@ -1485,6 +1530,7 @@ export class ClineProvider
 		const taskHistory = this.getGlobalState("taskHistory") ?? []
 		const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
+		this.recentTasksCache = undefined
 		await this.postStateToWebview()
 	}
 
@@ -1726,10 +1772,10 @@ export class ClineProvider
 			autoCondenseContext: autoCondenseContext ?? true,
 			autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
 			uriScheme: vscode.env.uriScheme,
-			currentTaskItem: this.getCurrentCline()?.taskId
-				? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
+			currentTaskItem: this.getCurrentTask()?.taskId
+				? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentTask()?.taskId)
 				: undefined,
-			clineMessages: this.getCurrentCline()?.clineMessages || [],
+			clineMessages: this.getCurrentTask()?.clineMessages || [],
 			taskHistory: (taskHistory || [])
 				.filter((item: HistoryItem) => item.ts && item.task)
 				.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
@@ -2025,6 +2071,8 @@ export class ClineProvider
 		}
 
 		await this.updateGlobalState("taskHistory", history)
+		this.recentTasksCache = undefined
+
 		return history
 	}
 
@@ -2090,17 +2138,20 @@ export class ClineProvider
 		console.log(message)
 	}
 
-	// integration tests
+	// getters
+
+	public get workspaceTracker(): WorkspaceTracker | undefined {
+		return this._workspaceTracker
+	}
 
 	get viewLaunched() {
 		return this.isViewLaunched
 	}
 
 	get messages() {
-		return this.getCurrentCline()?.clineMessages || []
+		return this.getCurrentTask()?.clineMessages || []
 	}
 
-	// Add public getter
 	public getMcpHub(): McpHub | undefined {
 		return this.mcpHub
 	}
@@ -2143,7 +2194,7 @@ export class ClineProvider
 		)
 
 		if (isRemoteControlEnabled(userInfo, enabled)) {
-			const currentTask = this.getCurrentCline()
+			const currentTask = this.getCurrentTask()
 
 			if (currentTask && !currentTask.bridgeService) {
 				try {
@@ -2176,18 +2227,29 @@ export class ClineProvider
 		}
 	}
 
-	/**
-	 * Returns properties to be included in every telemetry event
-	 * This method is called by the telemetry service to get context information
-	 * like the current mode, API provider, git repository information, etc.
-	 */
-	public async getTelemetryProperties(): Promise<TelemetryProperties> {
-		const { mode, apiConfiguration, language } = await this.getState()
-		const task = this.getCurrentCline()
+	private _appProperties?: StaticAppProperties
 
-		const packageJSON = this.context.extension?.packageJSON
+	private getAppProperties(): StaticAppProperties {
+		if (!this._appProperties) {
+			const packageJSON = this.context.extension?.packageJSON
 
-		// Get Roo Code Cloud authentication state
+			this._appProperties = {
+				appName: packageJSON?.name ?? Package.name,
+				appVersion: packageJSON?.version ?? Package.version,
+				vscodeVersion: vscode.version,
+				platform: process.platform,
+				editorName: vscode.env.appName,
+			}
+		}
+
+		return this._appProperties
+	}
+
+	public get appProperties(): StaticAppProperties {
+		return this._appProperties ?? this.getAppProperties()
+	}
+
+	private getCloudProperties(): CloudAppProperties {
 		let cloudIsAuthenticated: boolean | undefined
 
 		try {
@@ -2195,14 +2257,19 @@ export class ClineProvider
 				cloudIsAuthenticated = CloudService.instance.isAuthenticated()
 			}
 		} catch (error) {
-			// Silently handle errors to avoid breaking telemetry collection
+			// Silently handle errors to avoid breaking telemetry collection.
 			this.log(`[getTelemetryProperties] Failed to get cloud auth state: ${error}`)
 		}
 
-		// Get git repository information
-		const gitInfo = await getWorkspaceGitInfo()
+		return {
+			cloudIsAuthenticated,
+		}
+	}
 
-		// Calculate todo list statistics
+	private async getTaskProperties(): Promise<DynamicAppProperties & TaskProperties> {
+		const { language, mode, apiConfiguration } = await this.getState()
+
+		const task = this.getCurrentTask()
 		const todoList = task?.todoList
 		let todos: { total: number; completed: number; inProgress: number; pending: number } | undefined
 
@@ -2215,22 +2282,38 @@ export class ClineProvider
 			}
 		}
 
-		// Return all properties including git info - clients will filter as needed
 		return {
-			appName: packageJSON?.name ?? Package.name,
-			appVersion: packageJSON?.version ?? Package.version,
-			vscodeVersion: vscode.version,
-			platform: process.platform,
-			editorName: vscode.env.appName,
 			language,
 			mode,
+			taskId: task?.taskId,
 			apiProvider: apiConfiguration?.apiProvider,
 			modelId: task?.api?.getModel().id,
 			diffStrategy: task?.diffStrategy?.getName(),
 			isSubtask: task ? !!task.parentTask : undefined,
-			cloudIsAuthenticated,
 			...(todos && { todos }),
-			...gitInfo,
+		}
+	}
+
+	private _gitProperties?: GitProperties
+
+	private async getGitProperties(): Promise<GitProperties> {
+		if (!this._gitProperties) {
+			this._gitProperties = await getWorkspaceGitInfo()
+		}
+
+		return this._gitProperties
+	}
+
+	public get gitProperties(): GitProperties | undefined {
+		return this._gitProperties
+	}
+
+	public async getTelemetryProperties(): Promise<TelemetryProperties> {
+		return {
+			...this.getAppProperties(),
+			...this.getCloudProperties(),
+			...(await this.getTaskProperties()),
+			...(await this.getGitProperties()),
 		}
 	}
 

+ 18 - 18
src/core/webview/__tests__/ClineProvider.spec.ts

@@ -580,13 +580,13 @@ describe("ClineProvider", () => {
 		await provider.addClineToStack(mockCline)
 
 		// get the stack size before the abort call
-		const stackSizeBeforeAbort = provider.getClineStackSize()
+		const stackSizeBeforeAbort = provider.getTaskStackSize()
 
 		// call the removeClineFromStack method so it will call the current cline abort and remove it from the stack
 		await provider.removeClineFromStack()
 
 		// get the stack size after the abort call
-		const stackSizeAfterAbort = provider.getClineStackSize()
+		const stackSizeAfterAbort = provider.getTaskStackSize()
 
 		// check if the abort method was called
 		expect(mockCline.abortTask).toHaveBeenCalled()
@@ -692,7 +692,7 @@ describe("ClineProvider", () => {
 			await provider.addClineToStack(mockCline)
 
 			// Verify stack size is 1
-			expect(provider.getClineStackSize()).toBe(1)
+			expect(provider.getTaskStackSize()).toBe(1)
 
 			// Get the message handler
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
@@ -718,10 +718,10 @@ describe("ClineProvider", () => {
 		await provider.addClineToStack(mockCline2)
 
 		// verify cline instances were added to the stack
-		expect(provider.getClineStackSize()).toBe(2)
+		expect(provider.getTaskStackSize()).toBe(2)
 
 		// verify current cline instance is the last one added
-		expect(provider.getCurrentCline()).toBe(mockCline2)
+		expect(provider.getCurrentTask()).toBe(mockCline2)
 	})
 
 	test("getState returns correct initial state", async () => {
@@ -1205,8 +1205,8 @@ describe("ClineProvider", () => {
 				historyItem: { id: "test-task-id" },
 			})
 
-			// Mock initClineWithHistoryItem
-			;(provider as any).initClineWithHistoryItem = vi.fn()
+			// Mock createTaskWithHistoryItem
+			;(provider as any).createTaskWithHistoryItem = vi.fn()
 
 			// Trigger message deletion
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
@@ -1230,8 +1230,8 @@ describe("ClineProvider", () => {
 				mockApiHistory[1],
 			])
 
-			// Verify initClineWithHistoryItem was called
-			expect((provider as any).initClineWithHistoryItem).toHaveBeenCalledWith({ id: "test-task-id" })
+			// Verify createTaskWithHistoryItem was called
+			expect((provider as any).createTaskWithHistoryItem).toHaveBeenCalledWith({ id: "test-task-id" })
 		})
 
 		test("handles case when no current task exists", async () => {
@@ -1660,7 +1660,7 @@ describe("ClineProvider", () => {
 		})
 	})
 
-	describe("initClineWithHistoryItem mode validation", () => {
+	describe("createTaskWithHistoryItem mode validation", () => {
 		test("validates and falls back to default mode when restored mode no longer exists", async () => {
 			await provider.resolveWebviewView(mockWebviewView)
 
@@ -1711,7 +1711,7 @@ describe("ClineProvider", () => {
 			}
 
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify mode validation occurred
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
@@ -1780,7 +1780,7 @@ describe("ClineProvider", () => {
 			}
 
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify mode validation occurred
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
@@ -1832,7 +1832,7 @@ describe("ClineProvider", () => {
 			}
 
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify mode was preserved
 			expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect")
@@ -1863,7 +1863,7 @@ describe("ClineProvider", () => {
 			}
 
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify no mode validation occurred (mode update not called)
 			expect(mockContext.globalState.update).not.toHaveBeenCalledWith("mode", expect.any(String))
@@ -1913,7 +1913,7 @@ describe("ClineProvider", () => {
 			}
 
 			// Initialize with history item - should not throw
-			await expect(provider.initClineWithHistoryItem(historyItem)).resolves.not.toThrow()
+			await expect(provider.createTaskWithHistoryItem(historyItem)).resolves.not.toThrow()
 
 			// Verify error was logged but task restoration continued
 			expect(logSpy).toHaveBeenCalledWith(
@@ -3235,7 +3235,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
 
 		test("handles edit permission failures", async () => {
 			// Mock no current cline (simulating permission failure)
-			vi.spyOn(provider, "getCurrentCline").mockReturnValue(undefined)
+			vi.spyOn(provider, "getCurrentTask").mockReturnValue(undefined)
 
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
 
@@ -3654,7 +3654,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
 				;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({
 					historyItem: { id: "test-task-id" },
 				})
-				;(provider as any).initClineWithHistoryItem = vi.fn()
+				;(provider as any).createTaskWithHistoryItem = vi.fn()
 
 				const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
 
@@ -3671,7 +3671,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
 
 				// Verify successful operation completed
 				expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
-				expect(provider.initClineWithHistoryItem).toHaveBeenCalled()
+				expect(provider.createTaskWithHistoryItem).toHaveBeenCalled()
 				expect(vscode.window.showErrorMessage).not.toHaveBeenCalled()
 			})
 

+ 20 - 20
src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts

@@ -415,7 +415,7 @@ describe("ClineProvider - Sticky Mode", () => {
 		})
 	})
 
-	describe("initClineWithHistoryItem", () => {
+	describe("createTaskWithHistoryItem", () => {
 		it("should restore mode from history item when reopening task", async () => {
 			await provider.resolveWebviewView(mockWebviewView)
 
@@ -437,7 +437,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const updateGlobalStateSpy = vi.spyOn(provider as any, "updateGlobalState").mockResolvedValue(undefined)
 
 			// Initialize task with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify mode was restored via updateGlobalState
 			expect(updateGlobalStateSpy).toHaveBeenCalledWith("mode", "architect")
@@ -479,7 +479,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 			// Initialize task with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify mode was not changed (should use current mode)
 			expect(handleModeSwitchSpy).not.toHaveBeenCalled()
@@ -606,15 +606,15 @@ describe("ClineProvider - Sticky Mode", () => {
 			// Initialize subtask with parent's mode
 			taskModes[subtaskId] = "architect"
 
-			// Mock getCurrentCline to return the parent task initially
-			const getCurrentClineMock = vi.spyOn(provider, "getCurrentCline")
-			getCurrentClineMock.mockReturnValue(parentTask as any)
+			// Mock getCurrentTask to return the parent task initially
+			const getCurrentTaskMock = vi.spyOn(provider, "getCurrentTask")
+			getCurrentTaskMock.mockReturnValue(parentTask as any)
 
 			// Add subtask to stack
 			await provider.addClineToStack(subtask)
 
-			// Now mock getCurrentCline to return the subtask (simulating stack behavior)
-			getCurrentClineMock.mockReturnValue(subtask as any)
+			// Now mock getCurrentTask to return the subtask (simulating stack behavior)
+			getCurrentTaskMock.mockReturnValue(subtask as any)
 
 			// Switch subtask to code mode - this should only affect the subtask
 			await provider.handleModeSwitch("code")
@@ -678,7 +678,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 			// Initialize task with history item - should not throw
-			await expect(provider.initClineWithHistoryItem(historyItem)).resolves.not.toThrow()
+			await expect(provider.createTaskWithHistoryItem(historyItem)).resolves.not.toThrow()
 
 			// Verify mode switch was not called with null
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith(null)
@@ -719,7 +719,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			}
 
 			// Restore the task from history
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 			// Verify that the mode was restored
 			const state = await provider.getState()
@@ -764,7 +764,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 			// Initialize task with history item - should not throw
-			await expect(provider.initClineWithHistoryItem(historyItem)).resolves.not.toThrow()
+			await expect(provider.createTaskWithHistoryItem(historyItem)).resolves.not.toThrow()
 
 			// Verify mode switch was not called with deleted mode
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith("deleted-mode")
@@ -1105,17 +1105,17 @@ describe("ClineProvider - Sticky Mode", () => {
 				.spyOn(provider, "updateTaskHistory")
 				.mockImplementation(() => Promise.resolve([]))
 
-			// Mock getCurrentCline to return different tasks
-			const getCurrentClineSpy = vi.spyOn(provider, "getCurrentCline")
+			// Mock getCurrentTask to return different tasks
+			const getCurrentTaskSpy = vi.spyOn(provider, "getCurrentTask")
 
 			// Simulate simultaneous mode switches for different tasks
-			getCurrentClineSpy.mockReturnValue(task1 as any)
+			getCurrentTaskSpy.mockReturnValue(task1 as any)
 			const switch1 = provider.handleModeSwitch("architect")
 
-			getCurrentClineSpy.mockReturnValue(task2 as any)
+			getCurrentTaskSpy.mockReturnValue(task2 as any)
 			const switch2 = provider.handleModeSwitch("debug")
 
-			getCurrentClineSpy.mockReturnValue(task3 as any)
+			getCurrentTaskSpy.mockReturnValue(task3 as any)
 			const switch3 = provider.handleModeSwitch("code")
 
 			await Promise.all([switch1, switch2, switch3])
@@ -1166,7 +1166,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			vi.clearAllMocks()
 
 			// Start initialization
-			const initPromise = provider.initClineWithHistoryItem(historyItem)
+			const initPromise = provider.createTaskWithHistoryItem(historyItem)
 
 			// Try to switch mode during initialization
 			await provider.handleModeSwitch("code")
@@ -1201,13 +1201,13 @@ describe("ClineProvider - Sticky Mode", () => {
 				await provider.addClineToStack(task as any)
 			}
 
-			// Mock getCurrentCline
-			const getCurrentClineSpy = vi.spyOn(provider, "getCurrentCline")
+			// Mock getCurrentTask
+			const getCurrentTaskSpy = vi.spyOn(provider, "getCurrentTask")
 
 			// Rapidly switch between tasks and modes
 			const switches: Promise<void>[] = []
 			tasks.forEach((task, index) => {
-				getCurrentClineSpy.mockReturnValue(task as any)
+				getCurrentTaskSpy.mockReturnValue(task as any)
 				const mode = ["architect", "debug", "code"][index % 3]
 				switches.push(provider.handleModeSwitch(mode as any))
 			})

+ 7 - 7
src/core/webview/__tests__/webviewMessageHandler.spec.ts

@@ -32,9 +32,9 @@ const mockClineProvider = {
 	},
 	log: vi.fn(),
 	postStateToWebview: vi.fn(),
-	getCurrentCline: vi.fn(),
+	getCurrentTask: vi.fn(),
 	getTaskWithId: vi.fn(),
-	initClineWithHistoryItem: vi.fn(),
+	createTaskWithHistoryItem: vi.fn(),
 } as unknown as ClineProvider
 
 import { t } from "../../../i18n"
@@ -533,7 +533,7 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 	beforeEach(() => {
 		vi.clearAllMocks()
 		// Mock a current Cline instance
-		vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({
+		vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({
 			taskId: "test-task-id",
 			apiConversationHistory: [],
 			clineMessages: [],
@@ -544,7 +544,7 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 
 	describe("deleteMessage", () => {
 		it("should always show dialog for delete confirmation", async () => {
-			vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists
+			vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({} as any)
 
 			await webviewMessageHandler(mockClineProvider, {
 				type: "deleteMessage",
@@ -560,12 +560,12 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 
 	describe("submitEditedMessage", () => {
 		it("should always show dialog for edit confirmation", async () => {
-			vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({} as any) // Mock current cline exists
+			vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({} as any)
 
 			await webviewMessageHandler(mockClineProvider, {
 				type: "submitEditedMessage",
-				value: 123456789, // messageTs as number
-				editedMessageContent: "edited content", // text content in editedMessageContent field
+				value: 123456789,
+				editedMessageContent: "edited content",
 			})
 
 			expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({

+ 1 - 1
src/core/webview/generateSystemPrompt.ts

@@ -42,7 +42,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
 	const mode = message.mode ?? defaultModeSlug
 	const customModes = await provider.customModesManager.getCustomModes()
 
-	const rooIgnoreInstructions = provider.getCurrentCline()?.rooIgnoreController?.getInstructions()
+	const rooIgnoreInstructions = provider.getCurrentTask()?.rooIgnoreController?.getInstructions()
 
 	// Determine if browser tools can be used based on model support, mode, and user settings
 	let modelSupportsComputerUse = false

+ 29 - 22
src/core/webview/webviewMessageHandler.ts

@@ -111,9 +111,9 @@ export const webviewMessageHandler = async (
 	 * Handles confirmed message deletion from webview dialog
 	 */
 	const handleDeleteMessageConfirm = async (messageTs: number): Promise<void> => {
-		// Only proceed if we have a current cline
-		if (provider.getCurrentCline()) {
-			const currentCline = provider.getCurrentCline()!
+		// Only proceed if we have a current task.
+		if (provider.getCurrentTask()) {
+			const currentCline = provider.getCurrentTask()!
 			const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
 
 			if (messageIndex !== -1) {
@@ -124,7 +124,7 @@ export const webviewMessageHandler = async (
 					await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
 
 					// Initialize with history item after deletion
-					await provider.initClineWithHistoryItem(historyItem)
+					await provider.createTaskWithHistoryItem(historyItem)
 				} catch (error) {
 					console.error("Error in delete message:", error)
 					vscode.window.showErrorMessage(
@@ -156,9 +156,9 @@ export const webviewMessageHandler = async (
 		editedContent: string,
 		images?: string[],
 	): Promise<void> => {
-		// Only proceed if we have a current cline
-		if (provider.getCurrentCline()) {
-			const currentCline = provider.getCurrentCline()!
+		// Only proceed if we have a current task.
+		if (provider.getCurrentTask()) {
+			const currentCline = provider.getCurrentTask()!
 
 			// Use findMessageIndices to find messages based on timestamp
 			const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
@@ -288,7 +288,7 @@ export const webviewMessageHandler = async (
 			// Initializing new instance of Cline will make sure that any
 			// agentically running promises in old instance don't affect our new
 			// task. This essentially creates a fresh slate for the new task.
-			await provider.initClineWithTask(message.text, message.images)
+			await provider.createTask(message.text, message.images)
 			break
 		case "customInstructions":
 			await provider.updateCustomInstructions(message.text)
@@ -346,7 +346,7 @@ export const webviewMessageHandler = async (
 			await provider.postStateToWebview()
 			break
 		case "askResponse":
-			provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
+			provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
 			break
 		case "autoCondenseContext":
 			await updateGlobalState("autoCondenseContext", message.bool)
@@ -358,19 +358,23 @@ export const webviewMessageHandler = async (
 			break
 		case "terminalOperation":
 			if (message.terminalOperation) {
-				provider.getCurrentCline()?.handleTerminalOperation(message.terminalOperation)
+				provider.getCurrentTask()?.handleTerminalOperation(message.terminalOperation)
 			}
 			break
 		case "clearTask":
-			// clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
-			// Check if the current task actually has a parent task
-			const currentTask = provider.getCurrentCline()
+			// Clear task resets the current session and allows for a new task
+			// to be started, if this session is a subtask - it allows the
+			// parent task to be resumed.
+			// Check if the current task actually has a parent task.
+			const currentTask = provider.getCurrentTask()
+
 			if (currentTask && currentTask.parentTask) {
 				await provider.finishSubTask(t("common:tasks.canceled"))
 			} else {
 				// Regular task - just clear it
 				await provider.clearTask()
 			}
+
 			await provider.postStateToWebview()
 			break
 		case "didShowAnnouncement":
@@ -387,14 +391,15 @@ export const webviewMessageHandler = async (
 			})
 			break
 		case "exportCurrentTask":
-			const currentTaskId = provider.getCurrentCline()?.taskId
+			const currentTaskId = provider.getCurrentTask()?.taskId
 			if (currentTaskId) {
 				provider.exportTaskWithId(currentTaskId)
 			}
 			break
 		case "shareCurrentTask":
-			const shareTaskId = provider.getCurrentCline()?.taskId
-			const clineMessages = provider.getCurrentCline()?.clineMessages
+			const shareTaskId = provider.getCurrentTask()?.taskId
+			const clineMessages = provider.getCurrentTask()?.clineMessages
+
 			if (!shareTaskId) {
 				vscode.window.showErrorMessage(t("common:errors.share_no_active_task"))
 				break
@@ -725,7 +730,7 @@ export const webviewMessageHandler = async (
 			const result = checkoutDiffPayloadSchema.safeParse(message.payload)
 
 			if (result.success) {
-				await provider.getCurrentCline()?.checkpointDiff(result.data)
+				await provider.getCurrentTask()?.checkpointDiff(result.data)
 			}
 
 			break
@@ -736,13 +741,13 @@ export const webviewMessageHandler = async (
 				await provider.cancelTask()
 
 				try {
-					await pWaitFor(() => provider.getCurrentCline()?.isInitialized === true, { timeout: 3_000 })
+					await pWaitFor(() => provider.getCurrentTask()?.isInitialized === true, { timeout: 3_000 })
 				} catch (error) {
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
 				}
 
 				try {
-					await provider.getCurrentCline()?.checkpointRestore(result.data)
+					await provider.getCurrentTask()?.checkpointRestore(result.data)
 				} catch (error) {
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
 				}
@@ -1227,14 +1232,14 @@ export const webviewMessageHandler = async (
 			}
 			break
 		case "deleteMessage": {
-			if (provider.getCurrentCline() && typeof message.value === "number" && message.value) {
+			if (provider.getCurrentTask() && typeof message.value === "number" && message.value) {
 				await handleMessageModificationsOperation(message.value, "delete")
 			}
 			break
 		}
 		case "submitEditedMessage": {
 			if (
-				provider.getCurrentCline() &&
+				provider.getCurrentTask() &&
 				typeof message.value === "number" &&
 				message.value &&
 				message.editedMessageContent
@@ -1366,6 +1371,7 @@ export const webviewMessageHandler = async (
 			if (message.text) {
 				try {
 					const state = await provider.getState()
+
 					const {
 						apiConfiguration,
 						customSupportPrompts,
@@ -1374,7 +1380,8 @@ export const webviewMessageHandler = async (
 						includeTaskHistoryInEnhance,
 					} = state
 
-					const currentCline = provider.getCurrentCline()
+					const currentCline = provider.getCurrentTask()
+
 					const result = await MessageEnhancer.enhanceMessage({
 						text: message.text,
 						apiConfiguration,

+ 2 - 2
src/extension/api.ts

@@ -148,7 +148,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 		await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
 		await provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images })
 
-		const cline = await provider.initClineWithTask(text, images, undefined, {
+		const cline = await provider.createTask(text, images, undefined, {
 			consecutiveMistakeLimit: Number.MAX_SAFE_INTEGER,
 		})
 
@@ -161,7 +161,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 
 	public async resumeTask(taskId: string): Promise<void> {
 		const { historyItem } = await this.sidebarProvider.getTaskWithId(taskId)
-		await this.sidebarProvider.initClineWithHistoryItem(historyItem)
+		await this.sidebarProvider.createTaskWithHistoryItem(historyItem)
 		await this.sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
 	}
 

+ 1 - 1
src/package.json

@@ -427,7 +427,7 @@
 		"@mistralai/mistralai": "^1.3.6",
 		"@modelcontextprotocol/sdk": "^1.9.0",
 		"@qdrant/js-client-rest": "^1.14.0",
-		"@roo-code/cloud": "^0.14.0",
+		"@roo-code/cloud": "^0.15.0",
 		"@roo-code/ipc": "workspace:^",
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/types": "workspace:^",

+ 0 - 2
src/utils/__tests__/git.spec.ts

@@ -1,5 +1,4 @@
 import { ExecException } from "child_process"
-import * as vscode from "vscode"
 import * as fs from "fs"
 import * as path from "path"
 
@@ -12,7 +11,6 @@ import {
 	sanitizeGitUrl,
 	extractRepositoryName,
 	getWorkspaceGitInfo,
-	GitRepositoryInfo,
 	convertGitUrlToHttps,
 } from "../git"
 import { truncateOutput } from "../../integrations/misc/extract-text"

+ 1 - 1
src/utils/git.ts

@@ -196,7 +196,7 @@ export async function getWorkspaceGitInfo(): Promise<GitRepositoryInfo> {
 		return {}
 	}
 
-	// Use the first workspace folder
+	// Use the first workspace folder.
 	const workspaceRoot = workspaceFolders[0].uri.fsPath
 	return getGitRepositoryInfo(workspaceRoot)
 }