Compare commits

...

6 Commits

Author SHA1 Message Date
abeatrix 4c1f8bcb63 diff 2025-11-08 01:01:58 -08:00
abeatrix ad86ab6740 Merge branch 'main' into bee/background-edit 2025-10-30 18:16:51 -07:00
abeatrix 97041c2df4 Merge branch 'main' into bee/background-edit 2025-10-28 18:09:45 -07:00
abeatrix 27a224c112 rename to FileEditProvider 2025-10-28 17:37:29 -07:00
abeatrix bd5ba4e87b add changeset 2025-10-28 17:29:25 -07:00
abeatrix 9b3736ae63 feat: add background edit mode toggle setting
Add a new `backgroundEditEnabled` setting that allows users to toggle between traditional diff view and background edit mode. The setting:

- Adds `background_edit_enabled` field to UpdateSettingsRequest proto
- Stores the setting in global state with default value of false
- Conditionally initializes BackgroundEditProvider when enabled, otherwise uses default DiffViewProvider
- Propagates the setting through controller state to webview

This enables users to opt-in to background editing functionality while maintaining backward compatibility with the existing diff-based workflow.
2025-10-28 17:26:31 -07:00
24 changed files with 1327 additions and 1075 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
feat: background diff edit mode - allows cline to edit files in the background without stealing focus from your editor.
+45 -45
View File
@@ -1,47 +1,47 @@
{
"name": "cline-evals",
"version": "0.1.0",
"description": "Evaluation scripts and tools for Cline",
"main": "cli/dist/index.js",
"scripts": {
"build:cli": "cd cli && tsc",
"start:cli": "cd cli && node dist/index.js",
"dev:cli": "cd cli && ts-node src/index.ts",
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cline",
"evaluation",
"benchmark",
"diff-edits"
],
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.12.0",
"better-sqlite3": "^12.4.1",
"chalk": "5.6.2",
"dotenv": "^16.5.0",
"commander": "^9.4.1",
"execa": "^5.1.1",
"node-fetch": "^2.7.0",
"ora": "^5.4.1",
"sqlite": "^4.1.2",
"tiktoken": "^1.0.21",
"uuid": "^9.0.0",
"yargs": "^17.6.2"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.3",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.12",
"@types/uuid": "^9.0.0",
"@types/yargs": "^17.0.19",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"overrides": {
"tar-fs": "^3.1.1"
}
"name": "cline-evals",
"version": "0.1.0",
"description": "Evaluation scripts and tools for Cline",
"main": "cli/dist/index.js",
"scripts": {
"build:cli": "cd cli && tsc",
"start:cli": "cd cli && node dist/index.js",
"dev:cli": "cd cli && ts-node src/index.ts",
"diff-eval": "./diff-edits/run_and_open_dashboard.sh",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"cline",
"evaluation",
"benchmark",
"diff-edits"
],
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.12.0",
"better-sqlite3": "^12.4.1",
"chalk": "5.6.2",
"dotenv": "^16.5.0",
"commander": "^9.4.1",
"execa": "^5.1.1",
"node-fetch": "^2.7.0",
"ora": "^5.4.1",
"sqlite": "^4.1.2",
"tiktoken": "^1.0.21",
"uuid": "^9.0.0",
"yargs": "^17.6.2"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.3",
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.12",
"@types/uuid": "^9.0.0",
"@types/yargs": "^17.0.19",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"overrides": {
"tar-fs": "^3.1.1"
}
}
+1
View File
@@ -351,6 +351,7 @@ message UpdateSettingsRequest {
optional int32 subagent_terminal_output_line_limit = 30;
optional string cline_env = 31;
optional bool native_tool_call_enabled = 32;
optional bool background_edit_enabled = 33;
}
message UpdateTerminalConnectionTimeoutRequest {
+1
View File
@@ -65,6 +65,7 @@ enum ClineSay {
INFO = 26;
TASK_PROGRESS = 27;
ERROR_RETRY = 28;
DIFF_EDITING = 29;
}
// Enum for ClineSayTool tool types
+2
View File
@@ -820,6 +820,7 @@ export class Controller {
const lastDismissedModelBannerVersion = this.stateManager.getGlobalStateKey("lastDismissedModelBannerVersion") || 0
const lastDismissedCliBannerVersion = this.stateManager.getGlobalStateKey("lastDismissedCliBannerVersion") || 0
const subagentsEnabled = this.stateManager.getGlobalSettingsKey("subagentsEnabled")
const backgroundEditEnabled = this.stateManager.getGlobalSettingsKey("backgroundEditEnabled")
const localClineRulesToggles = this.stateManager.getWorkspaceStateKey("localClineRulesToggles")
const localWindsurfRulesToggles = this.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
@@ -915,6 +916,7 @@ export class Controller {
remoteConfigSettings: this.stateManager.getRemoteConfigSettings(),
lastDismissedCliBannerVersion,
subagentsEnabled,
backgroundEditEnabled,
nativeToolCallSetting: {
user: this.stateManager.getGlobalStateKey("nativeToolCallEnabled"),
featureFlag: featureFlagsService.getNativeToolCallEnabled(),
@@ -349,6 +349,10 @@ export async function updateSettings(controller: Controller, request: UpdateSett
controller.stateManager.setGlobalState("subagentsEnabled", !!request.subagentsEnabled)
}
if (request.backgroundEditEnabled !== undefined) {
controller.stateManager.setGlobalState("backgroundEditEnabled", !!request.backgroundEditEnabled)
}
if (request.nativeToolCallEnabled !== undefined) {
controller.stateManager.setGlobalState("nativeToolCallEnabled", !!request.nativeToolCallEnabled)
}
@@ -45,8 +45,7 @@ export const config = createVariant(ModelFamily.NATIVE_GPT_5)
.tools(
ClineDefaultTool.BASH,
ClineDefaultTool.FILE_READ,
ClineDefaultTool.FILE_NEW,
ClineDefaultTool.FILE_EDIT,
ClineDefaultTool.APPLY_PATCH,
ClineDefaultTool.SEARCH,
ClineDefaultTool.LIST_FILES,
ClineDefaultTool.LIST_CODE_DEF,
@@ -15,10 +15,6 @@ export const BASE = `{{${SystemPromptSection.AGENT_ROLE}}}
====
{{${SystemPromptSection.EDITING_FILES}}}
====
{{${SystemPromptSection.ACT_VS_PLAN}}}
====
+3
View File
@@ -296,6 +296,8 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
const openTelemetryLogMaxQueueSize =
context.globalState.get<GlobalStateAndSettings["openTelemetryLogMaxQueueSize"]>("openTelemetryLogMaxQueueSize")
const subagentsEnabled = context.globalState.get<GlobalStateAndSettings["subagentsEnabled"]>("subagentsEnabled")
const backgroundEditEnabled =
context.globalState.get<GlobalStateAndSettings["backgroundEditEnabled"]>("backgroundEditEnabled")
// Get mode-related configurations
const mode = context.globalState.get<GlobalStateAndSettings["mode"]>("mode")
@@ -622,6 +624,7 @@ export async function readGlobalStateFromDisk(context: ExtensionContext): Promis
// Hooks require explicit user opt-in
hooksEnabled: hooksEnabled ?? false,
subagentsEnabled: subagentsEnabled ?? false,
backgroundEditEnabled: backgroundEditEnabled ?? false,
lastDismissedInfoBannerVersion: lastDismissedInfoBannerVersion ?? 0,
lastDismissedModelBannerVersion: lastDismissedModelBannerVersion ?? 0,
lastDismissedCliBannerVersion: lastDismissedCliBannerVersion ?? 0,
+21 -1
View File
@@ -81,6 +81,7 @@ import type { SystemPromptContext } from "@/core/prompts/system-prompt"
import { getSystemPrompt } from "@/core/prompts/system-prompt"
import { HostProvider } from "@/hosts/host-provider"
import { isSubagentCommand, transformClineCommand } from "@/integrations/cli-subagents/subagent_command"
import { FileEditProvider } from "@/integrations/editor/FileEditProvider"
import { ClineError, ClineErrorType, ErrorService } from "@/services/error"
import { TerminalHangStage, TerminalUserInterventionAction, telemetryService } from "@/services/telemetry"
import { ShowMessageType } from "@/shared/proto/index.host"
@@ -273,12 +274,17 @@ export class Task {
this.urlContentFetcher = new UrlContentFetcher(controller.context)
this.browserSession = new BrowserSession(stateManager)
this.contextManager = new ContextManager()
this.diffViewProvider = HostProvider.get().createDiffViewProvider()
this.toolUseHandler = new ToolUseHandler()
this.cwd = cwd
this.stateManager = stateManager
this.workspaceManager = workspaceManager
const backgroundEditSettingEnabled = this.stateManager.getGlobalSettingsKey("backgroundEditEnabled")
this.diffViewProvider = backgroundEditSettingEnabled
? new FileEditProvider()
: HostProvider.get().createDiffViewProvider()
// Set up MCP notification callback for real-time notifications
this.mcpHub.setNotificationCallback(async (serverName: string, _level: string, message: string) => {
// Display notification in chat immediately
@@ -650,6 +656,20 @@ export class Task {
throw new Error("Cline instance aborted")
}
if (type === "diff_editing") {
this.messageStateHandler.setClineMessageInProgress({
ts: 0,
type: "say",
say: type,
text,
images,
files,
partial,
})
await this.postStateToWebview()
return undefined
}
if (partial !== undefined) {
const lastMessage = this.messageStateHandler.getClineMessages().at(-1)
const isUpdatingPreviousPartial =
+28 -1
View File
@@ -29,6 +29,12 @@ export class MessageStateHandler {
private taskId: string
private ulid: string
private taskState: TaskState
/**
* The Cline message that is currently being streamed to the UI.
* This message is not yet part of the clineMessages array until
* it is marked as complete (partial === false).
*/
private clineMessageInProgress: ClineMessage | null = null
constructor(params: MessageStateHandlerParams) {
this.taskId = params.taskId
@@ -38,6 +44,22 @@ export class MessageStateHandler {
this.updateTaskHistory = params.updateTaskHistory
}
/**
* Sets the cline message that is currently being streamed.
* If the message is marked as complete (partial === false),
* it is added to the list of cline messages and the in-progress
* message is cleared.
* @param message The cline message to set as in-progress.
*/
setClineMessageInProgress(stream: ClineMessage) {
const ts = this.clineMessageInProgress?.ts ?? Date.now()
this.clineMessageInProgress = { ...stream, ts }
if (stream.partial === false) {
this.clineMessages.push(this.clineMessageInProgress)
this.clineMessageInProgress = null
}
}
setCheckpointTracker(tracker: CheckpointTracker | undefined) {
this.checkpointTracker = tracker
}
@@ -51,7 +73,12 @@ export class MessageStateHandler {
}
getClineMessages(): ClineMessage[] {
return this.clineMessages
if (this.clineMessageInProgress === null) {
return this.clineMessages
}
// Append the in-progress message to the list for display purposes.
// We do not save this combined list until the in-progress message is complete.
return [...this.clineMessages, this.clineMessageInProgress]
}
setClineMessages(newMessages: ClineMessage[]) {
File diff suppressed because it is too large Load Diff
@@ -72,6 +72,11 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
await uiHelpers.ask("tool", partialMessage, block.partial).catch(() => {})
}
// const diffBlock = diff && generateDiff(diff)
// if (diffBlock) {
// await config.callbacks.say("diff_editing", diffBlock, undefined, undefined, true)
// }
// CRITICAL: Open editor and stream content in real-time (from original code)
if (!config.services.diffViewProvider.isEditing) {
// Open the editor and prepare to stream content in
@@ -130,6 +135,11 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
const { relPath, absolutePath, fileExists, diff, content, newContent, workspaceContext } = result
// const diffBlock = diff && generateDiff(diff)
// if (diffBlock) {
// await config.callbacks.say("diff_editing", diffBlock, undefined, undefined, true)
// }
// Handle approval flow
const sharedMessageProps: ClineSayTool = {
tool: fileExists ? "editedExistingFile" : "newFileCreated",
@@ -450,3 +460,43 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
return { relPath, absolutePath, fileExists, diff, content, newContent, workspaceContext }
}
}
function generateDiff(block: string): string | undefined {
// Regex to extract SEARCH and REPLACE blocks
// Matches content between "------- SEARCH" and "=======" for search block
// Matches content between "=======" and "+++++++ REPLACE" for replace block
const searchRegex = /-------\s*SEARCH\s*\n([\s\S]*?)\n\s*=======/
const replaceRegex = /=======\s*\n([\s\S]*?)\n\s*\+\+\+\+\+\+\+\s*REPLACE/
// Extract the blocks
const searchMatch = block.match(searchRegex)
const replaceMatch = block.match(replaceRegex)
if (!searchMatch || !replaceMatch) {
// throw new Error('Invalid diff block format. Expected "------- SEARCH", "=======", and "+++++++ REPLACE" markers.')
return undefined
}
const searchBlock = searchMatch[1]
const replaceBlock = replaceMatch[1]
// Split blocks into lines
const searchLines = searchBlock.split("\n")
const replaceLines = replaceBlock.split("\n")
// Build the diff output
const diffLines: string[] = []
// Add all search lines with "- " prefix
for (const line of searchLines) {
diffLines.push(`- ${line}`)
}
// Add all replace lines with "+ " prefix
for (const line of replaceLines) {
diffLines.push(`+ ${line}`)
}
// Join into final diff string
return diffLines.join("\n")
}
+28 -1
View File
@@ -150,6 +150,13 @@ export abstract class DiffViewProvider {
*/
protected abstract resetDiffView(): Promise<void>
/**
* Shows the document in the editor.
*/
async showDocument(absolutePath: string): Promise<void> {
await openFile(absolutePath, true)
}
async update(
accumulatedContent: string,
isFinal: boolean,
@@ -268,7 +275,7 @@ export abstract class DiffViewProvider {
// get text after save in case there is any auto-formatting done by the editor
const postSaveContent = (await this.getDocumentText()) || ""
await this.showFile(this.absolutePath)
await this.showDocument(this.absolutePath)
await this.closeAllDiffViews()
const newProblems = await this.getNewDiagnosticProblems()
@@ -372,6 +379,26 @@ export abstract class DiffViewProvider {
}
}
async deleteFile() {
if (!this.absolutePath || !this.isEditing) {
return
}
// Close diff views before deleting the file
await this.closeAllDiffViews()
// Delete the file
try {
await fs.rm(this.absolutePath, { force: true })
console.log(`File ${this.absolutePath} has been deleted.`)
} catch (error) {
console.error(`Failed to delete file ${this.absolutePath}:`, error)
}
// edit is done
await this.reset()
}
// close editor if open?
async reset() {
this.isEditing = false
+4 -5
View File
@@ -16,11 +16,6 @@ export class FileEditProvider extends DiffViewProvider {
super()
}
override showFile(_absolutePath: string): Promise<void> {
// No-op: No visual editor to show the file
return Promise.resolve()
}
protected async openDiffEditor(): Promise<void> {
// No-op: No visual editor to open in a file-system-only provider
// The file content is already loaded in the base class's open() method
@@ -58,6 +53,10 @@ export class FileEditProvider extends DiffViewProvider {
this.documentContent = lines.join("\n")
}
override async showDocument(_absolutePath: string): Promise<void> {
// No-op: No visual editor to show the file
}
protected async scrollEditorToLine(_line: number): Promise<void> {
// No-op: No visual editor to scroll
}
+2
View File
@@ -98,6 +98,7 @@ export interface ExtensionState {
remoteConfigSettings?: Partial<GlobalStateAndSettings>
subagentsEnabled?: boolean
nativeToolCallSetting?: ClineFeatureSetting
backgroundEditEnabled?: boolean
}
export interface ClineMessage {
@@ -167,6 +168,7 @@ export type ClineSay =
| "load_mcp_documentation"
| "info" // Added for general informational messages like retry status
| "task_progress"
| "diff_editing"
export interface ClineSayTool {
tool:
@@ -100,6 +100,7 @@ function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | un
info: ClineSay.INFO,
task_progress: ClineSay.TASK_PROGRESS,
error_retry: ClineSay.ERROR_RETRY,
diff_editing: ClineSay.DIFF_EDITING,
}
const result = mapping[say]
@@ -146,6 +147,7 @@ function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
[ClineSay.INFO]: "info",
[ClineSay.TASK_PROGRESS]: "task_progress",
[ClineSay.ERROR_RETRY]: "error_retry",
[ClineSay.DIFF_EDITING]: "diff_editing",
}
return mapping[say]
+1
View File
@@ -111,6 +111,7 @@ export interface Settings {
ocaMode: string | undefined
hooksEnabled: boolean
subagentsEnabled: boolean
backgroundEditEnabled: boolean
// Plan mode configurations
planModeApiProvider: ApiProvider
@@ -34,6 +34,7 @@ import { cn } from "@/lib/utils"
import { FileServiceClient, TaskServiceClient, UiServiceClient } from "@/services/grpc-client"
import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "@/utils/mcp"
import CodeAccordian, { cleanPathPrefix } from "../common/CodeAccordian"
import { DiffEditRow } from "./DiffEditRow"
import { ErrorBlockTitle } from "./ErrorBlockTitle"
import ErrorRow from "./ErrorRow"
import NewTaskPreview from "./NewTaskPreview"
@@ -865,6 +866,17 @@ export const ChatRowContent = memo(
}
}, [isCommandMessage, isCommandExecuting, isExpanded, onToggleExpand, message.ts])
if (message.say === "diff_editing" && message.text) {
return (
<div className="flex flex-col w-full">
{/* <div className="flex items-center">
<PenIcon className="size-2 stroke-1 mr-2 font-bold" /> Applying Patch
</div> */}
<DiffEditRow patch={message.text} />
</div>
)
}
if (message.ask === "command" || message.say === "command") {
const splitMessage = (text: string) => {
const outputIndex = text.indexOf(COMMAND_OUTPUT_STRING)
@@ -0,0 +1,181 @@
import { ChevronDown, ChevronRight, ChevronsDownUpIcon, FilePlus, FileText, FileX } from "lucide-react"
import React, { useEffect, useState } from "react"
import { cn } from "@/lib/utils"
interface Patch {
action: string
path: string
lines: string[]
additions: number
deletions: number
}
export const DiffEditRow: React.FC<{ patch: string }> = ({ patch }) => {
const [isStreaming, setIsStreaming] = useState<boolean>(true)
const [parsedFiles, setParsedFiles] = useState<Patch[]>([])
useEffect(() => {
const started = patch.includes("*** Begin Patch")
const ended = patch.includes("*** End Patch")
if (started && ended) {
setIsStreaming(false)
}
if (!started) {
return
}
// Extract patches between the begin and end markers
const parts = patch.split("*** Begin Patch")[1].split("*** End Patch")
const patchContent = parts[0].trim()
const patches = patchContent.split("\n*** ").map((p) => p.trim())
const parsed = parsePatchContent(patches.join("\n"))
if (patches.length > 0 && parsed.length > 0) {
setParsedFiles(parsed)
}
}, [patch])
if (parsedFiles.length === 0) {
return null
}
return (
<div className="space-y-4 border border-code-block-background/70 rounded-sm">
{parsedFiles.map((file, _idx) => (
<FileBlock file={file} key={file.path} />
))}
{isStreaming && (
<div className="bg-gray-800 rounded-lg flex items-center text-gray-400">
<div className="animate-pulse mr-3"></div>
Streaming content...
</div>
)}
</div>
)
}
const FileBlock: React.FC<{ file: Patch }> = ({ file }) => {
const [isExpanded, setIsExpanded] = useState(true)
const getActionIcon = (action: string) => {
switch (action) {
case "Add":
return <FilePlus className="w-5 h-5 text-success" />
case "Delete":
return <FileX className="w-5 h-5 text-error" />
default:
return <FileText className="w-5 h-5 text-info" />
}
}
const getActionColor = (action: string) => {
switch (action) {
case "Add":
return "border-l-success"
case "Delete":
return "border-l-error"
default:
return "border-l-background"
}
}
return (
<div className="p-1 bg-editor-background rounded-lg border border-gray-800">
<button
className="w-full flex items-center gap-2 px-4 py-3 bg-editor-background hover:bg-gray-850 transition-colors rounded-t-lg border-b border-gray-800"
onClick={() => setIsExpanded(!isExpanded)}>
<div className="flex items-center gap-3">
{isExpanded ? (
<ChevronDown className="w-5 h-5 text-gray-400" />
) : (
<ChevronRight className="w-5 h-5 text-gray-400" />
)}
<span className={cn("flex items-center gap-2", getActionColor(file.action))}>
{getActionIcon(file.action)}
<span className="font-medium">{file.action}</span>
</span>
</div>
<span className="text-xs text-gray-500">
{file.additions > 0 && <span className="text-success">+{file.additions}</span>}
{file.additions > 0 && file.deletions > 0 && <span className="mx-1">·</span>}
{file.deletions > 0 && <span className="text-error">-{file.deletions}</span>}
</span>
</button>
{isExpanded && (
<div className="border-t border-code-block-background">
<div className="font-mono text-xs">
{file.lines.map((line, _idx) => (
<DiffLine key={line} line={line} />
))}
</div>
</div>
)}
</div>
)
}
const DiffLine: React.FC<{ line: string }> = ({ line }) => {
const getLineStyle = () => {
if (line.startsWith("+")) {
return "bg-green-900/30 text-success border-l-1 border-green-500"
} else if (line.startsWith("-")) {
return "bg-red-900/30 text-error border-l-1 border-red-500"
} else {
return "bg-editor-background text-editor-foreground"
}
}
if (line.trim() === "@@") {
return (
<div className="inline-flex items-center px-3 py-1 text-xs font-mono bg-description/10 w-full text-description">
<ChevronsDownUpIcon className="size-2 mr-2" />
@@
</div>
)
}
return (
<div className={cn("px-4 py-1 text-xs font-mono w-full", getLineStyle())}>
<span>{line}</span>
</div>
)
}
const parsePatchContent = (content: string) => {
const files: Patch[] = []
const lines = content.split("\n")
let currentFile: Patch | null = null
for (const line of lines) {
const fileMatch = line.match(/^\*\*\* (Add|Update|Delete) File: (.+)$/)
if (fileMatch) {
if (currentFile) {
files.push(currentFile)
}
currentFile = {
action: fileMatch[1],
path: fileMatch[2].trim(),
lines: [],
additions: 0,
deletions: 0,
}
} else if (currentFile && line.trim()) {
currentFile.lines.push(line)
if (line.startsWith("+")) {
currentFile.additions++
} else if (line.startsWith("-")) {
currentFile.deletions++
}
}
}
if (currentFile) {
files.push(currentFile)
}
return files
}
@@ -56,6 +56,8 @@ const TaskTimelineTooltip = ({ message, children }: TaskTimelineTooltipProps) =>
return "Task Completed"
case "checkpoint_created":
return "Checkpoint Created"
case "diff_editing":
return "Diff Editing"
default:
return message.say || "Unknown"
}
@@ -47,6 +47,8 @@ export const getColor = (message: ClineMessage): string => {
return COLOR_PURPLE // Purple for browser actions
case "completion_result":
return COLOR_GREEN // Green for task success
case "diff_editing":
return COLOR_BLUE // Green for task success
default:
return COLOR_DARK_GRAY // Dark gray for unknown
}
@@ -34,6 +34,7 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
remoteConfigSettings,
subagentsEnabled,
nativeToolCallSetting,
backgroundEditEnabled,
} = useExtensionState()
const [isClineCliInstalled, setIsClineCliInstalled] = useState(false)
@@ -152,7 +153,7 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
</span>
</VSCodeCheckbox>
<p className="text-xs mt-1 mb-0">
<span className="text-[var(--vscode-errorForeground)]">Experimental: </span>{" "}
<span className="text-input-error-foreground">Experimental: </span>{" "}
<span className="text-description">
Allows Cline to spawn subprocesses to handle focused tasks like exploring large codebases,
keeping your main context clean.
@@ -356,6 +357,24 @@ const FeatureSettingsSection = ({ renderSectionHeader }: FeatureSettingsSectionP
</a>
</p>
</div>
<div className="mt-2.5">
<VSCodeCheckbox
checked={backgroundEditEnabled}
onChange={(e: any) => {
const checked = e.target.checked === true
updateSetting("backgroundEditEnabled", checked)
}}>
Enable Background Edit
</VSCodeCheckbox>
<p className="text-xs">
<span className="text-xs bg-button-background/80 text-button-foreground px-2 py-1 rounded-lg mr-1">
Experimental
</span>
<span className="text-description">
Allows Cline to edit documents in the background without stealing focus from your editor.
</span>
</p>
</div>
{multiRootSetting.featureFlag && (
<div className="mt-2.5">
<VSCodeCheckbox
@@ -225,6 +225,7 @@ export const ExtensionStateContextProvider: React.FC<{
backgroundCommandTaskId: undefined,
lastDismissedCliBannerVersion: 0,
subagentsEnabled: false,
backgroundEditEnabled: false,
// NEW: Add workspace information with defaults
workspaceRoots: [],