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
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Changelog

## [v5.0.1] - 2026-01-05
## [v5.0.2] - 2026-01-09

### Changed

- Minor improvements to file read tool
- Settings UI Cleanup

---

## [v5.0.1] - 2026-01-09

### Changed

Expand Down
2 changes: 1 addition & 1 deletion apps/storybook/.storybook/theme-variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ classes at build time based on the @theme */
--card-foreground: var(--vscode-editor-foreground);
--popover: var(--vscode-menu-background, var(--vscode-editor-background));
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
--primary: var(--vscode-button-background);
--primary: var(--color-matterai-green-dark);
--primary-foreground: var(--vscode-button-foreground);
--secondary: var(--vscode-button-secondaryBackground);
--secondary-foreground: var(--vscode-button-secondaryForeground);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ classes at build time based on the @theme */
--card-foreground: var(--vscode-editor-foreground);
--popover: var(--vscode-menu-background, var(--vscode-editor-background));
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
--primary: var(--vscode-button-background);
--primary: var(--color-matterai-green-dark);
--primary-foreground: var(--vscode-button-foreground);
--secondary: var(--vscode-button-secondaryBackground);
--secondary-foreground: var(--vscode-button-secondaryForeground);
Expand Down
18 changes: 9 additions & 9 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,15 +283,15 @@ export async function presentAssistantMessage(cline: Task) {
break
}

