Skip to content

Commit 7b416cc

Browse files
authored
Feat: Task Favorites ⭐️ (RooCodeInc#3392)
* Task Favorites * Task management docs
1 parent 29f3cfa commit 7b416cc

File tree

15 files changed

+1555
-48
lines changed

15 files changed

+1555
-48
lines changed

.changeset/selfish-garlics-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Add Task Favorites and several proto messages related to tasks

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"getting-started/installing-dev-essentials",
6363
"getting-started/model-selection-guide",
6464
"getting-started/our-favorite-tech-stack",
65+
"getting-started/task-management",
6566
"getting-started/understanding-context-management",
6667
"getting-started/what-is-cline"
6768
]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: "Task Management in Cline"
3+
description: "Learn how to effectively manage your task history, use favorites, and organize your work in Cline."
4+
---
5+
6+
# Task Management
7+
8+
As you use Cline, you'll accumulate many tasks over time. The task management system helps you organize, filter, search, and clean up your task history to keep your workspace efficient.
9+
10+
## Accessing Task History
11+
12+
You can access your task history by:
13+
14+
1. Clicking on the "History" button in the Cline sidebar
15+
2. Using the command palette to search for "Cline: Show Task History"
16+
17+
## Task History Features
18+
19+
The task history view provides several powerful features:
20+
21+
### Searching and Filtering
22+
23+
- **Search Bar**: Use the fuzzy search at the top to quickly find tasks by content
24+
- **Sort Options**: Sort tasks by:
25+
- Newest (default)
26+
- Oldest
27+
- Most Expensive (highest API cost)
28+
- Most Tokens (highest token usage)
29+
- Most Relevant (when searching)
30+
- **Favorites Filter**: Toggle to show only favorited tasks
31+
32+
### Task Actions
33+
34+
Each task in the history view has several actions available:
35+
36+
- **Open**: Click on a task to reopen it in the Cline chat
37+
- **Favorite**: Click the star icon to mark a task as a favorite
38+
- **Delete**: Remove individual tasks (favorites are protected from deletion)
39+
- **Export**: Export a task's conversation to markdown
40+
41+
## ⭐ Task Favorites
42+
43+
The favorites feature allows you to mark important tasks that you want to preserve and find quickly.
44+
45+
### How Favorites Work
46+
47+
- **Marking Favorites**: Click the star icon next to any task to toggle its favorite status
48+
- **Protection**: Favorited tasks are protected from individual and bulk deletion operations (can be overridden)
49+
- **Filtering**: Use the favorites filter to quickly access your important tasks
50+
51+
## Batch Operations
52+
53+
The task history view supports several batch operations:
54+
55+
- **Select Multiple**: Use the checkboxes to select multiple tasks
56+
- **Select All/None**: Quickly select or deselect all tasks
57+
- **Delete Selected**: Remove all selected tasks
58+
- **Delete All**: Remove all tasks from history (favorites are preserved unless you choose to include them)
59+
60+
## Best Practices
61+
62+
1. **Favorite Important Tasks**: Mark reference tasks or frequently accessed conversations as favorites
63+
2. **Regular Cleanup**: Periodically remove old or unused tasks to improve performance
64+
3. **Use Search**: Leverage the fuzzy search to quickly find specific conversations
65+
4. **Export Valuable Tasks**: Export important tasks to markdown for external reference
66+
67+
Task management helps you maintain an organized workflow when using Cline, allowing you to quickly find past conversations, preserve important work, and keep your history clean and efficient.

proto/task.proto

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ service TaskService {
1616
// Creates a new task with the given text and optional images
1717
rpc newTask(NewTaskRequest) returns (Empty);
1818
// Shows a task with the specified ID
19-
rpc showTaskWithId(StringRequest) returns (Empty);
19+
rpc showTaskWithId(StringRequest) returns (TaskResponse);
2020
// Exports a task with the given ID to markdown
2121
rpc exportTaskWithId(StringRequest) returns (Empty);
22+
// Toggles the favorite status of a task
23+
rpc toggleTaskFavorite(TaskFavoriteRequest) returns (Empty);
24+
// Deletes all non-favorited tasks
25+
rpc deleteNonFavoritedTasks(EmptyRequest) returns (DeleteNonFavoritedTasksResults);
26+
// Gets filtered task history
27+
rpc getTaskHistory(GetTaskHistoryRequest) returns (TaskHistoryArray);
2228
}
2329

