Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),

// Task history auto-cleanup settings
taskHistoryAutoCleanupEnabled: z.boolean().optional(),
taskHistoryMaxCount: z.number().min(0).optional(),
taskHistoryMaxDiskSpaceMB: z.number().min(0).optional(),
taskHistoryMaxAgeDays: z.number().min(0).optional(),
})

export type GlobalSettings = z.infer<typeof globalSettingsSchema>
Expand Down
89 changes: 89 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckp
import { CodeIndexManager } from "../../services/code-index/manager"
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
import { MdmService } from "../../services/mdm/MdmService"
import {
TaskHistoryCleanupService,
type TaskHistoryCleanupConfig,
} from "../../services/task-cleanup/TaskHistoryCleanupService"

import { fileExistsAtPath } from "../../utils/fs"
import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
Expand Down Expand Up @@ -138,6 +142,7 @@ export class ClineProvider
private recentTasksCache?: string[]
private pendingOperations: Map<string, PendingEditOperation> = new Map()
private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds
private taskHistoryCleanupService?: TaskHistoryCleanupService

public isViewLaunched = false
public settingsImportedAt?: number
Expand Down Expand Up @@ -253,6 +258,15 @@ export class ClineProvider
} else {
this.log("CloudService not ready, deferring cloud profile sync")
}

// Initialize task history cleanup service
// Only initialize if globalStorageUri is available (not in test environment)
if (this.context.globalStorageUri) {
this.taskHistoryCleanupService = new TaskHistoryCleanupService(
this.context.globalStorageUri.fsPath,
this.log.bind(this),
)
}
}

/**
Expand Down Expand Up @@ -1783,6 +1797,10 @@ export class ClineProvider
openRouterImageGenerationSelectedModel,
openRouterUseMiddleOutTransform,
featureRoomoteControlEnabled,
taskHistoryAutoCleanupEnabled,
taskHistoryMaxCount,
taskHistoryMaxDiskSpaceMB,
taskHistoryMaxAgeDays,
} = await this.getState()

const telemetryKey = process.env.POSTHOG_API_KEY
Expand Down Expand Up @@ -1920,6 +1938,10 @@ export class ClineProvider
openRouterImageGenerationSelectedModel,
openRouterUseMiddleOutTransform,
featureRoomoteControlEnabled,
taskHistoryAutoCleanupEnabled: taskHistoryAutoCleanupEnabled ?? false,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default values here (100 tasks, 1000MB, 7 days) might be too aggressive for some users. Have we considered:

  1. More conservative defaults (e.g., 200 tasks, 2000MB, 14 days)?
  2. Making these workspace-specific rather than global?
  3. Adding a first-run prompt to let users configure their preferences?

This could prevent accidental data loss for users who don't realize cleanup is happening.

taskHistoryMaxCount: taskHistoryMaxCount ?? 100,
taskHistoryMaxDiskSpaceMB: taskHistoryMaxDiskSpaceMB ?? 1000,
taskHistoryMaxAgeDays: taskHistoryMaxAgeDays ?? 7,
}
}

Expand Down Expand Up @@ -2166,9 +2188,76 @@ export class ClineProvider
await this.updateGlobalState("taskHistory", history)
this.recentTasksCache = undefined

// Trigger auto-cleanup after updating task history
void this.triggerAutoCleanup()

return history
}

/**
* Triggers automatic cleanup of task history if configured
*/
private async triggerAutoCleanup(): Promise<void> {
// Skip if cleanup service is not initialized (e.g., in test environment)
if (!this.taskHistoryCleanupService) {
return
}

try {
const {
taskHistoryAutoCleanupEnabled,
taskHistoryMaxCount,
taskHistoryMaxDiskSpaceMB,
taskHistoryMaxAgeDays,
taskHistory,
} = await this.getState()

const config: TaskHistoryCleanupConfig = {
enabled: taskHistoryAutoCleanupEnabled ?? false,
maxCount: taskHistoryMaxCount,
maxDiskSpaceMB: taskHistoryMaxDiskSpaceMB,
maxAgeDays: taskHistoryMaxAgeDays,
}

// Check if cleanup should be triggered
if (this.taskHistoryCleanupService.shouldTriggerCleanup(taskHistory ?? [], config)) {
// Run cleanup in the background during idle time
setTimeout(async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? Using a 5-second setTimeout could cause race conditions if multiple tasks complete in quick succession. Each task update would schedule its own cleanup, potentially leading to multiple concurrent cleanup operations.

Consider using a debounced queue or mutex pattern to ensure only one cleanup runs at a time.

try {
const result = await this.taskHistoryCleanupService!.performCleanup(
taskHistory ?? [],
config,
async (updatedHistory) => {
await this.updateGlobalState("taskHistory", updatedHistory)
this.recentTasksCache = undefined
await this.postStateToWebview()
},
this.deleteTaskWithId.bind(this),
)

if (result.deletedCount > 0) {
this.log(
`[ClineProvider] Auto-cleanup completed: deleted ${result.deletedCount} tasks, freed ${result.freedSpaceMB.toFixed(2)}MB`,
)
}

if (result.errors.length > 0) {
this.log(`[ClineProvider] Auto-cleanup errors: ${result.errors.join(", ")}`)
}
} catch (error) {
this.log(
`[ClineProvider] Auto-cleanup failed: ${error instanceof Error ? error.message : String(error)}`,
)
}
}, 5000) // Run after 5 seconds to avoid blocking current operations
}
} catch (error) {
this.log(
`[ClineProvider] Failed to trigger auto-cleanup: ${error instanceof Error ? error.message : String(error)}`,
)
}
}

// ContextProxy

// @deprecated - Use `ContextProxy#setValue` instead.
Expand Down
Loading
Loading