if (cline.didAlreadyUseTool) {
// Ignore any content after a tool has already been used.
pushToolResult_withToolUseId_kilocode({
type: "text",
text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`,
})

break
}
// if (cline.didAlreadyUseTool) {
// // Ignore any content after a tool has already been used.
// pushToolResult_withToolUseId_kilocode({
// type: "text",
// text: `Tool [${block.name}] was not executed because a tool has already been used in this message. Only one tool may be used per message. You must assess the first tool's result before proceeding to use the next tool.`,
// })

// break
// }

const pushToolResult = (content: ToolResponse) => {
// kilocode_change start
Expand Down
8 changes: 4 additions & 4 deletions src/core/prompts/tools/native-tools/read_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const read_file_multi = {
function: {
name: "read_file",
description:
"Read one or more files and return their contents with line numbers. Use offset and limit to read specific portions of files efficiently. By default reads from the beginning with a reasonable limit.",
"Read one or more files and return their contents with line numbers. Use offset and limit to read specific portions of files efficiently. Default and maximum limit is 1000 lines to prevent context overflow.",
strict: true,
parameters: {
type: "object",
Expand All @@ -28,7 +28,7 @@ export const read_file_multi = {
limit: {
type: ["number", "null"],
description:
"Maximum number of lines to read from offset. If not specified, reads the complete file from offset. Use smaller values for targeted reads.",
"Maximum number of lines to read from offset. Default and maximum limit is 1000 lines. Use smaller values for targeted reads.",
},
},
required: ["file_path"],
Expand All @@ -48,7 +48,7 @@ export const read_file_single = {
function: {
name: "read_file",
description:
"Read a file and return its contents with line numbers. Use offset and limit to read specific portions efficiently.",
"Read a file and return its contents with line numbers. Use offset and limit to read specific portions efficiently. Default and maximum limit is 1000 lines to prevent context overflow.",
strict: true,
parameters: {
type: "object",
Expand All @@ -64,7 +64,7 @@ export const read_file_single = {
limit: {
type: ["number", "null"],
description:
"Maximum number of lines to read from offset. If not specified, reads the complete file from offset.",
"Maximum number of lines to read from offset. Default and maximum limit is 1000 lines.",
},
},
required: ["file_path"],
Expand Down
16 changes: 13 additions & 3 deletions src/core/tools/kilocode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,28 @@ type FileEntry = {
}

export function parseNativeFiles(
nativeFiles: { file_path?: string; path?: string; offset?: number; limit?: number; line_ranges?: string[] }[],
nativeFiles: {
file_path?: string
path?: string
offset?: number | string
limit?: number | string
line_ranges?: string[]
}[],
) {
const fileEntries = new Array<FileEntry>()
for (const file of nativeFiles) {
// Support both file_path (new) and path (legacy)
const filePath = file.file_path || file.path
if (!filePath) continue

// Parse offset and limit as integers - LLM may send them as strings
const parsedOffset = file.offset !== undefined ? parseInt(String(file.offset), 10) : undefined
const parsedLimit = file.limit !== undefined ? parseInt(String(file.limit), 10) : undefined

const fileEntry: FileEntry = {
path: filePath,
offset: file.offset ?? 1,
limit: file.limit, // undefined means read complete file
offset: !isNaN(parsedOffset as number) ? parsedOffset : 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Robustness

Issue: parseInt can return 0 or negative numbers. If offset is 0, it may cause issues with 1-based line indexing in downstream functions (potentially resulting in -1 index).

Fix: Clamp the parsed offset to be at least 1.

Impact: Prevents potential runtime errors or incorrect file reads with invalid offsets.

Suggested change
offset: !isNaN(parsedOffset as number) ? parsedOffset : 1,
offset: !isNaN(parsedOffset as number) ? Math.max(1, parsedOffset as number) : 1,

limit: !isNaN(parsedLimit as number) ? parsedLimit : undefined, // undefined means read complete file
}

// Legacy support: convert line_ranges to offset+limit if provided
Expand Down
65 changes: 40 additions & 25 deletions src/core/tools/readFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import {
ImageMemoryTracker,
} from "./helpers/imageHelpers"

// Maximum number of lines to read when limit is not specified - prevents context window overflow
const MAX_READ_FILE_LINES = 1000

export function getReadFileToolDescription(blockName: string, blockParams: any): string {
// Handle both single file_path and multiple files via args
// kilocode_change start
Expand Down Expand Up @@ -95,8 +98,11 @@ export async function readFileTool(
const newFilePath: string | undefined = (block.params as any).file_path // New: support file_path directly
const legacyStartLineStr: string | undefined = block.params.start_line
const legacyEndLineStr: string | undefined = block.params.end_line
const offsetParam: number | undefined = (block.params as any).offset
const limitParam: number | undefined = (block.params as any).limit
// Parse offset and limit as integers - LLM may send them as strings causing string concatenation bugs
const rawOffset = (block.params as any).offset
const rawLimit = (block.params as any).limit
const offsetParam: number | undefined = rawOffset !== undefined ? parseInt(String(rawOffset), 10) : undefined
const limitParam: number | undefined = rawLimit !== undefined ? parseInt(String(rawLimit), 10) : undefined

const nativeFiles: any[] | undefined = (block.params as any).files // kilocode_change: Native JSON format from OpenAI-style tool calls

Expand Down Expand Up @@ -159,10 +165,14 @@ export async function readFileTool(
const filePath = file.file_path || file.path
if (!filePath) continue // Skip if no path in a file entry

// Parse offset and limit as integers - XML parsing may produce strings
const parsedOffset = file.offset !== undefined ? parseInt(String(file.offset), 10) : undefined
const parsedLimit = file.limit !== undefined ? parseInt(String(file.limit), 10) : undefined

const fileEntry: FileEntry = {
path: filePath,
offset: file.offset ?? 1,
limit: file.limit, // undefined means read complete file
offset: !isNaN(parsedOffset as number) ? parsedOffset : 1,
limit: !isNaN(parsedLimit as number) ? parsedLimit : undefined, // undefined means read complete file
}

// Legacy support: convert line_range to offset+limit
Expand Down Expand Up @@ -350,10 +360,11 @@ export async function readFileTool(
const imageMemoryTracker = new ImageMemoryTracker()
const state = await cline.providerRef.deref()?.getState()
const {
maxReadFileLine = -1,
maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
} = state ?? {}
// Always use MAX_READ_FILE_LINES - setting will be removed later
const maxReadFileLine = MAX_READ_FILE_LINES

// Then process only approved files
for (const fileResult of fileResults) {
Expand Down Expand Up @@ -444,33 +455,37 @@ export async function readFileTool(
// Handle offset/limit reads (if limit is specified)
if (fileResult.limit !== undefined) {
const startLine = fileResult.offset ?? 1
const endLine = startLine + fileResult.limit - 1
// Cap limit to MAX_READ_FILE_LINES to prevent context window overflow
const effectiveLimit = Math.min(fileResult.limit, MAX_READ_FILE_LINES)
const endLine = startLine + effectiveLimit - 1
const content = addLineNumbers(await readLines(fullPath, endLine - 1, startLine - 1), startLine)
let xmlContent = content
if (fileResult.limit > MAX_READ_FILE_LINES) {
xmlContent = `[showing ${effectiveLimit} lines, capped from requested ${fileResult.limit} lines to prevent context overflow]\n${content}`
}
updateFileResult(relPath, {
xmlContent: content,
xmlContent,
})
continue
}

// Handle definitions-only mode
if (maxReadFileLine === 0) {
try {
const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
if (defResult) {
// kilocode_change: Return raw definitions without path header
updateFileResult(relPath, {
xmlContent: `[definitions only, ${totalLines} total lines]\n${defResult}`,
})
}
} catch (error) {
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
console.warn(`[read_file] Warning: ${error.message}`)
} else {
console.error(
`[read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`,
)
}
// Handle offset-only reads (no limit specified) - cap to MAX_READ_FILE_LINES
if (fileResult.offset !== undefined && fileResult.offset > 1) {
const startLine = fileResult.offset
const endLine = startLine + MAX_READ_FILE_LINES - 1
const actualEndLine = Math.min(endLine, totalLines)
const linesRead = actualEndLine - startLine + 1
const content = addLineNumbers(
await readLines(fullPath, actualEndLine - 1, startLine - 1),
startLine,
)
let xmlContent = content
if (totalLines > actualEndLine) {
xmlContent = `[showing ${linesRead} lines from offset ${startLine}, capped at ${MAX_READ_FILE_LINES} lines. Total file length: ${totalLines} lines]\n${content}`
}
updateFileResult(relPath, {
xmlContent,
})
continue
}
Comment on lines +472 to 490
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Security & Performance

Issue: The current logic handles explicit limit and explicit offset > 1, but falls through for the default case (no params or offset=1 with no limit). This means a standard read_file call will bypass the MAX_READ_FILE_LINES cap and read the entire file, violating the tool's new constraint.

Fix: Update the logic to handle the default case (where limit is undefined) by applying the cap relative to the offset (defaulting to 1).

Impact: Prevents context window overflow and ensures consistent behavior for all file read operations.

Suggested change
// Handle offset-only reads (no limit specified) - cap to MAX_READ_FILE_LINES
if (fileResult.offset !== undefined && fileResult.offset > 1) {
const startLine = fileResult.offset
const endLine = startLine + MAX_READ_FILE_LINES - 1
const actualEndLine = Math.min(endLine, totalLines)
const linesRead = actualEndLine - startLine + 1
const content = addLineNumbers(
await readLines(fullPath, actualEndLine - 1, startLine - 1),
startLine,
)
let xmlContent = content
if (totalLines > actualEndLine) {
xmlContent = `[showing ${linesRead} lines from offset ${startLine}, capped at ${MAX_READ_FILE_LINES} lines. Total file length: ${totalLines} lines]\n${content}`
}
updateFileResult(relPath, {
xmlContent,
})
continue
}
// Handle reads with no explicit limit - cap to MAX_READ_FILE_LINES
// This handles both offset-only reads and default reads (no offset, no limit)
if (fileResult.limit === undefined) {
const startLine = fileResult.offset ?? 1
const endLine = startLine + MAX_READ_FILE_LINES - 1
const actualEndLine = Math.min(endLine, totalLines)
const linesRead = actualEndLine - startLine + 1
const content = addLineNumbers(
await readLines(fullPath, actualEndLine - 1, startLine - 1),
startLine,
)
let xmlContent = content
if (totalLines > actualEndLine) {
xmlContent = `[showing ${linesRead} lines from offset ${startLine}, capped at ${MAX_READ_FILE_LINES} lines. Total file length: ${totalLines} lines]\n${content}`
}
updateFileResult(relPath, {
xmlContent,
})
continue
}


Expand Down
2 changes: 1 addition & 1 deletion src/integrations/theme/default-themes/theme-variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ classes at build time based on the @theme */
--card-foreground: var(--vscode-editor-foreground);
--popover: var(--vscode-menu-background, var(--vscode-editor-background));
--popover-foreground: var(--vscode-menu-foreground, var(--vscode-editor-foreground));
--primary: var(--vscode-button-background);
--primary: var(--color-matterai-green-dark);
--primary-foreground: var(--vscode-button-foreground);
--secondary: var(--vscode-button-secondaryBackground);
--secondary-foreground: var(--vscode-button-secondaryForeground);
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "matterai",
"version": "5.0.1",
"version": "5.0.2",
"icon": "assets/icons/matterai-ic.png",
"galleryBanner": {
"color": "#FFFFFF",
Expand Down
47 changes: 22 additions & 25 deletions webview-ui/src/components/settings/AutoApproveSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { CheckCheck, X } from "lucide-react"
import { HTMLAttributes, useState } from "react"
import { X, CheckCheck } from "lucide-react"
import { Trans } from "react-i18next"
import { Package } from "@roo/package"

import { Button, Input } from "@/components/ui"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { vscode } from "@/utils/vscode"
import { Button, Input, Slider } from "@/components/ui"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"

import { SetCachedStateField } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
import { AutoApproveToggle } from "./AutoApproveToggle"
import { MaxLimitInputs } from "./MaxLimitInputs"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAutoApprovalState } from "@/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
import { AutoApproveToggle } from "./AutoApproveToggle"
import { Section } from "./Section"
import { SectionHeader } from "./SectionHeader"
import { SetCachedStateField } from "./types"

type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
alwaysAllowReadOnly?: boolean
Expand All @@ -32,7 +29,7 @@ type AutoApproveSettingsProps = HTMLAttributes<HTMLDivElement> & {
alwaysAllowExecute?: boolean
alwaysAllowFollowupQuestions?: boolean
alwaysAllowUpdateTodoList?: boolean
followupAutoApproveTimeoutMs?: number
// followupAutoApproveTimeoutMs?: number
allowedCommands?: string[]
allowedMaxRequests?: number | undefined
allowedMaxCost?: number | undefined
Expand Down Expand Up @@ -78,7 +75,7 @@ export const AutoApproveSettings = ({
alwaysAllowSubtasks,
alwaysAllowExecute,
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs = 60000,
// followupAutoApproveTimeoutMs = 60000,
alwaysAllowUpdateTodoList,
allowedCommands,
allowedMaxRequests,
Expand Down Expand Up @@ -130,7 +127,7 @@ export const AutoApproveSettings = ({
</SectionHeader>

{/* kilocode_change start */}
<Section>
{/* <Section>
<div>
<VSCodeCheckbox
checked={showAutoApproveMenu}
Expand All @@ -142,10 +139,10 @@ export const AutoApproveSettings = ({
{t("settings:autoApprove.showMenu.description")}
</div>
</div>
</Section>
</Section> */}

{/* YOLO MODE SECTION */}
{process.env.NODE_ENV === "development" && (
{/* {process.env.NODE_ENV === "development" && (
<Section>
<div className="border-2 border-yellow-500 rounded-md p-4 bg-yellow-500/10">
<div className="flex items-center gap-2 mb-3">
Expand All @@ -170,9 +167,9 @@ export const AutoApproveSettings = ({
</div>
</div>
</Section>
)}
)} */}

{process.env.NODE_ENV === "development" && yoloMode && (
{/* {process.env.NODE_ENV === "development" && yoloMode && (
<Section>
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded p-3 flex items-center gap-2">
<span className="text-lg">⚡</span>
Expand All @@ -181,7 +178,7 @@ export const AutoApproveSettings = ({
</span>
</div>
</Section>
)}
)} */}
{/* kilocode_change end */}

<Section>
Expand All @@ -196,7 +193,7 @@ export const AutoApproveSettings = ({
}}>
<span className="font-medium">{t("settings:autoApprove.enabled")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{/* <div className="text-vscode-descriptionForeground text-sm mt-1">
<p>{t("settings:autoApprove.description")}</p>
<p>
<Trans
Expand All @@ -219,7 +216,7 @@ export const AutoApproveSettings = ({
}}
/>
</p>
</div>
</div> */}

<AutoApproveToggle
alwaysAllowReadOnly={alwaysAllowReadOnly}
Expand All @@ -235,17 +232,17 @@ export const AutoApproveSettings = ({
onToggle={(key, value) => setCachedStateField(key, value)}
/>

<MaxLimitInputs
{/* <MaxLimitInputs
allowedMaxRequests={allowedMaxRequests}
allowedMaxCost={allowedMaxCost}
onMaxRequestsChange={(value) => setCachedStateField("allowedMaxRequests", value)}
onMaxCostChange={(value) => setCachedStateField("allowedMaxCost", value)}
/>
/> */}
</div>

{/* ADDITIONAL SETTINGS */}

{alwaysAllowReadOnly && (
{/* {alwaysAllowReadOnly && (
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
<div className="flex items-center gap-4 font-bold">
<span className="codicon codicon-eye" />
Expand Down Expand Up @@ -356,10 +353,10 @@ export const AutoApproveSettings = ({
</div>
</div>
</div>
)}
)} */}

{alwaysAllowExecute && (
<div className="flex flex-col gap-3 pl-3 border-l-2 border-vscode-button-background">
<div className="flex flex-col gap-3 pl-3 border-vscode-button-background">
<div className="flex items-center gap-4 font-bold">
<span className="codicon codicon-terminal" />
<div>{t("settings:autoApprove.execute.label")}</div>
Expand Down
Loading
Loading