2430
// Request message for creating a new task
@@ -27,3 +33,58 @@ message NewTaskRequest {
2733
string text = 2;
2834
repeated string images = 3;
2935
}
36+
37+
// Request message for toggling task favorite status
38+
message TaskFavoriteRequest {
39+
Metadata metadata = 1;
40+
string task_id = 2;
41+
bool is_favorited = 3;
42+
}
43+
44+
// Response for task details
45+
message TaskResponse {
46+
string id = 1;
47+
string task = 2;
48+
int64 ts = 3;
49+
bool is_favorited = 4;
50+
int64 size = 5;
51+
double total_cost = 6;
52+
int32 tokens_in = 7;
53+
int32 tokens_out = 8;
54+
int32 cache_writes = 9;
55+
int32 cache_reads = 10;
56+
}
57+
58+
// Results returned when deleting non-favorited tasks
59+
message DeleteNonFavoritedTasksResults {
60+
int32 tasks_preserved = 1;
61+
int32 tasks_deleted = 2;
62+
}
63+
64+
// Request for getting task history with filtering
65+
message GetTaskHistoryRequest {
66+
Metadata metadata = 1;
67+
bool favorites_only = 2;
68+
string search_query = 3;
69+
string sort_by = 4;
70+
}
71+
72+
// Response for task history
73+
message TaskHistoryArray {
74+
repeated TaskItem tasks = 1;
75+
int32 total_count = 2;
76+
}
77+
78+
// Task item details for history list
79+
message TaskItem {
80+
string id = 1;
81+
string task = 2;
82+
int64 ts = 3;
83+
bool is_favorited = 4;
84+
int64 size = 5;
85+
double total_cost = 6;
86+
int32 tokens_in = 7;
87+
int32 tokens_out = 8;
88+
int32 cache_writes = 9;
89+
int32 cache_reads = 10;
90+
}

