Repository navigation
fix(history): prompt on workspace mismatch #1660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
68ff869
9d6aefa
f0cc263
6b91307
7448eff
31822b1
2733098
16203ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -101,8 +101,9 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { fileExistsAtPath } from "../../utils/fs" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { setTtsEnabled, setTtsSpeed } from "../../utils/tts" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getWorkspaceGitInfo } from "../../utils/git" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getWorkspacePath } from "../../utils/path" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { arePathsEqual, getWorkspacePath } from "../../utils/path" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { OrganizationAllowListViolationError } from "../../utils/errors" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getTaskDirectoryPath } from "../../utils/storage" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { setPanel } from "../../activate/registerCommands" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -131,7 +132,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interruptDelegatedChild, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LifecycleTransitionError, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "../task-persistence" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readTaskMessages } from "../task-persistence/taskMessages" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { readTaskMessages, updateTaskMessages } from "../task-persistence/taskMessages" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getNonce } from "./getNonce" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getUri } from "./getUri" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { omitOriginalContentFromExtensionMessage } from "./stripOriginalContent" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2430,12 +2431,193 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (id !== this.getCurrentTask()?.taskId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Non-current task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { historyItem } = await this.getTaskWithId(id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.createTaskWithHistoryItem(historyItem) // Clears existing task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const preparedHistoryItem = await this.prepareHistoryItemForResume(historyItem) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!preparedHistoryItem) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.createTaskWithHistoryItem(preparedHistoryItem) // Clears existing task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async prepareHistoryItemForResume<T extends HistoryItem>(historyItem: T): Promise<T | undefined> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentWorkspace = this.cwd | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const originalWorkspace = historyItem.workspace | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The CLI has no modal to answer, and its host would wait forever for a task that is never created. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (process.env.ROO_CLI_RUNTIME === "1") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return historyItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!currentWorkspace || !originalWorkspace || arePathsEqual(currentWorkspace, originalWorkspace)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return historyItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // A live Task owns this history and would re-add checkpoint rows or rewrite its workspace after a reset. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.isTaskLiveInAnyProvider(historyItem.id)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return historyItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const useCurrentWorkspace = { title: "Use Current Workspace" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const openOriginalWorkspace = { title: "Open Original Workspace" } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selection = await vscode.window.showWarningMessage( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `This conversation was created in "${originalWorkspace}", but the current workspace is "${currentWorkspace}". ` + | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2465 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Choose where to continue. Using the current workspace resets checkpoints created in the original workspace.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2466 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { modal: true }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useCurrentWorkspace, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| openOriginalWorkspace, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (selection?.title === openOriginalWorkspace.title) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(originalWorkspace), { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| forceNewWindow: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (selection?.title !== useCurrentWorkspace.title) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2464
to
+2481
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Bypass the workspace prompt for CLI resume. The CLI sends The CLI does not call 🐛 Suggested fix
🧰 Tools
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The modal has no timeout, so the task may have been opened elsewhere while it was shown. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (this.isTaskLiveInAnyProvider(historyItem.id)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log(`[prepareHistoryItemForResume] Task ${historyItem.id} became live during workspace selection`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2485 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updatedHistoryItem = { ...historyItem, workspace: currentWorkspace } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, fixed in c47b767. Both the forward write and the rollback now go through |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const persisted = await this.resetTaskCheckpointsForWorkspaceChange(historyItem, updatedHistoryItem) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Resume from the record as persisted now, not the snapshot read before the prompt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { ...historyItem, ...persisted, workspace: currentWorkspace } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private isTaskLiveInAnyProvider(taskId: string): boolean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ClineProvider.getAllInstances().some((provider) => provider.taskRegistry.getById(taskId) !== undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Persist only `workspace`, read-modify-writing the stored record so fields that changed meanwhile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * (status, pendingAction, ...) are never overwritten from a stale snapshot. With `expectedWorkspace`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * the stored record is left unchanged unless it still has that workspace. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async setTaskHistoryWorkspace( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| historyItem: HistoryItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspace: string | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expectedWorkspace?: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<HistoryItem | undefined> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const taskId = historyItem.id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!this.taskHistoryStore.get(taskId)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Legacy fallback before the store is initialized: there is no stored record to preserve. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (expectedWorkspace !== undefined) return undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.updateTaskHistory({ ...historyItem, workspace }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.taskHistoryStore.get(taskId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let changed = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.taskHistoryStore.atomicReadAndUpdate(taskId, (current) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (expectedWorkspace !== undefined && current.workspace !== expectedWorkspace) return current | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| changed = current.workspace !== workspace | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2520 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { ...current, workspace } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2510
to
+2522
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Cover all The existing rollback test proves that a newer workspace is preserved, but it does not prove that the guarded rollback avoids a second The legacy fallback is reachable while startup is incomplete because
Additional assertion for the existing rollback test
🧰 Tools
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.recentTasksCache = undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const updated = this.taskHistoryStore.get(taskId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (changed && updated && this.isViewLaunched) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.postMessageToWebview({ type: "taskHistoryItemUpdated", taskHistoryItem: updated }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2526 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return updated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async resetTaskCheckpointsForWorkspaceChange( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| originalHistoryItem: HistoryItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedHistoryItem: HistoryItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<HistoryItem | undefined> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const taskId = originalHistoryItem.id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const globalStoragePath = this.contextProxy.globalStorageUri.fsPath | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let removedCheckpoints: ClineMessage[] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2537 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let persisted: HistoryItem | undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const taskDir = await getTaskDirectoryPath(globalStoragePath, taskId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const checkpointsDir = path.join(taskDir, "checkpoints") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const checkpointBackupDir = path.join(taskDir, `checkpoints.workspace-change-${crypto.randomUUID()}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let checkpointDirectoryStaged = false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 2542 in src/core/webview/ClineProvider.ts
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await fs.rename(checkpointsDir, checkpointBackupDir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkpointDirectoryStaged = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // History migration can create a record for a task directory that has no ui_messages.json; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // that is an empty set of checkpoint rows, not an error. Invalid existing content still rejects. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (await fileExistsAtPath(path.join(taskDir, GlobalFileNames.uiMessages))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await updateTaskMessages({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| taskId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| globalStoragePath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| update: (messages) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| removedCheckpoints = messages.filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (message) => message.type === "say" && message.say === "checkpoint_saved", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return messages.filter((message) => !removedCheckpoints.includes(message)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| persisted = await this.setTaskHistoryWorkspace(originalHistoryItem, updatedHistoryItem.workspace) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2553
to
+2568
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Check whether migrated/legacy history items can lack ui_messages.json, and how Task resume handles a missing file.
rg -nP -C4 'migrateFromGlobalState\s*\(' src/core/task-persistence/TaskHistoryStore.ts
rg -nP -C4 'readTaskMessages\s*\(|not_found' src/core/task --type=ts -g '!**/__tests__/**'Repository: Zoo-Code-Org/Zoo-Code Length of output: 1048 🏁 Script executed: set -o pipefail
printf '%s\n' '--- ClineProvider reset and imports ---'
rg -n -C8 -F -- 'resetTaskCheckpointsForWorkspaceChange' src/core/webview/ClineProvider.ts
rg -n -C5 -F -- 'updateTaskMessages' src/core/webview/ClineProvider.ts
printf '%s\n' '--- persistence definitions and consumers ---'
rg -n -C8 -F -- 'function updateTaskMessages' src/core
rg -n -C8 -F -- 'const updateTaskMessages' src/core
rg -n -C8 -F -- 'function readTaskMessages' src/core
rg -n -C8 -F -- 'readTaskMessages' src/core/task-persistence src/core/task --glob '*.ts'
printf '%s\n' '--- migration implementation ---'
sed -n '790,875p' src/core/task-persistence/TaskHistoryStore.ts
printf '%s\n' '--- relevant file-name and task creation writes ---'
rg -n -C5 -F -- 'ui_messages.json' src/core --glob '*.ts'Repository: Zoo-Code-Org/Zoo-Code Length of output: 41852 🏁 Script executed: set -o pipefail
printf '%s\n' '--- exact reset block and imports ---'
nl -ba src/core/webview/ClineProvider.ts | sed -n '1,155p;2525,2610p'
printf '%s\n' '--- exact task message persistence implementation ---'
nl -ba src/core/task-persistence/taskMessages.ts | sed -n '1,125p'
printf '%s\n' '--- migration implementation ---'
nl -ba src/core/task-persistence/TaskHistoryStore.ts | sed -n '790,875p'
printf '%s\n' '--- history/task file creation paths ---'
rg -n -C6 -F -- 'saveTaskMessages' src/core --glob '*.ts'
rg -n -C6 -F -- 'migrateFromGlobalState' src/core --glob '*.ts'Repository: Zoo-Code-Org/Zoo-Code Length of output: 42338 🏁 Script executed: set -o pipefail
printf '%s\n' '--- historical resume entrypoints ---'
rg -n -C12 -F -- 'showTaskWithId' src/core/webview/ClineProvider.ts src/core --glob '*.ts'
rg -n -C12 -F -- 'prepareHistoryItemForResume' src/core --glob '*.ts'
rg -n -C10 -F -- 'resumeTask' src/core --glob '*.ts'
printf '%s\n' '--- task construction/message loading ---'
rg -n -C10 -F -- 'getSavedClineMessages' src/core/task/Task.ts
rg -n -C10 -F -- 'readTaskMessages({' src/core/webview/ClineProvider.ts src/core/task/Task.tsRepository: Zoo-Code-Org/Zoo-Code Length of output: 42765 Skip checkpoint-message cleanup when When History migration creates Suggested fix try {
- await updateTaskMessages({
- taskId,
- globalStoragePath,
- update: (messages) => {
- removedCheckpoints = messages.filter(
- (message) => message.type === "say" && message.say === "checkpoint_saved",
- )
- return messages.filter((message) => !removedCheckpoints.includes(message))
- },
- })
+ const messagesPath = path.join(taskDir, GlobalFileNames.uiMessages)
+ if (await fileExistsAtPath(messagesPath)) {
+ await updateTaskMessages({
+ taskId,
+ globalStoragePath,
+ update: (messages) => {
+ removedCheckpoints = messages.filter(
+ (message) => message.type === "say" && message.say === "checkpoint_saved",
+ )
+ return messages.filter((message) => !removedCheckpoints.includes(message))
+ },
+ })
+ }
persisted = await this.setTaskHistoryWorkspace(originalHistoryItem, updatedHistoryItem.workspace)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (removedCheckpoints.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Restore only the removed rows; never replace intervening message writes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await saveTaskMessages({ messages: removedCheckpoints, taskId, globalStoragePath, merge: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (rollbackError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[resetTaskCheckpointsForWorkspaceChange] Failed to restore messages for ${taskId}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (checkpointDirectoryStaged) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await fs.rename(checkpointBackupDir, checkpointsDir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (rollbackError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[resetTaskCheckpointsForWorkspaceChange] Failed to restore checkpoints for ${taskId} from ${checkpointBackupDir}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Revert only the workspace, and only if no newer write has moved it again. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.setTaskHistoryWorkspace( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| originalHistoryItem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| originalHistoryItem.workspace, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updatedHistoryItem.workspace, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (rollbackError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[resetTaskCheckpointsForWorkspaceChange] Failed to restore history for ${taskId}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (checkpointDirectoryStaged) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let attempt = 0; attempt < 3; attempt++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await fs.rm(checkpointBackupDir, { recursive: true, force: true }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.log( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `[resetTaskCheckpointsForWorkspaceChange] Failed to remove checkpoint backup ${checkpointBackupDir}: ${error instanceof Error ? error.message : String(error)}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (attempt < 2) await delay(100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return persisted | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async exportTaskWithId(id: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { historyItem, apiConversationHistory } = await this.getTaskWithId(id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileName = getTaskFileName(historyItem.ts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.