From 19976973cbf56eafa0c1e9a5d57e348b2353b406 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 12 May 2026 16:50:43 +0000 Subject: [PATCH] feat: consolidate Phase 7 + wire replay view for background tasks + add interrupted tooltip - Consolidate Phase 7a (FileLockManager), 7b (LockGuardedToolExecutor + tool runner integration), and 7c (persistent background task history) into single branch - Wire background task clicks in History view to open the read-only BackgroundTaskReplayView instead of creating a resumable task - Add StandardTooltip on interrupted background tasks explaining they were interrupted due to VS Code closing - Add interruptedTooltip i18n key across all locales - Add tests for replay view routing and tooltip rendering Addresses feedback from #12330 --- packages/types/src/index.ts | 2 +- packages/types/src/vscode-extension-host.ts | 3 +- .../prompts/tools/native-tools/new_task.ts | 1 + .../src/components/history/TaskItem.tsx | 3 ++ .../src/components/history/TaskItemFooter.tsx | 15 ++++--- .../history/__tests__/TaskItem.spec.tsx | 44 +++++++++++++++++++ .../history/__tests__/TaskItemFooter.spec.tsx | 13 ++++++ webview-ui/src/i18n/locales/ca/history.json | 3 +- webview-ui/src/i18n/locales/de/history.json | 3 +- webview-ui/src/i18n/locales/en/history.json | 1 + webview-ui/src/i18n/locales/es/history.json | 3 +- webview-ui/src/i18n/locales/fr/history.json | 3 +- webview-ui/src/i18n/locales/hi/history.json | 3 +- webview-ui/src/i18n/locales/id/history.json | 3 +- webview-ui/src/i18n/locales/it/history.json | 3 +- webview-ui/src/i18n/locales/ja/history.json | 3 +- webview-ui/src/i18n/locales/ko/history.json | 3 +- webview-ui/src/i18n/locales/nl/history.json | 3 +- webview-ui/src/i18n/locales/pl/history.json | 3 +- .../src/i18n/locales/pt-BR/history.json | 3 +- webview-ui/src/i18n/locales/ru/history.json | 3 +- webview-ui/src/i18n/locales/tr/history.json | 3 +- webview-ui/src/i18n/locales/vi/history.json | 3 +- .../src/i18n/locales/zh-CN/history.json | 3 +- .../src/i18n/locales/zh-TW/history.json | 3 +- 25 files changed, 108 insertions(+), 25 deletions(-) diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1eec32bbea..5e954732df 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -21,7 +21,7 @@ export * from "./mode.js" export * from "./model.js" export * from "./provider-settings.js" export * from "./task.js" -export * from "./task-context.js" +export { taskContextSchema, type TaskContext, mergePermissions } from "./task-context.js" export * from "./task-permissions.js" export * from "./todo.js" export * from "./skills.js" diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index d47cf2d490..01b6fdbbd0 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -573,8 +573,7 @@ export interface WebviewMessage { text?: string taskId?: string editedMessageContent?: string - tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "cloud" - tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "bgTaskReplay" | "bgTask" + tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "cloud" | "bgTaskReplay" | "bgTask" disabled?: boolean context?: string dataUri?: string diff --git a/src/core/prompts/tools/native-tools/new_task.ts b/src/core/prompts/tools/native-tools/new_task.ts index f846eaf73a..f0c22f2352 100644 --- a/src/core/prompts/tools/native-tools/new_task.ts +++ b/src/core/prompts/tools/native-tools/new_task.ts @@ -44,6 +44,7 @@ export default { permissions: { type: ["string", "null"], description: PERMISSIONS_PARAMETER_DESCRIPTION, + }, background: { type: ["string", "null"], description: BACKGROUND_PARAMETER_DESCRIPTION, diff --git a/webview-ui/src/components/history/TaskItem.tsx b/webview-ui/src/components/history/TaskItem.tsx index eba5e59ac9..9cb2104713 100644 --- a/webview-ui/src/components/history/TaskItem.tsx +++ b/webview-ui/src/components/history/TaskItem.tsx @@ -35,6 +35,9 @@ const TaskItem = ({ const handleClick = () => { if (isSelectionMode && onToggleSelection) { onToggleSelection(item.id, !isSelected) + } else if (item.background) { + // Background tasks open in the read-only replay view + vscode.postMessage({ type: "switchTab", tab: "bgTaskReplay", values: { taskId: item.id } }) } else { vscode.postMessage({ type: "showTaskWithId", text: item.id }) } diff --git a/webview-ui/src/components/history/TaskItemFooter.tsx b/webview-ui/src/components/history/TaskItemFooter.tsx index a1d440781e..465c16100c 100644 --- a/webview-ui/src/components/history/TaskItemFooter.tsx +++ b/webview-ui/src/components/history/TaskItemFooter.tsx @@ -32,13 +32,18 @@ const TaskItemFooter: React.FC = ({ {item.background && ( <> {item.status === "interrupted" ? ( - + + + + {t("history:interruptedTag")} + + ) : ( - + <> + + {t("history:backgroundTag")} + )} - - {item.status === "interrupted" ? t("history:interruptedTag") : t("history:backgroundTag")} - · )} diff --git a/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx index df8fc742d3..9651327c01 100644 --- a/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx +++ b/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx @@ -109,4 +109,48 @@ describe("TaskItem", () => { const taskItem = screen.getByTestId("task-item-1") expect(taskItem).toHaveClass("hover:text-vscode-foreground") }) + + it("sends switchTab message for background tasks to open replay view", async () => { + const { vscode } = await import("@/utils/vscode") + const backgroundTask = { ...mockTask, id: "bg-1", background: true } + + render( + , + ) + + fireEvent.click(screen.getByTestId("task-item-bg-1")) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "switchTab", + tab: "bgTaskReplay", + values: { taskId: "bg-1" }, + }) + }) + + it("sends showTaskWithId message for non-background tasks", async () => { + const { vscode } = await import("@/utils/vscode") + + render( + , + ) + + fireEvent.click(screen.getByTestId("task-item-1")) + + expect(vscode.postMessage).toHaveBeenCalledWith({ + type: "showTaskWithId", + text: "1", + }) + }) }) diff --git a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx index 337ec2aecd..6de1ab0fd3 100644 --- a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx +++ b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx @@ -123,4 +123,17 @@ describe("TaskItemFooter", () => { expect(screen.getByText("history:backgroundTag")).toBeInTheDocument() expect(screen.queryByText("history:interruptedTag")).not.toBeInTheDocument() }) + + it("wraps interrupted tag in a tooltip explaining VS Code was closed", () => { + const interruptedItem = { ...mockItem, background: true, status: "interrupted" as const } + render() + + // The interrupted tag should be present + expect(screen.getByText("history:interruptedTag")).toBeInTheDocument() + // The tooltip trigger wraps the tag -- verify the tooltip content key is used + // StandardTooltip renders a trigger element with the content as a prop + const tagElement = screen.getByText("history:interruptedTag") + // The tag and icon should be grouped inside a styled span + expect(tagElement.closest("span")).toHaveClass("text-vscode-editorWarning-foreground") + }) }) diff --git a/webview-ui/src/i18n/locales/ca/history.json b/webview-ui/src/i18n/locales/ca/history.json index 7c0ced7e5f..0601cbb556 100644 --- a/webview-ui/src/i18n/locales/ca/history.json +++ b/webview-ui/src/i18n/locales/ca/history.json @@ -63,5 +63,6 @@ "prefix": "Filtre:", "all": "Totes les tasques", "foregroundOnly": "Només primer pla" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/de/history.json b/webview-ui/src/i18n/locales/de/history.json index 2006bf2645..98abd97fec 100644 --- a/webview-ui/src/i18n/locales/de/history.json +++ b/webview-ui/src/i18n/locales/de/history.json @@ -63,5 +63,6 @@ "prefix": "Filter:", "all": "Alle Aufgaben", "foregroundOnly": "Nur Vordergrund" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/en/history.json b/webview-ui/src/i18n/locales/en/history.json index 6d1d54606f..e4429459d1 100644 --- a/webview-ui/src/i18n/locales/en/history.json +++ b/webview-ui/src/i18n/locales/en/history.json @@ -50,6 +50,7 @@ "collapseSubtasks": "Collapse subtasks", "backgroundTag": "Background", "interruptedTag": "Interrupted", + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running.", "showBackgroundTasks": "Show background tasks", "hideBackgroundTasks": "Hide background tasks", "filter": { diff --git a/webview-ui/src/i18n/locales/es/history.json b/webview-ui/src/i18n/locales/es/history.json index f05bf52900..18c8ea4eaa 100644 --- a/webview-ui/src/i18n/locales/es/history.json +++ b/webview-ui/src/i18n/locales/es/history.json @@ -63,5 +63,6 @@ "prefix": "Filtro:", "all": "Todas las tareas", "foregroundOnly": "Solo primer plano" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/fr/history.json b/webview-ui/src/i18n/locales/fr/history.json index eaf219e626..f278216918 100644 --- a/webview-ui/src/i18n/locales/fr/history.json +++ b/webview-ui/src/i18n/locales/fr/history.json @@ -63,5 +63,6 @@ "prefix": "Filtre :", "all": "Toutes les tâches", "foregroundOnly": "Premier plan uniquement" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/hi/history.json b/webview-ui/src/i18n/locales/hi/history.json index 81eea38e7c..2bf9aa3a6e 100644 --- a/webview-ui/src/i18n/locales/hi/history.json +++ b/webview-ui/src/i18n/locales/hi/history.json @@ -56,5 +56,6 @@ "prefix": "फ़िल्टर:", "all": "सभी टास्क", "foregroundOnly": "केवल फ़ोरग्राउंड" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/id/history.json b/webview-ui/src/i18n/locales/id/history.json index 5ae44f939f..a874a63271 100644 --- a/webview-ui/src/i18n/locales/id/history.json +++ b/webview-ui/src/i18n/locales/id/history.json @@ -65,5 +65,6 @@ "prefix": "Filter:", "all": "Semua Tugas", "foregroundOnly": "Hanya Latar Depan" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/it/history.json b/webview-ui/src/i18n/locales/it/history.json index 340e7d59ea..454033ea57 100644 --- a/webview-ui/src/i18n/locales/it/history.json +++ b/webview-ui/src/i18n/locales/it/history.json @@ -56,5 +56,6 @@ "prefix": "Filtro:", "all": "Tutte le attività", "foregroundOnly": "Solo primo piano" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/ja/history.json b/webview-ui/src/i18n/locales/ja/history.json index 6d47b2d217..e16efbb26e 100644 --- a/webview-ui/src/i18n/locales/ja/history.json +++ b/webview-ui/src/i18n/locales/ja/history.json @@ -56,5 +56,6 @@ "prefix": "フィルター:", "all": "すべてのタスク", "foregroundOnly": "フォアグラウンドのみ" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/ko/history.json b/webview-ui/src/i18n/locales/ko/history.json index 3b9c3e88f9..76ee71d839 100644 --- a/webview-ui/src/i18n/locales/ko/history.json +++ b/webview-ui/src/i18n/locales/ko/history.json @@ -56,5 +56,6 @@ "prefix": "필터:", "all": "모든 작업", "foregroundOnly": "포그라운드만" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/nl/history.json b/webview-ui/src/i18n/locales/nl/history.json index cd769522e5..216de93a52 100644 --- a/webview-ui/src/i18n/locales/nl/history.json +++ b/webview-ui/src/i18n/locales/nl/history.json @@ -56,5 +56,6 @@ "prefix": "Filter:", "all": "Alle taken", "foregroundOnly": "Alleen voorgrond" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/pl/history.json b/webview-ui/src/i18n/locales/pl/history.json index 859f3ce9b9..0932d0b216 100644 --- a/webview-ui/src/i18n/locales/pl/history.json +++ b/webview-ui/src/i18n/locales/pl/history.json @@ -56,5 +56,6 @@ "prefix": "Filtr:", "all": "Wszystkie zadania", "foregroundOnly": "Tylko na pierwszym planie" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/pt-BR/history.json b/webview-ui/src/i18n/locales/pt-BR/history.json index 202df2173c..ad5b785b03 100644 --- a/webview-ui/src/i18n/locales/pt-BR/history.json +++ b/webview-ui/src/i18n/locales/pt-BR/history.json @@ -56,5 +56,6 @@ "prefix": "Filtro:", "all": "Todas as tarefas", "foregroundOnly": "Somente primeiro plano" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/ru/history.json b/webview-ui/src/i18n/locales/ru/history.json index 25214a17d7..70c332f34f 100644 --- a/webview-ui/src/i18n/locales/ru/history.json +++ b/webview-ui/src/i18n/locales/ru/history.json @@ -56,5 +56,6 @@ "prefix": "Фильтр:", "all": "Все задачи", "foregroundOnly": "Только активные" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/tr/history.json b/webview-ui/src/i18n/locales/tr/history.json index 183ccbce31..aeee83bbf5 100644 --- a/webview-ui/src/i18n/locales/tr/history.json +++ b/webview-ui/src/i18n/locales/tr/history.json @@ -56,5 +56,6 @@ "prefix": "Filtre:", "all": "Tüm Görevler", "foregroundOnly": "Yalnızca Ön Plan" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/vi/history.json b/webview-ui/src/i18n/locales/vi/history.json index 22db4c20bc..c7192e9ce8 100644 --- a/webview-ui/src/i18n/locales/vi/history.json +++ b/webview-ui/src/i18n/locales/vi/history.json @@ -56,5 +56,6 @@ "prefix": "Bộ lọc:", "all": "Tất cả tác vụ", "foregroundOnly": "Chỉ tiền cảnh" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/zh-CN/history.json b/webview-ui/src/i18n/locales/zh-CN/history.json index 32e5a7bb47..dd7b9b79b7 100644 --- a/webview-ui/src/i18n/locales/zh-CN/history.json +++ b/webview-ui/src/i18n/locales/zh-CN/history.json @@ -56,5 +56,6 @@ "prefix": "筛选:", "all": "所有任务", "foregroundOnly": "仅前台" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." } diff --git a/webview-ui/src/i18n/locales/zh-TW/history.json b/webview-ui/src/i18n/locales/zh-TW/history.json index 6b5bc57764..367e5e04ea 100644 --- a/webview-ui/src/i18n/locales/zh-TW/history.json +++ b/webview-ui/src/i18n/locales/zh-TW/history.json @@ -56,5 +56,6 @@ "prefix": "篩選:", "all": "所有工作", "foregroundOnly": "僅前景" - } + }, + "interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running." }