src/core/controller/index.ts

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,11 +597,18 @@ export class Controller {
597597
}
598598
case "clearAllTaskHistory": {
599599
const answer = await vscode.window.showWarningMessage(
600-
"Are you sure you want to delete all history?",
601-
"Delete",
600+
"What would you like to delete?",
601+
{ modal: true },
602+
"Delete All Except Favorites",
603+
"Delete Everything",
602604
"Cancel",
603605
)
604-
if (answer === "Delete") {
606+
607+
if (answer === "Delete All Except Favorites") {
608+
await this.deleteNonFavoriteTaskHistory()
609+
await this.postStateToWebview()
610+
this.refreshTotalTasksSize()
611+
} else if (answer === "Delete Everything") {
605612
await this.deleteAllTaskHistory()
606613
await this.postStateToWebview()
607614
this.refreshTotalTasksSize()
@@ -1538,6 +1545,43 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
15381545
// await this.postStateToWebview()
15391546
}
15401547

1548+
async deleteNonFavoriteTaskHistory() {
1549+
await this.clearTask()
1550+
1551+
const taskHistory = ((await getGlobalState(this.context, "taskHistory")) as HistoryItem[]) || []
1552+
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
1553+
1554+
// If user has no favorited tasks, show a warning message
1555+
if (favoritedTasks.length === 0) {
1556+
vscode.window.showWarningMessage("No favorited tasks found. Please favorite tasks before using this option.")
1557+
await this.postStateToWebview()
1558+
return
1559+
}
1560+
1561+
await updateGlobalState(this.context, "taskHistory", favoritedTasks)
1562+
1563+
// Delete non-favorited task directories
1564+
try {
1565+
const preserveTaskIds = favoritedTasks.map((task) => task.id)
1566+
const taskDirPath = path.join(this.context.globalStorageUri.fsPath, "tasks")
1567+
1568+
if (await fileExistsAtPath(taskDirPath)) {
1569+
const taskDirs = await fs.readdir(taskDirPath)
1570+
for (const taskDir of taskDirs) {
1571+
if (!preserveTaskIds.includes(taskDir)) {
1572+
await fs.rm(path.join(taskDirPath, taskDir), { recursive: true, force: true })
1573+
}
1574+
}
1575+
}
1576+
} catch (error) {
1577+
vscode.window.showErrorMessage(
1578+
`Error deleting task history: ${error instanceof Error ? error.message : String(error)}`,
1579+
)
1580+
}
1581+
1582+
await this.postStateToWebview()
1583+
}
1584+
15411585
async refreshTotalTasksSize() {
15421586
getTotalTasksSize(this.context.globalStorageUri.fsPath)
15431587
.then((newTotalSize) => {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import path from "path"
2+
import fs from "fs/promises"
3+
import { Controller } from ".."
4+
import { EmptyRequest } from "../../../shared/proto/common"
5+
import { DeleteNonFavoritedTasksResults } from "../../../shared/proto/task"
6+
import { getGlobalState, updateGlobalState } from "../../storage/state"
7+
import { fileExistsAtPath } from "../../../utils/fs"
8+
9+
/**
10+
* Deletes all non-favorited tasks, preserving only favorited ones
11+
* @param controller The controller instance
12+
* @param request Empty request
13+
* @returns DeleteNonFavoritedTasksResults with counts of preserved and deleted tasks
14+
*/
15+
export async function deleteNonFavoritedTasks(
16+
controller: Controller,
17+
_request: EmptyRequest,
18+
): Promise<DeleteNonFavoritedTasksResults> {
19+
try {
20+
// Clear current task first
21+
await controller.clearTask()
22+
23+
// Get existing task history
24+
const taskHistory = ((await getGlobalState(controller.context, "taskHistory")) as any[]) || []
25+
26+
// Filter out non-favorited tasks
27+
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
28+
const deletedCount = taskHistory.length - favoritedTasks.length
29+
30+
console.log(`[deleteNonFavoritedTasks] Found ${favoritedTasks.length} favorited tasks to preserve`)
31+
32+
// Update global state
33+
if (favoritedTasks.length > 0) {
34+
await updateGlobalState(controller.context, "taskHistory", favoritedTasks)
35+
} else {
36+
await updateGlobalState(controller.context, "taskHistory", undefined)
37+
}
38+
39+
// Handle file system cleanup for deleted tasks
40+
const preserveTaskIds = favoritedTasks.map((task) => task.id)
41+
await cleanupTaskFiles(controller, preserveTaskIds)
42+
43+
// Update webview
44+
try {
45+
await controller.postStateToWebview()
46+
} catch (webviewErr) {
47+
console.error("Error posting to webview:", webviewErr)
48+
}
49+
50+
return {
51+
tasksPreserved: favoritedTasks.length,
52+
tasksDeleted: deletedCount,
53+
}
54+
} catch (error) {
55+
console.error("Error in deleteNonFavoritedTasks:", error)
56+
throw error
57+
}
58+
}
59+
60+
/**
61+
* Helper function to cleanup task files while preserving specified tasks
62+
*/
63+
async function cleanupTaskFiles(controller: Controller, preserveTaskIds: string[]) {
64+
const taskDirPath = path.join(controller.context.globalStorageUri.fsPath, "tasks")
65+
66+
try {
67+
if (await fileExistsAtPath(taskDirPath)) {
68+
if (preserveTaskIds.length > 0) {
69+
const taskDirs = await fs.readdir(taskDirPath)
70+
console.debug(`[cleanupTaskFiles] Found ${taskDirs.length} task directories`)
71+
72+
// Delete only non-preserved task directories
73+
for (const dir of taskDirs) {
74+
if (!preserveTaskIds.includes(dir)) {
75+
await fs.rm(path.join(taskDirPath, dir), { recursive: true, force: true })
76+
}
77+
}
78+
} else {
79+
// No tasks to preserve, delete everything
80+
await fs.rm(taskDirPath, { recursive: true, force: true })
81+
}
82+
}
83+
} catch (error) {
84+
console.error("Error cleaning up task files:", error)
85+
}
86+
87+
return true
88+
}

0 commit comments

Comments
 (0)