mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add deferFileApprovalToCompletion setting for batch file approval
- Add new global setting deferFileApprovalToCompletion - Auto-approve file write operations when setting is enabled - Show Keep All Changes / Undo All Changes buttons at task completion - Restore initial checkpoint when user chooses to undo - Add i18n translations for new buttons Addresses Issue #10123
This commit is contained in:
parent
596783d365
commit
7b5b40e784
9 changed files with 106 additions and 7 deletions
|
|
@ -86,6 +86,13 @@ export const globalSettingsSchema = z.object({
|
|||
commandExecutionTimeout: z.number().optional(),
|
||||
commandTimeoutAllowlist: z.array(z.string()).optional(),
|
||||
preventCompletionWithOpenTodos: z.boolean().optional(),
|
||||
/**
|
||||
* When enabled, file write operations are auto-approved during task execution.
|
||||
* At task completion, the user is presented with "Keep All Changes" / "Undo All Changes" options.
|
||||
* This allows users to see the full result before deciding to keep or revert file changes.
|
||||
* @default false
|
||||
*/
|
||||
deferFileApprovalToCompletion: z.boolean().optional(),
|
||||
allowedMaxRequests: z.number().nullish(),
|
||||
allowedMaxCost: z.number().nullish(),
|
||||
autoCondenseContext: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export type AutoApprovalStateOptions =
|
|||
| "alwaysAllowReadOnlyOutsideWorkspace" // For `alwaysAllowReadOnly`.
|
||||
| "alwaysAllowWriteOutsideWorkspace" // For `alwaysAllowWrite`.
|
||||
| "alwaysAllowWriteProtected"
|
||||
| "deferFileApprovalToCompletion" // Auto-approve file writes, let user undo at completion.
|
||||
| "followupAutoApproveTimeoutMs" // For `alwaysAllowFollowupQuestions`.
|
||||
| "mcpServers" // For `alwaysAllowMcp`.
|
||||
| "allowedCommands" // For `alwaysAllowExecute`.
|
||||
|
|
@ -173,11 +174,20 @@ export async function checkAutoApproval({
|
|||
}
|
||||
|
||||
if (isWriteToolAction(tool)) {
|
||||
return state.alwaysAllowWrite === true &&
|
||||
// When deferFileApprovalToCompletion is enabled, auto-approve file writes
|
||||
// (except for protected files and files outside workspace, unless explicitly allowed)
|
||||
// The user can undo all changes at task completion
|
||||
const isDeferredApproval =
|
||||
state.deferFileApprovalToCompletion === true &&
|
||||
(!isOutsideWorkspace || state.alwaysAllowWriteOutsideWorkspace === true) &&
|
||||
(!isProtected || state.alwaysAllowWriteProtected === true)
|
||||
? { decision: "approve" }
|
||||
: { decision: "ask" }
|
||||
|
||||
const isNormalApproval =
|
||||
state.alwaysAllowWrite === true &&
|
||||
(!isOutsideWorkspace || state.alwaysAllowWriteOutsideWorkspace === true) &&
|
||||
(!isProtected || state.alwaysAllowWriteProtected === true)
|
||||
|
||||
return isDeferredApproval || isNormalApproval ? { decision: "approve" } : { decision: "ask" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4089,6 +4089,18 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
return checkpointDiff(this, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial checkpoint hash (first checkpoint saved for this task).
|
||||
* This is used for "undo all changes" functionality when deferFileApprovalToCompletion is enabled.
|
||||
*
|
||||
* @returns The hash of the first checkpoint, or undefined if no checkpoints exist
|
||||
*/
|
||||
public getInitialCheckpointHash(): string | undefined {
|
||||
// Find the first checkpoint_saved message in clineMessages
|
||||
const firstCheckpoint = this.clineMessages.find((msg) => msg.say === "checkpoint_saved")
|
||||
return firstCheckpoint?.text
|
||||
}
|
||||
|
||||
// Metrics
|
||||
|
||||
public combineMessages(messages: ClineMessage[]) {
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,39 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
case "keepAllChanges":
|
||||
// User chose to keep all changes at task completion - nothing to do
|
||||
// The changes were already applied during task execution
|
||||
provider.log("User chose to keep all changes at task completion")
|
||||
break
|
||||
case "undoAllChanges": {
|
||||
// User chose to undo all changes at task completion
|
||||
// Restore to the initial checkpoint from the beginning of the task
|
||||
provider.log("User chose to undo all changes at task completion")
|
||||
const currentCline = provider.getCurrentTask()
|
||||
if (currentCline?.checkpointService) {
|
||||
try {
|
||||
// Get the initial checkpoint hash - the first checkpoint saved for this task
|
||||
const initialCheckpointHash = currentCline.getInitialCheckpointHash?.()
|
||||
if (initialCheckpointHash) {
|
||||
await currentCline.checkpointService.restoreCheckpoint(initialCheckpointHash)
|
||||
provider.log(`Restored to initial checkpoint: ${initialCheckpointHash}`)
|
||||
} else {
|
||||
provider.log("No initial checkpoint hash available for restoration")
|
||||
vscode.window.showWarningMessage(t("common:errors.no_initial_checkpoint"))
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
provider.log(`Failed to restore initial checkpoint: ${errorMessage}`)
|
||||
vscode.window.showErrorMessage(
|
||||
t("common:errors.checkpoint_restore_failed", { error: errorMessage }),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
provider.log("No checkpoint service available for restoration")
|
||||
}
|
||||
break
|
||||
}
|
||||
case "cancelTask":
|
||||
await provider.cancelTask()
|
||||
break
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@
|
|||
"could_not_open_file_generic": "Could not open file!",
|
||||
"checkpoint_timeout": "Timed out when attempting to restore checkpoint.",
|
||||
"checkpoint_failed": "Failed to restore checkpoint.",
|
||||
"checkpoint_restore_failed": "Failed to restore initial checkpoint: {{error}}",
|
||||
"no_initial_checkpoint": "No initial checkpoint available. Unable to undo changes.",
|
||||
"git_not_installed": "Git is required for the checkpoints feature. Please install Git to enable checkpoints.",
|
||||
"checkpoint_no_first": "No first checkpoint to compare.",
|
||||
"checkpoint_no_previous": "No previous checkpoint to compare.",
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ export type ExtensionState = Pick<
|
|||
| "deniedCommands"
|
||||
| "allowedMaxRequests"
|
||||
| "allowedMaxCost"
|
||||
| "deferFileApprovalToCompletion"
|
||||
| "browserToolEnabled"
|
||||
| "browserViewportSize"
|
||||
| "screenshotQuality"
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ export interface WebviewMessage {
|
|||
| "browserPanelDidLaunch"
|
||||
| "openDebugApiHistory"
|
||||
| "openDebugUiHistory"
|
||||
| "keepAllChanges"
|
||||
| "undoAllChanges"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
cloudIsAuthenticated,
|
||||
messageQueue = [],
|
||||
isBrowserSessionActive,
|
||||
deferFileApprovalToCompletion,
|
||||
} = useExtensionState()
|
||||
|
||||
const messagesRef = useRef(messages)
|
||||
|
|
@ -360,8 +361,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
setSendingDisabled(isPartial)
|
||||
setClineAsk("completion_result")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
// When deferFileApprovalToCompletion is enabled, show Keep/Undo buttons
|
||||
if (deferFileApprovalToCompletion) {
|
||||
setPrimaryButtonText(t("chat:keepAllChanges.title"))
|
||||
setSecondaryButtonText(t("chat:undoAllChanges.title"))
|
||||
} else {
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
}
|
||||
break
|
||||
case "resume_task":
|
||||
setSendingDisabled(false)
|
||||
|
|
@ -722,6 +729,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
break
|
||||
case "completion_result":
|
||||
// When deferFileApprovalToCompletion is enabled, primary button is "Keep All Changes"
|
||||
// which keeps the changes and starts a new task
|
||||
if (deferFileApprovalToCompletion) {
|
||||
// Notify extension to keep all changes (don't restore checkpoint)
|
||||
vscode.postMessage({ type: "keepAllChanges" })
|
||||
}
|
||||
startNewTask()
|
||||
break
|
||||
case "resume_completed_task":
|
||||
// Waiting for feedback, but we can just present a new task button
|
||||
startNewTask()
|
||||
|
|
@ -735,7 +750,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
setClineAsk(undefined)
|
||||
setEnableButtons(false)
|
||||
},
|
||||
[clineAsk, startNewTask, currentTaskItem?.parentTaskId],
|
||||
[clineAsk, startNewTask, currentTaskItem?.parentTaskId, deferFileApprovalToCompletion],
|
||||
)
|
||||
|
||||
const handleSecondaryButtonClick = useCallback(
|
||||
|
|
@ -757,6 +772,15 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
case "resume_task":
|
||||
startNewTask()
|
||||
break
|
||||
case "completion_result":
|
||||
// When deferFileApprovalToCompletion is enabled, secondary button is "Undo All Changes"
|
||||
// which restores the checkpoint and starts a new task
|
||||
if (deferFileApprovalToCompletion) {
|
||||
// Notify extension to undo all changes (restore to initial checkpoint)
|
||||
vscode.postMessage({ type: "undoAllChanges" })
|
||||
}
|
||||
startNewTask()
|
||||
break
|
||||
case "command":
|
||||
case "tool":
|
||||
case "browser_action_launch":
|
||||
|
|
@ -785,7 +809,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
setClineAsk(undefined)
|
||||
setEnableButtons(false)
|
||||
},
|
||||
[clineAsk, startNewTask, isStreaming],
|
||||
[clineAsk, startNewTask, isStreaming, deferFileApprovalToCompletion],
|
||||
)
|
||||
|
||||
const { info: model } = useSelectedModel(apiConfiguration)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@
|
|||
"title": "Terminate",
|
||||
"tooltip": "End the current task"
|
||||
},
|
||||
"keepAllChanges": {
|
||||
"title": "Keep All Changes",
|
||||
"tooltip": "Keep all file changes and start a new task"
|
||||
},
|
||||
"undoAllChanges": {
|
||||
"title": "Undo All Changes",
|
||||
"tooltip": "Restore files to initial state and start a new task"
|
||||
},
|
||||
"cancel": {
|
||||
"title": "Cancel",
|
||||
"tooltip": "Cancel the current operation"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue