|
|
@@ -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()),
|
|
|
}
|
|
|
}
|
|
|
|