Skip to content

Commit c1d9d42

Browse files
committed
improve: show Roo history import progress
1 parent d2d1b1c commit c1d9d42

7 files changed

Lines changed: 509 additions & 55 deletions

File tree

packages/types/src/vscode-extension-host.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export interface ExtensionMessage {
102102
| "worktreeIncludeStatus"
103103
| "branchWorktreeIncludeResult"
104104
| "folderSelected"
105+
| "rooHistoryImportProgress"
105106
| "skills"
106107
| "fileContent"
107108
text?: string
@@ -237,6 +238,16 @@ export interface ExtensionMessage {
237238
copyProgressItemName?: string
238239
// folderSelected
239240
path?: string
241+
// rooHistoryImportProgress
242+
rooHistoryImportProgress?: {
243+
status: "starting" | "copying" | "finished" | "failed"
244+
copiedFileCount: number
245+
totalFileCount: number
246+
importedTaskCount: number
247+
totalTaskCount: number
248+
currentTaskId?: string
249+
currentFileName?: string
250+
}
240251
}
241252

242253
export interface OpenAiCodexRateLimitsMessage {

src/core/task-persistence/__tests__/importRooTaskHistory.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,70 @@ describe("importRooTaskHistory", () => {
146146
})
147147
})
148148

149+
it("reports Roo history import progress as files are copied", async () => {
150+
const zooGlobalStoragePath = path.join(tempRoot, "globalStorage", "zoocodeorganization.zoo-code")
151+
const rooDefaultStorageRoot = path.join(tempRoot, "globalStorage", "rooveterinaryinc.roo-cline")
152+
const onProgress = vi.fn()
153+
154+
mockStorageConfiguration()
155+
156+
await fs.mkdir(path.join(rooDefaultStorageRoot, "tasks", "task-progress"), { recursive: true })
157+
await fs.writeFile(
158+
path.join(rooDefaultStorageRoot, "tasks", "task-progress", "history_item.json"),
159+
JSON.stringify({ id: "task-progress" }),
160+
)
161+
await fs.writeFile(path.join(rooDefaultStorageRoot, "tasks", "task-progress", "ui_messages.json"), "ui")
162+
await fs.writeFile(
163+
path.join(rooDefaultStorageRoot, "tasks", "task-progress", "api_conversation_history.json"),
164+
"api",
165+
)
166+
167+
await importRooTaskHistory(zooGlobalStoragePath, onProgress)
168+
169+
expect(onProgress.mock.calls).toEqual([
170+
[
171+
{
172+
copiedFileCount: 0,
173+
totalFileCount: 3,
174+
importedTaskCount: 0,
175+
totalTaskCount: 1,
176+
currentTaskId: undefined,
177+
currentFileName: undefined,
178+
},
179+
],
180+
[
181+
{
182+
copiedFileCount: 1,
183+
totalFileCount: 3,
184+
importedTaskCount: 1,
185+
totalTaskCount: 1,
186+
currentTaskId: "task-progress",
187+
currentFileName: "history_item.json",
188+
},
189+
],
190+
[
191+
{
192+
copiedFileCount: 2,
193+
totalFileCount: 3,
194+
importedTaskCount: 1,
195+
totalTaskCount: 1,
196+
currentTaskId: "task-progress",
197+
currentFileName: "ui_messages.json",
198+
},
199+
],
200+
[
201+
{
202+
copiedFileCount: 3,
203+
totalFileCount: 3,
204+
importedTaskCount: 1,
205+
totalTaskCount: 1,
206+
currentTaskId: "task-progress",
207+
currentFileName: "api_conversation_history.json",
208+
},
209+
],
210+
])
211+
})
212+
149213
it("imports only top-level task history files and skips checkpoint directories", async () => {
150214
const zooGlobalStoragePath = path.join(tempRoot, "globalStorage", "zoocodeorganization.zoo-code")
151215
const rooDefaultStorageRoot = path.join(tempRoot, "globalStorage", "rooveterinaryinc.roo-cline")

src/core/task-persistence/importRooTaskHistory.ts

Lines changed: 130 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ export interface RooHistoryImportResult extends RooHistoryImportPaths {
2929
importedFileCount: number
3030
}
3131

32+
export interface RooHistoryImportProgress {
33+
copiedFileCount: number
34+
totalFileCount: number
35+
importedTaskCount: number
36+
totalTaskCount: number
37+
currentTaskId?: string
38+
currentFileName?: string
39+
}
40+
41+
interface ImportableTaskPlan {
42+
taskId: string
43+
sourceTaskDirectory: string
44+
fileNames: string[]
45+
}
46+
3247
const toComparablePath = (candidatePath: string) => {
3348
const resolvedPath = path.resolve(candidatePath)
3449
return process.platform === "win32" ? resolvedPath.toLowerCase() : resolvedPath
@@ -90,6 +105,70 @@ const pathExists = async (candidatePath: string) => {
90105
}
91106
}
92107

108+
const getImportableTaskFileNames = async (sourceTaskDirectory: string) => {
109+
const fileNames: string[] = []
110+
111+
for (const fileName of IMPORTABLE_TASK_FILE_NAMES) {
112+
try {
113+
await fs.access(path.join(sourceTaskDirectory, fileName))
114+
fileNames.push(fileName)
115+
} catch (error) {
116+
if (isSkippableImportError(error)) {
117+
continue
118+
}
119+
120+
throw error
121+
}
122+
}
123+
124+
return fileNames
125+
}
126+
127+
const collectImportableTaskPlans = async (sourceRoots: string[]) => {
128+
const taskPlans: ImportableTaskPlan[] = []
129+
const taskIds = new Set<string>()
130+
131+
for (const sourceRoot of sourceRoots) {
132+
const sourceTasksRoot = path.join(sourceRoot, "tasks")
133+
let entries: Dirent[]
134+
135+
try {
136+
entries = await fs.readdir(sourceTasksRoot, { withFileTypes: true })
137+
} catch (error) {
138+
const nodeError = error as NodeJS.ErrnoException
139+
if (nodeError.code === "ENOENT") {
140+
continue
141+
}
142+
throw error
143+
}
144+
145+
for (const entry of entries) {
146+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) {
147+
continue
148+
}
149+
150+
const sourceTaskDirectory = path.join(sourceTasksRoot, entry.name)
151+
const fileNames = await getImportableTaskFileNames(sourceTaskDirectory)
152+
153+
if (!fileNames.includes(GlobalFileNames.historyItem)) {
154+
continue
155+
}
156+
157+
taskPlans.push({
158+
taskId: entry.name,
159+
sourceTaskDirectory,
160+
fileNames,
161+
})
162+
taskIds.add(entry.name)
163+
}
164+
}
165+
166+
return {
167+
taskPlans,
168+
totalTaskCount: taskIds.size,
169+
}
170+
}
171+
93172
export const resolveRooHistoryImportPaths = async (globalStoragePath: string): Promise<RooHistoryImportPaths> => {
94173
const zooExtensionDomain = `${Package.publisher}.${Package.name}`
95174
const zooStorageRoot = await getStorageBasePath(globalStoragePath)
@@ -104,65 +183,77 @@ export const resolveRooHistoryImportPaths = async (globalStoragePath: string): P
104183
}
105184
}
106185

