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",
 	"name": "@roo-code/types",
-	"version": "1.48.0",
+	"version": "1.49.0",
 	"description": "TypeScript type definitions for Roo Code.",
 	"description": "TypeScript type definitions for Roo Code.",
 	"publishConfig": {
 	"publishConfig": {
 		"access": "public",
 		"access": "public",

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

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

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

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

+ 9 - 9
pnpm-lock.yaml

@@ -584,8 +584,8 @@ importers:
         specifier: ^1.14.0
         specifier: ^1.14.0
         version: 1.14.0([email protected])
         version: 1.14.0([email protected])
       '@roo-code/cloud':
       '@roo-code/cloud':
-        specifier: ^0.14.0
-        version: 0.14.0
+        specifier: ^0.15.0
+        version: 0.15.0
       '@roo-code/ipc':
       '@roo-code/ipc':
         specifier: workspace:^
         specifier: workspace:^
         version: link:../packages/ipc
         version: link:../packages/ipc
@@ -3103,11 +3103,11 @@ packages:
     cpu: [x64]
     cpu: [x64]
     os: [win32]
     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]':
   '@sec-ant/[email protected]':
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -12305,9 +12305,9 @@ snapshots:
   '@rollup/[email protected]':
   '@rollup/[email protected]':
     optional: true
     optional: true
 
 
-  '@roo-code/[email protected]4.0':
+  '@roo-code/[email protected]5.0':
     dependencies:
     dependencies:
-      '@roo-code/types': 1.48.0
+      '@roo-code/types': 1.49.0
       ioredis: 5.6.1
       ioredis: 5.6.1
       p-wait-for: 5.0.2
       p-wait-for: 5.0.2
       socket.io-client: 4.8.1
       socket.io-client: 4.8.1
@@ -12317,7 +12317,7 @@ snapshots:
       - supports-color
       - supports-color
       - utf-8-validate
       - utf-8-validate
 
 
-  '@roo-code/[email protected]8.0':
+  '@roo-code/[email protected]9.0':
     dependencies:
     dependencies:
       zod: 3.25.76
       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}`)
 		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, {
 	const child = spawn(cmd, args, {
 		cwd: path.resolve(__dirname, "..", pkg.sourcePath),
 		cwd: path.resolve(__dirname, "..", pkg.sourcePath),
@@ -251,7 +251,7 @@ function startWatch(pkg: PackageConfig): WatcherResult {
 				debounceTimer = setTimeout(() => {
 				debounceTimer = setTimeout(() => {
 					linkPackage(pkg)
 					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
 					debounceTimer = null
 				}, DEBOUNCE_DELAY)
 				}, 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)
 						const history = await provider?.getTaskWithId(this.taskId)
 
 
 						if (history) {
 						if (history) {
-							await provider?.initClineWithHistoryItem(history.historyItem)
+							await provider?.createTaskWithHistoryItem(history.historyItem)
 						}
 						}
 					}
 					}
 				} finally {
 				} 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 mockHandleError = vi.fn<HandleError>()
 const mockPushToolResult = vi.fn()
 const mockPushToolResult = vi.fn()
 const mockRemoveClosingTag = vi.fn((_name: string, value: string | undefined) => value ?? "")
 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 mockEmit = vi.fn()
 const mockRecordToolError = vi.fn()
 const mockRecordToolError = vi.fn()
 const mockSayAndCreateMissingParamError = vi.fn()
 const mockSayAndCreateMissingParamError = vi.fn()
@@ -40,7 +40,7 @@ const mockCline = {
 		deref: vi.fn(() => ({
 		deref: vi.fn(() => ({
 			getState: vi.fn(() => ({ customModes: [], mode: "ask" })),
 			getState: vi.fn(() => ({ customModes: [], mode: "ask" })),
 			handleModeSwitch: vi.fn(),
 			handleModeSwitch: vi.fn(),
-			initClineWithTask: mockInitClineWithTask,
+			createTask: mockCreateTask,
 		})),
 		})),
 	},
 	},
 }
 }
@@ -88,8 +88,8 @@ describe("newTaskTool", () => {
 		// Verify askApproval was called
 		// Verify askApproval was called
 		expect(mockAskApproval).toHaveBeenCalled()
 		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: \\@ -> \@, \\\\@ -> \\\\@
 			"Review this: \\@file1.txt and also \\\\\\@file2.txt", // Unit Test Expectation: \\@ -> \@, \\\\@ -> \\\\@
 			undefined,
 			undefined,
 			mockCline,
 			mockCline,
@@ -122,7 +122,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 			mockRemoveClosingTag,
 		)
 		)
 
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
 			"This is already unescaped: \\@file1.txt", // Expected: \@ remains \@
 			undefined,
 			undefined,
 			mockCline,
 			mockCline,
@@ -149,7 +149,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 			mockRemoveClosingTag,
 		)
 		)
 
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"A normal mention @file1.txt", // Expected: @ remains @
 			"A normal mention @file1.txt", // Expected: @ remains @
 			undefined,
 			undefined,
 			mockCline,
 			mockCline,
@@ -176,7 +176,7 @@ describe("newTaskTool", () => {
 			mockRemoveClosingTag,
 			mockRemoveClosingTag,
 		)
 		)
 
 
-		expect(mockInitClineWithTask).toHaveBeenCalledWith(
+		expect(mockCreateTask).toHaveBeenCalledWith(
 			"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
 			"Mix: @file0.txt, \\@file1.txt, \\@file2.txt, \\\\\\@file3.txt", // Unit Test Expectation: @->@, \@->\@, \\@->\@, \\\\@->\\\\@
 			undefined,
 			undefined,
 			mockCline,
 			mockCline,

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

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

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

@@ -17,6 +17,11 @@ import {
 	type ProviderSettings,
 	type ProviderSettings,
 	type RooCodeSettings,
 	type RooCodeSettings,
 	type ProviderSettingsEntry,
 	type ProviderSettingsEntry,
+	type StaticAppProperties,
+	type DynamicAppProperties,
+	type CloudAppProperties,
+	type TaskProperties,
+	type GitProperties,
 	type TelemetryProperties,
 	type TelemetryProperties,
 	type TelemetryPropertiesProvider,
 	type TelemetryPropertiesProvider,
 	type CodeActionId,
 	type CodeActionId,
@@ -104,15 +109,14 @@ export class ClineProvider
 	private codeIndexStatusSubscription?: vscode.Disposable
 	private codeIndexStatusSubscription?: vscode.Disposable
 	private currentWorkspaceManager?: CodeIndexManager
 	private currentWorkspaceManager?: CodeIndexManager
 	private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
 	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
 	protected mcpHub?: McpHub // Change from private to protected
 	private marketplaceManager: MarketplaceManager
 	private marketplaceManager: MarketplaceManager
 	private mdmService?: MdmService
 	private mdmService?: MdmService
 	private taskCreationCallback: (task: Task) => void
 	private taskCreationCallback: (task: Task) => void
 	private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
 	private taskEventListeners: WeakMap<Task, Array<() => void>> = new WeakMap()
 
 
+	private recentTasksCache?: string[]
+
 	public isViewLaunched = false
 	public isViewLaunched = false
 	public settingsImportedAt?: number
 	public settingsImportedAt?: number
 	public readonly latestAnnouncementId = "jul-29-2025-3-25-0" // Update for v3.25.0 announcement
 	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)
 	// returns the current cline object in the stack (the top one)
 	// if the stack is empty, returns undefined
 	// if the stack is empty, returns undefined
-	getCurrentCline(): Task | undefined {
+	getCurrentTask(): Task | undefined {
 		if (this.clineStack.length === 0) {
 		if (this.clineStack.length === 0) {
 			return undefined
 			return undefined
 		}
 		}
@@ -374,7 +378,7 @@ export class ClineProvider
 	}
 	}
 
 
 	// returns the current clineStack length (how many cline objects are in the stack)
 	// returns the current clineStack length (how many cline objects are in the stack)
-	getClineStackSize(): number {
+	getTaskStackSize(): number {
 		return this.clineStack.length
 		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)
 		// remove the last cline instance from the stack (this is the finished sub task)
 		await this.removeClineFromStack()
 		await this.removeClineFromStack()
 		// resume the last cline instance in the stack (if it exists - this is the 'parent' calling task)
 		// 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
 	// Clear the current task without treating it as a subtask
@@ -399,6 +403,51 @@ export class ClineProvider
 		await this.removeClineFromStack()
 		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.
 	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/
 	- 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)
 		// 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
 			return true
 		}
 		}
 
 
@@ -520,7 +569,7 @@ export class ClineProvider
 			return
 			return
 		}
 		}
 
 
-		await visibleProvider.initClineWithTask(prompt)
+		await visibleProvider.createTask(prompt)
 	}
 	}
 
 
 	public static async handleTerminalAction(
 	public static async handleTerminalAction(
@@ -545,7 +594,7 @@ export class ClineProvider
 		}
 		}
 
 
 		try {
 		try {
-			await visibleProvider.initClineWithTask(prompt)
+			await visibleProvider.createTask(prompt)
 		} catch (error) {
 		} catch (error) {
 			if (error instanceof OrganizationAllowListViolationError) {
 			if (error instanceof OrganizationAllowListViolationError) {
 				// Errors from terminal commands seem to get swallowed / ignored.
 				// Errors from terminal commands seem to get swallowed / ignored.
@@ -681,17 +730,13 @@ export class ClineProvider
 		this.log("Webview view resolved")
 		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
 	// 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
 	// 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
 	// 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
 	// 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
 	// of tasks, each one being a sub task of the previous one until the main
 	// task is finished.
 	// task is finished.
-	public async initClineWithTask(
+	public async createTask(
 		text?: string,
 		text?: string,
 		images?: string[],
 		images?: string[],
 		parentTask?: Task,
 		parentTask?: Task,
@@ -744,7 +789,7 @@ export class ClineProvider
 		return task
 		return task
 	}
 	}
 
 
-	public async initClineWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
+	public async createTaskWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
 		await this.removeClineFromStack()
 		await this.removeClineFromStack()
 
 
 		// If the history item has a saved mode, restore it and its associated API configuration
 		// 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
 	 * @param newMode The mode to switch to
 	 */
 	 */
 	public async handleModeSwitch(newMode: Mode) {
 	public async handleModeSwitch(newMode: Mode) {
-		const cline = this.getCurrentCline()
+		const cline = this.getCurrentTask()
 
 
 		if (cline) {
 		if (cline) {
 			TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
 			TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
@@ -1133,7 +1178,7 @@ export class ClineProvider
 
 
 				// Change the provider for the current task.
 				// Change the provider for the current task.
 				// TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
 				// TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
-				const task = this.getCurrentCline()
+				const task = this.getCurrentTask()
 
 
 				if (task) {
 				if (task) {
 					task.api = buildApiHandler(providerSettings)
 					task.api = buildApiHandler(providerSettings)
@@ -1194,7 +1239,7 @@ export class ClineProvider
 		}
 		}
 
 
 		// Change the provider for the current task.
 		// Change the provider for the current task.
-		const task = this.getCurrentCline()
+		const task = this.getCurrentTask()
 
 
 		if (task) {
 		if (task) {
 			task.api = buildApiHandler(providerSettings)
 			task.api = buildApiHandler(providerSettings)
@@ -1206,7 +1251,7 @@ export class ClineProvider
 	// Task Management
 	// Task Management
 
 
 	async cancelTask() {
 	async cancelTask() {
-		const cline = this.getCurrentCline()
+		const cline = this.getCurrentTask()
 
 
 		if (!cline) {
 		if (!cline) {
 			return
 			return
@@ -1223,13 +1268,13 @@ export class ClineProvider
 
 
 		await pWaitFor(
 		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
 				// If only the first chunk is processed, then there's no
 				// need to wait for graceful abort (closes edits, browser,
 				// need to wait for graceful abort (closes edits, browser,
 				// etc).
 				// etc).
-				this.getCurrentCline()!.isWaitingForFirstChunk,
+				this.getCurrentTask()!.isWaitingForFirstChunk,
 			{
 			{
 				timeout: 3_000,
 				timeout: 3_000,
 			},
 			},
@@ -1237,15 +1282,15 @@ export class ClineProvider
 			console.error("Failed to abort task")
 			console.error("Failed to abort task")
 		})
 		})
 
 
-		if (this.getCurrentCline()) {
+		if (this.getCurrentTask()) {
 			// 'abandoned' will prevent this Cline instance from affecting
 			// 'abandoned' will prevent this Cline instance from affecting
 			// future Cline instances. This may happen if its hanging on a
 			// future Cline instances. This may happen if its hanging on a
 			// streaming request.
 			// streaming request.
-			this.getCurrentCline()!.abandoned = true
+			this.getCurrentTask()!.abandoned = true
 		}
 		}
 
 
 		// Clears task again, so we need to abortTask manually above.
 		// 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) {
 	async updateCustomInstructions(instructions?: string) {
@@ -1403,10 +1448,10 @@ export class ClineProvider
 	}
 	}
 
 
 	async showTaskWithId(id: string) {
 	async showTaskWithId(id: string) {
-		if (id !== this.getCurrentCline()?.taskId) {
+		if (id !== this.getCurrentTask()?.taskId) {
 			// Non-current task.
 			// Non-current task.
 			const { historyItem } = await this.getTaskWithId(id)
 			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" })
 		await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
@@ -1440,7 +1485,7 @@ export class ClineProvider
 			const { taskDirPath } = await this.getTaskWithId(id)
 			const { taskDirPath } = await this.getTaskWithId(id)
 
 
 			// remove task from stack if it's the current task
 			// 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 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)
 				// 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"))
 				await this.finishSubTask(t("common:tasks.deleted"))
@@ -1485,6 +1530,7 @@ export class ClineProvider
 		const taskHistory = this.getGlobalState("taskHistory") ?? []
 		const taskHistory = this.getGlobalState("taskHistory") ?? []
 		const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
 		const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
 		await this.updateGlobalState("taskHistory", updatedTaskHistory)
+		this.recentTasksCache = undefined
 		await this.postStateToWebview()
 		await this.postStateToWebview()
 	}
 	}
 
 
@@ -1726,10 +1772,10 @@ export class ClineProvider
 			autoCondenseContext: autoCondenseContext ?? true,
 			autoCondenseContext: autoCondenseContext ?? true,
 			autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
 			autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
 			uriScheme: vscode.env.uriScheme,
 			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,
 				: undefined,
-			clineMessages: this.getCurrentCline()?.clineMessages || [],
+			clineMessages: this.getCurrentTask()?.clineMessages || [],
 			taskHistory: (taskHistory || [])
 			taskHistory: (taskHistory || [])
 				.filter((item: HistoryItem) => item.ts && item.task)
 				.filter((item: HistoryItem) => item.ts && item.task)
 				.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
 				.sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
@@ -2025,6 +2071,8 @@ export class ClineProvider
 		}
 		}
 
 
 		await this.updateGlobalState("taskHistory", history)
 		await this.updateGlobalState("taskHistory", history)
+		this.recentTasksCache = undefined
+
 		return history
 		return history
 	}
 	}
 
 
@@ -2090,17 +2138,20 @@ export class ClineProvider
 		console.log(message)
 		console.log(message)
 	}
 	}
 
 
-	// integration tests
+	// getters
+
+	public get workspaceTracker(): WorkspaceTracker | undefined {
+		return this._workspaceTracker
+	}
 
 
 	get viewLaunched() {
 	get viewLaunched() {
 		return this.isViewLaunched
 		return this.isViewLaunched
 	}
 	}
 
 
 	get messages() {
 	get messages() {
-		return this.getCurrentCline()?.clineMessages || []
+		return this.getCurrentTask()?.clineMessages || []
 	}
 	}
 
 
-	// Add public getter
 	public getMcpHub(): McpHub | undefined {
 	public getMcpHub(): McpHub | undefined {
 		return this.mcpHub
 		return this.mcpHub
 	}
 	}
@@ -2143,7 +2194,7 @@ export class ClineProvider
 		)
 		)
 
 
 		if (isRemoteControlEnabled(userInfo, enabled)) {
 		if (isRemoteControlEnabled(userInfo, enabled)) {
-			const currentTask = this.getCurrentCline()
+			const currentTask = this.getCurrentTask()
 
 
 			if (currentTask && !currentTask.bridgeService) {
 			if (currentTask && !currentTask.bridgeService) {
 				try {
 				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
 		let cloudIsAuthenticated: boolean | undefined
 
 
 		try {
 		try {
@@ -2195,14 +2257,19 @@ export class ClineProvider
 				cloudIsAuthenticated = CloudService.instance.isAuthenticated()
 				cloudIsAuthenticated = CloudService.instance.isAuthenticated()
 			}
 			}
 		} catch (error) {
 		} 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}`)
 			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
 		const todoList = task?.todoList
 		let todos: { total: number; completed: number; inProgress: number; pending: number } | undefined
 		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 {
 		return {
-			appName: packageJSON?.name ?? Package.name,
-			appVersion: packageJSON?.version ?? Package.version,
-			vscodeVersion: vscode.version,
-			platform: process.platform,
-			editorName: vscode.env.appName,
 			language,
 			language,
 			mode,
 			mode,
+			taskId: task?.taskId,
 			apiProvider: apiConfiguration?.apiProvider,
 			apiProvider: apiConfiguration?.apiProvider,
 			modelId: task?.api?.getModel().id,
 			modelId: task?.api?.getModel().id,
 			diffStrategy: task?.diffStrategy?.getName(),
 			diffStrategy: task?.diffStrategy?.getName(),
 			isSubtask: task ? !!task.parentTask : undefined,
 			isSubtask: task ? !!task.parentTask : undefined,
-			cloudIsAuthenticated,
 			...(todos && { todos }),
 			...(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)
 		await provider.addClineToStack(mockCline)
 
 
 		// get the stack size before the abort call
 		// 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
 		// call the removeClineFromStack method so it will call the current cline abort and remove it from the stack
 		await provider.removeClineFromStack()
 		await provider.removeClineFromStack()
 
 
 		// get the stack size after the abort call
 		// get the stack size after the abort call
-		const stackSizeAfterAbort = provider.getClineStackSize()
+		const stackSizeAfterAbort = provider.getTaskStackSize()
 
 
 		// check if the abort method was called
 		// check if the abort method was called
 		expect(mockCline.abortTask).toHaveBeenCalled()
 		expect(mockCline.abortTask).toHaveBeenCalled()
@@ -692,7 +692,7 @@ describe("ClineProvider", () => {
 			await provider.addClineToStack(mockCline)
 			await provider.addClineToStack(mockCline)
 
 
 			// Verify stack size is 1
 			// Verify stack size is 1
-			expect(provider.getClineStackSize()).toBe(1)
+			expect(provider.getTaskStackSize()).toBe(1)
 
 
 			// Get the message handler
 			// Get the message handler
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
@@ -718,10 +718,10 @@ describe("ClineProvider", () => {
 		await provider.addClineToStack(mockCline2)
 		await provider.addClineToStack(mockCline2)
 
 
 		// verify cline instances were added to the stack
 		// 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
 		// 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 () => {
 	test("getState returns correct initial state", async () => {
@@ -1205,8 +1205,8 @@ describe("ClineProvider", () => {
 				historyItem: { id: "test-task-id" },
 				historyItem: { id: "test-task-id" },
 			})
 			})
 
 
-			// Mock initClineWithHistoryItem
-			;(provider as any).initClineWithHistoryItem = vi.fn()
+			// Mock createTaskWithHistoryItem
+			;(provider as any).createTaskWithHistoryItem = vi.fn()
 
 
 			// Trigger message deletion
 			// Trigger message deletion
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
 			const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
@@ -1230,8 +1230,8 @@ describe("ClineProvider", () => {
 				mockApiHistory[1],
 				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 () => {
 		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 () => {
 		test("validates and falls back to default mode when restored mode no longer exists", async () => {
 			await provider.resolveWebviewView(mockWebviewView)
 			await provider.resolveWebviewView(mockWebviewView)
 
 
@@ -1711,7 +1711,7 @@ describe("ClineProvider", () => {
 			}
 			}
 
 
 			// Initialize with history item
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify mode validation occurred
 			// Verify mode validation occurred
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
@@ -1780,7 +1780,7 @@ describe("ClineProvider", () => {
 			}
 			}
 
 
 			// Initialize with history item
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify mode validation occurred
 			// Verify mode validation occurred
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
 			expect(mockCustomModesManager.getCustomModes).toHaveBeenCalled()
@@ -1832,7 +1832,7 @@ describe("ClineProvider", () => {
 			}
 			}
 
 
 			// Initialize with history item
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify mode was preserved
 			// Verify mode was preserved
 			expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect")
 			expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect")
@@ -1863,7 +1863,7 @@ describe("ClineProvider", () => {
 			}
 			}
 
 
 			// Initialize with history item
 			// Initialize with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify no mode validation occurred (mode update not called)
 			// Verify no mode validation occurred (mode update not called)
 			expect(mockContext.globalState.update).not.toHaveBeenCalledWith("mode", expect.any(String))
 			expect(mockContext.globalState.update).not.toHaveBeenCalledWith("mode", expect.any(String))
@@ -1913,7 +1913,7 @@ describe("ClineProvider", () => {
 			}
 			}
 
 
 			// Initialize with history item - should not throw
 			// 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
 			// Verify error was logged but task restoration continued
 			expect(logSpy).toHaveBeenCalledWith(
 			expect(logSpy).toHaveBeenCalledWith(
@@ -3235,7 +3235,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => {
 
 
 		test("handles edit permission failures", async () => {
 		test("handles edit permission failures", async () => {
 			// Mock no current cline (simulating permission failure)
 			// 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]
 			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({
 				;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({
 					historyItem: { id: "test-task-id" },
 					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]
 				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
 				// Verify successful operation completed
 				expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
 				expect(mockCline.overwriteClineMessages).toHaveBeenCalled()
-				expect(provider.initClineWithHistoryItem).toHaveBeenCalled()
+				expect(provider.createTaskWithHistoryItem).toHaveBeenCalled()
 				expect(vscode.window.showErrorMessage).not.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 () => {
 		it("should restore mode from history item when reopening task", async () => {
 			await provider.resolveWebviewView(mockWebviewView)
 			await provider.resolveWebviewView(mockWebviewView)
 
 
@@ -437,7 +437,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const updateGlobalStateSpy = vi.spyOn(provider as any, "updateGlobalState").mockResolvedValue(undefined)
 			const updateGlobalStateSpy = vi.spyOn(provider as any, "updateGlobalState").mockResolvedValue(undefined)
 
 
 			// Initialize task with history item
 			// Initialize task with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify mode was restored via updateGlobalState
 			// Verify mode was restored via updateGlobalState
 			expect(updateGlobalStateSpy).toHaveBeenCalledWith("mode", "architect")
 			expect(updateGlobalStateSpy).toHaveBeenCalledWith("mode", "architect")
@@ -479,7 +479,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 
 			// Initialize task with history item
 			// Initialize task with history item
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify mode was not changed (should use current mode)
 			// Verify mode was not changed (should use current mode)
 			expect(handleModeSwitchSpy).not.toHaveBeenCalled()
 			expect(handleModeSwitchSpy).not.toHaveBeenCalled()
@@ -606,15 +606,15 @@ describe("ClineProvider - Sticky Mode", () => {
 			// Initialize subtask with parent's mode
 			// Initialize subtask with parent's mode
 			taskModes[subtaskId] = "architect"
 			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
 			// Add subtask to stack
 			await provider.addClineToStack(subtask)
 			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
 			// Switch subtask to code mode - this should only affect the subtask
 			await provider.handleModeSwitch("code")
 			await provider.handleModeSwitch("code")
@@ -678,7 +678,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 
 			// Initialize task with history item - should not throw
 			// 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
 			// Verify mode switch was not called with null
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith(null)
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith(null)
@@ -719,7 +719,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			}
 			}
 
 
 			// Restore the task from history
 			// Restore the task from history
-			await provider.initClineWithHistoryItem(historyItem)
+			await provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Verify that the mode was restored
 			// Verify that the mode was restored
 			const state = await provider.getState()
 			const state = await provider.getState()
@@ -764,7 +764,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 			const handleModeSwitchSpy = vi.spyOn(provider, "handleModeSwitch").mockResolvedValue()
 
 
 			// Initialize task with history item - should not throw
 			// 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
 			// Verify mode switch was not called with deleted mode
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith("deleted-mode")
 			expect(handleModeSwitchSpy).not.toHaveBeenCalledWith("deleted-mode")
@@ -1105,17 +1105,17 @@ describe("ClineProvider - Sticky Mode", () => {
 				.spyOn(provider, "updateTaskHistory")
 				.spyOn(provider, "updateTaskHistory")
 				.mockImplementation(() => Promise.resolve([]))
 				.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
 			// Simulate simultaneous mode switches for different tasks
-			getCurrentClineSpy.mockReturnValue(task1 as any)
+			getCurrentTaskSpy.mockReturnValue(task1 as any)
 			const switch1 = provider.handleModeSwitch("architect")
 			const switch1 = provider.handleModeSwitch("architect")
 
 
-			getCurrentClineSpy.mockReturnValue(task2 as any)
+			getCurrentTaskSpy.mockReturnValue(task2 as any)
 			const switch2 = provider.handleModeSwitch("debug")
 			const switch2 = provider.handleModeSwitch("debug")
 
 
-			getCurrentClineSpy.mockReturnValue(task3 as any)
+			getCurrentTaskSpy.mockReturnValue(task3 as any)
 			const switch3 = provider.handleModeSwitch("code")
 			const switch3 = provider.handleModeSwitch("code")
 
 
 			await Promise.all([switch1, switch2, switch3])
 			await Promise.all([switch1, switch2, switch3])
@@ -1166,7 +1166,7 @@ describe("ClineProvider - Sticky Mode", () => {
 			vi.clearAllMocks()
 			vi.clearAllMocks()
 
 
 			// Start initialization
 			// Start initialization
-			const initPromise = provider.initClineWithHistoryItem(historyItem)
+			const initPromise = provider.createTaskWithHistoryItem(historyItem)
 
 
 			// Try to switch mode during initialization
 			// Try to switch mode during initialization
 			await provider.handleModeSwitch("code")
 			await provider.handleModeSwitch("code")
@@ -1201,13 +1201,13 @@ describe("ClineProvider - Sticky Mode", () => {
 				await provider.addClineToStack(task as any)
 				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
 			// Rapidly switch between tasks and modes
 			const switches: Promise<void>[] = []
 			const switches: Promise<void>[] = []
 			tasks.forEach((task, index) => {
 			tasks.forEach((task, index) => {
-				getCurrentClineSpy.mockReturnValue(task as any)
+				getCurrentTaskSpy.mockReturnValue(task as any)
 				const mode = ["architect", "debug", "code"][index % 3]
 				const mode = ["architect", "debug", "code"][index % 3]
 				switches.push(provider.handleModeSwitch(mode as any))
 				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(),
 	log: vi.fn(),
 	postStateToWebview: vi.fn(),
 	postStateToWebview: vi.fn(),
-	getCurrentCline: vi.fn(),
+	getCurrentTask: vi.fn(),
 	getTaskWithId: vi.fn(),
 	getTaskWithId: vi.fn(),
-	initClineWithHistoryItem: vi.fn(),
+	createTaskWithHistoryItem: vi.fn(),
 } as unknown as ClineProvider
 } as unknown as ClineProvider
 
 
 import { t } from "../../../i18n"
 import { t } from "../../../i18n"
@@ -533,7 +533,7 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 	beforeEach(() => {
 	beforeEach(() => {
 		vi.clearAllMocks()
 		vi.clearAllMocks()
 		// Mock a current Cline instance
 		// Mock a current Cline instance
-		vi.mocked(mockClineProvider.getCurrentCline).mockReturnValue({
+		vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({
 			taskId: "test-task-id",
 			taskId: "test-task-id",
 			apiConversationHistory: [],
 			apiConversationHistory: [],
 			clineMessages: [],
 			clineMessages: [],
@@ -544,7 +544,7 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 
 
 	describe("deleteMessage", () => {
 	describe("deleteMessage", () => {
 		it("should always show dialog for delete confirmation", async () => {
 		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, {
 			await webviewMessageHandler(mockClineProvider, {
 				type: "deleteMessage",
 				type: "deleteMessage",
@@ -560,12 +560,12 @@ describe("webviewMessageHandler - message dialog preferences", () => {
 
 
 	describe("submitEditedMessage", () => {
 	describe("submitEditedMessage", () => {
 		it("should always show dialog for edit confirmation", async () => {
 		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, {
 			await webviewMessageHandler(mockClineProvider, {
 				type: "submitEditedMessage",
 				type: "submitEditedMessage",
-				value: 123456789, // messageTs as number
-				editedMessageContent: "edited content", // text content in editedMessageContent field
+				value: 123456789,
+				editedMessageContent: "edited content",
 			})
 			})
 
 
 			expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({
 			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 mode = message.mode ?? defaultModeSlug
 	const customModes = await provider.customModesManager.getCustomModes()
 	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
 	// Determine if browser tools can be used based on model support, mode, and user settings
 	let modelSupportsComputerUse = false
 	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
 	 * Handles confirmed message deletion from webview dialog
 	 */
 	 */
 	const handleDeleteMessageConfirm = async (messageTs: number): Promise<void> => {
 	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)
 			const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
 
 
 			if (messageIndex !== -1) {
 			if (messageIndex !== -1) {
@@ -124,7 +124,7 @@ export const webviewMessageHandler = async (
 					await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
 					await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex)
 
 
 					// Initialize with history item after deletion
 					// Initialize with history item after deletion
-					await provider.initClineWithHistoryItem(historyItem)
+					await provider.createTaskWithHistoryItem(historyItem)
 				} catch (error) {
 				} catch (error) {
 					console.error("Error in delete message:", error)
 					console.error("Error in delete message:", error)
 					vscode.window.showErrorMessage(
 					vscode.window.showErrorMessage(
@@ -156,9 +156,9 @@ export const webviewMessageHandler = async (
 		editedContent: string,
 		editedContent: string,
 		images?: string[],
 		images?: string[],
 	): Promise<void> => {
 	): 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
 			// Use findMessageIndices to find messages based on timestamp
 			const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
 			const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline)
@@ -288,7 +288,7 @@ export const webviewMessageHandler = async (
 			// Initializing new instance of Cline will make sure that any
 			// Initializing new instance of Cline will make sure that any
 			// agentically running promises in old instance don't affect our new
 			// agentically running promises in old instance don't affect our new
 			// task. This essentially creates a fresh slate for the new task.
 			// 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
 			break
 		case "customInstructions":
 		case "customInstructions":
 			await provider.updateCustomInstructions(message.text)
 			await provider.updateCustomInstructions(message.text)
@@ -346,7 +346,7 @@ export const webviewMessageHandler = async (
 			await provider.postStateToWebview()
 			await provider.postStateToWebview()
 			break
 			break
 		case "askResponse":
 		case "askResponse":
-			provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
+			provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
 			break
 			break
 		case "autoCondenseContext":
 		case "autoCondenseContext":
 			await updateGlobalState("autoCondenseContext", message.bool)
 			await updateGlobalState("autoCondenseContext", message.bool)
@@ -358,19 +358,23 @@ export const webviewMessageHandler = async (
 			break
 			break
 		case "terminalOperation":
 		case "terminalOperation":
 			if (message.terminalOperation) {
 			if (message.terminalOperation) {
-				provider.getCurrentCline()?.handleTerminalOperation(message.terminalOperation)
+				provider.getCurrentTask()?.handleTerminalOperation(message.terminalOperation)
 			}
 			}
 			break
 			break
 		case "clearTask":
 		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) {
 			if (currentTask && currentTask.parentTask) {
 				await provider.finishSubTask(t("common:tasks.canceled"))
 				await provider.finishSubTask(t("common:tasks.canceled"))
 			} else {
 			} else {
 				// Regular task - just clear it
 				// Regular task - just clear it
 				await provider.clearTask()
 				await provider.clearTask()
 			}
 			}
+
 			await provider.postStateToWebview()
 			await provider.postStateToWebview()
 			break
 			break
 		case "didShowAnnouncement":
 		case "didShowAnnouncement":
@@ -387,14 +391,15 @@ export const webviewMessageHandler = async (
 			})
 			})
 			break
 			break
 		case "exportCurrentTask":
 		case "exportCurrentTask":
-			const currentTaskId = provider.getCurrentCline()?.taskId
+			const currentTaskId = provider.getCurrentTask()?.taskId
 			if (currentTaskId) {
 			if (currentTaskId) {
 				provider.exportTaskWithId(currentTaskId)
 				provider.exportTaskWithId(currentTaskId)
 			}
 			}
 			break
 			break
 		case "shareCurrentTask":
 		case "shareCurrentTask":
-			const shareTaskId = provider.getCurrentCline()?.taskId
-			const clineMessages = provider.getCurrentCline()?.clineMessages
+			const shareTaskId = provider.getCurrentTask()?.taskId
+			const clineMessages = provider.getCurrentTask()?.clineMessages
+
 			if (!shareTaskId) {
 			if (!shareTaskId) {
 				vscode.window.showErrorMessage(t("common:errors.share_no_active_task"))
 				vscode.window.showErrorMessage(t("common:errors.share_no_active_task"))
 				break
 				break
@@ -725,7 +730,7 @@ export const webviewMessageHandler = async (
 			const result = checkoutDiffPayloadSchema.safeParse(message.payload)
 			const result = checkoutDiffPayloadSchema.safeParse(message.payload)
 
 
 			if (result.success) {
 			if (result.success) {
-				await provider.getCurrentCline()?.checkpointDiff(result.data)
+				await provider.getCurrentTask()?.checkpointDiff(result.data)
 			}
 			}
 
 
 			break
 			break
@@ -736,13 +741,13 @@ export const webviewMessageHandler = async (
 				await provider.cancelTask()
 				await provider.cancelTask()
 
 
 				try {
 				try {
-					await pWaitFor(() => provider.getCurrentCline()?.isInitialized === true, { timeout: 3_000 })
+					await pWaitFor(() => provider.getCurrentTask()?.isInitialized === true, { timeout: 3_000 })
 				} catch (error) {
 				} catch (error) {
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
 				}
 				}
 
 
 				try {
 				try {
-					await provider.getCurrentCline()?.checkpointRestore(result.data)
+					await provider.getCurrentTask()?.checkpointRestore(result.data)
 				} catch (error) {
 				} catch (error) {
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
 					vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
 				}
 				}
@@ -1227,14 +1232,14 @@ export const webviewMessageHandler = async (
 			}
 			}
 			break
 			break
 		case "deleteMessage": {
 		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")
 				await handleMessageModificationsOperation(message.value, "delete")
 			}
 			}
 			break
 			break
 		}
 		}
 		case "submitEditedMessage": {
 		case "submitEditedMessage": {
 			if (
 			if (
-				provider.getCurrentCline() &&
+				provider.getCurrentTask() &&
 				typeof message.value === "number" &&
 				typeof message.value === "number" &&
 				message.value &&
 				message.value &&
 				message.editedMessageContent
 				message.editedMessageContent
@@ -1366,6 +1371,7 @@ export const webviewMessageHandler = async (
 			if (message.text) {
 			if (message.text) {
 				try {
 				try {
 					const state = await provider.getState()
 					const state = await provider.getState()
+
 					const {
 					const {
 						apiConfiguration,
 						apiConfiguration,
 						customSupportPrompts,
 						customSupportPrompts,
@@ -1374,7 +1380,8 @@ export const webviewMessageHandler = async (
 						includeTaskHistoryInEnhance,
 						includeTaskHistoryInEnhance,
 					} = state
 					} = state
 
 
-					const currentCline = provider.getCurrentCline()
+					const currentCline = provider.getCurrentTask()
+
 					const result = await MessageEnhancer.enhanceMessage({
 					const result = await MessageEnhancer.enhanceMessage({
 						text: message.text,
 						text: message.text,
 						apiConfiguration,
 						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: "action", action: "chatButtonClicked" })
 		await provider.postMessageToWebview({ type: "invoke", invoke: "newChat", text, images })
 		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,
 			consecutiveMistakeLimit: Number.MAX_SAFE_INTEGER,
 		})
 		})
 
 
@@ -161,7 +161,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
 
 
 	public async resumeTask(taskId: string): Promise<void> {
 	public async resumeTask(taskId: string): Promise<void> {
 		const { historyItem } = await this.sidebarProvider.getTaskWithId(taskId)
 		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" })
 		await this.sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
 	}
 	}
 
 

+ 1 - 1
src/package.json

@@ -427,7 +427,7 @@
 		"@mistralai/mistralai": "^1.3.6",
 		"@mistralai/mistralai": "^1.3.6",
 		"@modelcontextprotocol/sdk": "^1.9.0",
 		"@modelcontextprotocol/sdk": "^1.9.0",
 		"@qdrant/js-client-rest": "^1.14.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/ipc": "workspace:^",
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/types": "workspace:^",
 		"@roo-code/types": "workspace:^",

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

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

+ 1 - 1
src/utils/git.ts

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