Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
Loading