From 8ffeaed6f247febe59d3d37a6e6d24e421d0300f Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 13 Nov 2025 22:49:49 -0700 Subject: [PATCH 01/37] feat: task history retention purge uses provider delete path; checkpoint-only cleanup; quieter logging --- packages/types/src/global-settings.ts | 2 + packages/types/src/vscode-extension-host.ts | 1 + src/__tests__/extension.spec.ts | 12 + src/__tests__/task-history-retention.spec.ts | 141 +++++++++ src/core/webview/ClineProvider.ts | 5 + src/core/webview/webviewMessageHandler.ts | 6 + src/extension.ts | 39 ++- src/package.json | 13 + src/package.nls.json | 3 +- src/utils/task-history-retention.ts | 267 ++++++++++++++++++ webview-ui/src/components/settings/About.tsx | 49 +++- .../src/components/settings/SettingsView.tsx | 15 + webview-ui/src/i18n/locales/en/settings.json | 13 + 13 files changed, 559 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/task-history-retention.spec.ts create mode 100644 src/utils/task-history-retention.ts diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 0f75e1d6107..d1266b34321 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -181,6 +181,8 @@ export const globalSettingsSchema = z.object({ customSupportPrompts: customSupportPromptsSchema.optional(), enhancementApiConfigId: z.string().optional(), includeTaskHistoryInEnhance: z.boolean().optional(), + // Auto-delete task history on extension reload. "never" | "90" | "60" | "30" | "7" | "3" + taskHistoryRetention: z.union([z.enum(["never", "90", "60", "30", "7", "3"]), z.number()]).optional(), historyPreviewCollapsed: z.boolean().optional(), reasoningBlockCollapsed: z.boolean().optional(), /** diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 49f9687f009..9e55fab1769 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -335,6 +335,7 @@ export type ExtensionState = Pick< | "maxGitStatusFiles" | "requestDelaySeconds" | "showWorktreesInHomeScreen" + | "taskHistoryRetention" > & { version: string clineMessages: ClineMessage[] diff --git a/src/__tests__/extension.spec.ts b/src/__tests__/extension.spec.ts index 5b072672699..1d215493116 100644 --- a/src/__tests__/extension.spec.ts +++ b/src/__tests__/extension.spec.ts @@ -114,6 +114,8 @@ vi.mock("../core/config/ContextProxy", () => ({ setValue: vi.fn(), getValues: vi.fn().mockReturnValue({}), getProviderSettings: vi.fn().mockReturnValue({}), + // Needed by retention purge on activation + globalStorageUri: { fsPath: "/tmp/roo-retention-test" }, }), }, })) @@ -157,6 +159,16 @@ vi.mock("../utils/autoImportSettings", () => ({ autoImportSettings: vi.fn().mockResolvedValue(undefined), })) +// Avoid filesystem access during activation by stubbing purge +vi.mock("../utils/task-history-retention", () => ({ + purgeOldTasks: vi.fn().mockResolvedValue({ purgedCount: 0, cutoff: null }), +})) + +// Ensure storage base path resolves to provided path to avoid touching VS Code config +vi.mock("../utils/storage", () => ({ + getStorageBasePath: (p: string) => Promise.resolve(p), +})) + vi.mock("../extension/api", () => ({ API: vi.fn().mockImplementation(() => ({})), })) diff --git a/src/__tests__/task-history-retention.spec.ts b/src/__tests__/task-history-retention.spec.ts new file mode 100644 index 00000000000..3bb4b2a55a2 --- /dev/null +++ b/src/__tests__/task-history-retention.spec.ts @@ -0,0 +1,141 @@ +// npx vitest run __tests__/task-history-retention.spec.ts +import * as fs from "fs/promises" +import * as path from "path" +import * as os from "os" + +import { describe, it, expect } from "vitest" + +// Ensure purge uses the provided base path without touching VS Code config +vi.mock("../utils/storage", () => ({ + getStorageBasePath: (p: string) => Promise.resolve(p), +})) + +import { purgeOldTasks } from "../utils/task-history-retention" +import { GlobalFileNames } from "../shared/globalFileNames" + +// Helpers +async function exists(p: string): Promise { + try { + await fs.access(p) + return true + } catch { + return false + } +} + +async function mkTempBase(): Promise { + const base = await fs.mkdtemp(path.join(os.tmpdir(), "roo-retention-")) + // Ensure /tasks exists + await fs.mkdir(path.join(base, "tasks"), { recursive: true }) + return base +} + +async function createTask(base: string, id: string, ts?: number | "invalid"): Promise { + const dir = path.join(base, "tasks", id) + await fs.mkdir(dir, { recursive: true }) + const metadataPath = path.join(dir, GlobalFileNames.taskMetadata) + const metadata = ts === "invalid" ? "{ invalid json" : JSON.stringify({ ts: ts ?? Date.now() }, null, 2) + await fs.writeFile(metadataPath, metadata, "utf8") + return dir +} + +describe("utils/task-history-retention.ts purgeOldTasks()", () => { + it("purges tasks older than 7 days when retention is '7'", async () => { + const base = await mkTempBase() + try { + const now = Date.now() + const days = (n: number) => n * 24 * 60 * 60 * 1000 + + const old = await createTask(base, "task-8d", now - days(8)) + const recent = await createTask(base, "task-6d", now - days(6)) + + const { purgedCount } = await purgeOldTasks("7", base, () => {}, false) + expect(purgedCount).toBe(1) + expect(await exists(old)).toBe(false) + expect(await exists(recent)).toBe(true) + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) + + it("purges tasks older than 3 days when retention is '3'", async () => { + const base = await mkTempBase() + try { + const now = Date.now() + const days = (n: number) => n * 24 * 60 * 60 * 1000 + + const old = await createTask(base, "task-4d", now - days(4)) + const recent = await createTask(base, "task-2d", now - days(2)) + + const { purgedCount } = await purgeOldTasks("3", base, () => {}, false) + expect(purgedCount).toBe(1) + expect(await exists(old)).toBe(false) + expect(await exists(recent)).toBe(true) + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) + + it("does not delete anything in dry run mode but still reports purgedCount", async () => { + const base = await mkTempBase() + try { + const now = Date.now() + const days = (n: number) => n * 24 * 60 * 60 * 1000 + + const old = await createTask(base, "task-8d", now - days(8)) + const recent = await createTask(base, "task-6d", now - days(6)) + + const { purgedCount } = await purgeOldTasks("7", base, () => {}, true) + expect(purgedCount).toBe(1) + // In dry run, nothing is deleted + expect(await exists(old)).toBe(true) + expect(await exists(recent)).toBe(true) + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) + + it("does nothing when retention is 'never'", async () => { + const base = await mkTempBase() + try { + const now = Date.now() + const oldTs = now - 45 * 24 * 60 * 60 * 1000 // 45 days ago + const t1 = await createTask(base, "task-old", oldTs) + const t2 = await createTask(base, "task-new", now) + + const { purgedCount, cutoff } = await purgeOldTasks("never", base, () => {}) + + expect(purgedCount).toBe(0) + expect(cutoff).toBeNull() + expect(await exists(t1)).toBe(true) + expect(await exists(t2)).toBe(true) + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) + + it("purges tasks older than 30 days and keeps newer or invalid-metadata ones", async () => { + const base = await mkTempBase() + try { + const now = Date.now() + const days = (n: number) => n * 24 * 60 * 60 * 1000 + + // One older than 30 days => delete + const old = await createTask(base, "task-31d", now - days(31)) + // One newer than 30 days => keep + const recent = await createTask(base, "task-29d", now - days(29)) + // Invalid metadata => skipped (kept) + const invalid = await createTask(base, "task-invalid", "invalid") + + const { purgedCount, cutoff } = await purgeOldTasks("30", base, () => {}) + + expect(typeof cutoff).toBe("number") + expect(purgedCount).toBe(1) + expect(await exists(old)).toBe(false) + expect(await exists(recent)).toBe(true) + expect(await exists(invalid)).toBe(true) + } finally { + await fs.rm(base, { recursive: true, force: true }) + } + }) +}) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index fd70c41af09..64ad5a06403 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2053,6 +2053,7 @@ export class ClineProvider reasoningBlockCollapsed, enterBehavior, cloudUserInfo, + taskHistoryRetention, cloudIsAuthenticated, sharingEnabled, publicSharingEnabled, @@ -2236,6 +2237,8 @@ export class ClineProvider includeDiagnosticMessages: includeDiagnosticMessages ?? true, maxDiagnosticMessages: maxDiagnosticMessages ?? 50, includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true, + // Task history retention setting for About tab dropdown + taskHistoryRetention: taskHistoryRetention ?? "never", includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, maxGitStatusFiles: maxGitStatusFiles ?? 0, @@ -2451,6 +2454,8 @@ export class ClineProvider organizationAllowList, organizationSettingsVersion, customCondensingPrompt: stateValues.customCondensingPrompt, + // Task history retention selection + taskHistoryRetention: stateValues.taskHistoryRetention ?? "never", codebaseIndexModels: stateValues.codebaseIndexModels ?? EMBEDDING_MODEL_PROFILES, codebaseIndexConfig: { codebaseIndexEnabled: stateValues.codebaseIndexConfig?.codebaseIndexEnabled ?? false, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 2dc77d05027..9adbbbf0035 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -593,6 +593,12 @@ export const webviewMessageHandler = async ( await vscode.workspace .getConfiguration(Package.name) .update("deniedCommands", newValue, vscode.ConfigurationTarget.Global) + } else if (key === "taskHistoryRetention") { + const val = ((value ?? "never") as string).toString() + newValue = val + await vscode.workspace + .getConfiguration(Package.name) + .update("taskHistoryRetention", val, vscode.ConfigurationTarget.Global) } else if (key === "ttsEnabled") { newValue = value ?? true setTtsEnabled(newValue as boolean) diff --git a/src/extension.ts b/src/extension.ts index bcfbe339932..b3184ecbdc5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import * as dotenvx from "@dotenvx/dotenvx" import * as path from "path" +import * as fs from "fs/promises" // Load environment variables from .env file try { @@ -44,6 +45,7 @@ import { } from "./activate" import { initializeI18n } from "./i18n" import { flushModels, initializeModelCacheRefresh, refreshModels } from "./api/providers/fetchers/modelCache" +import { purgeOldTasks } from "./utils/task-history-retention" /** * Built using https://github.com/microsoft/vscode-webview-ui-toolkit @@ -163,6 +165,40 @@ export async function activate(context: vscode.ExtensionContext) { const contextProxy = await ContextProxy.getInstance(context) + // Initialize the provider *before* the Roo Code Cloud service so we can reuse its task deletion logic. + const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService) + + // Task history retention purge (runs only on activation) + try { + const config = vscode.workspace.getConfiguration(Package.name) + const retention = config.get("taskHistoryRetention", "never") ?? "never" + + outputChannel.appendLine(`[Retention] Startup purge: setting=${retention}`) + + const result = await purgeOldTasks( + retention as any, + contextProxy.globalStorageUri.fsPath, + (m) => { + outputChannel.appendLine(m) + console.log(m) + }, + false, + async (taskId: string, _taskDirPath: string) => { + // Reuse the same internal deletion logic as the History view so that + // checkpoints, shadow repositories, and task state are cleaned up consistently. + await provider.deleteTaskWithId(taskId) + }, + ) + + outputChannel.appendLine( + `[Retention] Startup purge complete: purged=${result.purgedCount}, cutoff=${result.cutoff ?? "none"}`, + ) + } catch (error) { + outputChannel.appendLine( + `[Retention] Failed during startup purge: ${error instanceof Error ? error.message : String(error)}`, + ) + } + // Initialize code index managers for all workspace folders. const codeIndexManagers: CodeIndexManager[] = [] @@ -186,9 +222,6 @@ export async function activate(context: vscode.ExtensionContext) { } } - // Initialize the provider *before* the Roo Code Cloud service. - const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService) - // Initialize Roo Code Cloud service. const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebview() diff --git a/src/package.json b/src/package.json index e5ade27ed3d..7e4a8c21644 100644 --- a/src/package.json +++ b/src/package.json @@ -429,6 +429,19 @@ "default": false, "description": "%settings.debugProxy.tlsInsecure.description%", "markdownDescription": "%settings.debugProxy.tlsInsecure.description%" + }, + "roo-cline.taskHistoryRetention": { + "type": "string", + "enum": [ + "never", + "90", + "60", + "30", + "7", + "3" + ], + "default": "never", + "description": "%settings.taskHistoryRetention.description%" } } } diff --git a/src/package.nls.json b/src/package.nls.json index 177b392f775..94107aed14d 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -45,5 +45,6 @@ "settings.debug.description": "Enable debug mode to show additional buttons for viewing API conversation history and UI messages as prettified JSON in temporary files.", "settings.debugProxy.enabled.description": "**Enable Debug Proxy** — Route all outbound network requests through a proxy for MITM debugging. Only active when running in debug mode (F5).", "settings.debugProxy.serverUrl.description": "Proxy URL (e.g., `http://127.0.0.1:8888`). Only used when **Debug Proxy** is enabled.", - "settings.debugProxy.tlsInsecure.description": "Accept self-signed certificates from the proxy. **Required for MITM inspection.** ⚠️ Insecure — only use for local debugging." + "settings.debugProxy.tlsInsecure.description": "Accept self-signed certificates from the proxy. **Required for MITM inspection.** ⚠️ Insecure — only use for local debugging.", + "settings.taskHistoryRetention.description": "Auto-delete task history on extension reload. Deletes tasks older than the selected period. Options: Never (default), 90 days, 60 days, 30 days, 7 days, or 3 days. Warning: This cannot be undone and only runs on plugin reload." } diff --git a/src/utils/task-history-retention.ts b/src/utils/task-history-retention.ts new file mode 100644 index 00000000000..274033648ac --- /dev/null +++ b/src/utils/task-history-retention.ts @@ -0,0 +1,267 @@ +import * as path from "path" +import * as fs from "fs/promises" +import type { Dirent } from "fs" + +import { getStorageBasePath } from "./storage" +import { GlobalFileNames } from "../shared/globalFileNames" + +/** + * Supported values for the retention setting. + * - "never" or 0 disables purging + * - "90" | "60" | "30" | "7" | "3" (string) or 90 | 60 | 30 | 7 | 3 (number) specify days + */ +export type RetentionSetting = "never" | "90" | "60" | "30" | "7" | "3" | 90 | 60 | 30 | 7 | 3 | 0 | "0" | number + +export type PurgeResult = { + purgedCount: number + cutoff: number | null +} + +/** + * Purge old task directories under /tasks based on task_metadata.json ts value. + * Executes best-effort deletes; errors are logged and skipped. + * + * @param retention Retention setting: "never" | "90" | "60" | "30" | "7" | "3" or number of days + * @param globalStoragePath VS Code global storage fsPath (context.globalStorageUri.fsPath) + * @param log Optional logger + * @param dryRun When true, logs which tasks would be deleted but does not delete anything + * @returns PurgeResult with count and cutoff used + */ +export async function purgeOldTasks( + retention: RetentionSetting, + globalStoragePath: string, + log?: (message: string) => void, + dryRun: boolean = false, + deleteTaskById?: (taskId: string, taskDirPath: string) => Promise, + verbose: boolean = false, +): Promise { + const days = normalizeDays(retention) + if (!days) { + log?.("[Retention] No purge (setting is 'never' or not a positive number)") + return { purgedCount: 0, cutoff: null } + } + + const cutoff = Date.now() - days * 24 * 60 * 60 * 1000 + const logv = (msg: string) => { + if (verbose) log?.(msg) + } + logv(`[Retention] Starting purge with retention=${retention} (${days} day(s))${dryRun ? " (dry run)" : ""}`) + + let basePath: string + + try { + basePath = await getStorageBasePath(globalStoragePath) + } catch (e) { + log?.( + `[Retention] Failed to resolve storage base path: ${ + e instanceof Error ? e.message : String(e) + }${dryRun ? " (dry run)" : ""}`, + ) + return { purgedCount: 0, cutoff } + } + + const tasksDir = path.join(basePath, "tasks") + + let entries: Dirent[] + try { + entries = await fs.readdir(tasksDir, { withFileTypes: true }) + } catch (e) { + // No tasks directory yet or unreadable; nothing to purge. + logv(`[Retention] Tasks directory not found or unreadable at ${tasksDir}${dryRun ? " (dry run)" : ""}`) + return { purgedCount: 0, cutoff } + } + + const taskDirs = entries.filter((d) => d.isDirectory()) + + logv(`[Retention] Found ${taskDirs.length} task director${taskDirs.length === 1 ? "y" : "ies"} under ${tasksDir}`) + + // Small helpers + const pathExists = async (p: string): Promise => { + try { + await fs.access(p) + return true + } catch { + return false + } + } + + const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) + + // Aggressive recursive remove with retries; also directly clears checkpoints if needed + const removeDirAggressive = async (dir: string): Promise => { + // Try up to 3 passes with short delays + for (let attempt = 1; attempt <= 3; attempt++) { + try { + await fs.rm(dir, { recursive: true, force: true }) + } catch { + // ignore and try more targeted cleanup below + } + + // Verify + if (!(await pathExists(dir))) return true + + // Targeted cleanup for stubborn checkpoint-only directories + try { + await fs.rm(path.join(dir, "checkpoints"), { recursive: true, force: true }) + } catch { + // ignore + } + + // Remove children one by one in case some FS impls struggle with rm -r + try { + const entries = await fs.readdir(dir, { withFileTypes: true }) + for (const entry of entries) { + const entryPath = path.join(dir, entry.name) + try { + if (entry.isDirectory()) { + await fs.rm(entryPath, { recursive: true, force: true }) + } else { + await fs.unlink(entryPath) + } + } catch { + // ignore individual failures; we'll retry the parent + } + } + } catch { + // ignore + } + + // Final attempt this pass + try { + await fs.rm(dir, { recursive: true, force: true }) + } catch { + // ignore + } + + if (!(await pathExists(dir))) return true + + // Backoff a bit before next attempt + await sleep(50 * attempt) + } + + return !(await pathExists(dir)) + } + + const results = await Promise.all( + taskDirs.map(async (d) => { + const taskDir = path.join(tasksDir, d.name) + const metadataPath = path.join(taskDir, GlobalFileNames.taskMetadata) + + let ts: number | null = null + + // First try to get a timestamp from task_metadata.json (if present) + try { + const raw = await fs.readFile(metadataPath, "utf8") + const meta = JSON.parse(raw) + const maybeTs = Number((meta as any)?.ts) + if (Number.isFinite(maybeTs)) { + ts = maybeTs + } + } catch { + // Missing or invalid metadata; we'll fall back to directory mtime. + } + + let shouldDelete = false + let reason = "" + + // Check for checkpoint-only orphan directories (delete regardless of age) + try { + const childEntries = await fs.readdir(taskDir, { withFileTypes: true }) + const visibleNames = childEntries.map((e) => e.name).filter((n) => !n.startsWith(".")) + const hasCheckpointsDir = childEntries.some((e) => e.isDirectory() && e.name === "checkpoints") + const nonCheckpointVisible = visibleNames.filter((n) => n !== "checkpoints") + const hasMetadataFile = visibleNames.includes(GlobalFileNames.taskMetadata) + if (hasCheckpointsDir && nonCheckpointVisible.length === 0 && !hasMetadataFile) { + shouldDelete = true + reason = "orphan checkpoints_only" + } + } catch { + // Ignore errors while scanning children; proceed with normal logic + } + + if (!shouldDelete && ts !== null && ts < cutoff) { + // Normal case: metadata has a valid ts older than cutoff + shouldDelete = true + reason = `ts=${ts}` + } else if (!shouldDelete) { + // Orphan/legacy case: no valid ts; fall back to directory mtime + try { + const stat = await fs.stat(taskDir) + const mtimeMs = stat.mtime.getTime() + if (mtimeMs < cutoff) { + shouldDelete = true + reason = `no valid ts, mtime=${stat.mtime.toISOString()}` + } + } catch { + // If we can't stat the directory, skip it. + } + } + + if (!shouldDelete) { + return 0 + } + + if (dryRun) { + logv(`[Retention][DRY RUN] Would delete task ${d.name} (${reason}) @ ${taskDir}`) + return 1 + } + + // Attempt deletion using provider callback (for full cleanup) or direct rm + let deletionError: unknown | null = null + try { + if (deleteTaskById) { + logv(`[Retention] Deleting task ${d.name} via provider @ ${taskDir} (${reason})`) + await deleteTaskById(d.name, taskDir) + } else { + logv(`[Retention] Deleting task ${d.name} via fs.rm @ ${taskDir} (${reason})`) + await fs.rm(taskDir, { recursive: true, force: true }) + } + } catch (e) { + deletionError = e + } + + // Verify deletion; if still exists, attempt aggressive cleanup with retries + let deleted = await removeDirAggressive(taskDir) + + if (!deleted) { + // Did not actually remove; report the most relevant error + if (deletionError) { + log?.( + `[Retention] Failed to delete task ${d.name} @ ${taskDir}: ${ + deletionError instanceof Error ? deletionError.message : String(deletionError) + } (directory still present)`, + ) + } else { + log?.( + `[Retention] Failed to delete task ${d.name} @ ${taskDir}: directory still present after cleanup attempts`, + ) + } + return 0 + } + + logv(`[Retention] Deleted task ${d.name} (${reason}) @ ${taskDir}`) + return 1 + }), + ) + + const purged = results.reduce((sum, n) => sum + n, 0) + + if (purged > 0) { + log?.( + `[Retention] Purged ${purged} task(s)${dryRun ? " (dry run)" : ""}; cutoff=${new Date(cutoff).toISOString()}`, + ) + } else { + log?.(`[Retention] No tasks met purge criteria${dryRun ? " (dry run)" : ""}`) + } + + return { purgedCount: purged, cutoff } +} + +/** + * Normalize retention into a positive integer day count or 0 (no-op). + */ +function normalizeDays(value: RetentionSetting): number { + if (value === "never") return 0 + const n = typeof value === "number" ? value : parseInt(String(value), 10) + return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0 +} diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx index 17e3d6cfa95..f86414189c3 100644 --- a/webview-ui/src/components/settings/About.tsx +++ b/webview-ui/src/components/settings/About.tsx @@ -10,7 +10,7 @@ import { Package } from "@roo/package" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" -import { Button } from "@/components/ui" +import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui" import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" @@ -21,9 +21,20 @@ type AboutProps = HTMLAttributes & { setTelemetrySetting: (setting: TelemetrySetting) => void debug?: boolean setDebug?: (debug: boolean) => void + taskHistoryRetention: "never" | "90" | "60" | "30" | "7" | "3" + setTaskHistoryRetention: (value: "never" | "90" | "60" | "30" | "7" | "3") => void } -export const About = ({ telemetrySetting, setTelemetrySetting, debug, setDebug, className, ...props }: AboutProps) => { +export const About = ({ + telemetrySetting, + setTelemetrySetting, + debug, + setDebug, + taskHistoryRetention, + setTaskHistoryRetention, + className, + ...props +}: AboutProps) => { const { t } = useAppTranslation() return ( @@ -131,10 +142,42 @@ export const About = ({ telemetrySetting, setTelemetrySetting, debug, setDebug,
+ +

{t("settings:aboutRetention.label")}

+
+ +
+ {t("settings:aboutRetention.description")} +
+
{t("settings:aboutRetention.warning")}
+
+
+ + label={t("settings:about.manageSettings")} + className="mt-4 pt-4 border-t border-vscode-settings-headerBorder">

{t("settings:about.manageSettings")}

+
+
+ (({ onDone, t taskHistoryRetention, } = cachedState + // taskHistorySize is read-only (not a saveable setting) so we use extensionState directly + const taskHistorySize = extensionState.taskHistorySize + const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) useEffect(() => { @@ -561,6 +564,13 @@ const SettingsView = forwardRef(({ onDone, t scrollToActiveTab() }, [activeTab, scrollToActiveTab]) + // Effect to trigger task history size calculation when About tab is opened + useEffect(() => { + if (activeTab === "about") { + vscode.postMessage({ type: "refreshTaskHistorySize" }) + } + }, [activeTab]) + // Effect to scroll when the webview becomes visible useLayoutEffect(() => { const handleMessage = (event: MessageEvent) => { @@ -962,6 +972,7 @@ const SettingsView = forwardRef(({ onDone, t setDebug={setDebug} taskHistoryRetention={normalizedTaskHistoryRetention} setTaskHistoryRetention={(value) => setCachedStateField("taskHistoryRetention", value)} + taskHistorySize={taskHistorySize} /> )} From 86f29ec3a6f29e2658b89651ee29a09890fb64b7 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 19 Jan 2026 23:00:07 -0500 Subject: [PATCH 16/37] i18n: add taskHistoryStorage translations to all locales --- webview-ui/src/i18n/locales/ca/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/de/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/es/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/fr/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/hi/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/id/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/it/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/ja/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/ko/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/nl/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/pl/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/pt-BR/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/ru/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/tr/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/vi/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 9 +++++++++ webview-ui/src/i18n/locales/zh-TW/settings.json | 9 +++++++++ 17 files changed, 153 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index c835a6cf6bd..46216a2f1e9 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -909,6 +909,15 @@ "3": "3 dies" } }, + "taskHistoryStorage": { + "label": "Ús d'emmagatzematge", + "calculating": "Calculant...", + "format": "{{size}} ({{count}} tasques)", + "formatSingular": "{{size}} (1 tasca)", + "empty": "Cap tasca emmagatzemada", + "refresh": "Actualitzar", + "error": "No es pot calcular" + }, "footer": { "feedback": "Si teniu qualsevol pregunta o comentari, no dubteu a obrir un issue a github.com/RooCodeInc/Roo-Code o unir-vos a reddit.com/r/RooCode o discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8d21d6b425b..fe841dd5a38 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -909,6 +909,15 @@ "3": "3 Tage" } }, + "taskHistoryStorage": { + "label": "Speichernutzung", + "calculating": "Berechne...", + "format": "{{size}} ({{count}} Aufgaben)", + "formatSingular": "{{size}} (1 Aufgabe)", + "empty": "Keine Aufgaben gespeichert", + "refresh": "Aktualisieren", + "error": "Berechnung nicht möglich" + }, "footer": { "feedback": "Wenn du Fragen oder Feedback hast, kannst du gerne ein Issue auf github.com/RooCodeInc/Roo-Code eröffnen oder reddit.com/r/RooCode oder discord.gg/roocode beitreten", "telemetry": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 3c8d7638e63..2d8b6e4b659 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -909,6 +909,15 @@ "3": "3 días" } }, + "taskHistoryStorage": { + "label": "Uso de almacenamiento", + "calculating": "Calculando...", + "format": "{{size}} ({{count}} tareas)", + "formatSingular": "{{size}} (1 tarea)", + "empty": "No hay tareas almacenadas", + "refresh": "Actualizar", + "error": "No se puede calcular" + }, "footer": { "feedback": "Si tiene alguna pregunta o comentario, no dude en abrir un issue en github.com/RooCodeInc/Roo-Code o unirse a reddit.com/r/RooCode o discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 0234af74b2d..07b7222afdc 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -909,6 +909,15 @@ "3": "3 jours" } }, + "taskHistoryStorage": { + "label": "Utilisation du stockage", + "calculating": "Calcul en cours...", + "format": "{{size}} ({{count}} tâches)", + "formatSingular": "{{size}} (1 tâche)", + "empty": "Aucune tâche stockée", + "refresh": "Actualiser", + "error": "Impossible de calculer" + }, "footer": { "feedback": "Si vous avez des questions ou des commentaires, n'hésitez pas à ouvrir un problème sur github.com/RooCodeInc/Roo-Code ou à rejoindre reddit.com/r/RooCode ou discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 950403e8064..c2c162b8177 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -910,6 +910,15 @@ "3": "3 दिन" } }, + "taskHistoryStorage": { + "label": "स्टोरेज उपयोग", + "calculating": "गणना हो रही है...", + "format": "{{size}} ({{count}} कार्य)", + "formatSingular": "{{size}} (1 कार्य)", + "empty": "कोई कार्य संग्रहीत नहीं", + "refresh": "रिफ्रेश करें", + "error": "गणना करने में असमर्थ" + }, "footer": { "feedback": "यदि आपके कोई प्रश्न या प्रतिक्रिया है, तो github.com/RooCodeInc/Roo-Code पर एक मुद्दा खोलने या reddit.com/r/RooCode या discord.gg/roocode में शामिल होने में संकोच न करें", "telemetry": { diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index eedb8a8c05d..3d753c2f4ac 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -939,6 +939,15 @@ "3": "3 hari" } }, + "taskHistoryStorage": { + "label": "Penggunaan penyimpanan", + "calculating": "Menghitung...", + "format": "{{size}} ({{count}} tugas)", + "formatSingular": "{{size}} (1 tugas)", + "empty": "Tidak ada tugas tersimpan", + "refresh": "Segarkan", + "error": "Tidak dapat menghitung" + }, "footer": { "feedback": "Jika kamu punya pertanyaan atau feedback, jangan ragu untuk membuka issue di github.com/RooCodeInc/Roo-Code atau bergabung reddit.com/r/RooCode atau discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index b037283b317..2b4f5129ee7 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -910,6 +910,15 @@ "3": "3 giorni" } }, + "taskHistoryStorage": { + "label": "Utilizzo dello spazio di archiviazione", + "calculating": "Calcolo in corso...", + "format": "{{size}} ({{count}} attività)", + "formatSingular": "{{size}} (1 attività)", + "empty": "Nessuna attività archiviata", + "refresh": "Aggiorna", + "error": "Impossibile calcolare" + }, "footer": { "feedback": "Se hai domande o feedback, sentiti libero di aprire un issue su github.com/RooCodeInc/Roo-Code o unirti a reddit.com/r/RooCode o discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 197a5917251..ba90931b53d 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -910,6 +910,15 @@ "3": "3日" } }, + "taskHistoryStorage": { + "label": "ストレージ使用量", + "calculating": "計算中...", + "format": "{{size}} ({{count}} タスク)", + "formatSingular": "{{size}} (1 タスク)", + "empty": "保存されたタスクがありません", + "refresh": "更新", + "error": "計算できません" + }, "footer": { "feedback": "質問やフィードバックがある場合は、github.com/RooCodeInc/Roo-Codeで問題を開くか、reddit.com/r/RooCodediscord.gg/roocodeに参加してください", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index f07f5788927..c3c7490b979 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -910,6 +910,15 @@ "3": "3일" } }, + "taskHistoryStorage": { + "label": "저장소 사용량", + "calculating": "계산 중...", + "format": "{{size}} ({{count}}개 작업)", + "formatSingular": "{{size}} (1개 작업)", + "empty": "저장된 작업 없음", + "refresh": "새로고침", + "error": "계산할 수 없음" + }, "footer": { "feedback": "질문이나 피드백이 있으시면 github.com/RooCodeInc/Roo-Code에서 이슈를 열거나 reddit.com/r/RooCode 또는 discord.gg/roocode에 가입하세요", "telemetry": { diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 974e6e9a041..8eebef23905 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -910,6 +910,15 @@ "3": "3 dagen" } }, + "taskHistoryStorage": { + "label": "Opslaggebruik", + "calculating": "Berekenen...", + "format": "{{size}} ({{count}} taken)", + "formatSingular": "{{size}} (1 taak)", + "empty": "Geen opgeslagen taken", + "refresh": "Vernieuwen", + "error": "Kan niet berekenen" + }, "footer": { "feedback": "Heb je vragen of feedback? Open gerust een issue op github.com/RooCodeInc/Roo-Code of sluit je aan bij reddit.com/r/RooCode of discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index ad2260844f9..9b4ca5d4292 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -910,6 +910,15 @@ "3": "3 dni" } }, + "taskHistoryStorage": { + "label": "Wykorzystanie pamięci", + "calculating": "Obliczanie...", + "format": "{{size}} ({{count}} zadań)", + "formatSingular": "{{size}} (1 zadanie)", + "empty": "Brak zapisanych zadań", + "refresh": "Odśwież", + "error": "Nie można obliczyć" + }, "footer": { "feedback": "Jeśli masz jakiekolwiek pytania lub opinie, śmiało otwórz zgłoszenie na github.com/RooCodeInc/Roo-Code lub dołącz do reddit.com/r/RooCode lub discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index e924d329591..af7a27112c0 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -910,6 +910,15 @@ "3": "3 dias" } }, + "taskHistoryStorage": { + "label": "Uso de armazenamento", + "calculating": "Calculando...", + "format": "{{size}} ({{count}} tarefas)", + "formatSingular": "{{size}} (1 tarefa)", + "empty": "Nenhuma tarefa armazenada", + "refresh": "Atualizar", + "error": "Não foi possível calcular" + }, "footer": { "feedback": "Se tiver alguma dúvida ou feedback, sinta-se à vontade para abrir um problema em github.com/RooCodeInc/Roo-Code ou juntar-se a reddit.com/r/RooCode ou discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index b34fa8ea525..f6b471876b3 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -910,6 +910,15 @@ "3": "3 дня" } }, + "taskHistoryStorage": { + "label": "Использование хранилища", + "calculating": "Подсчет...", + "format": "{{size}} ({{count}} задач)", + "formatSingular": "{{size}} (1 задача)", + "empty": "Нет сохраненных задач", + "refresh": "Обновить", + "error": "Невозможно подсчитать" + }, "footer": { "feedback": "Если у вас есть вопросы или предложения, откройте issue на github.com/RooCodeInc/Roo-Code или присоединяйтесь к reddit.com/r/RooCode или discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index c1bbb3f6130..63d97b554fd 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -910,6 +910,15 @@ "3": "3 gün" } }, + "taskHistoryStorage": { + "label": "Depolama kullanımı", + "calculating": "Hesaplanıyor...", + "format": "{{size}} ({{count}} görev)", + "formatSingular": "{{size}} (1 görev)", + "empty": "Depolanmış görev yok", + "refresh": "Yenile", + "error": "Hesaplanamıyor" + }, "footer": { "feedback": "Herhangi bir sorunuz veya geri bildiriminiz varsa, github.com/RooCodeInc/Roo-Code adresinde bir konu açmaktan veya reddit.com/r/RooCode ya da discord.gg/roocode'a katılmaktan çekinmeyin", "telemetry": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 7d6a3393777..4ab3fbb3b0e 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -910,6 +910,15 @@ "3": "3 ngày" } }, + "taskHistoryStorage": { + "label": "Dung lượng lưu trữ", + "calculating": "Đang tính toán...", + "format": "{{size}} ({{count}} tác vụ)", + "formatSingular": "{{size}} (1 tác vụ)", + "empty": "Không có tác vụ được lưu trữ", + "refresh": "Làm mới", + "error": "Không thể tính toán" + }, "footer": { "feedback": "Nếu bạn có bất kỳ câu hỏi hoặc phản hồi nào, vui lòng mở một vấn đề tại github.com/RooCodeInc/Roo-Code hoặc tham gia reddit.com/r/RooCode hoặc discord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index bcfb46ae3fc..7dfa021917e 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -910,6 +910,15 @@ "3": "3天" } }, + "taskHistoryStorage": { + "label": "存储使用量", + "calculating": "计算中...", + "format": "{{size}} ({{count}} 个任务)", + "formatSingular": "{{size}} (1 个任务)", + "empty": "无任务存储", + "refresh": "刷新", + "error": "无法计算" + }, "footer": { "feedback": "如果您有任何问题或反馈,请随时在 github.com/RooCodeInc/Roo-Code 上提出问题或加入 reddit.com/r/RooCodediscord.gg/roocode", "telemetry": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 67b5f750ffd..2c64ae99774 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -918,6 +918,15 @@ "3": "3天" } }, + "taskHistoryStorage": { + "label": "儲存空間使用量", + "calculating": "計算中...", + "format": "{{size}} ({{count}} 個工作)", + "formatSingular": "{{size}} (1 個工作)", + "empty": "無已儲存工作", + "refresh": "重新整理", + "error": "無法計算" + }, "footer": { "telemetry": { "label": "允許匿名錯誤與使用情況回報", From 33b58beed04741735309dd58ac2cd0a67b031716 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 19 Jan 2026 21:02:50 -0700 Subject: [PATCH 17/37] fix: add background purgeOldTasks call and remove duplicate NLS keys - Add non-blocking background task history purge on extension activation - Fire-and-forget pattern prevents blocking startup - Skip purge entirely when retention is 'never' (default) - Remove duplicate 'settings.taskHistoryRetention.description' keys from all 18 package.nls.*.json files - Keep consistent 'extension reload' terminology (vs 'plugin reload') --- src/extension.ts | 38 ++++++++++++++++++++++++++++++++++++++ src/package.nls.ca.json | 1 - src/package.nls.de.json | 1 - src/package.nls.es.json | 1 - src/package.nls.fr.json | 1 - src/package.nls.hi.json | 1 - src/package.nls.id.json | 1 - src/package.nls.it.json | 1 - src/package.nls.ja.json | 1 - src/package.nls.json | 1 - src/package.nls.ko.json | 1 - src/package.nls.nl.json | 1 - src/package.nls.pl.json | 1 - src/package.nls.pt-BR.json | 1 - src/package.nls.ru.json | 1 - src/package.nls.tr.json | 2 -- src/package.nls.vi.json | 1 - src/package.nls.zh-CN.json | 1 - src/package.nls.zh-TW.json | 1 - 19 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 07636d2fb65..ae188a0d5fe 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -388,6 +388,44 @@ export async function activate(context: vscode.ExtensionContext) { // Allows other extensions to activate once Roo is ready. vscode.commands.executeCommand(`${Package.name}.activationCompleted`) + // Task history retention purge (runs in background after activation) + // Fire-and-forget to avoid blocking extension startup + void (async () => { + try { + const config = vscode.workspace.getConfiguration(Package.name) + const retention = config.get("taskHistoryRetention", "never") ?? "never" + + // Skip if retention is disabled (default) + if (retention === "never") { + return + } + + outputChannel.appendLine(`[Retention] Starting background purge: setting=${retention}`) + + const result = await purgeOldTasks( + retention as RetentionSetting, + contextProxy.globalStorageUri.fsPath, + (m) => outputChannel.appendLine(m), + false, + async (taskId: string) => { + // Reuse the same internal deletion logic as the History view so that + // checkpoints, shadow repositories, and task state are cleaned up consistently. + await provider.deleteTaskWithId(taskId) + }, + ) + + if (result.purgedCount > 0) { + outputChannel.appendLine( + `[Retention] Background purge complete: purged=${result.purgedCount}, cutoff=${result.cutoff ? new Date(result.cutoff).toISOString() : "none"}`, + ) + } + } catch (error) { + outputChannel.appendLine( + `[Retention] Failed during background purge: ${error instanceof Error ? error.message : String(error)}`, + ) + } + })() + // Implements the `RooCodeAPI` interface. const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH const enableLogging = typeof socketPath === "string" diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json index fd1fa2f2c12..77d625144a7 100644 --- a/src/package.nls.ca.json +++ b/src/package.nls.ca.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Habilita el Debug Proxy** — Redirigeix totes les sol·licituds de xarxa sortints a través d'un proxy per a debugging MITM. Només està actiu quan s'executa en mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (p. ex., `http://127.0.0.1:8888`). Només s'utilitza quan el **Debug Proxy** està habilitat.", "settings.debugProxy.tlsInsecure.description": "Accepta certificats auto-signats del proxy. **Requerit per a la inspecció MITM.** ⚠️ Insegur — utilitza-ho només per a debugging local.", - "settings.taskHistoryRetention.description": "Eliminar automàticament l'historial de tasques en recarregar l'extensió. Elimina les tasques més antigues que el període seleccionat. Opcions: Mai (per defecte), 90 dies, 60 dies, 30 dies, 7 dies o 3 dies. Advertència: Això no es pot desfer i només s'executa en recarregar el connector.", "settings.taskHistoryRetention.description": "Eliminar automàticament l'historial de tasques en recarregar l'extensió. Elimina les tasques més antigues que el període seleccionat. Opcions: Mai (per defecte), 90 dies, 60 dies, 30 dies, 7 dies o 3 dies. Advertència: Això no es pot desfer i només s'executa en recarregar l'extensió." } diff --git a/src/package.nls.de.json b/src/package.nls.de.json index 1f5d9b07985..ff96f6659eb 100644 --- a/src/package.nls.de.json +++ b/src/package.nls.de.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug-Proxy aktivieren** — Leite alle ausgehenden Netzwerkanfragen über einen Proxy für MITM-Debugging. Nur aktiv, wenn du im Debug-Modus (F5) läufst.", "settings.debugProxy.serverUrl.description": "Proxy-URL (z. B. `http://127.0.0.1:8888`). Wird nur verwendet, wenn der **Debug-Proxy** aktiviert ist.", "settings.debugProxy.tlsInsecure.description": "Akzeptiere selbstsignierte Zertifikate vom Proxy. **Erforderlich für MITM-Inspektion.** ⚠️ Unsicher – verwende das nur für lokales Debugging.", - "settings.taskHistoryRetention.description": "Aufgabenverlauf beim Neuladen der Erweiterung automatisch löschen. Löscht Aufgaben, die älter als der ausgewählte Zeitraum sind. Optionen: Niemals (Standard), 90 Tage, 60 Tage, 30 Tage, 7 Tage oder 3 Tage. Warnung: Dies kann nicht rückgängig gemacht werden und wird nur beim Plugin-Neustart ausgeführt.", "settings.taskHistoryRetention.description": "Aufgabenverlauf beim Neuladen der Erweiterung automatisch löschen. Löscht Aufgaben, die älter als der ausgewählte Zeitraum sind. Optionen: Niemals (Standard), 90 Tage, 60 Tage, 30 Tage, 7 Tage oder 3 Tage. Warnung: Dies kann nicht rückgängig gemacht werden und wird nur beim Neuladen der Erweiterung ausgeführt." } diff --git a/src/package.nls.es.json b/src/package.nls.es.json index 1be664dbbbe..38fa197ba40 100644 --- a/src/package.nls.es.json +++ b/src/package.nls.es.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Activar Debug Proxy** — Redirige todas las solicitudes de red salientes a través de un proxy para depuración MITM. Solo está activo cuando se ejecuta en modo depuración (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (p. ej., `http://127.0.0.1:8888`). Solo se usa cuando **Debug Proxy** está activado.", "settings.debugProxy.tlsInsecure.description": "Aceptar certificados autofirmados del proxy. **Necesario para la inspección MITM.** ⚠️ Inseguro: úsalo solo para depuración local.", - "settings.taskHistoryRetention.description": "Eliminar automáticamente el historial de tareas al recargar la extensión. Elimina tareas más antiguas que el período seleccionado. Opciones: Nunca (predeterminado), 90 días, 60 días, 30 días, 7 días o 3 días. Advertencia: Esto no se puede deshacer y solo se ejecuta al recargar el plugin.", "settings.taskHistoryRetention.description": "Eliminar automáticamente el historial de tareas al recargar la extensión. Elimina tareas más antiguas que el período seleccionado. Opciones: Nunca (predeterminado), 90 días, 60 días, 30 días, 7 días o 3 días. Advertencia: Esto no se puede deshacer y solo se ejecuta al recargar la extensión." } diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json index 47c0f61f75e..96654a0fed0 100644 --- a/src/package.nls.fr.json +++ b/src/package.nls.fr.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Activer le Debug Proxy** — Redirige toutes les requêtes réseau sortantes via un proxy pour le debug MITM. Actif uniquement quand tu es en mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL du proxy (par ex. `http://127.0.0.1:8888`). Utilisée uniquement quand le **Debug Proxy** est activé.", "settings.debugProxy.tlsInsecure.description": "Accepter les certificats auto-signés du proxy. **Requis pour l'inspection MITM.** ⚠️ Non sécurisé — à utiliser uniquement pour le debug local.", - "settings.taskHistoryRetention.description": "Supprimer automatiquement l'historique des tâches lors du rechargement de l'extension. Supprime les tâches plus anciennes que la période sélectionnée. Options : Jamais (par défaut), 90 jours, 60 jours, 30 jours, 7 jours ou 3 jours. Attention : Cette action ne peut pas être annulée et ne s'exécute qu'au rechargement du plugin.", "settings.taskHistoryRetention.description": "Supprimer automatiquement l'historique des tâches lors du rechargement de l'extension. Supprime les tâches plus anciennes que la période sélectionnée. Options : Jamais (par défaut), 90 jours, 60 jours, 30 jours, 7 jours ou 3 jours. Attention : Cette action ne peut pas être annulée et ne s'exécute qu'au rechargement de l'extension." } diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json index 2b4350bfd43..857e83d3e2b 100644 --- a/src/package.nls.hi.json +++ b/src/package.nls.hi.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug Proxy सक्षम करो** — सभी आउटबाउंड network requests को MITM debugging के लिए proxy के ज़रिए route करो। सिर्फ तब active रहेगा जब तुम debug mode (F5) में चला रहे हो।", "settings.debugProxy.serverUrl.description": "Proxy URL (जैसे `http://127.0.0.1:8888`)। सिर्फ तब इस्तेमाल होती है जब **Debug Proxy** enabled हो।", "settings.debugProxy.tlsInsecure.description": "Proxy से आने वाले self-signed certificates accept करो। **MITM inspection के लिए ज़रूरी।** ⚠️ Insecure — सिर्फ local debugging के लिए इस्तेमाल करो।", - "settings.taskHistoryRetention.description": "एक्सटेंशन रीलोड पर कार्य इतिहास को स्वचालित रूप से हटाएं। चयनित अवधि से पुराने कार्यों को हटाता है। विकल्प: कभी नहीं (डिफ़ॉल्ट), 90 दिन, 60 दिन, 30 दिन, 7 दिन, या 3 दिन। चेतावनी: इसे पूर्ववत नहीं किया जा सकता और यह केवल प्लगइन रीलोड पर चलता है।", "settings.taskHistoryRetention.description": "एक्सटेंशन रीलोड पर कार्य इतिहास को स्वचालित रूप से हटाएं। चयनित अवधि से पुराने कार्यों को हटाता है। विकल्प: कभी नहीं (डिफ़ॉल्ट), 90 दिन, 60 दिन, 30 दिन, 7 दिन, या 3 दिन। चेतावनी: इसे पूर्ववत नहीं किया जा सकता और यह केवल एक्सटेंशन रीलोड पर चलता है।" } diff --git a/src/package.nls.id.json b/src/package.nls.id.json index 5c73affba4e..d7d7073068a 100644 --- a/src/package.nls.id.json +++ b/src/package.nls.id.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Aktifkan Debug Proxy** — Arahkan semua permintaan jaringan keluar lewat proxy untuk debugging MITM. Hanya aktif saat kamu berjalan dalam mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL proxy (mis. `http://127.0.0.1:8888`). Hanya digunakan ketika **Debug Proxy** diaktifkan.", "settings.debugProxy.tlsInsecure.description": "Terima sertifikat self-signed dari proxy. **Diperlukan untuk inspeksi MITM.** ⚠️ Tidak aman — gunakan hanya untuk debugging lokal.", - "settings.taskHistoryRetention.description": "Hapus otomatis riwayat tugas saat memuat ulang ekstensi. Menghapus tugas yang lebih lama dari periode yang dipilih. Opsi: Tidak Pernah (default), 90 hari, 60 hari, 30 hari, 7 hari, atau 3 hari. Peringatan: Ini tidak dapat dibatalkan dan hanya berjalan saat memuat ulang plugin.", "settings.taskHistoryRetention.description": "Hapus otomatis riwayat tugas saat memuat ulang ekstensi. Menghapus tugas yang lebih lama dari periode yang dipilih. Opsi: Tidak Pernah (default), 90 hari, 60 hari, 30 hari, 7 hari, atau 3 hari. Peringatan: Ini tidak dapat dibatalkan dan hanya berjalan saat memuat ulang ekstensi." } diff --git a/src/package.nls.it.json b/src/package.nls.it.json index 25b193cb86e..8e8d9274491 100644 --- a/src/package.nls.it.json +++ b/src/package.nls.it.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Abilita Debug Proxy** — Instrada tutte le richieste di rete in uscita tramite un proxy per il debugging MITM. Attivo solo quando esegui in modalità debug (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (ad es. `http://127.0.0.1:8888`). Usato solo quando **Debug Proxy** è abilitato.", "settings.debugProxy.tlsInsecure.description": "Accetta certificati autofirmati dal proxy. **Necessario per l'ispezione MITM.** ⚠️ Non sicuro — usalo solo per il debugging locale.", - "settings.taskHistoryRetention.description": "Elimina automaticamente la cronologia delle attività al ricaricamento dell'estensione. Elimina le attività più vecchie del periodo selezionato. Opzioni: Mai (predefinito), 90 giorni, 60 giorni, 30 giorni, 7 giorni o 3 giorni. Attenzione: Questa operazione non può essere annullata e viene eseguita solo al ricaricamento del plugin.", "settings.taskHistoryRetention.description": "Elimina automaticamente la cronologia delle attività al ricaricamento dell'estensione. Elimina le attività più vecchie del periodo selezionato. Opzioni: Mai (predefinito), 90 giorni, 60 giorni, 30 giorni, 7 giorni o 3 giorni. Attenzione: Questa operazione non può essere annullata e viene eseguita solo al ricaricamento dell'estensione." } diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json index a68ec973c70..3050943b5ae 100644 --- a/src/package.nls.ja.json +++ b/src/package.nls.ja.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug Proxy を有効化** — すべての送信ネットワーク要求を MITM デバッグのためにプロキシ経由でルーティングします。デバッグモード (F5) で実行しているときだけ有効です。", "settings.debugProxy.serverUrl.description": "プロキシ URL(例: `http://127.0.0.1:8888`)。**Debug Proxy** が有効なときにだけ使用されます。", "settings.debugProxy.tlsInsecure.description": "プロキシからの自己署名証明書を許可します。**MITM インスペクションに必須です。** ⚠️ 危険な設定なので、ローカルでのデバッグにだけ使用してください。", - "settings.taskHistoryRetention.description": "拡張機能の再読み込み時にタスク履歴を自動削除します。選択した期間より古いタスクを削除します。オプション:なし(デフォルト)、90日、60日、30日、7日、または3日。警告:この操作は元に戻せず、プラグインの再読み込み時にのみ実行されます。", "settings.taskHistoryRetention.description": "拡張機能の再読み込み時にタスク履歴を自動削除します。選択した期間より古いタスクを削除します。オプション:なし(デフォルト)、90日、60日、30日、7日、または3日。警告:この操作は元に戻せず、拡張機能の再読み込み時にのみ実行されます。" } diff --git a/src/package.nls.json b/src/package.nls.json index d28e8525b4b..3df82958ec4 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Enable Debug Proxy** — Route all outbound network requests through a proxy for MITM debugging. Only active when running in debug mode (F5).", "settings.debugProxy.serverUrl.description": "Proxy URL (e.g., `http://127.0.0.1:8888`). Only used when **Debug Proxy** is enabled.", "settings.debugProxy.tlsInsecure.description": "Accept self-signed certificates from the proxy. **Required for MITM inspection.** ⚠️ Insecure — only use for local debugging.", - "settings.taskHistoryRetention.description": "Auto-delete task history on extension reload. Deletes tasks older than the selected period. Options: Never (default), 90 days, 60 days, 30 days, 7 days, or 3 days. Warning: This cannot be undone and only runs on plugin reload.", "settings.taskHistoryRetention.description": "Auto-delete task history on extension reload. Deletes tasks older than the selected period. Options: Never (default), 90 days, 60 days, 30 days, 7 days, or 3 days. Warning: This cannot be undone and only runs on extension reload." } diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json index b942400d820..a4b90526052 100644 --- a/src/package.nls.ko.json +++ b/src/package.nls.ko.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug Proxy 활성화** — 모든 아웃바운드 네트워크 요청을 MITM 디버깅을 위해 프록시를 통해 라우팅합니다. 디버그 모드(F5)로 실행 중일 때만 활성화됩니다.", "settings.debugProxy.serverUrl.description": "프록시 URL(예: `http://127.0.0.1:8888`). **Debug Proxy** 가 활성화된 경우에만 사용됩니다.", "settings.debugProxy.tlsInsecure.description": "프록시의 self-signed 인증서를 허용합니다. **MITM 검사에 필요합니다.** ⚠️ 안전하지 않으므로 로컬 디버깅에만 사용하세요.", - "settings.taskHistoryRetention.description": "확장 프로그램 재로드 시 작업 기록을 자동 삭제합니다. 선택한 기간보다 오래된 작업을 삭제합니다. 옵션: 안 함(기본값), 90일, 60일, 30일, 7일 또는 3일. 경고: 이 작업은 취소할 수 없으며 플러그인 재로드 시에만 실행됩니다.", "settings.taskHistoryRetention.description": "확장 프로그램 재로드 시 작업 기록을 자동 삭제합니다. 선택한 기간보다 오래된 작업을 삭제합니다. 옵션: 안 함(기본값), 90일, 60일, 30일, 7일 또는 3일. 경고: 이 작업은 취소할 수 없으며 확장 프로그램 재로드 시에만 실행됩니다." } diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json index 62d03daaf44..19d744ad644 100644 --- a/src/package.nls.nl.json +++ b/src/package.nls.nl.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug Proxy inschakelen** — Leid alle uitgaande netwerkverzoeken via een proxy voor MITM-debugging. Alleen actief wanneer je in debugmodus (F5) draait.", "settings.debugProxy.serverUrl.description": "Proxy-URL (bijv. `http://127.0.0.1:8888`). Wordt alleen gebruikt wanneer **Debug Proxy** is ingeschakeld.", "settings.debugProxy.tlsInsecure.description": "Accepteer zelfondertekende certificaten van de proxy. **Vereist voor MITM-inspectie.** ⚠️ Onveilig — gebruik dit alleen voor lokale debugging.", - "settings.taskHistoryRetention.description": "Taakgeschiedenis automatisch verwijderen bij het herladen van de extensie. Verwijdert taken die ouder zijn dan de geselecteerde periode. Opties: Nooit (standaard), 90 dagen, 60 dagen, 30 dagen, 7 dagen of 3 dagen. Waarschuwing: Dit kan niet ongedaan worden gemaakt en wordt alleen uitgevoerd bij het herladen van de plugin.", "settings.taskHistoryRetention.description": "Taakgeschiedenis automatisch verwijderen bij het herladen van de extensie. Verwijdert taken die ouder zijn dan de geselecteerde periode. Opties: Nooit (standaard), 90 dagen, 60 dagen, 30 dagen, 7 dagen of 3 dagen. Waarschuwing: Dit kan niet ongedaan worden gemaakt en wordt alleen uitgevoerd bij het herladen van de extensie." } diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json index 0811a204898..6556259231f 100644 --- a/src/package.nls.pl.json +++ b/src/package.nls.pl.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Włącz Debug Proxy** — Kieruj wszystkie wychodzące żądania sieciowe przez proxy na potrzeby debugowania MITM. Aktywne tylko wtedy, gdy uruchamiasz w trybie debugowania (F5).", "settings.debugProxy.serverUrl.description": "URL proxy (np. `http://127.0.0.1:8888`). Używany tylko wtedy, gdy **Debug Proxy** jest włączony.", "settings.debugProxy.tlsInsecure.description": "Akceptuj certyfikaty self-signed z proxy. **Wymagane do inspekcji MITM.** ⚠️ Niezabezpieczone — używaj tylko do lokalnego debugowania.", - "settings.taskHistoryRetention.description": "Automatyczne usuwanie historii zadań przy przeładowaniu rozszerzenia. Usuwa zadania starsze niż wybrany okres. Opcje: Nigdy (domyślnie), 90 dni, 60 dni, 30 dni, 7 dni lub 3 dni. Ostrzeżenie: Tej operacji nie można cofnąć i jest wykonywana tylko przy przeładowaniu wtyczki.", "settings.taskHistoryRetention.description": "Automatyczne usuwanie historii zadań przy przeładowaniu rozszerzenia. Usuwa zadania starsze niż wybrany okres. Opcje: Nigdy (domyślnie), 90 dni, 60 dni, 30 dni, 7 dni lub 3 dni. Ostrzeżenie: Tej operacji nie można cofnąć i jest wykonywana tylko przy przeładowaniu rozszerzenia." } diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json index 2cf8d2f8d98..3e96de94715 100644 --- a/src/package.nls.pt-BR.json +++ b/src/package.nls.pt-BR.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Ativar Debug Proxy** — Redireciona todas as solicitações de rede de saída por meio de um proxy para depuração MITM. Só fica ativo quando você está executando em modo de depuração (F5).", "settings.debugProxy.serverUrl.description": "URL do proxy (por exemplo, `http://127.0.0.1:8888`). Só é usada quando o **Debug Proxy** está ativado.", "settings.debugProxy.tlsInsecure.description": "Aceitar certificados self-signed do proxy. **Necessário para inspeção MITM.** ⚠️ Inseguro — use apenas para depuração local.", - "settings.taskHistoryRetention.description": "Excluir automaticamente o histórico de tarefas ao recarregar a extensão. Exclui tarefas mais antigas que o período selecionado. Opções: Nunca (padrão), 90 dias, 60 dias, 30 dias, 7 dias ou 3 dias. Aviso: Isso não pode ser desfeito e só é executado ao recarregar o plugin.", "settings.taskHistoryRetention.description": "Excluir automaticamente o histórico de tarefas ao recarregar a extensão. Exclui tarefas mais antigas que o período selecionado. Opções: Nunca (padrão), 90 dias, 60 dias, 30 dias, 7 dias ou 3 dias. Aviso: Isso não pode ser desfeito e só é executado ao recarregar a extensão." } diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json index 474427bf709..b202a512c58 100644 --- a/src/package.nls.ru.json +++ b/src/package.nls.ru.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Включить Debug Proxy** — направлять все исходящие сетевые запросы через прокси для MITM-отладки. Активен только когда ты запускаешь расширение в режиме отладки (F5).", "settings.debugProxy.serverUrl.description": "URL прокси (например, `http://127.0.0.1:8888`). Используется только если **Debug Proxy** включён.", "settings.debugProxy.tlsInsecure.description": "Принимать self-signed сертификаты от прокси. **Требуется для MITM-инспекции.** ⚠️ Небезопасно — используй только для локальной отладки.", - "settings.taskHistoryRetention.description": "Автоматически удалять историю задач при перезагрузке расширения. Удаляет задачи старше выбранного периода. Варианты: Никогда (по умолчанию), 90 дней, 60 дней, 30 дней, 7 дней или 3 дня. Предупреждение: Это действие нельзя отменить, и оно выполняется только при перезагрузке плагина.", "settings.taskHistoryRetention.description": "Автоматически удалять историю задач при перезагрузке расширения. Удаляет задачи старше выбранного периода. Варианты: Никогда (по умолчанию), 90 дней, 60 дней, 30 дней, 7 дней или 3 дня. Предупреждение: Это действие нельзя отменить, и оно выполняется только при перезагрузке расширения." } diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json index 09f58c9f7d8..b282f31fb6f 100644 --- a/src/package.nls.tr.json +++ b/src/package.nls.tr.json @@ -46,7 +46,5 @@ "settings.debugProxy.enabled.description": "**Debug Proxy'yi etkinleştir** — Tüm giden ağ isteklerini MITM hata ayıklaması için bir proxy üzerinden yönlendir. Yalnızca debug modunda (F5) çalıştırırken aktiftir.", "settings.debugProxy.serverUrl.description": "Proxy URL'si (ör. `http://127.0.0.1:8888`). Yalnızca **Debug Proxy** etkin olduğunda kullanılır.", "settings.debugProxy.tlsInsecure.description": "Proxy'den gelen self-signed sertifikaları kabul et. **MITM incelemesi için gerekli.** ⚠️ Güvensiz — yalnızca lokal debugging için kullan.", - "settings.taskHistoryRetention.description": "Uzantı yeniden yüklendiğinde görev geçmişini otomatik olarak sil. Seçilen süreден eski görevleri siler. Seçenekler: Asla (varsayılan), 90 gün, 60 gün, 30 gün, 7 gün veya 3 gün. Uyarı: Bu işlem geri alınamaz ve yalnızca eklenti yeniden yüklendiğinde çalışır.", - "settings.taskHistoryRetention.description": "Uzantı yeniden yüklendiğinde görev geçmişini otomatik olarak sil. Seçilen süreден eski görevleri siler. Seçenekler: Asla (varsayılan), 90 gün, 60 gün, 30 gün, 7 gün veya 3 gün. Uyarı: Bu işlem geri alınamaz ve yalnızca uzantı yeniden yüklendiğinde çalışır.", "settings.taskHistoryRetention.description": "Uzantı yeniden yüklendiğinde görev geçmişini otomatik olarak sil. Seçilen süreden eski görevleri siler. Seçenekler: Asla (varsayılan), 90 gün, 60 gün, 30 gün, 7 gün veya 3 gün. Uyarı: Bu işlem geri alınamaz ve yalnızca uzantı yeniden yüklendiğinde çalışır." } diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json index 2e2460f6b51..dc49f3215db 100644 --- a/src/package.nls.vi.json +++ b/src/package.nls.vi.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**Bật Debug Proxy** — Chuyển hướng tất cả yêu cầu mạng đi ra qua một proxy để debug MITM. Chỉ hoạt động khi bạn chạy ở chế độ gỡ lỗi (F5).", "settings.debugProxy.serverUrl.description": "Proxy URL (vd: `http://127.0.0.1:8888`). Chỉ được dùng khi **Debug Proxy** được bật.", "settings.debugProxy.tlsInsecure.description": "Chấp nhận chứng chỉ self-signed từ proxy. **Bắt buộc cho việc kiểm tra MITM.** ⚠️ Không an toàn — chỉ dùng cho debug cục bộ.", - "settings.taskHistoryRetention.description": "Tự động xóa lịch sử nhiệm vụ khi tải lại tiện ích mở rộng. Xóa các nhiệm vụ cũ hơn khoảng thời gian đã chọn. Tùy chọn: Không bao giờ (mặc định), 90 ngày, 60 ngày, 30 ngày, 7 ngày hoặc 3 ngày. Cảnh báo: Thao tác này không thể hoàn tác và chỉ chạy khi tải lại plugin.", "settings.taskHistoryRetention.description": "Tự động xóa lịch sử nhiệm vụ khi tải lại tiện ích mở rộng. Xóa các nhiệm vụ cũ hơn khoảng thời gian đã chọn. Tùy chọn: Không bao giờ (mặc định), 90 ngày, 60 ngày, 30 ngày, 7 ngày hoặc 3 ngày. Cảnh báo: Thao tác này không thể hoàn tác và chỉ chạy khi tải lại tiện ích mở rộng." } diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json index 83fb5823494..7cdb497f614 100644 --- a/src/package.nls.zh-CN.json +++ b/src/package.nls.zh-CN.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**启用 Debug Proxy** — 通过代理转发所有出站网络请求,用于 MITM 调试。只在调试模式 (F5) 运行时生效。", "settings.debugProxy.serverUrl.description": "代理 URL(例如 `http://127.0.0.1:8888`)。仅在启用 **Debug Proxy** 时使用。", "settings.debugProxy.tlsInsecure.description": "接受来自代理的 self-signed 证书。**MITM 检查所必需。** ⚠️ 不安全——只在本地调试时使用。", - "settings.taskHistoryRetention.description": "扩展重新加载时自动删除任务历史。删除早于所选时间段的任务。选项:从不(默认)、90天、60天、30天、7天或3天。警告:此操作不可逆,仅在插件重新加载时运行。", "settings.taskHistoryRetention.description": "扩展重新加载时自动删除任务历史。删除早于所选时间段的任务。选项:从不(默认)、90天、60天、30天、7天或3天。警告:此操作不可逆,仅在扩展重新加载时运行。" } diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json index 8d7b8d3dbe1..847d22596ef 100644 --- a/src/package.nls.zh-TW.json +++ b/src/package.nls.zh-TW.json @@ -46,6 +46,5 @@ "settings.debugProxy.enabled.description": "**啟用 Debug Proxy** — 將所有出站網路要求透過代理進行路由,以進行 MITM 偵錯。只有在除錯模式 (F5) 執行時才會啟用。", "settings.debugProxy.serverUrl.description": "代理 URL(例如 `http://127.0.0.1:8888`)。只有在啟用 **Debug Proxy** 時才會使用。", "settings.debugProxy.tlsInsecure.description": "接受來自代理的 self-signed 憑證。**MITM 檢查所必需。** ⚠️ 不安全——只在本機偵錯時使用。", - "settings.taskHistoryRetention.description": "擴充功能重新載入時自動刪除工作歷程記錄。刪除早於所選時間段的工作。選項:永不(預設)、90天、60天、30天、7天或3天。警告:此操作無法復原,且僅在外掛程式重新載入時執行。", "settings.taskHistoryRetention.description": "擴充功能重新載入時自動刪除工作歷程記錄。刪除早於所選時間段的工作。選項:永不(預設)、90天、60天、30天、7天或3天。警告:此操作無法復原,且僅在擴充功能重新載入時執行。" } From 523abdb25d28ca1ee581de2f5e2becedbbe073fd Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 19 Jan 2026 23:16:57 -0500 Subject: [PATCH 18/37] fix: resolve merge conflict, use startBackgroundRetentionPurge() for cleaner code - Resolved merge conflict in extension.ts, keeping extracted function approach - startBackgroundRetentionPurge() encapsulates all purge logic in task-history-retention.ts - Added getStorageBasePath mock to task-storage-size tests (addresses roomote review) --- src/__tests__/extension.spec.ts | 4 +- src/extension.ts | 48 +++---------- src/utils/__tests__/task-storage-size.spec.ts | 5 ++ src/utils/task-history-retention.ts | 71 +++++++++++++++++++ 4 files changed, 89 insertions(+), 39 deletions(-) diff --git a/src/__tests__/extension.spec.ts b/src/__tests__/extension.spec.ts index 1d215493116..1803eb4d367 100644 --- a/src/__tests__/extension.spec.ts +++ b/src/__tests__/extension.spec.ts @@ -159,9 +159,9 @@ vi.mock("../utils/autoImportSettings", () => ({ autoImportSettings: vi.fn().mockResolvedValue(undefined), })) -// Avoid filesystem access during activation by stubbing purge +// Avoid filesystem access during activation by stubbing background purge vi.mock("../utils/task-history-retention", () => ({ - purgeOldTasks: vi.fn().mockResolvedValue({ purgedCount: 0, cutoff: null }), + startBackgroundRetentionPurge: vi.fn(), })) // Ensure storage base path resolves to provided path to avoid touching VS Code config diff --git a/src/extension.ts b/src/extension.ts index ae188a0d5fe..cff5ea0d467 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,7 +44,7 @@ import { } from "./activate" import { initializeI18n } from "./i18n" import { flushModels, initializeModelCacheRefresh, refreshModels } from "./api/providers/fetchers/modelCache" -import { purgeOldTasks, RetentionSetting } from "./utils/task-history-retention" +import { startBackgroundRetentionPurge } from "./utils/task-history-retention" /** * Built using https://github.com/microsoft/vscode-webview-ui-toolkit @@ -389,42 +389,16 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand(`${Package.name}.activationCompleted`) // Task history retention purge (runs in background after activation) - // Fire-and-forget to avoid blocking extension startup - void (async () => { - try { - const config = vscode.workspace.getConfiguration(Package.name) - const retention = config.get("taskHistoryRetention", "never") ?? "never" - - // Skip if retention is disabled (default) - if (retention === "never") { - return - } - - outputChannel.appendLine(`[Retention] Starting background purge: setting=${retention}`) - - const result = await purgeOldTasks( - retention as RetentionSetting, - contextProxy.globalStorageUri.fsPath, - (m) => outputChannel.appendLine(m), - false, - async (taskId: string) => { - // Reuse the same internal deletion logic as the History view so that - // checkpoints, shadow repositories, and task state are cleaned up consistently. - await provider.deleteTaskWithId(taskId) - }, - ) - - if (result.purgedCount > 0) { - outputChannel.appendLine( - `[Retention] Background purge complete: purged=${result.purgedCount}, cutoff=${result.cutoff ? new Date(result.cutoff).toISOString() : "none"}`, - ) - } - } catch (error) { - outputChannel.appendLine( - `[Retention] Failed during background purge: ${error instanceof Error ? error.message : String(error)}`, - ) - } - })() + // By this point, provider is fully initialized and ready to handle deletions + startBackgroundRetentionPurge({ + globalStoragePath: contextProxy.globalStorageUri.fsPath, + log: (m) => outputChannel.appendLine(m), + deleteTaskById: async (taskId: string) => { + // Reuse the same internal deletion logic as the History view so that + // checkpoints, shadow repositories, and task state are cleaned up consistently. + await provider.deleteTaskWithId(taskId) + }, + }) // Implements the `RooCodeAPI` interface. const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH diff --git a/src/utils/__tests__/task-storage-size.spec.ts b/src/utils/__tests__/task-storage-size.spec.ts index f2ae2ad98f6..cc7997b146d 100644 --- a/src/utils/__tests__/task-storage-size.spec.ts +++ b/src/utils/__tests__/task-storage-size.spec.ts @@ -1,6 +1,11 @@ import * as path from "path" import { calculateTaskStorageSize, formatBytes } from "../task-storage-size" +// Mock storage to avoid VS Code config access during tests +vi.mock("../storage", () => ({ + getStorageBasePath: (p: string) => Promise.resolve(p), +})) + // Mock fs/promises const mockReaddir = vi.fn() const mockStat = vi.fn() diff --git a/src/utils/task-history-retention.ts b/src/utils/task-history-retention.ts index 98ce4b8e195..1b132f2174d 100644 --- a/src/utils/task-history-retention.ts +++ b/src/utils/task-history-retention.ts @@ -1,9 +1,11 @@ +import * as vscode from "vscode" import * as path from "path" import * as fs from "fs/promises" import type { Dirent } from "fs" import { getStorageBasePath } from "./storage" import { GlobalFileNames } from "../shared/globalFileNames" +import { Package } from "../shared/package" /** * Allowed retention day values (as numbers). @@ -281,3 +283,72 @@ function normalizeDays(value: RetentionSetting): number { const n = typeof value === "number" ? value : parseInt(String(value), 10) return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0 } + +/** + * Options for starting the background retention purge. + */ +export interface BackgroundPurgeOptions { + /** VS Code global storage fsPath */ + globalStoragePath: string + /** Logger function */ + log: (message: string) => void + /** Function to delete a task by ID (should use ClineProvider.deleteTaskWithId for full cleanup) */ + deleteTaskById: (taskId: string, taskDirPath: string) => Promise +} + +/** + * Starts the task history retention purge in the background. + * This function is designed to be called after extension activation completes, + * using a fire-and-forget pattern (void) to avoid blocking activation. + * + * It reads the retention setting from VS Code configuration, executes the purge, + * and shows a notification if tasks were deleted. + * + * @param options Configuration options for the background purge + */ +export function startBackgroundRetentionPurge(options: BackgroundPurgeOptions): void { + const { globalStoragePath, log, deleteTaskById } = options + + void (async () => { + try { + const config = vscode.workspace.getConfiguration(Package.name) + const retention = config.get("taskHistoryRetention", "never") ?? "never" + + // Skip if retention is disabled + if (retention === "never") { + log("[Retention] Background purge skipped: retention is set to 'never'") + return + } + + log(`[Retention] Starting background purge: setting=${retention}`) + + const result = await purgeOldTasks( + retention as RetentionSetting, + globalStoragePath, + log, + false, + deleteTaskById, + ) + + log( + `[Retention] Background purge complete: purged=${result.purgedCount}, cutoff=${result.cutoff ?? "none"}`, + ) + + // Show user notification if tasks were deleted + if (result.purgedCount > 0) { + const message = `Roo Code deleted ${result.purgedCount} task${result.purgedCount === 1 ? "" : "s"} older than ${retention} day${retention === "1" ? "" : "s"}` + + vscode.window.showInformationMessage(message, "View Settings", "Dismiss").then((action) => { + if (action === "View Settings") { + vscode.commands.executeCommand( + "workbench.action.openSettings", + "roo-cline.taskHistoryRetention", + ) + } + }) + } + } catch (error) { + log(`[Retention] Failed during background purge: ${error instanceof Error ? error.message : String(error)}`) + } + })() +} From 7c0ce3ef66fdda18e544704201908d114859778f Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 19 Jan 2026 23:50:35 -0500 Subject: [PATCH 19/37] fix: handle non-existent workspace repo in checkpoint cleanup - Check if workspace repo directory exists before git operations in deleteTask() - Reduce log verbosity - only log actual errors, not expected cases - Add test case for non-existent directory handling --- src/core/webview/ClineProvider.ts | 9 +++---- .../checkpoints/ShadowCheckpointService.ts | 15 ++++++----- .../__tests__/ShadowCheckpointService.spec.ts | 25 +++++++++++++++++++ 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index bbe8d388a77..1567d4864a3 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1802,8 +1802,8 @@ export class ClineProvider try { await ShadowCheckpointService.deleteTask({ taskId, globalStorageDir, workspaceDir }) } catch (error) { - console.error( - `[deleteTaskWithId${taskId}] failed to delete associated shadow repository or branch: ${error instanceof Error ? error.message : String(error)}`, + this.log( + `[deleteTaskWithId] failed to delete shadow repository for task ${taskId}: ${error instanceof Error ? error.message : String(error)}`, ) } @@ -1811,10 +1811,9 @@ export class ClineProvider try { const dirPath = await getTaskDirectoryPath(globalStoragePath, taskId) await fs.rm(dirPath, { recursive: true, force: true }) - console.log(`[deleteTaskWithId${taskId}] removed task directory`) } catch (error) { - console.error( - `[deleteTaskWithId${taskId}] failed to remove task directory: ${error instanceof Error ? error.message : String(error)}`, + this.log( + `[deleteTaskWithId] failed to remove task directory for task ${taskId}: ${error instanceof Error ? error.message : String(error)}`, ) } } diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index fee08b2fa4b..417ba9d8870 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -449,22 +449,21 @@ export abstract class ShadowCheckpointService extends EventEmitter { workspaceDir: string }) { const workspaceRepoDir = this.workspaceRepoDir({ globalStorageDir, workspaceDir }) - const branchName = `roo-${taskId}` - const git = createSanitizedGit(workspaceRepoDir) - const success = await this.deleteBranch(git, branchName) - if (success) { - console.log(`[${this.name}#deleteTask.${taskId}] deleted branch ${branchName}`) - } else { - console.error(`[${this.name}#deleteTask.${taskId}] failed to delete branch ${branchName}`) + // Check if the workspace repo directory exists before attempting git operations + if (!(await fileExistsAtPath(workspaceRepoDir))) { + return } + + const branchName = `roo-${taskId}` + const git = createSanitizedGit(workspaceRepoDir) + await this.deleteBranch(git, branchName) } public static async deleteBranch(git: SimpleGit, branchName: string) { const branches = await git.branchLocal() if (!branches.all.includes(branchName)) { - console.error(`[${this.constructor.name}#deleteBranch] branch ${branchName} does not exist`) return false } diff --git a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts index 07efa4ac2cb..7c531b6d3b6 100644 --- a/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts +++ b/src/services/checkpoints/__tests__/ShadowCheckpointService.spec.ts @@ -928,5 +928,30 @@ describe.each([[RepoPerTaskCheckpointService, "RepoPerTaskCheckpointService"]])( } }) }) + + describe(`${klass.name}#deleteTask`, () => { + it("handles non-existent workspace repo directory gracefully", async () => { + const nonExistentWorkspaceDir = path.join(tmpDir, `non-existent-workspace-${Date.now()}`) + const nonExistentGlobalStorageDir = path.join(tmpDir, `non-existent-storage-${Date.now()}`) + const taskIdToDelete = "non-existent-task" + + // Verify the workspace repo directory doesn't exist + const workspaceRepoDir = path.join( + nonExistentGlobalStorageDir, + "checkpoints", + klass.hashWorkspaceDir(nonExistentWorkspaceDir), + ) + expect(await fileExistsAtPath(workspaceRepoDir)).toBe(false) + + // Should not throw when the directory doesn't exist + await expect( + klass.deleteTask({ + taskId: taskIdToDelete, + globalStorageDir: nonExistentGlobalStorageDir, + workspaceDir: nonExistentWorkspaceDir, + }), + ).resolves.not.toThrow() + }) + }) }, ) From 0ba7cdac5802cbda76a5f01254b7110ef87a5125 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 19 Jan 2026 22:14:17 -0700 Subject: [PATCH 20/37] chore: move task history retention to app state --- src/core/webview/webviewMessageHandler.ts | 7 ++---- src/extension.ts | 30 ++++++++++++++++------- src/package.json | 13 ---------- src/package.nls.ca.json | 3 +-- src/package.nls.de.json | 3 +-- src/package.nls.es.json | 3 +-- src/package.nls.fr.json | 3 +-- src/package.nls.hi.json | 3 +-- src/package.nls.id.json | 3 +-- src/package.nls.it.json | 3 +-- src/package.nls.ja.json | 3 +-- src/package.nls.json | 3 +-- src/package.nls.ko.json | 3 +-- src/package.nls.nl.json | 3 +-- src/package.nls.pl.json | 3 +-- src/package.nls.pt-BR.json | 3 +-- src/package.nls.ru.json | 3 +-- src/package.nls.tr.json | 3 +-- src/package.nls.vi.json | 3 +-- src/package.nls.zh-CN.json | 3 +-- src/package.nls.zh-TW.json | 3 +-- src/utils/task-history-retention.ts | 28 +++++++-------------- 22 files changed, 50 insertions(+), 82 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 2042ff2fb63..24a9b9e4176 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -597,11 +597,8 @@ export const webviewMessageHandler = async ( .getConfiguration(Package.name) .update("deniedCommands", newValue, vscode.ConfigurationTarget.Global) } else if (key === "taskHistoryRetention") { - const val = ((value ?? "never") as string).toString() - newValue = val - await vscode.workspace - .getConfiguration(Package.name) - .update("taskHistoryRetention", val, vscode.ConfigurationTarget.Global) + // taskHistoryRetention is stored in Roo application state, not VS Code settings + newValue = ((value ?? "never") as string).toString() } else if (key === "ttsEnabled") { newValue = value ?? true setTtsEnabled(newValue as boolean) diff --git a/src/extension.ts b/src/extension.ts index cff5ea0d467..11e3e702e73 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -390,15 +390,27 @@ export async function activate(context: vscode.ExtensionContext) { // Task history retention purge (runs in background after activation) // By this point, provider is fully initialized and ready to handle deletions - startBackgroundRetentionPurge({ - globalStoragePath: contextProxy.globalStorageUri.fsPath, - log: (m) => outputChannel.appendLine(m), - deleteTaskById: async (taskId: string) => { - // Reuse the same internal deletion logic as the History view so that - // checkpoints, shadow repositories, and task state are cleaned up consistently. - await provider.deleteTaskWithId(taskId) - }, - }) + { + const retentionValue = contextProxy.getValue("taskHistoryRetention") + const retention = + retentionValue === "90" || + retentionValue === "60" || + retentionValue === "30" || + retentionValue === "7" || + retentionValue === "3" + ? retentionValue + : "never" + startBackgroundRetentionPurge({ + globalStoragePath: contextProxy.globalStorageUri.fsPath, + log: (m) => outputChannel.appendLine(m), + deleteTaskById: async (taskId: string) => { + // Reuse the same internal deletion logic as the History view so that + // checkpoints, shadow repositories, and task state are cleaned up consistently. + await provider.deleteTaskWithId(taskId) + }, + retention, + }) + } // Implements the `RooCodeAPI` interface. const socketPath = process.env.ROO_CODE_IPC_SOCKET_PATH diff --git a/src/package.json b/src/package.json index 7e4a8c21644..e5ade27ed3d 100644 --- a/src/package.json +++ b/src/package.json @@ -429,19 +429,6 @@ "default": false, "description": "%settings.debugProxy.tlsInsecure.description%", "markdownDescription": "%settings.debugProxy.tlsInsecure.description%" - }, - "roo-cline.taskHistoryRetention": { - "type": "string", - "enum": [ - "never", - "90", - "60", - "30", - "7", - "3" - ], - "default": "never", - "description": "%settings.taskHistoryRetention.description%" } } } diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json index 77d625144a7..2781ed169cf 100644 --- a/src/package.nls.ca.json +++ b/src/package.nls.ca.json @@ -45,6 +45,5 @@ "settings.debug.description": "Activa el mode de depuració per mostrar botons addicionals per veure l'historial de conversa de l'API i els missatges de la interfície d'usuari com a JSON embellert en fitxers temporals.", "settings.debugProxy.enabled.description": "**Habilita el Debug Proxy** — Redirigeix totes les sol·licituds de xarxa sortints a través d'un proxy per a debugging MITM. Només està actiu quan s'executa en mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (p. ex., `http://127.0.0.1:8888`). Només s'utilitza quan el **Debug Proxy** està habilitat.", - "settings.debugProxy.tlsInsecure.description": "Accepta certificats auto-signats del proxy. **Requerit per a la inspecció MITM.** ⚠️ Insegur — utilitza-ho només per a debugging local.", - "settings.taskHistoryRetention.description": "Eliminar automàticament l'historial de tasques en recarregar l'extensió. Elimina les tasques més antigues que el període seleccionat. Opcions: Mai (per defecte), 90 dies, 60 dies, 30 dies, 7 dies o 3 dies. Advertència: Això no es pot desfer i només s'executa en recarregar l'extensió." + "settings.debugProxy.tlsInsecure.description": "Accepta certificats auto-signats del proxy. **Requerit per a la inspecció MITM.** ⚠️ Insegur — utilitza-ho només per a debugging local." } diff --git a/src/package.nls.de.json b/src/package.nls.de.json index ff96f6659eb..a77a253ef06 100644 --- a/src/package.nls.de.json +++ b/src/package.nls.de.json @@ -45,6 +45,5 @@ "settings.debug.description": "Aktiviere den Debug-Modus, um zusätzliche Schaltflächen zum Anzeigen des API-Konversationsverlaufs und der UI-Nachrichten als formatiertes JSON in temporären Dateien anzuzeigen.", "settings.debugProxy.enabled.description": "**Debug-Proxy aktivieren** — Leite alle ausgehenden Netzwerkanfragen über einen Proxy für MITM-Debugging. Nur aktiv, wenn du im Debug-Modus (F5) läufst.", "settings.debugProxy.serverUrl.description": "Proxy-URL (z. B. `http://127.0.0.1:8888`). Wird nur verwendet, wenn der **Debug-Proxy** aktiviert ist.", - "settings.debugProxy.tlsInsecure.description": "Akzeptiere selbstsignierte Zertifikate vom Proxy. **Erforderlich für MITM-Inspektion.** ⚠️ Unsicher – verwende das nur für lokales Debugging.", - "settings.taskHistoryRetention.description": "Aufgabenverlauf beim Neuladen der Erweiterung automatisch löschen. Löscht Aufgaben, die älter als der ausgewählte Zeitraum sind. Optionen: Niemals (Standard), 90 Tage, 60 Tage, 30 Tage, 7 Tage oder 3 Tage. Warnung: Dies kann nicht rückgängig gemacht werden und wird nur beim Neuladen der Erweiterung ausgeführt." + "settings.debugProxy.tlsInsecure.description": "Akzeptiere selbstsignierte Zertifikate vom Proxy. **Erforderlich für MITM-Inspektion.** ⚠️ Unsicher – verwende das nur für lokales Debugging." } diff --git a/src/package.nls.es.json b/src/package.nls.es.json index 38fa197ba40..a1c729080e2 100644 --- a/src/package.nls.es.json +++ b/src/package.nls.es.json @@ -45,6 +45,5 @@ "settings.debug.description": "Activa el modo de depuración para mostrar botones adicionales para ver el historial de conversación de API y los mensajes de la interfaz de usuario como JSON embellecido en archivos temporales.", "settings.debugProxy.enabled.description": "**Activar Debug Proxy** — Redirige todas las solicitudes de red salientes a través de un proxy para depuración MITM. Solo está activo cuando se ejecuta en modo depuración (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (p. ej., `http://127.0.0.1:8888`). Solo se usa cuando **Debug Proxy** está activado.", - "settings.debugProxy.tlsInsecure.description": "Aceptar certificados autofirmados del proxy. **Necesario para la inspección MITM.** ⚠️ Inseguro: úsalo solo para depuración local.", - "settings.taskHistoryRetention.description": "Eliminar automáticamente el historial de tareas al recargar la extensión. Elimina tareas más antiguas que el período seleccionado. Opciones: Nunca (predeterminado), 90 días, 60 días, 30 días, 7 días o 3 días. Advertencia: Esto no se puede deshacer y solo se ejecuta al recargar la extensión." + "settings.debugProxy.tlsInsecure.description": "Aceptar certificados autofirmados del proxy. **Necesario para la inspección MITM.** ⚠️ Inseguro: úsalo solo para depuración local." } diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json index 96654a0fed0..2d009c0038d 100644 --- a/src/package.nls.fr.json +++ b/src/package.nls.fr.json @@ -45,6 +45,5 @@ "settings.debug.description": "Active le mode debug pour afficher des boutons supplémentaires permettant de visualiser l'historique de conversation de l'API et les messages de l'interface utilisateur sous forme de JSON formaté dans des fichiers temporaires.", "settings.debugProxy.enabled.description": "**Activer le Debug Proxy** — Redirige toutes les requêtes réseau sortantes via un proxy pour le debug MITM. Actif uniquement quand tu es en mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL du proxy (par ex. `http://127.0.0.1:8888`). Utilisée uniquement quand le **Debug Proxy** est activé.", - "settings.debugProxy.tlsInsecure.description": "Accepter les certificats auto-signés du proxy. **Requis pour l'inspection MITM.** ⚠️ Non sécurisé — à utiliser uniquement pour le debug local.", - "settings.taskHistoryRetention.description": "Supprimer automatiquement l'historique des tâches lors du rechargement de l'extension. Supprime les tâches plus anciennes que la période sélectionnée. Options : Jamais (par défaut), 90 jours, 60 jours, 30 jours, 7 jours ou 3 jours. Attention : Cette action ne peut pas être annulée et ne s'exécute qu'au rechargement de l'extension." + "settings.debugProxy.tlsInsecure.description": "Accepter les certificats auto-signés du proxy. **Requis pour l'inspection MITM.** ⚠️ Non sécurisé — à utiliser uniquement pour le debug local." } diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json index 857e83d3e2b..c51f3ee95ee 100644 --- a/src/package.nls.hi.json +++ b/src/package.nls.hi.json @@ -45,6 +45,5 @@ "settings.debug.description": "API conversation history और UI messages को temporary files में prettified JSON के रूप में देखने के लिए अतिरिक्त बटन दिखाने के लिए debug mode सक्षम करें।", "settings.debugProxy.enabled.description": "**Debug Proxy सक्षम करो** — सभी आउटबाउंड network requests को MITM debugging के लिए proxy के ज़रिए route करो। सिर्फ तब active रहेगा जब तुम debug mode (F5) में चला रहे हो।", "settings.debugProxy.serverUrl.description": "Proxy URL (जैसे `http://127.0.0.1:8888`)। सिर्फ तब इस्तेमाल होती है जब **Debug Proxy** enabled हो।", - "settings.debugProxy.tlsInsecure.description": "Proxy से आने वाले self-signed certificates accept करो। **MITM inspection के लिए ज़रूरी।** ⚠️ Insecure — सिर्फ local debugging के लिए इस्तेमाल करो।", - "settings.taskHistoryRetention.description": "एक्सटेंशन रीलोड पर कार्य इतिहास को स्वचालित रूप से हटाएं। चयनित अवधि से पुराने कार्यों को हटाता है। विकल्प: कभी नहीं (डिफ़ॉल्ट), 90 दिन, 60 दिन, 30 दिन, 7 दिन, या 3 दिन। चेतावनी: इसे पूर्ववत नहीं किया जा सकता और यह केवल एक्सटेंशन रीलोड पर चलता है।" + "settings.debugProxy.tlsInsecure.description": "Proxy से आने वाले self-signed certificates accept करो। **MITM inspection के लिए ज़रूरी।** ⚠️ Insecure — सिर्फ local debugging के लिए इस्तेमाल करो।" } diff --git a/src/package.nls.id.json b/src/package.nls.id.json index d7d7073068a..2a7607f3e7c 100644 --- a/src/package.nls.id.json +++ b/src/package.nls.id.json @@ -45,6 +45,5 @@ "settings.debug.description": "Aktifkan mode debug untuk menampilkan tombol tambahan untuk melihat riwayat percakapan API dan pesan UI sebagai JSON yang diformat dalam file sementara.", "settings.debugProxy.enabled.description": "**Aktifkan Debug Proxy** — Arahkan semua permintaan jaringan keluar lewat proxy untuk debugging MITM. Hanya aktif saat kamu berjalan dalam mode debug (F5).", "settings.debugProxy.serverUrl.description": "URL proxy (mis. `http://127.0.0.1:8888`). Hanya digunakan ketika **Debug Proxy** diaktifkan.", - "settings.debugProxy.tlsInsecure.description": "Terima sertifikat self-signed dari proxy. **Diperlukan untuk inspeksi MITM.** ⚠️ Tidak aman — gunakan hanya untuk debugging lokal.", - "settings.taskHistoryRetention.description": "Hapus otomatis riwayat tugas saat memuat ulang ekstensi. Menghapus tugas yang lebih lama dari periode yang dipilih. Opsi: Tidak Pernah (default), 90 hari, 60 hari, 30 hari, 7 hari, atau 3 hari. Peringatan: Ini tidak dapat dibatalkan dan hanya berjalan saat memuat ulang ekstensi." + "settings.debugProxy.tlsInsecure.description": "Terima sertifikat self-signed dari proxy. **Diperlukan untuk inspeksi MITM.** ⚠️ Tidak aman — gunakan hanya untuk debugging lokal." } diff --git a/src/package.nls.it.json b/src/package.nls.it.json index 8e8d9274491..c94471355d4 100644 --- a/src/package.nls.it.json +++ b/src/package.nls.it.json @@ -45,6 +45,5 @@ "settings.debug.description": "Abilita la modalità debug per mostrare pulsanti aggiuntivi per visualizzare la cronologia delle conversazioni API e i messaggi dell'interfaccia utente come JSON formattato in file temporanei.", "settings.debugProxy.enabled.description": "**Abilita Debug Proxy** — Instrada tutte le richieste di rete in uscita tramite un proxy per il debugging MITM. Attivo solo quando esegui in modalità debug (F5).", "settings.debugProxy.serverUrl.description": "URL del proxy (ad es. `http://127.0.0.1:8888`). Usato solo quando **Debug Proxy** è abilitato.", - "settings.debugProxy.tlsInsecure.description": "Accetta certificati autofirmati dal proxy. **Necessario per l'ispezione MITM.** ⚠️ Non sicuro — usalo solo per il debugging locale.", - "settings.taskHistoryRetention.description": "Elimina automaticamente la cronologia delle attività al ricaricamento dell'estensione. Elimina le attività più vecchie del periodo selezionato. Opzioni: Mai (predefinito), 90 giorni, 60 giorni, 30 giorni, 7 giorni o 3 giorni. Attenzione: Questa operazione non può essere annullata e viene eseguita solo al ricaricamento dell'estensione." + "settings.debugProxy.tlsInsecure.description": "Accetta certificati autofirmati dal proxy. **Necessario per l'ispezione MITM.** ⚠️ Non sicuro — usalo solo per il debugging locale." } diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json index 3050943b5ae..ff6040d7734 100644 --- a/src/package.nls.ja.json +++ b/src/package.nls.ja.json @@ -45,6 +45,5 @@ "settings.debug.description": "デバッグモードを有効にして、API会話履歴とUIメッセージをフォーマットされたJSONとして一時ファイルで表示するための追加ボタンを表示します。", "settings.debugProxy.enabled.description": "**Debug Proxy を有効化** — すべての送信ネットワーク要求を MITM デバッグのためにプロキシ経由でルーティングします。デバッグモード (F5) で実行しているときだけ有効です。", "settings.debugProxy.serverUrl.description": "プロキシ URL(例: `http://127.0.0.1:8888`)。**Debug Proxy** が有効なときにだけ使用されます。", - "settings.debugProxy.tlsInsecure.description": "プロキシからの自己署名証明書を許可します。**MITM インスペクションに必須です。** ⚠️ 危険な設定なので、ローカルでのデバッグにだけ使用してください。", - "settings.taskHistoryRetention.description": "拡張機能の再読み込み時にタスク履歴を自動削除します。選択した期間より古いタスクを削除します。オプション:なし(デフォルト)、90日、60日、30日、7日、または3日。警告:この操作は元に戻せず、拡張機能の再読み込み時にのみ実行されます。" + "settings.debugProxy.tlsInsecure.description": "プロキシからの自己署名証明書を許可します。**MITM インスペクションに必須です。** ⚠️ 危険な設定なので、ローカルでのデバッグにだけ使用してください。" } diff --git a/src/package.nls.json b/src/package.nls.json index 3df82958ec4..177b392f775 100644 --- a/src/package.nls.json +++ b/src/package.nls.json @@ -45,6 +45,5 @@ "settings.debug.description": "Enable debug mode to show additional buttons for viewing API conversation history and UI messages as prettified JSON in temporary files.", "settings.debugProxy.enabled.description": "**Enable Debug Proxy** — Route all outbound network requests through a proxy for MITM debugging. Only active when running in debug mode (F5).", "settings.debugProxy.serverUrl.description": "Proxy URL (e.g., `http://127.0.0.1:8888`). Only used when **Debug Proxy** is enabled.", - "settings.debugProxy.tlsInsecure.description": "Accept self-signed certificates from the proxy. **Required for MITM inspection.** ⚠️ Insecure — only use for local debugging.", - "settings.taskHistoryRetention.description": "Auto-delete task history on extension reload. Deletes tasks older than the selected period. Options: Never (default), 90 days, 60 days, 30 days, 7 days, or 3 days. Warning: This cannot be undone and only runs on extension reload." + "settings.debugProxy.tlsInsecure.description": "Accept self-signed certificates from the proxy. **Required for MITM inspection.** ⚠️ Insecure — only use for local debugging." } diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json index a4b90526052..f0912835b8b 100644 --- a/src/package.nls.ko.json +++ b/src/package.nls.ko.json @@ -45,6 +45,5 @@ "settings.debug.description": "디버그 모드를 활성화하여 API 대화 기록과 UI 메시지를 임시 파일에 포맷된 JSON으로 보기 위한 추가 버튼을 표시합니다.", "settings.debugProxy.enabled.description": "**Debug Proxy 활성화** — 모든 아웃바운드 네트워크 요청을 MITM 디버깅을 위해 프록시를 통해 라우팅합니다. 디버그 모드(F5)로 실행 중일 때만 활성화됩니다.", "settings.debugProxy.serverUrl.description": "프록시 URL(예: `http://127.0.0.1:8888`). **Debug Proxy** 가 활성화된 경우에만 사용됩니다.", - "settings.debugProxy.tlsInsecure.description": "프록시의 self-signed 인증서를 허용합니다. **MITM 검사에 필요합니다.** ⚠️ 안전하지 않으므로 로컬 디버깅에만 사용하세요.", - "settings.taskHistoryRetention.description": "확장 프로그램 재로드 시 작업 기록을 자동 삭제합니다. 선택한 기간보다 오래된 작업을 삭제합니다. 옵션: 안 함(기본값), 90일, 60일, 30일, 7일 또는 3일. 경고: 이 작업은 취소할 수 없으며 확장 프로그램 재로드 시에만 실행됩니다." + "settings.debugProxy.tlsInsecure.description": "프록시의 self-signed 인증서를 허용합니다. **MITM 검사에 필요합니다.** ⚠️ 안전하지 않으므로 로컬 디버깅에만 사용하세요." } diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json index 19d744ad644..fef3ca7219c 100644 --- a/src/package.nls.nl.json +++ b/src/package.nls.nl.json @@ -45,6 +45,5 @@ "settings.debug.description": "Schakel debug-modus in om extra knoppen te tonen voor het bekijken van API-conversatiegeschiedenis en UI-berichten als opgemaakte JSON in tijdelijke bestanden.", "settings.debugProxy.enabled.description": "**Debug Proxy inschakelen** — Leid alle uitgaande netwerkverzoeken via een proxy voor MITM-debugging. Alleen actief wanneer je in debugmodus (F5) draait.", "settings.debugProxy.serverUrl.description": "Proxy-URL (bijv. `http://127.0.0.1:8888`). Wordt alleen gebruikt wanneer **Debug Proxy** is ingeschakeld.", - "settings.debugProxy.tlsInsecure.description": "Accepteer zelfondertekende certificaten van de proxy. **Vereist voor MITM-inspectie.** ⚠️ Onveilig — gebruik dit alleen voor lokale debugging.", - "settings.taskHistoryRetention.description": "Taakgeschiedenis automatisch verwijderen bij het herladen van de extensie. Verwijdert taken die ouder zijn dan de geselecteerde periode. Opties: Nooit (standaard), 90 dagen, 60 dagen, 30 dagen, 7 dagen of 3 dagen. Waarschuwing: Dit kan niet ongedaan worden gemaakt en wordt alleen uitgevoerd bij het herladen van de extensie." + "settings.debugProxy.tlsInsecure.description": "Accepteer zelfondertekende certificaten van de proxy. **Vereist voor MITM-inspectie.** ⚠️ Onveilig — gebruik dit alleen voor lokale debugging." } diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json index 6556259231f..8c1f66450d1 100644 --- a/src/package.nls.pl.json +++ b/src/package.nls.pl.json @@ -45,6 +45,5 @@ "settings.debug.description": "Włącz tryb debugowania, aby wyświetlić dodatkowe przyciski do przeglądania historii rozmów API i komunikatów interfejsu użytkownika jako sformatowany JSON w plikach tymczasowych.", "settings.debugProxy.enabled.description": "**Włącz Debug Proxy** — Kieruj wszystkie wychodzące żądania sieciowe przez proxy na potrzeby debugowania MITM. Aktywne tylko wtedy, gdy uruchamiasz w trybie debugowania (F5).", "settings.debugProxy.serverUrl.description": "URL proxy (np. `http://127.0.0.1:8888`). Używany tylko wtedy, gdy **Debug Proxy** jest włączony.", - "settings.debugProxy.tlsInsecure.description": "Akceptuj certyfikaty self-signed z proxy. **Wymagane do inspekcji MITM.** ⚠️ Niezabezpieczone — używaj tylko do lokalnego debugowania.", - "settings.taskHistoryRetention.description": "Automatyczne usuwanie historii zadań przy przeładowaniu rozszerzenia. Usuwa zadania starsze niż wybrany okres. Opcje: Nigdy (domyślnie), 90 dni, 60 dni, 30 dni, 7 dni lub 3 dni. Ostrzeżenie: Tej operacji nie można cofnąć i jest wykonywana tylko przy przeładowaniu rozszerzenia." + "settings.debugProxy.tlsInsecure.description": "Akceptuj certyfikaty self-signed z proxy. **Wymagane do inspekcji MITM.** ⚠️ Niezabezpieczone — używaj tylko do lokalnego debugowania." } diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json index 3e96de94715..84cbf42c097 100644 --- a/src/package.nls.pt-BR.json +++ b/src/package.nls.pt-BR.json @@ -45,6 +45,5 @@ "settings.debug.description": "Ativa o modo de depuração para mostrar botões adicionais para visualizar o histórico de conversas da API e mensagens da interface como JSON formatado em arquivos temporários.", "settings.debugProxy.enabled.description": "**Ativar Debug Proxy** — Redireciona todas as solicitações de rede de saída por meio de um proxy para depuração MITM. Só fica ativo quando você está executando em modo de depuração (F5).", "settings.debugProxy.serverUrl.description": "URL do proxy (por exemplo, `http://127.0.0.1:8888`). Só é usada quando o **Debug Proxy** está ativado.", - "settings.debugProxy.tlsInsecure.description": "Aceitar certificados self-signed do proxy. **Necessário para inspeção MITM.** ⚠️ Inseguro — use apenas para depuração local.", - "settings.taskHistoryRetention.description": "Excluir automaticamente o histórico de tarefas ao recarregar a extensão. Exclui tarefas mais antigas que o período selecionado. Opções: Nunca (padrão), 90 dias, 60 dias, 30 dias, 7 dias ou 3 dias. Aviso: Isso não pode ser desfeito e só é executado ao recarregar a extensão." + "settings.debugProxy.tlsInsecure.description": "Aceitar certificados self-signed do proxy. **Necessário para inspeção MITM.** ⚠️ Inseguro — use apenas para depuração local." } diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json index b202a512c58..be8df040323 100644 --- a/src/package.nls.ru.json +++ b/src/package.nls.ru.json @@ -45,6 +45,5 @@ "settings.debug.description": "Включить режим отладки, чтобы отображать дополнительные кнопки для просмотра истории разговоров API и сообщений интерфейса в виде форматированного JSON во временных файлах.", "settings.debugProxy.enabled.description": "**Включить Debug Proxy** — направлять все исходящие сетевые запросы через прокси для MITM-отладки. Активен только когда ты запускаешь расширение в режиме отладки (F5).", "settings.debugProxy.serverUrl.description": "URL прокси (например, `http://127.0.0.1:8888`). Используется только если **Debug Proxy** включён.", - "settings.debugProxy.tlsInsecure.description": "Принимать self-signed сертификаты от прокси. **Требуется для MITM-инспекции.** ⚠️ Небезопасно — используй только для локальной отладки.", - "settings.taskHistoryRetention.description": "Автоматически удалять историю задач при перезагрузке расширения. Удаляет задачи старше выбранного периода. Варианты: Никогда (по умолчанию), 90 дней, 60 дней, 30 дней, 7 дней или 3 дня. Предупреждение: Это действие нельзя отменить, и оно выполняется только при перезагрузке расширения." + "settings.debugProxy.tlsInsecure.description": "Принимать self-signed сертификаты от прокси. **Требуется для MITM-инспекции.** ⚠️ Небезопасно — используй только для локальной отладки." } diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json index b282f31fb6f..a815188e8aa 100644 --- a/src/package.nls.tr.json +++ b/src/package.nls.tr.json @@ -45,6 +45,5 @@ "settings.debug.description": "API konuşma geçmişini ve kullanıcı arayüzü mesajlarını geçici dosyalarda biçimlendirilmiş JSON olarak görüntülemek için ek düğmeler göstermek üzere hata ayıklama modunu etkinleştir.", "settings.debugProxy.enabled.description": "**Debug Proxy'yi etkinleştir** — Tüm giden ağ isteklerini MITM hata ayıklaması için bir proxy üzerinden yönlendir. Yalnızca debug modunda (F5) çalıştırırken aktiftir.", "settings.debugProxy.serverUrl.description": "Proxy URL'si (ör. `http://127.0.0.1:8888`). Yalnızca **Debug Proxy** etkin olduğunda kullanılır.", - "settings.debugProxy.tlsInsecure.description": "Proxy'den gelen self-signed sertifikaları kabul et. **MITM incelemesi için gerekli.** ⚠️ Güvensiz — yalnızca lokal debugging için kullan.", - "settings.taskHistoryRetention.description": "Uzantı yeniden yüklendiğinde görev geçmişini otomatik olarak sil. Seçilen süreden eski görevleri siler. Seçenekler: Asla (varsayılan), 90 gün, 60 gün, 30 gün, 7 gün veya 3 gün. Uyarı: Bu işlem geri alınamaz ve yalnızca uzantı yeniden yüklendiğinde çalışır." + "settings.debugProxy.tlsInsecure.description": "Proxy'den gelen self-signed sertifikaları kabul et. **MITM incelemesi için gerekli.** ⚠️ Güvensiz — yalnızca lokal debugging için kullan." } diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json index dc49f3215db..6052080dfa3 100644 --- a/src/package.nls.vi.json +++ b/src/package.nls.vi.json @@ -45,6 +45,5 @@ "settings.debug.description": "Bật chế độ gỡ lỗi để hiển thị các nút bổ sung để xem lịch sử hội thoại API và thông điệp giao diện người dùng dưới dạng JSON được định dạng trong các tệp tạm thời.", "settings.debugProxy.enabled.description": "**Bật Debug Proxy** — Chuyển hướng tất cả yêu cầu mạng đi ra qua một proxy để debug MITM. Chỉ hoạt động khi bạn chạy ở chế độ gỡ lỗi (F5).", "settings.debugProxy.serverUrl.description": "Proxy URL (vd: `http://127.0.0.1:8888`). Chỉ được dùng khi **Debug Proxy** được bật.", - "settings.debugProxy.tlsInsecure.description": "Chấp nhận chứng chỉ self-signed từ proxy. **Bắt buộc cho việc kiểm tra MITM.** ⚠️ Không an toàn — chỉ dùng cho debug cục bộ.", - "settings.taskHistoryRetention.description": "Tự động xóa lịch sử nhiệm vụ khi tải lại tiện ích mở rộng. Xóa các nhiệm vụ cũ hơn khoảng thời gian đã chọn. Tùy chọn: Không bao giờ (mặc định), 90 ngày, 60 ngày, 30 ngày, 7 ngày hoặc 3 ngày. Cảnh báo: Thao tác này không thể hoàn tác và chỉ chạy khi tải lại tiện ích mở rộng." + "settings.debugProxy.tlsInsecure.description": "Chấp nhận chứng chỉ self-signed từ proxy. **Bắt buộc cho việc kiểm tra MITM.** ⚠️ Không an toàn — chỉ dùng cho debug cục bộ." } diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json index 7cdb497f614..9254d494d9b 100644 --- a/src/package.nls.zh-CN.json +++ b/src/package.nls.zh-CN.json @@ -45,6 +45,5 @@ "settings.debug.description": "启用调试模式以显示额外按钮,用于在临时文件中以格式化 JSON 查看 API 对话历史和 UI 消息。", "settings.debugProxy.enabled.description": "**启用 Debug Proxy** — 通过代理转发所有出站网络请求,用于 MITM 调试。只在调试模式 (F5) 运行时生效。", "settings.debugProxy.serverUrl.description": "代理 URL(例如 `http://127.0.0.1:8888`)。仅在启用 **Debug Proxy** 时使用。", - "settings.debugProxy.tlsInsecure.description": "接受来自代理的 self-signed 证书。**MITM 检查所必需。** ⚠️ 不安全——只在本地调试时使用。", - "settings.taskHistoryRetention.description": "扩展重新加载时自动删除任务历史。删除早于所选时间段的任务。选项:从不(默认)、90天、60天、30天、7天或3天。警告:此操作不可逆,仅在扩展重新加载时运行。" + "settings.debugProxy.tlsInsecure.description": "接受来自代理的 self-signed 证书。**MITM 检查所必需。** ⚠️ 不安全——只在本地调试时使用。" } diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json index 847d22596ef..a8030d69141 100644 --- a/src/package.nls.zh-TW.json +++ b/src/package.nls.zh-TW.json @@ -45,6 +45,5 @@ "settings.debug.description": "啟用偵錯模式以顯示額外按鈕,用於在暫存檔案中以格式化 JSON 檢視 API 對話歷史紀錄和使用者介面訊息。", "settings.debugProxy.enabled.description": "**啟用 Debug Proxy** — 將所有出站網路要求透過代理進行路由,以進行 MITM 偵錯。只有在除錯模式 (F5) 執行時才會啟用。", "settings.debugProxy.serverUrl.description": "代理 URL(例如 `http://127.0.0.1:8888`)。只有在啟用 **Debug Proxy** 時才會使用。", - "settings.debugProxy.tlsInsecure.description": "接受來自代理的 self-signed 憑證。**MITM 檢查所必需。** ⚠️ 不安全——只在本機偵錯時使用。", - "settings.taskHistoryRetention.description": "擴充功能重新載入時自動刪除工作歷程記錄。刪除早於所選時間段的工作。選項:永不(預設)、90天、60天、30天、7天或3天。警告:此操作無法復原,且僅在擴充功能重新載入時執行。" + "settings.debugProxy.tlsInsecure.description": "接受來自代理的 self-signed 憑證。**MITM 檢查所必需。** ⚠️ 不安全——只在本機偵錯時使用。" } diff --git a/src/utils/task-history-retention.ts b/src/utils/task-history-retention.ts index 1b132f2174d..e7a7157f3e3 100644 --- a/src/utils/task-history-retention.ts +++ b/src/utils/task-history-retention.ts @@ -5,7 +5,6 @@ import type { Dirent } from "fs" import { getStorageBasePath } from "./storage" import { GlobalFileNames } from "../shared/globalFileNames" -import { Package } from "../shared/package" /** * Allowed retention day values (as numbers). @@ -294,6 +293,8 @@ export interface BackgroundPurgeOptions { log: (message: string) => void /** Function to delete a task by ID (should use ClineProvider.deleteTaskWithId for full cleanup) */ deleteTaskById: (taskId: string, taskDirPath: string) => Promise + /** Retention setting value from Roo application state */ + retention: RetentionSetting } /** @@ -301,34 +302,25 @@ export interface BackgroundPurgeOptions { * This function is designed to be called after extension activation completes, * using a fire-and-forget pattern (void) to avoid blocking activation. * - * It reads the retention setting from VS Code configuration, executes the purge, + * It reads the retention setting from Roo application state, executes the purge, * and shows a notification if tasks were deleted. * * @param options Configuration options for the background purge */ export function startBackgroundRetentionPurge(options: BackgroundPurgeOptions): void { - const { globalStoragePath, log, deleteTaskById } = options + const { globalStoragePath, log, deleteTaskById, retention } = options void (async () => { try { - const config = vscode.workspace.getConfiguration(Package.name) - const retention = config.get("taskHistoryRetention", "never") ?? "never" - // Skip if retention is disabled - if (retention === "never") { + if (retention === "never" || retention === "0" || retention === 0) { log("[Retention] Background purge skipped: retention is set to 'never'") return } log(`[Retention] Starting background purge: setting=${retention}`) - const result = await purgeOldTasks( - retention as RetentionSetting, - globalStoragePath, - log, - false, - deleteTaskById, - ) + const result = await purgeOldTasks(retention, globalStoragePath, log, false, deleteTaskById) log( `[Retention] Background purge complete: purged=${result.purgedCount}, cutoff=${result.cutoff ?? "none"}`, @@ -336,14 +328,12 @@ export function startBackgroundRetentionPurge(options: BackgroundPurgeOptions): // Show user notification if tasks were deleted if (result.purgedCount > 0) { - const message = `Roo Code deleted ${result.purgedCount} task${result.purgedCount === 1 ? "" : "s"} older than ${retention} day${retention === "1" ? "" : "s"}` + const message = `Roo Code deleted ${result.purgedCount} task${result.purgedCount === 1 ? "" : "s"} older than ${retention} days` vscode.window.showInformationMessage(message, "View Settings", "Dismiss").then((action) => { if (action === "View Settings") { - vscode.commands.executeCommand( - "workbench.action.openSettings", - "roo-cline.taskHistoryRetention", - ) + // Navigate to Roo Code settings About tab + vscode.commands.executeCommand("roo-cline.settingsButtonClicked") } }) } From bf483c92765e3afe924a0fd7c68cc285d9f913f8 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 19 Jan 2026 22:35:17 -0700 Subject: [PATCH 21/37] fix: address mrubens review feedback for task history retention --- packages/types/src/global-settings.ts | 13 ++++++++++-- src/core/webview/webviewMessageHandler.ts | 2 +- src/i18n/locales/en/common.json | 8 ++++++++ src/utils/formatBytes.ts | 18 +++++++++++++++++ src/utils/task-history-retention.ts | 13 +++++++++--- src/utils/task-storage-size.ts | 20 +++---------------- webview-ui/src/components/settings/About.tsx | 8 ++++---- .../src/components/settings/SettingsView.tsx | 17 +++++++--------- 8 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 src/utils/formatBytes.ts diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 681324fe7a8..f9b92857ff3 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -44,6 +44,14 @@ export const MAX_CHECKPOINT_TIMEOUT_SECONDS = 60 */ export const DEFAULT_CHECKPOINT_TIMEOUT_SECONDS = 15 +/** + * Allowed values for the task history retention setting. + * Stored as strings in most UI/extension flows. + */ +export const TASK_HISTORY_RETENTION_OPTIONS = ["never", "90", "60", "30", "7", "3"] as const + +export type TaskHistoryRetentionSetting = (typeof TASK_HISTORY_RETENTION_OPTIONS)[number] + /** * GlobalSettings */ @@ -181,8 +189,9 @@ export const globalSettingsSchema = z.object({ customSupportPrompts: customSupportPromptsSchema.optional(), enhancementApiConfigId: z.string().optional(), includeTaskHistoryInEnhance: z.boolean().optional(), - // Auto-delete task history on extension reload. "never" | "90" | "60" | "30" | "7" | "3" - taskHistoryRetention: z.union([z.enum(["never", "90", "60", "30", "7", "3"]), z.number()]).optional(), + // Auto-delete task history on extension reload. + // Note: we accept `number` for backwards compatibility with older persisted state. + taskHistoryRetention: z.union([z.enum(TASK_HISTORY_RETENTION_OPTIONS), z.number()]).optional(), // Calculated task history storage size info for the Settings > About page taskHistorySize: z .object({ diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 24a9b9e4176..14a469f5a38 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -597,7 +597,7 @@ export const webviewMessageHandler = async ( .getConfiguration(Package.name) .update("deniedCommands", newValue, vscode.ConfigurationTarget.Global) } else if (key === "taskHistoryRetention") { - // taskHistoryRetention is stored in Roo application state, not VS Code settings + // taskHistoryRetention is stored in Roo application state (global state), not VS Code settings. newValue = ((value ?? "never") as string).toString() } else if (key === "ttsEnabled") { newValue = value ?? true diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 636d26f76cb..89043321d66 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -252,5 +252,13 @@ "docsLink": { "label": "Docs", "url": "https://docs.roocode.com" + }, + "taskHistoryRetention": { + "purgeNotification": "Roo Code deleted {{count}} task older than {{days}} days", + "purgeNotification_plural": "Roo Code deleted {{count}} tasks older than {{days}} days", + "actions": { + "viewSettings": "View Settings", + "dismiss": "Dismiss" + } } } diff --git a/src/utils/formatBytes.ts b/src/utils/formatBytes.ts new file mode 100644 index 00000000000..51a621fdd71 --- /dev/null +++ b/src/utils/formatBytes.ts @@ -0,0 +1,18 @@ +/** + * Formats bytes into a human-readable string with appropriate units. + * + * Note: This is intentionally simple (base-2 / 1024) and consistent with existing + * formatting expectations in tests and UI. + */ +export function formatBytes(bytes: number): string { + if (bytes === 0) return "0 B" + + const units = ["B", "KB", "MB", "GB", "TB"] + const k = 1024 + const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1) + const size = bytes / Math.pow(k, i) + + // Use 2 decimal places for MB and above, 0 for B and KB + const decimals = i >= 2 ? 2 : 0 + return `${size.toFixed(decimals)} ${units[i]}` +} diff --git a/src/utils/task-history-retention.ts b/src/utils/task-history-retention.ts index e7a7157f3e3..567b819e6b4 100644 --- a/src/utils/task-history-retention.ts +++ b/src/utils/task-history-retention.ts @@ -5,6 +5,7 @@ import type { Dirent } from "fs" import { getStorageBasePath } from "./storage" import { GlobalFileNames } from "../shared/globalFileNames" +import { t } from "../i18n" /** * Allowed retention day values (as numbers). @@ -328,10 +329,16 @@ export function startBackgroundRetentionPurge(options: BackgroundPurgeOptions): // Show user notification if tasks were deleted if (result.purgedCount > 0) { - const message = `Roo Code deleted ${result.purgedCount} task${result.purgedCount === 1 ? "" : "s"} older than ${retention} days` + const message = t("common:taskHistoryRetention.purgeNotification", { + count: result.purgedCount, + days: retention, + }) + + const viewSettingsLabel = t("common:taskHistoryRetention.actions.viewSettings") + const dismissLabel = t("common:taskHistoryRetention.actions.dismiss") - vscode.window.showInformationMessage(message, "View Settings", "Dismiss").then((action) => { - if (action === "View Settings") { + vscode.window.showInformationMessage(message, viewSettingsLabel, dismissLabel).then((action) => { + if (action === viewSettingsLabel) { // Navigate to Roo Code settings About tab vscode.commands.executeCommand("roo-cline.settingsButtonClicked") } diff --git a/src/utils/task-storage-size.ts b/src/utils/task-storage-size.ts index 669b6d733d1..cd16766735d 100644 --- a/src/utils/task-storage-size.ts +++ b/src/utils/task-storage-size.ts @@ -3,6 +3,7 @@ import * as fs from "fs/promises" import type { Dirent, Stats } from "fs" import { getStorageBasePath } from "./storage" +import { formatBytes } from "./formatBytes" /** * Result of calculating task history storage size @@ -16,23 +17,8 @@ export interface TaskStorageSizeResult { formattedSize: string } -/** - * Formats bytes into a human-readable string with appropriate units. - * @param bytes Number of bytes - * @returns Formatted string (e.g., "12.34 MB", "5.67 GB") - */ -export function formatBytes(bytes: number): string { - if (bytes === 0) return "0 B" - - const units = ["B", "KB", "MB", "GB", "TB"] - const k = 1024 - const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1) - const size = bytes / Math.pow(k, i) - - // Use 2 decimal places for MB and above, 0 for B and KB - const decimals = i >= 2 ? 2 : 0 - return `${size.toFixed(decimals)} ${units[i]}` -} +// Re-export for backwards compatibility with existing imports/tests. +export { formatBytes } /** * Recursively calculates the total size of a directory. diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx index 77be9d22fbf..4899f43b3f4 100644 --- a/webview-ui/src/components/settings/About.tsx +++ b/webview-ui/src/components/settings/About.tsx @@ -16,7 +16,7 @@ import { } from "lucide-react" import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" -import type { TelemetrySetting } from "@roo-code/types" +import type { TelemetrySetting, TaskHistoryRetentionSetting } from "@roo-code/types" import { Package } from "@roo/package" @@ -39,8 +39,8 @@ type AboutProps = HTMLAttributes & { setTelemetrySetting: (setting: TelemetrySetting) => void debug?: boolean setDebug?: (debug: boolean) => void - taskHistoryRetention: "never" | "90" | "60" | "30" | "7" | "3" - setTaskHistoryRetention: (value: "never" | "90" | "60" | "30" | "7" | "3") => void + taskHistoryRetention: TaskHistoryRetentionSetting + setTaskHistoryRetention: (value: TaskHistoryRetentionSetting) => void taskHistorySize?: TaskHistorySize } @@ -206,7 +206,7 @@ export const About = ({
{ + handleRetentionChange(value) + }}> + + + + + + {t("settings:aboutRetention.options.never")} + + + {t("settings:aboutRetention.options.90")} + + + {t("settings:aboutRetention.options.60")} + + + {t("settings:aboutRetention.options.30")} + + {t("settings:aboutRetention.options.7")} + {t("settings:aboutRetention.options.3")} + + +

+ {t("settings:aboutRetention.description")} +

+

{t("settings:aboutRetention.warning")}

+
+ + + + + +
& { setTelemetrySetting: (setting: TelemetrySetting) => void debug?: boolean setDebug?: (debug: boolean) => void - taskHistoryRetention: TaskHistoryRetentionSetting - setTaskHistoryRetention: (value: TaskHistoryRetentionSetting) => void taskHistorySize?: TaskHistorySize } @@ -47,8 +45,6 @@ export const About = ({ setTelemetrySetting, debug, setDebug, - taskHistoryRetention, - setTaskHistoryRetention, taskHistorySize, className, ...props @@ -195,37 +191,6 @@ export const About = ({
- -

{t("settings:aboutRetention.label")}

-
- -
- {t("settings:aboutRetention.description")} -
-
{t("settings:aboutRetention.warning")}
-
-
- (({ onDone, t setTelemetrySetting={setTelemetrySetting} debug={cachedState.debug} setDebug={setDebug} - taskHistoryRetention={normalizedTaskHistoryRetention} - setTaskHistoryRetention={(value) => setCachedStateField("taskHistoryRetention", value)} taskHistorySize={taskHistorySize} /> )} diff --git a/webview-ui/src/components/settings/__tests__/About.spec.tsx b/webview-ui/src/components/settings/__tests__/About.spec.tsx index 92791b4ac53..b62b7c13581 100644 --- a/webview-ui/src/components/settings/__tests__/About.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/About.spec.tsx @@ -31,8 +31,6 @@ describe("About", () => { const defaultProps: ComponentProps = { telemetrySetting: "enabled" as const, setTelemetrySetting: vi.fn(), - taskHistoryRetention: "never", - setTaskHistoryRetention: vi.fn(), } beforeEach(() => { From fea999b3b5d7175a61cff08fe47996fe4a136dac Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 26 Jan 2026 18:11:45 -0700 Subject: [PATCH 35/37] feat: improve retention UX with confirmation dialog and friendly messaging - Add confirmation dialog before changing retention settings to prevent accidental changes - Move task count display from About tab to History View settings popover - Combine redundant description/warning into single, clear warning message - Update task count display to be more human-friendly ('X tasks in history') - Update all 18 locale files with consistent messaging - Revert About.tsx to original state (no retention UI there) --- .../src/components/history/HistoryView.tsx | 125 ++++++++++++++++-- webview-ui/src/components/settings/About.tsx | 95 +------------ .../src/components/settings/SettingsView.tsx | 4 - webview-ui/src/i18n/locales/ca/settings.json | 26 ++-- webview-ui/src/i18n/locales/de/settings.json | 26 ++-- webview-ui/src/i18n/locales/en/settings.json | 25 ++-- webview-ui/src/i18n/locales/es/settings.json | 26 ++-- webview-ui/src/i18n/locales/fr/settings.json | 26 ++-- webview-ui/src/i18n/locales/hi/settings.json | 26 ++-- webview-ui/src/i18n/locales/id/settings.json | 26 ++-- webview-ui/src/i18n/locales/it/settings.json | 26 ++-- webview-ui/src/i18n/locales/ja/settings.json | 26 ++-- webview-ui/src/i18n/locales/ko/settings.json | 26 ++-- webview-ui/src/i18n/locales/nl/settings.json | 26 ++-- webview-ui/src/i18n/locales/pl/settings.json | 26 ++-- .../src/i18n/locales/pt-BR/settings.json | 26 ++-- webview-ui/src/i18n/locales/ru/settings.json | 26 ++-- webview-ui/src/i18n/locales/tr/settings.json | 26 ++-- webview-ui/src/i18n/locales/vi/settings.json | 26 ++-- .../src/i18n/locales/zh-CN/settings.json | 26 ++-- .../src/i18n/locales/zh-TW/settings.json | 26 ++-- 21 files changed, 409 insertions(+), 282 deletions(-) diff --git a/webview-ui/src/components/history/HistoryView.tsx b/webview-ui/src/components/history/HistoryView.tsx index fb621f5ec0e..eba11ce581c 100644 --- a/webview-ui/src/components/history/HistoryView.tsx +++ b/webview-ui/src/components/history/HistoryView.tsx @@ -1,5 +1,5 @@ -import React, { memo, useState, useMemo } from "react" -import { ArrowLeft, Settings } from "lucide-react" +import React, { memo, useState, useMemo, useCallback, useEffect } from "react" +import { ArrowLeft, Settings, FolderOpen, RefreshCw, Loader2 } from "lucide-react" import { DeleteTaskDialog } from "./DeleteTaskDialog" import { BatchDeleteTaskDialog } from "./BatchDeleteTaskDialog" import { Virtuoso } from "react-virtuoso" @@ -11,6 +11,14 @@ import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { vscode } from "@/utils/vscode" import { useExtensionState } from "@/context/ExtensionStateContext" import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, Button, Checkbox, Popover, @@ -48,7 +56,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { showAllWorkspaces, setShowAllWorkspaces, } = useTaskSearch() - const { taskHistoryRetention } = useExtensionState() + const { taskHistoryRetention, taskHistorySize } = useExtensionState() const { t } = useAppTranslation() // Use grouped tasks hook @@ -60,6 +68,39 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { const [selectedTaskIds, setSelectedTaskIds] = useState([]) const [showBatchDeleteDialog, setShowBatchDeleteDialog] = useState(false) const [isRetentionPopoverOpen, setIsRetentionPopoverOpen] = useState(false) + const [pendingRetention, setPendingRetention] = useState(null) + const [showRetentionConfirmDialog, setShowRetentionConfirmDialog] = useState(false) + const [isRefreshingTaskCount, setIsRefreshingTaskCount] = useState(false) + const [cachedTaskCount, setCachedTaskCount] = useState(taskHistorySize?.taskCount) + + // Update cached task count when taskHistorySize changes + useEffect(() => { + if (taskHistorySize) { + setCachedTaskCount(taskHistorySize.taskCount) + setIsRefreshingTaskCount(false) + } + }, [taskHistorySize]) + + // Handle refresh task count + const handleRefreshTaskCount = useCallback(() => { + setIsRefreshingTaskCount(true) + vscode.postMessage({ type: "refreshTaskHistorySize" }) + }, []) + + // Get task count display text + const getTaskCountDisplayText = (): string => { + const count = taskHistorySize?.taskCount ?? cachedTaskCount + if (count === undefined) { + return t("settings:taskHistoryStorage.clickToCount") + } + if (count === 0) { + return t("settings:taskHistoryStorage.empty") + } + if (count === 1) { + return t("settings:taskHistoryStorage.countSingular") + } + return t("settings:taskHistoryStorage.count", { count }) + } // Normalize retention setting to ensure it's valid const normalizedRetention: TaskHistoryRetentionSetting = TASK_HISTORY_RETENTION_OPTIONS.includes( @@ -68,9 +109,31 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { ? (taskHistoryRetention as TaskHistoryRetentionSetting) : "never" - // Handle retention setting change + // Handle retention setting change - show confirmation dialog first const handleRetentionChange = (value: TaskHistoryRetentionSetting) => { - vscode.postMessage({ type: "updateSettings", updatedSettings: { taskHistoryRetention: value } }) + // If selecting the same value, do nothing + if (value === normalizedRetention) { + return + } + // Show confirmation dialog for any change + setPendingRetention(value) + setShowRetentionConfirmDialog(true) + } + + // Confirm retention change + const confirmRetentionChange = () => { + if (pendingRetention !== null) { + vscode.postMessage({ type: "updateSettings", updatedSettings: { taskHistoryRetention: pendingRetention } }) + } + setShowRetentionConfirmDialog(false) + setPendingRetention(null) + setIsRetentionPopoverOpen(false) + } + + // Cancel retention change + const cancelRetentionChange = () => { + setShowRetentionConfirmDialog(false) + setPendingRetention(null) } // Get subtask count for a task @@ -148,7 +211,28 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
-

{t("settings:aboutRetention.label")}

+ {/* Task count display */} +
+ + {getTaskCountDisplayText()} + +
+ +
+

{t("settings:aboutRetention.label")}

+

- {t("settings:aboutRetention.description")} + {t("settings:aboutRetention.warning")}

-

{t("settings:aboutRetention.warning")}

@@ -421,6 +504,32 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { }} /> )} + + {/* Retention change confirmation dialog */} + + + + {t("settings:aboutRetention.confirmDialog.title")} + + {pendingRetention === "never" + ? t("settings:aboutRetention.confirmDialog.descriptionNever") + : t("settings:aboutRetention.confirmDialog.description", { + period: pendingRetention, + })} + + + + + {t("settings:aboutRetention.confirmDialog.cancel")} + + + {pendingRetention === "never" + ? t("settings:aboutRetention.confirmDialog.confirmNever") + : t("settings:aboutRetention.confirmDialog.confirm")} + + + + ) } diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx index c5345f8da1e..17e3d6cfa95 100644 --- a/webview-ui/src/components/settings/About.tsx +++ b/webview-ui/src/components/settings/About.tsx @@ -1,19 +1,7 @@ -import { HTMLAttributes, useState, useCallback, useEffect } from "react" +import { HTMLAttributes } from "react" import { useAppTranslation } from "@/i18n/TranslationContext" import { Trans } from "react-i18next" -import { - Download, - Upload, - TriangleAlert, - Bug, - Lightbulb, - Shield, - MessageCircle, - MessagesSquare, - RefreshCw, - FolderOpen, - Loader2, -} from "lucide-react" +import { Download, Upload, TriangleAlert, Bug, Lightbulb, Shield, MessageCircle, MessagesSquare } from "lucide-react" import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" import type { TelemetrySetting } from "@roo-code/types" @@ -28,63 +16,15 @@ import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" import { SearchableSetting } from "./SearchableSetting" -type TaskHistorySize = { - taskCount: number -} - type AboutProps = HTMLAttributes & { telemetrySetting: TelemetrySetting setTelemetrySetting: (setting: TelemetrySetting) => void debug?: boolean setDebug?: (debug: boolean) => void - taskHistorySize?: TaskHistorySize } -export const About = ({ - telemetrySetting, - setTelemetrySetting, - debug, - setDebug, - taskHistorySize, - className, - ...props -}: AboutProps) => { +export const About = ({ telemetrySetting, setTelemetrySetting, debug, setDebug, className, ...props }: AboutProps) => { const { t } = useAppTranslation() - const [isRefreshing, setIsRefreshing] = useState(false) - const [cachedSize, setCachedSize] = useState(taskHistorySize) - - // Update cached size when taskHistorySize changes and reset refreshing state - useEffect(() => { - if (taskHistorySize) { - setCachedSize(taskHistorySize) - setIsRefreshing(false) - } - }, [taskHistorySize]) - - // NOTE: No auto-trigger on mount - user must click refresh button - // This is intentional for performance with large task counts (e.g., 9000+ tasks) - - const handleRefreshTaskCount = useCallback(() => { - setIsRefreshing(true) - vscode.postMessage({ type: "refreshTaskHistorySize" }) - }, []) - - const getTaskCountDisplayText = (): string => { - // Use cached size if available, otherwise prompt user to click refresh - const displaySize = taskHistorySize || cachedSize - if (!displaySize) { - return t("settings:taskHistoryStorage.clickToCount") - } - if (displaySize.taskCount === 0) { - return t("settings:taskHistoryStorage.empty") - } - if (displaySize.taskCount === 1) { - return t("settings:taskHistoryStorage.countSingular") - } - return t("settings:taskHistoryStorage.count", { - count: displaySize.taskCount, - }) - } return (
@@ -191,37 +131,10 @@ export const About = ({
- -
- - - {t("settings:taskHistoryStorage.label")}: {getTaskCountDisplayText()} - - -
-
- + label={t("settings:about.manageSettings")}>

{t("settings:about.manageSettings")}