Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/types/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { Keys, Equals, AssertEqual } from "./type-fu.js"
* ExperimentId
*/

export const experimentIds = ["powerSteering", "multiFileApplyDiff"] as const
export const experimentIds = ["powerSteering", "multiFileApplyDiff", "preventFocusDisruption"] as const

export const experimentIdsSchema = z.enum(experimentIds)

Expand All @@ -19,6 +19,7 @@ export type ExperimentId = z.infer<typeof experimentIdsSchema>
export const experimentsSchema = z.object({
powerSteering: z.boolean().optional(),
multiFileApplyDiff: z.boolean().optional(),
preventFocusDisruption: z.boolean().optional(),
})

export type Experiments = z.infer<typeof experimentsSchema>
Expand Down
95 changes: 66 additions & 29 deletions src/core/tools/applyDiffTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatResponse } from "../prompts/responses"
import { fileExistsAtPath } from "../../utils/fs"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"

export async function applyDiffToolLegacy(
cline: Task,
Expand Down Expand Up @@ -87,7 +88,7 @@ export async function applyDiffToolLegacy(
return
}

let originalContent: string | null = await fs.readFile(absolutePath, "utf-8")
const originalContent: string = await fs.readFile(absolutePath, "utf-8")

// Apply the diff to the original content
const diffResult = (await cline.diffStrategy?.applyDiff(
Expand All @@ -99,9 +100,6 @@ export async function applyDiffToolLegacy(
error: "No diff strategy available",
}

// Release the original content from memory as it's no longer needed
originalContent = null

if (!diffResult.success) {
cline.consecutiveMistakeCount++
const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1
Expand Down Expand Up @@ -142,40 +140,79 @@ export async function applyDiffToolLegacy(
cline.consecutiveMistakeCount = 0
cline.consecutiveMistakeCountForApplyDiff.delete(relPath)

// Show diff view before asking for approval
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(diffResult.content, true)
cline.diffViewProvider.scrollToFirstDiff()
// Check if preventFocusDisruption experiment is enabled
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
const isPreventFocusDisruptionEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION,
)

// Check if file is write-protected
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false

const completeMessage = JSON.stringify({
...sharedMessageProps,
diff: diffContent,
isProtected: isWriteProtected,
} satisfies ClineSayTool)
if (isPreventFocusDisruptionEnabled) {
// Direct file write without diff view
const completeMessage = JSON.stringify({
...sharedMessageProps,
diff: diffContent,
isProtected: isWriteProtected,
} satisfies ClineSayTool)

let toolProgressStatus
let toolProgressStatus

if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) {
toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult)
}
if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) {
toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult)
}

const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected)
const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected)

if (!didApprove) {
await cline.diffViewProvider.revertChanges() // Cline likely handles closing the diff view
return
}
if (!didApprove) {
return
}

// Call saveChanges to update the DiffViewProvider properties
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Save directly without showing diff view or opening the file
cline.diffViewProvider.editType = "modify"
cline.diffViewProvider.originalContent = originalContent
await cline.diffViewProvider.saveDirectly(
relPath,
diffResult.content,
false,
diagnosticsEnabled,
writeDelayMs,
)
} else {
// Original behavior with diff view
// Show diff view before asking for approval
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(diffResult.content, true)
cline.diffViewProvider.scrollToFirstDiff()

const completeMessage = JSON.stringify({
...sharedMessageProps,
diff: diffContent,
isProtected: isWriteProtected,
} satisfies ClineSayTool)

let toolProgressStatus

if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) {
toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult)
}

const didApprove = await askApproval("tool", completeMessage, toolProgressStatus, isWriteProtected)

if (!didApprove) {
await cline.diffViewProvider.revertChanges() // Cline likely handles closing the diff view
return
}

// Call saveChanges to update the DiffViewProvider properties
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
}

// Track file edit operation
if (relPath) {
Expand Down
51 changes: 33 additions & 18 deletions src/core/tools/insertContentTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { fileExistsAtPath } from "../../utils/fs"
import { insertGroups } from "../diff/insert-groups"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"

export async function insertContentTool(
cline: Task,
Expand Down Expand Up @@ -107,15 +108,15 @@ export async function insertContentTool(
},
]).join("\n")

// Show changes in diff view
if (!cline.diffViewProvider.isEditing) {
await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {})
// First open with original content
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(fileContent, false)
cline.diffViewProvider.scrollToFirstDiff()
await delay(200)
}
// Check if preventFocusDisruption experiment is enabled
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
const isPreventFocusDisruptionEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION,
)

// For consistency with writeToFileTool, handle new files differently
let diff: string | undefined
Expand All @@ -135,8 +136,6 @@ export async function insertContentTool(
approvalContent = updatedContent
}

await cline.diffViewProvider.update(updatedContent, true)