107-
export const importRooTaskHistory = async (globalStoragePath: string): Promise<RooHistoryImportResult> => {
186+
export const importRooTaskHistory = async (
187+
globalStoragePath: string,
188+
onProgress?: (progress: RooHistoryImportProgress) => Promise<void> | void,
189+
): Promise<RooHistoryImportResult> => {
108190
const paths = await resolveRooHistoryImportPaths(globalStoragePath)
109191
const destinationComparablePath = toComparablePath(paths.zooStorageRoot)
110192
const sourceRoots = paths.rooStorageRoots.filter(
111193
(sourceRoot) => toComparablePath(sourceRoot) !== destinationComparablePath,
112194
)
113195
const destinationTasksRoot = path.join(paths.zooStorageRoot, "tasks")
196+
const { taskPlans, totalTaskCount } = await collectImportableTaskPlans(sourceRoots)
114197
const importedTaskIds = new Set<string>()
115198
let importedFileCount = 0
199+
let totalFileCount = taskPlans.reduce((count, taskPlan) => count + taskPlan.fileNames.length, 0)
200+
let copiedFileCount = 0
201+
202+
const reportProgress = async (currentTaskId?: string, currentFileName?: string) => {
203+
if (!onProgress) {
204+
return
205+
}
206+
207+
await onProgress({
208+
copiedFileCount,
209+
totalFileCount,
210+
importedTaskCount: importedTaskIds.size,
211+
totalTaskCount,
212+
currentTaskId,
213+
currentFileName,
214+
})
215+
}
116216

117217
await fs.mkdir(destinationTasksRoot, { recursive: true })
118218

119-
for (const sourceRoot of sourceRoots) {
120-
const sourceTasksRoot = path.join(sourceRoot, "tasks")
121-
let entries: Dirent[]
219+
await reportProgress()
122220

123-
try {
124-
entries = await fs.readdir(sourceTasksRoot, { withFileTypes: true })
125-
} catch (error) {
126-
const nodeError = error as NodeJS.ErrnoException
127-
if (nodeError.code === "ENOENT") {
128-
continue
221+
for (const taskPlan of taskPlans) {
222+
const destinationTaskDirectory = path.join(destinationTasksRoot, taskPlan.taskId)
223+
const destinationTaskDirectoryExisted = await pathExists(destinationTaskDirectory)
224+
const historyItemCopied = await copyTaskFileIfPresent(
225+
taskPlan.sourceTaskDirectory,
226+
destinationTaskDirectory,
227+
GlobalFileNames.historyItem,
228+
)
229+
230+
if (!historyItemCopied) {
231+
totalFileCount -= taskPlan.fileNames.length
232+
if (!destinationTaskDirectoryExisted) {
233+
await fs.rm(destinationTaskDirectory, { recursive: true, force: true })
129234
}
130-
throw error
235+
await reportProgress(taskPlan.taskId, GlobalFileNames.historyItem)
236+
continue
131237
}
132238

133-
for (const entry of entries) {
134-
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_")) {
135-
continue
136-
}
239+
importedTaskIds.add(taskPlan.taskId)
240+
importedFileCount += 1
241+
copiedFileCount += 1
242+
await reportProgress(taskPlan.taskId, GlobalFileNames.historyItem)
137243

138-
const sourceTaskDirectory = path.join(sourceTasksRoot, entry.name)
139-
const destinationTaskDirectory = path.join(destinationTasksRoot, entry.name)
140-
const destinationTaskDirectoryExisted = await pathExists(destinationTaskDirectory)
141-
const historyItemCopied = await copyTaskFileIfPresent(
142-
sourceTaskDirectory,
143-
destinationTaskDirectory,
144-
GlobalFileNames.historyItem,
145-
)
146-
147-
if (!historyItemCopied) {
148-
if (!destinationTaskDirectoryExisted) {
149-
await fs.rm(destinationTaskDirectory, { recursive: true, force: true })
150-
}
244+
for (const fileName of taskPlan.fileNames) {
245+
if (fileName === GlobalFileNames.historyItem) {
151246
continue
152247
}
153248

154-
importedTaskIds.add(entry.name)
155-
importedFileCount += 1
156-
157-
for (const fileName of IMPORTABLE_TASK_FILE_NAMES) {
158-
if (fileName === GlobalFileNames.historyItem) {
159-
continue
160-
}
161-
162-
if (await copyTaskFileIfPresent(sourceTaskDirectory, destinationTaskDirectory, fileName)) {
163-
importedFileCount += 1
164-
}
249+
if (await copyTaskFileIfPresent(taskPlan.sourceTaskDirectory, destinationTaskDirectory, fileName)) {
250+
importedFileCount += 1
251+
copiedFileCount += 1
252+
} else {
253+
totalFileCount -= 1
165254
}
255+
256+
await reportProgress(taskPlan.taskId, fileName)
166257
}
167258
}
168259

0 commit comments

Comments
 (0)