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
This commit is contained in:
Roo Code 2026-05-12 16:50:43 +00:00
parent 145263b385
commit 19976973cb
25 changed files with 108 additions and 25 deletions

View file

@ -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"

View file

@ -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

View file

@ -44,6 +44,7 @@ export default {
permissions: {
type: ["string", "null"],
description: PERMISSIONS_PARAMETER_DESCRIPTION,
},
background: {
type: ["string", "null"],
description: BACKGROUND_PARAMETER_DESCRIPTION,

View file

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

View file

@ -32,13 +32,18 @@ const TaskItemFooter: React.FC<TaskItemFooterProps> = ({
{item.background && (
<>
{item.status === "interrupted" ? (
<AlertTriangle className="size-3 text-vscode-editorWarning-foreground" />
<StandardTooltip content={t("history:interruptedTooltip")}>
<span className="inline-flex items-center gap-1 text-vscode-editorWarning-foreground">
<AlertTriangle className="size-3" />
{t("history:interruptedTag")}
</span>
</StandardTooltip>
) : (
<Layers className="size-3" />
<>
<Layers className="size-3" />
<span>{t("history:backgroundTag")}</span>
</>
)}
<span>
{item.status === "interrupted" ? t("history:interruptedTag") : t("history:backgroundTag")}
</span>
<span>&middot;</span>
</>
)}

View file

@ -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(
<TaskItem
item={backgroundTask}
variant="full"
isSelected={false}
onToggleSelection={vi.fn()}
isSelectionMode={false}
/>,
)
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(
<TaskItem
item={mockTask}
variant="full"
isSelected={false}
onToggleSelection={vi.fn()}
isSelectionMode={false}
/>,
)
fireEvent.click(screen.getByTestId("task-item-1"))
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "showTaskWithId",
text: "1",
})
})
})

View file

@ -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(<TaskItemFooter item={interruptedItem} variant="full" />)
// 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")
})
})

View file

@ -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."
}

View file

@ -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."
}

View file

@ -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": {

View file

@ -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."
}

View file

@ -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."
}

View file

@ -56,5 +56,6 @@
"prefix": "फ़िल्टर:",
"all": "सभी टास्क",
"foregroundOnly": "केवल फ़ोरग्राउंड"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -56,5 +56,6 @@
"prefix": "フィルター:",
"all": "すべてのタスク",
"foregroundOnly": "フォアグラウンドのみ"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}

View file

@ -56,5 +56,6 @@
"prefix": "필터:",
"all": "모든 작업",
"foregroundOnly": "포그라운드만"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -56,5 +56,6 @@
"prefix": "Фильтр:",
"all": "Все задачи",
"foregroundOnly": "Только активные"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}

View file

@ -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."
}

View file

@ -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."
}

View file

@ -56,5 +56,6 @@
"prefix": "筛选:",
"all": "所有任务",
"foregroundOnly": "仅前台"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}

View file

@ -56,5 +56,6 @@
"prefix": "篩選:",
"all": "所有工作",
"foregroundOnly": "僅前景"
}
},
"interruptedTooltip": "This background task was interrupted because VS Code was closed while it was still running."
}