const completeMessage = JSON.stringify({
...sharedMessageProps,
diff,
Expand All @@ -150,17 +149,33 @@ export async function insertContentTool(
.then((response) => response.response === "yesButtonClicked")

if (!didApprove) {
await cline.diffViewProvider.revertChanges()
if (!isPreventFocusDisruptionEnabled) {
await cline.diffViewProvider.revertChanges()
}
pushToolResult("Changes were rejected by the user.")
return
}

// Call saveChanges to update the DiffViewProvider properties
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
if (isPreventFocusDisruptionEnabled) {
// Direct file write without diff view or opening the file
await cline.diffViewProvider.saveDirectly(relPath, updatedContent, false, diagnosticsEnabled, writeDelayMs)
} else {
// Original behavior with diff view
// Show changes in diff view
if (!cline.diffViewProvider.isEditing) {
await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {})
// First open with original content
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(fileContent, false)
cline.diffViewProvider.scrollToFirstDiff()
await delay(200)
}

await cline.diffViewProvider.update(updatedContent, true)

// Call saveChanges to update the DiffViewProvider properties
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
}

// Track file edit operation
if (relPath) {
Expand Down
46 changes: 34 additions & 12 deletions src/core/tools/multiApplyDiffTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,11 +507,15 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
cline.consecutiveMistakeCount = 0
cline.consecutiveMistakeCountForApplyDiff.delete(relPath)

// Show diff view before asking for approval (only for single file or after batch approval)
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(originalContent!, true)
cline.diffViewProvider.scrollToFirstDiff()
// Check if preventFocusDisruption experiment is enabled
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
const isPreventFocusDisruptionEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION,
)

// For batch operations, we've already gotten approval
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false
Expand Down Expand Up @@ -548,17 +552,35 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
}

if (!didApprove) {
await cline.diffViewProvider.revertChanges()
if (!isPreventFocusDisruptionEnabled) {
await cline.diffViewProvider.revertChanges()
}
results.push(`Changes to ${relPath} were not approved by user`)
continue
}

// Call saveChanges to update the DiffViewProvider properties
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
if (isPreventFocusDisruptionEnabled) {
// Direct file write without diff view or opening the file
cline.diffViewProvider.editType = "modify"
cline.diffViewProvider.originalContent = await fs.readFile(absolutePath, "utf-8")
await cline.diffViewProvider.saveDirectly(
relPath,
originalContent!,
false,
diagnosticsEnabled,
writeDelayMs,
)
} else {
// Original behavior with diff view
// Show diff view before asking for approval (only for single file or after batch approval)
cline.diffViewProvider.editType = "modify"
await cline.diffViewProvider.open(relPath)
await cline.diffViewProvider.update(originalContent!, true)
cline.diffViewProvider.scrollToFirstDiff()

// Call saveChanges to update the DiffViewProvider properties
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
}

// Track file edit operation
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
Expand Down
49 changes: 32 additions & 17 deletions src/core/tools/searchAndReplaceTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getReadablePath } from "../../utils/path"
import { fileExistsAtPath } from "../../utils/fs"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"

/**
* Tool for performing search and replace operations on files
Expand Down Expand Up @@ -199,16 +200,15 @@ export async function searchAndReplaceTool(
return
}

// Show changes in diff view
if (!cline.diffViewProvider.isEditing) {
await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {})
await cline.diffViewProvider.open(validRelPath)
await cline.diffViewProvider.update(fileContent, false)
cline.diffViewProvider.scrollToFirstDiff()
await delay(200)
}

await cline.diffViewProvider.update(newContent, true)
// Check if preventFocusDisruption experiment is enabled
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
const isPreventFocusDisruptionEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION,
)

// Request user approval for changes
const completeMessage = JSON.stringify({
Expand All @@ -221,18 +221,33 @@ export async function searchAndReplaceTool(
.then((response) => response.response === "yesButtonClicked")

if (!didApprove) {
await cline.diffViewProvider.revertChanges()
if (!isPreventFocusDisruptionEnabled) {
await cline.diffViewProvider.revertChanges()
}
pushToolResult("Changes were rejected by the user.")
await cline.diffViewProvider.reset()
return
}

// Call saveChanges to update the DiffViewProvider properties
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
if (isPreventFocusDisruptionEnabled) {
// Direct file write without diff view or opening the file
await cline.diffViewProvider.saveDirectly(validRelPath, newContent, false, diagnosticsEnabled, writeDelayMs)
} else {
// Original behavior with diff view
// Show changes in diff view
if (!cline.diffViewProvider.isEditing) {
await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {})
await cline.diffViewProvider.open(validRelPath)
await cline.diffViewProvider.update(fileContent, false)
cline.diffViewProvider.scrollToFirstDiff()
await delay(200)
}

await cline.diffViewProvider.update(newContent, true)

// Call saveChanges to update the DiffViewProvider properties
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
}

// Track file edit operation
if (relPath) {
Expand Down
Loading