Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 19 additions & 1 deletion src/core/tools/listFilesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { listFiles } from "../../services/glob/list-files"
import { getReadablePath } from "../../utils/path"
import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
import { isPathOutsideWorkspace } from "../../utils/pathUtils"

/**
* Implements the list_files tool.
Expand Down Expand Up @@ -34,9 +35,14 @@
const recursiveRaw: string | undefined = block.params.recursive
const recursive = recursiveRaw?.toLowerCase() === "true"

// Determine if the path is outside the workspace
const fullPath = relDirPath ? path.resolve(cline.cwd, removeClosingTag("path", relDirPath)) : ""
const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)

const sharedMessageProps: ClineSayTool = {
tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive",
path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)),
isOutsideWorkspace,
}

try {
Expand Down Expand Up @@ -66,7 +72,19 @@
showRooIgnoredFiles,
)

const completeMessage = JSON.stringify({ ...sharedMessageProps, content: result } satisfies ClineSayTool)
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: result,
fileInteraction: {

Check failure on line 78 in src/core/tools/listFilesTool.ts

View workflow job for this annotation

GitHub Actions / compile

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.

Check failure on line 78 in src/core/tools/listFilesTool.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.
path: relDirPath,
operation: 'list',
timestamp: Date.now(),
success: true,
isOutsideWorkspace,
taskId: cline.taskId
}
} satisfies ClineSayTool)

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

if (!didApprove) {
Expand Down
34 changes: 26 additions & 8 deletions src/core/tools/readFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
cline.consecutiveMistakeCount++
cline.recordToolError("read_file")
const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path")
pushToolResult(`<file><path></path><error>${errorMsg}</error></file>`)
pushToolResult(`<file><path></path><e>${errorMsg}</e></file>`)
return
}

Expand All @@ -71,7 +71,7 @@
cline.consecutiveMistakeCount++
cline.recordToolError("read_file")
await cline.say("error", `Failed to parse start_line: ${startLineStr}`)
pushToolResult(`<file><path>${relPath}</path><error>Invalid start_line value</error></file>`)
pushToolResult(`<file><path>${relPath}</path><e>Invalid start_line value</e></file>`)
return
}

Expand All @@ -87,7 +87,7 @@
cline.consecutiveMistakeCount++
cline.recordToolError("read_file")
await cline.say("error", `Failed to parse end_line: ${endLineStr}`)
pushToolResult(`<file><path>${relPath}</path><error>Invalid end_line value</error></file>`)
pushToolResult(`<file><path>${relPath}</path><e>Invalid end_line value</e></file>`)
return
}

Expand All @@ -100,7 +100,7 @@
if (!accessAllowed) {
await cline.say("rooignore_error", relPath)
const errorMsg = formatResponse.rooIgnoreError(relPath)
pushToolResult(`<file><path>${relPath}</path><error>${errorMsg}</error></file>`)
pushToolResult(`<file><path>${relPath}</path><e>${errorMsg}</e></file>`)
return
}

Expand All @@ -124,11 +124,29 @@
cline.consecutiveMistakeCount = 0
const absolutePath = path.resolve(cline.cwd, relPath)

const completeMessage = JSON.stringify({
// Create the ClineSayTool object that conforms to the type
const completeMessageObj: ClineSayTool = {
...sharedMessageProps,
content: absolutePath,
reason: lineSnippet,
} satisfies ClineSayTool)
reason: lineSnippet
}

// Convert to JSON string
const completeMessage = JSON.stringify(completeMessageObj)

// Track file interaction separately (not as part of the ClineSayTool)
// This will be handled by the message handler in the webview
cline.postMessage?.({

Check failure on line 139 in src/core/tools/readFileTool.ts

View workflow job for this annotation

GitHub Actions / compile

Property 'postMessage' does not exist on type 'Cline'.

Check failure on line 139 in src/core/tools/readFileTool.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Property 'postMessage' does not exist on type 'Cline'.
type: "trackFileInteraction",
fileInteraction: {
path: relPath,
operation: 'read',
timestamp: Date.now(),
success: true,
isOutsideWorkspace,
taskId: cline.taskId
}
})

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

Expand Down Expand Up @@ -240,7 +258,7 @@
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
pushToolResult(`<file><path>${relPath || ""}</path><error>Error reading file: ${errorMsg}</error></file>`)
pushToolResult(`<file><path>${relPath || ""}</path><e>Error reading file: ${errorMsg}</e></file>`)
await handleError("reading file", error)
}
}
20 changes: 19 additions & 1 deletion src/core/tools/searchFilesTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { getReadablePath } from "../../utils/path"
import { regexSearchFiles } from "../../services/ripgrep"
import { isPathOutsideWorkspace } from "../../utils/pathUtils"

export async function searchFilesTool(
cline: Cline,
Expand All @@ -18,11 +19,16 @@
const regex: string | undefined = block.params.regex
const filePattern: string | undefined = block.params.file_pattern

// Determine if the path is outside the workspace
const fullPath = relDirPath ? path.resolve(cline.cwd, removeClosingTag("path", relDirPath)) : ""
const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)

const sharedMessageProps: ClineSayTool = {
tool: "searchFiles",
path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)),
regex: removeClosingTag("regex", regex),
filePattern: removeClosingTag("file_pattern", filePattern),
isOutsideWorkspace,
}

try {
Expand Down Expand Up @@ -57,7 +63,19 @@
cline.rooIgnoreController,
)

const completeMessage = JSON.stringify({ ...sharedMessageProps, content: results } satisfies ClineSayTool)
const completeMessage = JSON.stringify({
...sharedMessageProps,
content: results,
fileInteraction: {

Check failure on line 69 in src/core/tools/searchFilesTool.ts

View workflow job for this annotation

GitHub Actions / compile

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.

Check failure on line 69 in src/core/tools/searchFilesTool.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.
path: relDirPath,
operation: 'search',
timestamp: Date.now(),
success: true,
isOutsideWorkspace,
taskId: cline.taskId
}
} satisfies ClineSayTool)

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

if (!didApprove) {
Expand Down
8 changes: 8 additions & 0 deletions src/core/tools/writeToFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,14 @@
diff: fileExists
? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent)
: undefined,
fileInteraction: {

Check failure on line 201 in src/core/tools/writeToFileTool.ts

View workflow job for this annotation

GitHub Actions / compile

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.

Check failure on line 201 in src/core/tools/writeToFileTool.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Object literal may only specify known properties, and 'fileInteraction' does not exist in type 'ClineSayTool'.
path: relPath,
operation: fileExists ? 'edit' : 'create',
timestamp: Date.now(),
success: true,
isOutsideWorkspace,
taskId: cline.taskId
}
} satisfies ClineSayTool)

const didApprove = await askApproval("tool", completeMessage)
Expand Down
128 changes: 127 additions & 1 deletion src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { ApiConfiguration } from "../../shared/api"
import { supportPrompt } from "../../shared/support-prompt"

import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, FileInteraction, WebviewMessage } from "../../shared/WebviewMessage"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
Expand Down Expand Up @@ -38,6 +38,7 @@
import { GlobalState } from "../../schemas"
import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace"
import { getModels } from "../../api/providers/fetchers/cache"
import { getTaskDirectoryPath } from "../../shared/storagePathManager"

export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
// Utility functions provided for concise get/update of global state via contextProxy API.
Expand Down Expand Up @@ -1257,6 +1258,131 @@
await provider.postStateToWebview()
break
}

// File interaction handling
case "requestFileInteractions": {
if (message.taskId) {
try {
// Get the task directory path
const globalStoragePath = provider.contextProxy.globalStorageUri.fsPath
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, message.taskId)
const filePath = path.join(taskDirPath, 'fileInteractions.json')

// Check if the file exists
const exists = await fileExistsAtPath(filePath)

if (exists) {
// Read the file
const fileContent = await fs.readFile(filePath, 'utf8')
const interactions = JSON.parse(fileContent) as FileInteraction[]

// Send the interactions to the webview
await provider.postMessageToWebview({
type: "fileInteractions",

Check failure on line 1281 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.

Check failure on line 1281 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.
interactions,
taskId: message.taskId
})
} else {
// If file doesn't exist, send current file interactions from the Cline instance
const currentCline = provider.getCurrentCline()
if (currentCline && currentCline.taskId === message.taskId) {
// Extract file interactions from messages
const interactions: FileInteraction[] = []

currentCline.clineMessages.forEach(message => {
if (message.type === 'ask' && message.ask === 'tool' && message.text) {
try {
const payload = JSON.parse(message.text)
if (payload.fileInteraction) {
interactions.push({
...payload.fileInteraction,
timestamp: message.ts,
taskId: currentCline.taskId
})
}
} catch (e) {
console.error("Failed to parse tool message payload:", e)
}
}
})

// Save file interactions for future reference
try {
await fs.mkdir(taskDirPath, { recursive: true })
await fs.writeFile(filePath, JSON.stringify(interactions, null, 2))
} catch (e) {
console.error("Failed to save file interactions:", e)
}

// Send interactions to webview
await provider.postMessageToWebview({
type: "fileInteractions",

Check failure on line 1319 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.

Check failure on line 1319 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.
interactions,
taskId: message.taskId
})
} else {
// No file interactions found
await provider.postMessageToWebview({
type: "fileInteractions",

Check failure on line 1326 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.

Check failure on line 1326 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.
interactions: [],
taskId: message.taskId
})
}
}
} catch (error) {
console.error("Error processing file interactions:", error)
await provider.postMessageToWebview({
type: "fileInteractions",

Check failure on line 1335 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.

Check failure on line 1335 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Type '"fileInteractions"' is not assignable to type '"autoApprovalEnabled" | "browserToolEnabled" | "remoteBrowserEnabled" | "maxReadFileLine" | "action" | "state" | "selectedImages" | "theme" | "workspaceUpdated" | "invoke" | ... 26 more ... | "commandExecutionStatus"'.
interactions: [],
taskId: message.taskId
})
}
}
break
}

case "toggleStatsView": {
// This is handled directly in the ChatView component
// Just logging for now
console.log("Toggle stats view requested")
break
}

case "updateFileInteractionHistory": {
if (message.taskId && message.interactions) {
try {
// Get the task directory path
const globalStoragePath = provider.contextProxy.globalStorageUri.fsPath
const taskDirPath = await getTaskDirectoryPath(globalStoragePath, message.taskId)
const filePath = path.join(taskDirPath, 'fileInteractions.json')

// Ensure directory exists
await fs.mkdir(taskDirPath, { recursive: true })

// Save the interactions
await fs.writeFile(filePath, JSON.stringify(message.interactions, null, 2))

// Update state if needed
const currentState = getGlobalState("fileInteractionHistory") as Record<string, FileInteraction[]> || {}

Check failure on line 1366 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Argument of type '"fileInteractionHistory"' is not assignable to parameter of type '"reasoningEffort" | "apiProvider" | "customInstructions" | "customModes" | "apiModelId" | "anthropicBaseUrl" | "anthropicUseAuthToken" | "glamaModelId" | "openRouterModelId" | ... 101 more ... | "historyPreviewCollapsed"'.

Check failure on line 1366 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Argument of type '"fileInteractionHistory"' is not assignable to parameter of type '"reasoningEffort" | "apiProvider" | "customInstructions" | "customModes" | "apiModelId" | "anthropicBaseUrl" | "anthropicUseAuthToken" | "glamaModelId" | "openRouterModelId" | ... 101 more ... | "historyPreviewCollapsed"'.
const updatedHistory = {
...currentState,
[message.taskId]: message.interactions
}
await updateGlobalState("fileInteractionHistory", updatedHistory)

Check failure on line 1371 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / compile

Argument of type '"fileInteractionHistory"' is not assignable to parameter of type '"reasoningEffort" | "apiProvider" | "customInstructions" | "customModes" | "apiModelId" | "anthropicBaseUrl" | "anthropicUseAuthToken" | "glamaModelId" | "openRouterModelId" | ... 101 more ... | "historyPreviewCollapsed"'.

Check failure on line 1371 in src/core/webview/webviewMessageHandler.ts

View workflow job for this annotation

GitHub Actions / test-extension (ubuntu-latest)

Argument of type '"fileInteractionHistory"' is not assignable to parameter of type '"reasoningEffort" | "apiProvider" | "customInstructions" | "customModes" | "apiModelId" | "anthropicBaseUrl" | "anthropicUseAuthToken" | "glamaModelId" | "openRouterModelId" | ... 101 more ... | "historyPreviewCollapsed"'.

// Send back to webview if requested
if (message.requestUpdate) {
await provider.postMessageToWebview({
type: "fileInteractionHistory",
history: updatedHistory
})
}
} catch (error) {
console.error("Error saving file interaction history:", error)
}
}
break
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ export type PromptMode = Mode | "enhance"

export type AudioType = "notification" | "celebration" | "progress_loop"

export interface FileInteraction {
path: string;
operation: 'read' | 'write' | 'edit' | 'create' | 'delete' | 'insert' | 'search_replace' | 'list' | 'search';
timestamp: number;
success?: boolean;
isOutsideWorkspace?: boolean;
taskId?: string;
content?: string; // Optional for content logging
diff?: string; // Optional for diff logging
}

export interface WebviewMessage {
type:
| "apiConfiguration"
Expand Down Expand Up @@ -127,6 +138,11 @@ export interface WebviewMessage {
| "searchFiles"
| "toggleApiConfigPin"
| "setHistoryPreviewCollapsed"
| "fileInteractions"
| "fileInteractionHistory"
| "updateFileInteractionHistory"
| "requestFileInteractions"
| "toggleStatsView"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse
Expand Down Expand Up @@ -155,6 +171,10 @@ export interface WebviewMessage {
hasSystemPromptOverride?: boolean
terminalOperation?: "continue" | "abort"
historyPreviewCollapsed?: boolean
fileInteraction?: FileInteraction
interactions?: FileInteraction[]
taskId?: string
history?: Record<string, FileInteraction[]>
}

export const checkoutDiffPayloadSchema = z.object({
Expand Down
Loading
Loading