diff --git a/package.json b/package.json
index 47afde03ec..4597eac406 100644
--- a/package.json
+++ b/package.json
@@ -26,10 +26,13 @@
"devDependencies": {
"@changesets/cli": "^2.27.10",
"@dotenvx/dotenvx": "^1.34.0",
+ "@types/jest": "^29.5.14",
+ "@types/node": "^22.15.30",
"@vscode/vsce": "3.3.2",
"esbuild": "^0.25.0",
"eslint": "^9.27.0",
"husky": "^9.1.7",
+ "isbinaryfile": "^5.0.2",
"knip": "^5.44.4",
"lint-staged": "^16.0.0",
"mkdirp": "^3.0.1",
@@ -38,7 +41,8 @@
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
"turbo": "^2.5.3",
- "typescript": "^5.4.5"
+ "typescript": "^5.4.5",
+ "vitest": "^3.2.2"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,css,md}": [
diff --git a/packages/build/package.json b/packages/build/package.json
index b6897732db..71a8ffd3c5 100644
--- a/packages/build/package.json
+++ b/packages/build/package.json
@@ -13,7 +13,7 @@
"clean": "rimraf dist .turbo"
},
"dependencies": {
- "zod": "^3.24.2"
+ "zod": "^3.24.4"
},
"devDependencies": {
"@roo-code/config-eslint": "workspace:^",
diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json
index f9e228bc89..bad2cc6b33 100644
--- a/packages/telemetry/package.json
+++ b/packages/telemetry/package.json
@@ -12,7 +12,7 @@
},
"dependencies": {
"@roo-code/types": "workspace:^",
- "posthog-node": "^4.7.0",
+ "posthog-node": "^4.17.2",
"zod": "^3.24.2"
},
"devDependencies": {
diff --git a/packages/types/package.json b/packages/types/package.json
index d35b9501df..ca6208f032 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -29,7 +29,7 @@
"@roo-code/config-eslint": "workspace:^",
"@roo-code/config-typescript": "workspace:^",
"@types/node": "^22.15.20",
- "tsup": "^8.3.5",
+ "tsup": "^8.5.0",
"vitest": "^3.1.3"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 39ac271dfc..c1c8b81fe0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,12 @@ importers:
'@dotenvx/dotenvx':
specifier: ^1.34.0
version: 1.44.1
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/node':
+ specifier: ^22.15.30
+ version: 22.15.30
'@vscode/vsce':
specifier: 3.3.2
version: 3.3.2
@@ -26,9 +32,12 @@ importers:
husky:
specifier: ^9.1.7
version: 9.1.7
+ isbinaryfile:
+ specifier: ^5.0.2
+ version: 5.0.4
knip:
specifier: ^5.44.4
- version: 5.60.0(@types/node@22.15.29)(typescript@5.8.3)
+ version: 5.60.0(@types/node@22.15.30)(typescript@5.8.3)
lint-staged:
specifier: ^16.0.0
version: 16.1.0
@@ -53,6 +62,9 @@ importers:
typescript:
specifier: ^5.4.5
version: 5.8.3
+ vitest:
+ specifier: ^3.2.2
+ version: 3.2.2(@types/debug@4.1.12)(@types/node@22.15.30)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
apps/vscode-e2e:
devDependencies:
@@ -227,7 +239,7 @@ importers:
version: 4.1.6
vitest:
specifier: ^3.2.1
- version: 3.2.1(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ version: 3.2.1(@types/debug@4.1.12)(@types/node@22.15.30)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
apps/web-roo-code:
dependencies:
@@ -338,7 +350,7 @@ importers:
packages/build:
dependencies:
zod:
- specifier: ^3.24.2
+ specifier: ^3.24.4
version: 3.24.4
devDependencies:
'@roo-code/config-eslint':
@@ -521,7 +533,7 @@ importers:
specifier: workspace:^
version: link:../types
posthog-node:
- specifier: ^4.7.0
+ specifier: ^4.17.2
version: 4.17.2
zod:
specifier: ^3.24.2
@@ -559,7 +571,7 @@ importers:
specifier: ^22.15.20
version: 22.15.20
tsup:
- specifier: ^8.3.5
+ specifier: ^8.5.0
version: 8.5.0(jiti@2.4.2)(postcss@8.5.4)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.8.0)
vitest:
specifier: ^3.1.3
@@ -883,44 +895,47 @@ importers:
webview-ui:
dependencies:
+ '@anthropic-ai/sdk':
+ specifier: ^0.37.0
+ version: 0.37.0
'@radix-ui/react-alert-dialog':
- specifier: ^1.1.6
+ specifier: ^1.1.13
version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-checkbox':
- specifier: ^1.1.5
+ specifier: ^1.3.1
version: 1.3.1(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collapsible':
- specifier: ^1.1.3
+ specifier: ^1.1.10
version: 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dialog':
- specifier: ^1.1.6
+ specifier: ^1.1.13
version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dropdown-menu':
- specifier: ^2.1.5
+ specifier: ^2.1.14
version: 2.1.14(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-icons':
specifier: ^1.3.2
version: 1.3.2(react@18.3.1)
'@radix-ui/react-popover':
- specifier: ^1.1.6
+ specifier: ^1.1.13
version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-portal':
- specifier: ^1.1.5
+ specifier: ^1.1.8
version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-progress':
- specifier: ^1.1.2
+ specifier: ^1.1.6
version: 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-select':
- specifier: ^2.1.6
+ specifier: ^2.2.4
version: 2.2.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-separator':
- specifier: ^1.1.2
+ specifier: ^1.1.6
version: 1.1.6(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slider':
- specifier: ^1.2.3
+ specifier: ^1.3.4
version: 1.3.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot':
- specifier: ^1.1.2
+ specifier: ^1.2.2
version: 1.2.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-tooltip':
specifier: ^1.1.8
@@ -932,7 +947,7 @@ importers:
specifier: ^4.0.0
version: 4.1.6(vite@6.3.5(@types/node@18.19.100)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
'@tanstack/react-query':
- specifier: ^5.68.0
+ specifier: ^5.76.1
version: 5.76.1(react@18.3.1)
'@vscode/codicons':
specifier: ^0.0.36
@@ -950,13 +965,13 @@ importers:
specifier: ^2.1.1
version: 2.1.1
cmdk:
- specifier: ^1.0.0
+ specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
date-fns:
specifier: ^4.1.0
version: 4.1.0
debounce:
- specifier: ^2.1.1
+ specifier: ^2.2.0
version: 2.2.0
fast-deep-equal:
specifier: ^3.1.3
@@ -965,7 +980,7 @@ importers:
specifier: ^0.5.2
version: 0.5.2
i18next:
- specifier: ^24.2.2
+ specifier: ^24.2.3
version: 24.2.3(typescript@5.8.3)
i18next-http-backend:
specifier: ^3.0.2
@@ -979,8 +994,11 @@ importers:
lucide-react:
specifier: ^0.513.0
version: 0.513.0(react@18.3.1)
+ mammoth:
+ specifier: ^1.8.0
+ version: 1.9.0
mermaid:
- specifier: ^11.4.1
+ specifier: ^11.6.0
version: 11.6.0
posthog-js:
specifier: ^1.227.2
@@ -995,7 +1013,7 @@ importers:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
react-i18next:
- specifier: ^15.4.1
+ specifier: ^15.5.1
version: 15.5.1(i18next@24.2.3(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
react-markdown:
specifier: ^9.0.3
@@ -1004,13 +1022,13 @@ importers:
specifier: ^2.1.0
version: 2.1.0(react@18.3.1)
react-textarea-autosize:
- specifier: ^8.5.3
+ specifier: ^8.5.9
version: 8.5.9(@types/react@18.3.23)(react@18.3.1)
react-use:
- specifier: ^17.5.1
+ specifier: ^17.6.0
version: 17.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-virtuoso:
- specifier: ^4.7.13
+ specifier: ^4.12.7
version: 4.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
rehype-highlight:
specifier: ^7.0.0
@@ -1019,19 +1037,19 @@ importers:
specifier: ^4.0.1
version: 4.0.1
remove-markdown:
- specifier: ^0.6.0
+ specifier: ^0.6.2
version: 0.6.2
shell-quote:
specifier: ^1.8.2
version: 1.8.2
shiki:
- specifier: ^3.2.1
+ specifier: ^3.4.1
version: 3.4.1
source-map:
specifier: ^0.7.4
version: 0.7.4
styled-components:
- specifier: ^6.1.13
+ specifier: ^6.1.18
version: 6.1.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
tailwind-merge:
specifier: ^2.6.0
@@ -1055,7 +1073,7 @@ importers:
specifier: ^0.2.2
version: 0.2.2(@types/react@18.3.23)(react@18.3.1)
zod:
- specifier: ^3.24.2
+ specifier: ^3.24.4
version: 3.24.4
devDependencies:
'@jest/globals':
@@ -1074,7 +1092,7 @@ importers:
specifier: ^8.5.6
version: 8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))
'@storybook/react':
- specifier: ^8.5.6
+ specifier: ^8.6.12
version: 8.6.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.12(prettier@3.5.3))(typescript@5.8.3)
'@storybook/react-vite':
specifier: ^8.5.6
@@ -1083,7 +1101,7 @@ importers:
specifier: ^6.6.3
version: 6.6.3
'@testing-library/react':
- specifier: ^16.2.0
+ specifier: ^16.3.0
version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@testing-library/user-event':
specifier: ^14.6.1
@@ -1098,7 +1116,7 @@ importers:
specifier: ^18.3.23
version: 18.3.23
'@types/react-dom':
- specifier: ^18.3.5
+ specifier: ^18.3.7
version: 18.3.7(@types/react@18.3.23)
'@types/shell-quote':
specifier: ^1.7.5
@@ -4448,6 +4466,9 @@ packages:
'@types/node@22.15.29':
resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
+ '@types/node@22.15.30':
+ resolution: {integrity: sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==}
+
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
@@ -4591,6 +4612,9 @@ packages:
'@vitest/expect@3.2.1':
resolution: {integrity: sha512-FqS/BnDOzV6+IpxrTg5GQRyLOCtcJqkwMwcS8qGCI2IyRVDwPAtutztaf1CjtPHlZlWtl1yUPCd7HM0cNiDOYw==}
+ '@vitest/expect@3.2.2':
+ resolution: {integrity: sha512-ipHw0z669vEMjzz3xQE8nJX1s0rQIb7oEl4jjl35qWTwm/KIHERIg/p/zORrjAaZKXfsv7IybcNGHwhOOAPMwQ==}
+
'@vitest/mocker@3.1.3':
resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==}
peerDependencies:
@@ -4624,6 +4648,17 @@ packages:
vite:
optional: true
+ '@vitest/mocker@3.2.2':
+ resolution: {integrity: sha512-jKojcaRyIYpDEf+s7/dD3LJt53c0dPfp5zCPXz9H/kcGrSlovU/t1yEaNzM9oFME3dcd4ULwRI/x0Po1Zf+LTw==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
'@vitest/pretty-format@3.1.3':
resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==}
@@ -4633,6 +4668,9 @@ packages:
'@vitest/pretty-format@3.2.1':
resolution: {integrity: sha512-xBh1X2GPlOGBupp6E1RcUQWIxw0w/hRLd3XyBS6H+dMdKTAqHDNsIR2AnJwPA3yYe9DFy3VUKTe3VRTrAiQ01g==}
+ '@vitest/pretty-format@3.2.2':
+ resolution: {integrity: sha512-FY4o4U1UDhO9KMd2Wee5vumwcaHw7Vg4V7yR4Oq6uK34nhEJOmdRYrk3ClburPRUA09lXD/oXWZ8y/Sdma0aUQ==}
+
'@vitest/runner@3.1.3':
resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==}
@@ -4642,6 +4680,9 @@ packages:
'@vitest/runner@3.2.1':
resolution: {integrity: sha512-kygXhNTu/wkMYbwYpS3z/9tBe0O8qpdBuC3dD/AW9sWa0LE/DAZEjnHtWA9sIad7lpD4nFW1yQ+zN7mEKNH3yA==}
+ '@vitest/runner@3.2.2':
+ resolution: {integrity: sha512-GYcHcaS3ejGRZYed2GAkvsjBeXIEerDKdX3orQrBJqLRiea4NSS9qvn9Nxmuy1IwIB+EjFOaxXnX79l8HFaBwg==}
+
'@vitest/snapshot@3.1.3':
resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==}
@@ -4651,6 +4692,9 @@ packages:
'@vitest/snapshot@3.2.1':
resolution: {integrity: sha512-5xko/ZpW2Yc65NVK9Gpfg2y4BFvcF+At7yRT5AHUpTg9JvZ4xZoyuRY4ASlmNcBZjMslV08VRLDrBOmUe2YX3g==}
+ '@vitest/snapshot@3.2.2':
+ resolution: {integrity: sha512-aMEI2XFlR1aNECbBs5C5IZopfi5Lb8QJZGGpzS8ZUHML5La5wCbrbhLOVSME68qwpT05ROEEOAZPRXFpxZV2wA==}
+
'@vitest/spy@3.1.3':
resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==}
@@ -4660,6 +4704,9 @@ packages:
'@vitest/spy@3.2.1':
resolution: {integrity: sha512-Nbfib34Z2rfcJGSetMxjDCznn4pCYPZOtQYox2kzebIJcgH75yheIKd5QYSFmR8DIZf2M8fwOm66qSDIfRFFfQ==}
+ '@vitest/spy@3.2.2':
+ resolution: {integrity: sha512-6Utxlx3o7pcTxvp0u8kUiXtRFScMrUg28KjB3R2hon7w4YqOFAEA9QwzPVVS1QNL3smo4xRNOpNZClRVfpMcYg==}
+
'@vitest/utils@3.1.3':
resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==}
@@ -4669,6 +4716,9 @@ packages:
'@vitest/utils@3.2.1':
resolution: {integrity: sha512-KkHlGhePEKZSub5ViknBcN5KEF+u7dSUr9NW8QsVICusUojrgrOnnY3DEWWO877ax2Pyopuk2qHmt+gkNKnBVw==}
+ '@vitest/utils@3.2.2':
+ resolution: {integrity: sha512-qJYMllrWpF/OYfWHP32T31QCaLa3BAzT/n/8mNGhPdVcjY+JYazQFO1nsJvXU12Kp1xMpNY4AGuljPTNjQve6A==}
+
'@vscode/codicons@0.0.36':
resolution: {integrity: sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==}
@@ -10413,6 +10463,11 @@ packages:
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
+ vite-node@3.2.2:
+ resolution: {integrity: sha512-Xj/jovjZvDXOq2FgLXu8NsY4uHUMWtzVmMC2LkCu9HWdr9Qu1Is5sanX3Z4jOFKdohfaWDnEJWp9pRP0vVpAcA==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
vite@6.3.5:
resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -10537,6 +10592,34 @@ packages:
jsdom:
optional: true
+ vitest@3.2.2:
+ resolution: {integrity: sha512-fyNn/Rp016Bt5qvY0OQvIUCwW2vnaEBLxP42PmKbNIoasSYjML+8xyeADOPvBe+Xfl/ubIw4og7Lt9jflRsCNw==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.2
+ '@vitest/ui': 3.2.2
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
@@ -12304,7 +12387,7 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@@ -12389,7 +12472,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@@ -14630,7 +14713,7 @@ snapshots:
'@types/graceful-fs@4.1.9':
dependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
'@types/hast@3.0.4':
dependencies:
@@ -14689,7 +14772,7 @@ snapshots:
'@types/node-fetch@2.6.12':
dependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
form-data: 4.0.2
'@types/node-ipc@9.2.3':
@@ -14718,6 +14801,10 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/node@22.15.30':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/parse-json@4.0.2':
optional: true
@@ -14769,7 +14856,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
'@types/yargs-parser@21.0.3': {}
@@ -14779,7 +14866,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
optional: true
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)':
@@ -14903,21 +14990,29 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
- '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
+ '@vitest/expect@3.2.2':
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/spy': 3.2.2
+ '@vitest/utils': 3.2.2
+ chai: 5.2.0
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.1.3
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
+ '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.1.3
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
'@vitest/mocker@3.2.0(vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
@@ -14927,13 +15022,21 @@ snapshots:
optionalDependencies:
vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- '@vitest/mocker@3.2.1(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
+ '@vitest/mocker@3.2.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.1
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+
+ '@vitest/mocker@3.2.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))':
+ dependencies:
+ '@vitest/spy': 3.2.2
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ optionalDependencies:
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
'@vitest/pretty-format@3.1.3':
dependencies:
@@ -14947,6 +15050,10 @@ snapshots:
dependencies:
tinyrainbow: 2.0.0
+ '@vitest/pretty-format@3.2.2':
+ dependencies:
+ tinyrainbow: 2.0.0
+
'@vitest/runner@3.1.3':
dependencies:
'@vitest/utils': 3.1.3
@@ -14962,6 +15069,11 @@ snapshots:
'@vitest/utils': 3.2.1
pathe: 2.0.3
+ '@vitest/runner@3.2.2':
+ dependencies:
+ '@vitest/utils': 3.2.2
+ pathe: 2.0.3
+
'@vitest/snapshot@3.1.3':
dependencies:
'@vitest/pretty-format': 3.1.3
@@ -14980,6 +15092,12 @@ snapshots:
magic-string: 0.30.17
pathe: 2.0.3
+ '@vitest/snapshot@3.2.2':
+ dependencies:
+ '@vitest/pretty-format': 3.2.2
+ magic-string: 0.30.17
+ pathe: 2.0.3
+
'@vitest/spy@3.1.3':
dependencies:
tinyspy: 3.0.2
@@ -14992,6 +15110,10 @@ snapshots:
dependencies:
tinyspy: 4.0.3
+ '@vitest/spy@3.2.2':
+ dependencies:
+ tinyspy: 4.0.3
+
'@vitest/utils@3.1.3':
dependencies:
'@vitest/pretty-format': 3.1.3
@@ -15010,6 +15132,12 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 2.0.0
+ '@vitest/utils@3.2.2':
+ dependencies:
+ '@vitest/pretty-format': 3.2.2
+ loupe: 3.1.3
+ tinyrainbow: 2.0.0
+
'@vscode/codicons@0.0.36': {}
'@vscode/test-cli@0.0.11':
@@ -17939,7 +18067,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
chalk: 4.1.2
co: 4.6.0
dedent: 1.6.0(babel-plugin-macros@3.1.0)
@@ -18126,7 +18254,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -18136,7 +18264,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@@ -18210,7 +18338,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@@ -18238,7 +18366,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
chalk: 4.1.2
cjs-module-lexer: 1.4.3
collect-v8-coverage: 1.0.2
@@ -18305,7 +18433,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -18314,7 +18442,7 @@ snapshots:
jest-worker@29.7.0:
dependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -18502,10 +18630,10 @@ snapshots:
kleur@3.0.3: {}
- knip@5.60.0(@types/node@22.15.29)(typescript@5.8.3):
+ knip@5.60.0(@types/node@22.15.30)(typescript@5.8.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
fast-glob: 3.3.3
formatly: 0.2.4
jiti: 2.4.2
@@ -21878,13 +22006,34 @@ snapshots:
- tsx
- yaml
- vite-node@3.2.1(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vite-node@3.2.1(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@8.1.1)
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vite-node@3.2.2(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.1(supports-color@8.1.1)
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -21947,7 +22096,7 @@ snapshots:
tsx: 4.19.4
yaml: 2.8.0
- vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
esbuild: 0.25.5
fdir: 6.4.4(picomatch@4.0.2)
@@ -21956,7 +22105,7 @@ snapshots:
rollup: 4.40.2
tinyglobby: 0.2.13
optionalDependencies:
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.30.1
@@ -21966,7 +22115,7 @@ snapshots:
vitest@3.1.3(@types/debug@4.1.12)(@types/node@20.17.50)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
'@vitest/expect': 3.1.3
- '@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
+ '@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
'@vitest/pretty-format': 3.1.3
'@vitest/runner': 3.1.3
'@vitest/snapshot': 3.1.3
@@ -22088,11 +22237,11 @@ snapshots:
- tsx
- yaml
- vitest@3.2.1(@types/debug@4.1.12)(@types/node@22.15.29)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ vitest@3.2.1(@types/debug@4.1.12)(@types/node@22.15.30)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.1
- '@vitest/mocker': 3.2.1(vite@6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
+ '@vitest/mocker': 3.2.1(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.1
'@vitest/runner': 3.2.1
'@vitest/snapshot': 3.2.1
@@ -22110,12 +22259,55 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.0
tinyrainbow: 2.0.0
- vite: 6.3.5(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
- vite-node: 3.2.1(@types/node@22.15.29)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite-node: 3.2.1(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
- '@types/node': 22.15.29
+ '@types/node': 22.15.30
+ jsdom: 20.0.3
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ vitest@3.2.2(@types/debug@4.1.12)(@types/node@22.15.30)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0):
+ dependencies:
+ '@types/chai': 5.2.2
+ '@vitest/expect': 3.2.2
+ '@vitest/mocker': 3.2.2(vite@6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))
+ '@vitest/pretty-format': 3.2.2
+ '@vitest/runner': 3.2.2
+ '@vitest/snapshot': 3.2.2
+ '@vitest/spy': 3.2.2
+ '@vitest/utils': 3.2.2
+ chai: 5.2.0
+ debug: 4.4.1(supports-color@8.1.1)
+ expect-type: 1.2.1
+ magic-string: 0.30.17
+ pathe: 2.0.3
+ picomatch: 4.0.2
+ std-env: 3.9.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.14
+ tinypool: 1.1.0
+ tinyrainbow: 2.0.0
+ vite: 6.3.5(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ vite-node: 3.2.2(@types/node@22.15.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/debug': 4.1.12
+ '@types/node': 22.15.30
jsdom: 20.0.3
transitivePeerDependencies:
- jiti
diff --git a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
index 8f93b0353d..4bf0a13f87 100644
--- a/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system.test.ts.snap
@@ -37,22 +37,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -64,7 +64,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -74,11 +74,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -95,6 +96,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -531,22 +536,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -558,7 +563,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -568,11 +573,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -589,6 +595,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -1025,22 +1035,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -1052,7 +1062,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -1062,11 +1072,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -1083,6 +1094,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -1519,22 +1534,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -1546,7 +1561,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -1556,11 +1571,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -1577,6 +1593,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -2069,22 +2089,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -2096,7 +2116,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -2106,11 +2126,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -2127,6 +2148,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -2631,22 +2656,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -2658,7 +2683,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -2668,11 +2693,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -2689,6 +2715,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -3181,22 +3211,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -3208,7 +3238,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -3218,11 +3248,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -3239,6 +3270,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -3763,22 +3798,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -3790,7 +3825,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -3800,11 +3835,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -3821,6 +3857,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -4299,22 +4339,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -4326,7 +4366,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -4336,11 +4376,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -4357,6 +4398,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -4870,22 +4915,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -4897,7 +4942,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -4907,11 +4952,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -4928,6 +4974,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -5355,22 +5405,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -5382,7 +5432,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -5392,11 +5442,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -5413,6 +5464,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
@@ -5757,22 +5812,22 @@ Always use the actual tool name as the XML tag name for proper parsing and execu
# Tools
## read_file
-Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of one or more files. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
**IMPORTANT: You can read a maximum of 15 files in a single request.** If you need to read more files, use multiple sequential read_file requests.
-
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory /test/path)
-
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
-
+ start-end
@@ -5784,7 +5839,7 @@ Examples:
src/app.ts
-
+ 1-1000
@@ -5794,11 +5849,12 @@ Examples:
src/app.ts
-
+ 1-50
+ 100-150
src/utils.ts
-
+ 10-20
@@ -5815,6 +5871,10 @@ Examples:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- You MUST read all related files and implementations together in a single operation (up to 15 files at once)
- You MUST obtain all necessary context before proceeding with changes
+- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+- You MUST combine adjacent line ranges (<10 lines apart)
+- You MUST use multiple ranges for content separated by >10 lines
+- You MUST include sufficient line context for planned modifications while keeping ranges minimal
- When you need to read more than 15 files, prioritize the most critical files first, then use subsequent read_file requests for additional files
diff --git a/src/core/prompts/tools/read-file.ts b/src/core/prompts/tools/read-file.ts
index 9df1e0b1ab..41ca747624 100644
--- a/src/core/prompts/tools/read-file.ts
+++ b/src/core/prompts/tools/read-file.ts
@@ -5,22 +5,22 @@ export function getReadFileDescription(args: ToolArgs): string {
const isMultipleReadsEnabled = maxConcurrentReads > 1
return `## read_file
-Description: Request to read the contents of ${isMultipleReadsEnabled ? "one or more files" : "a file"}. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code.${args.partialReadsEnabled ? " Use line ranges to efficiently read specific portions of large files." : ""} Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
+Description: Request to read the contents of ${isMultipleReadsEnabled ? "one or more files" : "a file"}. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when creating diffs or discussing code. Use line ranges to efficiently read specific portions of large files. Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.
${isMultipleReadsEnabled ? `**IMPORTANT: You can read a maximum of ${maxConcurrentReads} files in a single request.** If you need to read more files, use multiple sequential read_file requests.` : "**IMPORTANT: Multiple file reads are currently disabled. You can only read one file at a time.**"}
-${args.partialReadsEnabled ? `By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.` : ""}
+By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.
Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory ${args.cwd})
- ${args.partialReadsEnabled ? `- line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)` : ""}
+ - line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)
Usage:
path/to/file
- ${args.partialReadsEnabled ? `start-end` : ""}
+ start-end
@@ -32,7 +32,7 @@ Examples:
src/app.ts
- ${args.partialReadsEnabled ? `1-1000` : ""}
+ 1-1000
@@ -44,16 +44,12 @@ ${isMultipleReadsEnabled ? `2. Reading multiple files (within the ${maxConcurren
src/app.ts
- ${
- args.partialReadsEnabled
- ? `1-50
- 100-150`
- : ""
- }
+ 1-50
+ 100-150
src/utils.ts
- ${args.partialReadsEnabled ? `10-20` : ""}
+ 10-20
`
@@ -72,14 +68,10 @@ ${isMultipleReadsEnabled ? "3. " : "2. "}Reading an entire file:
IMPORTANT: You MUST use this Efficient Reading Strategy:
- ${isMultipleReadsEnabled ? `You MUST read all related files and implementations together in a single operation (up to ${maxConcurrentReads} files at once)` : "You MUST read files one at a time, as multiple file reads are currently disabled"}
- You MUST obtain all necessary context before proceeding with changes
-${
- args.partialReadsEnabled
- ? `- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
+${`- You MUST use line ranges to read specific portions of large files, rather than reading entire files when not needed
- You MUST combine adjacent line ranges (<10 lines apart)
- You MUST use multiple ranges for content separated by >10 lines
- You MUST include sufficient line context for planned modifications while keeping ranges minimal
-`
- : ""
-}
+`}
${isMultipleReadsEnabled ? `- When you need to read more than ${maxConcurrentReads} files, prioritize the most critical files first, then use subsequent read_file requests for additional files` : ""}`
}
diff --git a/src/core/services/__tests__/fileReadCacheService.spec.ts b/src/core/services/__tests__/fileReadCacheService.spec.ts
new file mode 100644
index 0000000000..47449264ee
--- /dev/null
+++ b/src/core/services/__tests__/fileReadCacheService.spec.ts
@@ -0,0 +1,179 @@
+import { vi, describe, it, expect, beforeEach } from "vitest"
+import {
+ processAndFilterReadRequest,
+ subtractRange,
+ subtractRanges,
+ ConversationMessage,
+} from "../fileReadCacheService"
+import { stat } from "fs/promises"
+
+vi.mock("fs/promises", () => ({
+ stat: vi.fn(),
+}))
+
+const mockedStat = vi.mocked(stat)
+
+describe("fileReadCacheService", () => {
+ describe("subtractRange", () => {
+ it("should return the original range if there is no overlap", () => {
+ const original = { start: 1, end: 10 }
+ const toRemove = { start: 11, end: 20 }
+ expect(subtractRange(original, toRemove)).toEqual([original])
+ })
+
+ it("should return an empty array if the range is completely removed", () => {
+ const original = { start: 1, end: 10 }
+ const toRemove = { start: 1, end: 10 }
+ expect(subtractRange(original, toRemove)).toEqual([])
+ })
+
+ it("should subtract from the beginning", () => {
+ const original = { start: 1, end: 10 }
+ const toRemove = { start: 1, end: 5 }
+ expect(subtractRange(original, toRemove)).toEqual([{ start: 6, end: 10 }])
+ })
+
+ it("should subtract from the end", () => {
+ const original = { start: 1, end: 10 }
+ const toRemove = { start: 6, end: 10 }
+ expect(subtractRange(original, toRemove)).toEqual([{ start: 1, end: 5 }])
+ })
+
+ it("should subtract from the middle, creating two new ranges", () => {
+ const original = { start: 1, end: 10 }
+ const toRemove = { start: 4, end: 6 }
+ expect(subtractRange(original, toRemove)).toEqual([
+ { start: 1, end: 3 },
+ { start: 7, end: 10 },
+ ])
+ })
+ })
+
+ describe("subtractRanges", () => {
+ it("should subtract multiple ranges from a single original range", () => {
+ const originals = [{ start: 1, end: 20 }]
+ const toRemoves = [
+ { start: 1, end: 5 },
+ { start: 15, end: 20 },
+ ]
+ expect(subtractRanges(originals, toRemoves)).toEqual([{ start: 6, end: 14 }])
+ })
+ })
+
+ describe("processAndFilterReadRequest", () => {
+ const MOCK_FILE_PATH = "/test/file.txt"
+ const CURRENT_MTIME = 1000
+
+ beforeEach(() => {
+ mockedStat.mockResolvedValue({ mtime: { getTime: () => CURRENT_MTIME } } as any)
+ })
+
+ it("should allow all when history is empty", async () => {
+ const requestedRanges = [{ start: 1, end: 10 }]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, [])
+ expect(result.status).toBe("ALLOW_ALL")
+ expect(result.rangesToRead).toEqual(requestedRanges)
+ })
+
+ it("should reject all when a full cache hit occurs", async () => {
+ const requestedRanges = [{ start: 1, end: 10 }]
+ const conversationHistory: ConversationMessage[] = [
+ {
+ files: [
+ {
+ fileName: MOCK_FILE_PATH,
+ mtime: CURRENT_MTIME,
+ loadedRanges: [{ start: 1, end: 10 }],
+ },
+ ],
+ },
+ ]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
+ expect(result.status).toBe("REJECT_ALL")
+ expect(result.rangesToRead).toEqual([])
+ })
+
+ it("should allow partial when a partial cache hit occurs", async () => {
+ const requestedRanges = [{ start: 1, end: 20 }]
+ const conversationHistory: ConversationMessage[] = [
+ {
+ files: [
+ {
+ fileName: MOCK_FILE_PATH,
+ mtime: CURRENT_MTIME,
+ loadedRanges: [{ start: 1, end: 10 }],
+ },
+ ],
+ },
+ ]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
+ expect(result.status).toBe("ALLOW_PARTIAL")
+ expect(result.rangesToRead).toEqual([{ start: 11, end: 20 }])
+ })
+
+ it("should allow all when mtime is older in history", async () => {
+ const requestedRanges = [{ start: 1, end: 10 }]
+ const conversationHistory: ConversationMessage[] = [
+ {
+ files: [
+ {
+ fileName: MOCK_FILE_PATH,
+ mtime: CURRENT_MTIME - 100, // Older mtime
+ loadedRanges: [{ start: 1, end: 10 }],
+ },
+ ],
+ },
+ ]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
+ expect(result.status).toBe("ALLOW_ALL")
+ expect(result.rangesToRead).toEqual(requestedRanges)
+ })
+
+ it("should allow all for a file not in history", async () => {
+ const requestedRanges = [{ start: 1, end: 10 }]
+ const conversationHistory: ConversationMessage[] = [
+ {
+ files: [
+ {
+ fileName: "/another/file.txt",
+ mtime: CURRENT_MTIME,
+ loadedRanges: [{ start: 1, end: 10 }],
+ },
+ ],
+ },
+ ]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
+ expect(result.status).toBe("ALLOW_ALL")
+ expect(result.rangesToRead).toEqual(requestedRanges)
+ })
+
+ it("should correctly use the most recent valid history entry", async () => {
+ const requestedRanges = [{ start: 1, end: 20 }]
+ const conversationHistory: ConversationMessage[] = [
+ {
+ // Older, incorrect mtime
+ files: [
+ {
+ fileName: MOCK_FILE_PATH,
+ mtime: CURRENT_MTIME - 100,
+ loadedRanges: [{ start: 1, end: 20 }],
+ },
+ ],
+ },
+ {
+ // Newer, correct mtime but only partial coverage
+ files: [
+ {
+ fileName: MOCK_FILE_PATH,
+ mtime: CURRENT_MTIME,
+ loadedRanges: [{ start: 1, end: 5 }],
+ },
+ ],
+ },
+ ]
+ const result = await processAndFilterReadRequest(MOCK_FILE_PATH, requestedRanges, conversationHistory)
+ expect(result.status).toBe("ALLOW_PARTIAL")
+ expect(result.rangesToRead).toEqual([{ start: 6, end: 20 }])
+ })
+ })
+})
diff --git a/src/core/services/fileReadCacheService.ts b/src/core/services/fileReadCacheService.ts
new file mode 100644
index 0000000000..64044101ad
--- /dev/null
+++ b/src/core/services/fileReadCacheService.ts
@@ -0,0 +1,119 @@
+import { stat } from "fs/promises"
+
+export interface LineRange {
+ start: number
+ end: number
+}
+
+export interface FileMetadata {
+ fileName: string
+ mtime: number
+ loadedRanges: LineRange[]
+}
+
+export interface ConversationMessage {
+ files?: FileMetadata[]
+}
+
+export interface FilteredReadRequest {
+ status: "REJECT_ALL" | "ALLOW_PARTIAL" | "ALLOW_ALL"
+ rangesToRead: LineRange[]
+}
+
+/**
+ * Checks if two ranges overlap.
+ */
+function rangesOverlap(r1: LineRange, r2: LineRange): boolean {
+ return r1.start <= r2.end && r1.end >= r2.start
+}
+
+/**
+ * Subtracts one range from another.
+ * Returns an array of ranges that are in `original` but not in `toRemove`.
+ */
+export function subtractRange(original: LineRange, toRemove: LineRange): LineRange[] {
+ if (!rangesOverlap(original, toRemove)) {
+ return [original]
+ }
+
+ const result: LineRange[] = []
+
+ // Part of original before toRemove
+ if (original.start < toRemove.start) {
+ result.push({ start: original.start, end: toRemove.start - 1 })
+ }
+
+ // Part of original after toRemove
+ if (original.end > toRemove.end) {
+ result.push({ start: toRemove.end + 1, end: original.end })
+ }
+
+ return result
+}
+
+/**
+ * Subtracts a set of ranges from another set of ranges.
+ */
+export function subtractRanges(originals: LineRange[], toRemoves: LineRange[]): LineRange[] {
+ let remaining = [...originals]
+
+ for (const toRemove of toRemoves) {
+ remaining = remaining.flatMap((original) => subtractRange(original, toRemove))
+ }
+
+ return remaining
+}
+
+export async function processAndFilterReadRequest(
+ requestedFile: string,
+ requestedRanges: LineRange[],
+ conversationHistory: ConversationMessage[],
+): Promise {
+ let currentMtime: number
+ try {
+ currentMtime = (await stat(requestedFile)).mtime.getTime()
+ } catch (error) {
+ // File doesn't exist or other error, so we must read.
+ return {
+ status: "ALLOW_ALL",
+ rangesToRead: requestedRanges,
+ }
+ }
+
+ let rangesThatStillNeedToBeRead = [...requestedRanges]
+
+ for (let i = conversationHistory.length - 1; i >= 0; i--) {
+ const message = conversationHistory[i]
+ if (!message.files) {
+ continue
+ }
+
+ const relevantFileHistory = message.files.find((f) => f.fileName === requestedFile)
+
+ if (relevantFileHistory && relevantFileHistory.mtime >= currentMtime) {
+ rangesThatStillNeedToBeRead = subtractRanges(rangesThatStillNeedToBeRead, relevantFileHistory.loadedRanges)
+
+ if (rangesThatStillNeedToBeRead.length === 0) {
+ return {
+ status: "REJECT_ALL",
+ rangesToRead: [],
+ }
+ }
+ }
+ }
+
+ const originalRangesString = JSON.stringify(requestedRanges.sort((a, b) => a.start - b.start))
+ const finalRangesString = JSON.stringify(rangesThatStillNeedToBeRead.sort((a, b) => a.start - b.start))
+
+ if (originalRangesString === finalRangesString) {
+ return {
+ status: "ALLOW_ALL",
+ rangesToRead: requestedRanges,
+ }
+ }
+
+ return {
+ status: "ALLOW_PARTIAL",
+ rangesToRead: rangesThatStillNeedToBeRead,
+ }
+}
diff --git a/src/core/tools/__tests__/readFileTool.test.ts b/src/core/tools/__tests__/readFileTool.test.ts
index 3ed5cbe3f1..8158d98fc8 100644
--- a/src/core/tools/__tests__/readFileTool.test.ts
+++ b/src/core/tools/__tests__/readFileTool.test.ts
@@ -1,6 +1,8 @@
// npx jest src/core/tools/__tests__/readFileTool.test.ts
import * as path from "path"
+import * as fs from "fs"
+import * as fsp from "fs/promises"
import { countFileLines } from "../../../integrations/misc/line-counter"
import { readLines } from "../../../integrations/misc/read-lines"
@@ -11,24 +13,11 @@ import { ReadFileToolUse, ToolParamName, ToolResponse } from "../../../shared/to
import { readFileTool } from "../readFileTool"
import { formatResponse } from "../../prompts/responses"
-jest.mock("path", () => {
- const originalPath = jest.requireActual("path")
- return {
- ...originalPath,
- resolve: jest.fn().mockImplementation((...args) => args.join("/")),
- }
-})
-
-jest.mock("fs/promises", () => ({
- mkdir: jest.fn().mockResolvedValue(undefined),
- writeFile: jest.fn().mockResolvedValue(undefined),
- readFile: jest.fn().mockResolvedValue("{}"),
-}))
-
jest.mock("isbinaryfile")
jest.mock("../../../integrations/misc/line-counter")
jest.mock("../../../integrations/misc/read-lines")
+jest.mock("../../services/fileReadCacheService")
// Mock input content for tests
let mockInputContent = ""
@@ -64,14 +53,10 @@ jest.mock("../../ignore/RooIgnoreController", () => ({
},
}))
-jest.mock("../../../utils/fs", () => ({
- fileExistsAtPath: jest.fn().mockReturnValue(true),
-}))
-
describe("read_file tool with maxReadFileLine setting", () => {
// Test data
- const testFilePath = "test/file.txt"
- const absoluteFilePath = "/test/file.txt"
+ const testDir = path.join(__dirname, "test_files")
+ const testFilePath = path.join(testDir, "file.txt") // Use path.join for OS-agnostic paths
const fileContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
const numberedFileContent = "1 | Line 1\n2 | Line 2\n3 | Line 3\n4 | Line 4\n5 | Line 5\n"
const sourceCodeDef = "\n\n# file.txt\n1--5 | Content"
@@ -85,16 +70,26 @@ describe("read_file tool with maxReadFileLine setting", () => {
>
const mockedIsBinaryFile = isBinaryFile as jest.MockedFunction
- const mockedPathResolve = path.resolve as jest.MockedFunction
+ const { processAndFilterReadRequest } = require("../../services/fileReadCacheService")
+ const mockedProcessAndFilterReadRequest = processAndFilterReadRequest as jest.Mock
const mockCline: any = {}
let mockProvider: any
let toolResult: ToolResponse | undefined
- beforeEach(() => {
+ beforeEach(async () => {
jest.clearAllMocks()
+ mockedProcessAndFilterReadRequest.mockResolvedValue({
+ status: "ALLOW_ALL",
+ rangesToRead: [],
+ })
+
+ // Create test directory and file
+ if (!fs.existsSync(testDir)) {
+ await fsp.mkdir(testDir, { recursive: true })
+ }
+ await fsp.writeFile(testFilePath, fileContent)
- mockedPathResolve.mockReturnValue(absoluteFilePath)
mockedIsBinaryFile.mockResolvedValue(false)
mockInputContent = fileContent
@@ -117,9 +112,10 @@ describe("read_file tool with maxReadFileLine setting", () => {
deref: jest.fn().mockReturnThis(),
}
- mockCline.cwd = "/"
+ mockCline.cwd = process.cwd() // Use actual cwd for resolving test files
mockCline.task = "Test"
mockCline.providerRef = mockProvider
+ mockCline.apiConversationHistory = []
mockCline.rooIgnoreController = {
validateAccess: jest.fn().mockReturnValue(true),
}
@@ -140,6 +136,13 @@ describe("read_file tool with maxReadFileLine setting", () => {
toolResult = undefined
})
+ afterEach(async () => {
+ // Clean up test directory
+ if (fs.existsSync(testDir)) {
+ await fsp.rm(testDir, { recursive: true, force: true })
+ }
+ })
+
/**
* Helper function to execute the read file tool with different maxReadFileLine settings
*/
@@ -165,6 +168,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
addLineNumbersMock.mockClear()
// Format args string based on params
+ // Use the actual testFilePath which is now absolute
let argsContent = `${options.path || testFilePath}`
if (options.start_line && options.end_line) {
argsContent += `${options.start_line}-${options.end_line}`
@@ -380,13 +384,121 @@ describe("read_file tool with maxReadFileLine setting", () => {
})
})
+describe("readFileTool with fileReadCacheService", () => {
+ const testDir = path.join(__dirname, "test_files_cache")
+ const testFilePath = path.join(testDir, "cached_file.txt")
+ const fileContent = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`).join("\n")
+
+ const mockedCountFileLines = countFileLines as jest.MockedFunction
+ const mockedReadLines = readLines as jest.MockedFunction
+ const { processAndFilterReadRequest } = require("../../services/fileReadCacheService")
+ const mockedProcessAndFilterReadRequest = processAndFilterReadRequest as jest.Mock
+
+ const mockCline: any = {}
+ let mockProvider: any
+ let toolResult: ToolResponse | undefined
+
+ beforeEach(async () => {
+ jest.clearAllMocks()
+ if (!fs.existsSync(testDir)) {
+ await fsp.mkdir(testDir, { recursive: true })
+ }
+ await fsp.writeFile(testFilePath, fileContent)
+
+ mockedCountFileLines.mockResolvedValue(20)
+ mockedReadLines.mockImplementation(async (filePath, end, start) => {
+ const lines = fileContent.split("\n")
+ // Ensure start is a number, default to 0 if undefined
+ const startIndex = start ?? 0
+ // Handle undefined end, which slice can take to mean 'to the end'
+ const endIndex = end === undefined ? undefined : end + 1
+ return lines.slice(startIndex, endIndex).join("\n")
+ })
+
+ mockProvider = {
+ getState: jest.fn().mockResolvedValue({ maxReadFileLine: -1 }),
+ deref: jest.fn().mockReturnThis(),
+ }
+
+ mockCline.cwd = process.cwd()
+ mockCline.providerRef = mockProvider
+ mockCline.apiConversationHistory = []
+ mockCline.rooIgnoreController = { validateAccess: jest.fn().mockReturnValue(true) }
+ mockCline.ask = jest.fn().mockResolvedValue({ response: "yesButtonClicked" })
+ mockCline.fileContextTracker = { trackFileContext: jest.fn() }
+ mockCline.say = jest.fn()
+ })
+
+ afterEach(async () => {
+ if (fs.existsSync(testDir)) {
+ await fsp.rm(testDir, { recursive: true, force: true })
+ }
+ })
+
+ async function executeToolWithCache(
+ args: string,
+ cacheResponse: { status: string; rangesToRead: any[] },
+ ): Promise {
+ mockedProcessAndFilterReadRequest.mockResolvedValue(cacheResponse)
+
+ const toolUse: ReadFileToolUse = {
+ type: "tool_use",
+ name: "read_file",
+ params: { args },
+ partial: false,
+ }
+
+ let result: ToolResponse | undefined
+ await readFileTool(
+ mockCline,
+ toolUse,
+ mockCline.ask,
+ jest.fn(),
+ (res: ToolResponse) => {
+ result = res
+ },
+ (_: ToolParamName, content?: string) => content ?? "",
+ )
+ return result
+ }
+
+ it('should not call readLines when cache returns "REJECT_ALL"', async () => {
+ const result = await executeToolWithCache(`${testFilePath}`, {
+ status: "REJECT_ALL",
+ rangesToRead: [],
+ })
+
+ expect(mockedReadLines).not.toHaveBeenCalled()
+ expect(result).toContain("File content is already up-to-date in the conversation history.")
+ })
+
+ it('should call readLines with filtered ranges for "ALLOW_PARTIAL"', async () => {
+ const rangesToRead = [{ start: 5, end: 10 }]
+ await executeToolWithCache(`${testFilePath}1-20`, {
+ status: "ALLOW_PARTIAL",
+ rangesToRead,
+ })
+
+ expect(mockedReadLines).toHaveBeenCalledWith(testFilePath, 9, 4)
+ })
+
+ it('should call readLines with original ranges for "ALLOW_ALL"', async () => {
+ await executeToolWithCache(`${testFilePath}1-15`, {
+ status: "ALLOW_ALL",
+ rangesToRead: [], // This won't be used
+ })
+
+ expect(mockedReadLines).toHaveBeenCalledWith(testFilePath, 14, 0)
+ })
+})
+
describe("read_file tool XML output structure", () => {
// Add new test data for feedback messages
const _feedbackMessage = "Test feedback message"
const _feedbackImages = ["image1.png", "image2.png"]
// Test data
- const testFilePath = "test/file.txt"
- const absoluteFilePath = "/test/file.txt"
+ const testDir = path.join(__dirname, "test_files_xml") // Use a different directory for this describe block
+ const testFilePath = path.join(testDir, "file.txt")
const fileContent = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
const sourceCodeDef = "\n\n# file.txt\n1--5 | Content"
@@ -398,17 +510,21 @@ describe("read_file tool XML output structure", () => {
typeof parseSourceCodeDefinitionsForFile
>
const mockedIsBinaryFile = isBinaryFile as jest.MockedFunction
- const mockedPathResolve = path.resolve as jest.MockedFunction
// Mock instances
const mockCline: any = {}
let mockProvider: any
let toolResult: ToolResponse | undefined
- beforeEach(() => {
+ beforeEach(async () => {
jest.clearAllMocks()
- mockedPathResolve.mockReturnValue(absoluteFilePath)
+ // Create test directory and file
+ if (!fs.existsSync(testDir)) {
+ await fsp.mkdir(testDir, { recursive: true })
+ }
+ await fsp.writeFile(testFilePath, fileContent)
+
mockedIsBinaryFile.mockResolvedValue(false)
// Set default implementation for extractTextFromFile
@@ -426,7 +542,7 @@ describe("read_file tool XML output structure", () => {
deref: jest.fn().mockReturnThis(),
}
- mockCline.cwd = "/"
+ mockCline.cwd = process.cwd() // Use actual cwd
mockCline.task = "Test"
mockCline.providerRef = mockProvider
mockCline.rooIgnoreController = {
@@ -448,6 +564,13 @@ describe("read_file tool XML output structure", () => {
toolResult = undefined
})
+ afterEach(async () => {
+ // Clean up test directory
+ if (fs.existsSync(testDir)) {
+ await fsp.rm(testDir, { recursive: true, force: true })
+ }
+ })
+
/**
* Helper function to execute the read file tool with custom parameters
*/
@@ -554,8 +677,10 @@ describe("read_file tool XML output structure", () => {
const result = await executeReadFileTool()
// Verify
- expect(result).toBe(
- `\n${testFilePath}\n\n${numberedContent}\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\n\\n${numberedContent}\\n{.*}\\n\\n`,
+ ),
)
})
@@ -567,7 +692,7 @@ describe("read_file tool XML output structure", () => {
// Verify using regex to check structure
const xmlStructureRegex = new RegExp(
- `^\\n${testFilePath}\\n\\n.*\\n\\n$`,
+ `^\\n${testFilePath.replace(/\\/g, "\\\\")}\\n\\n.*\\n.*\\n\\n$`,
"s",
)
expect(result).toMatch(xmlStructureRegex)
@@ -598,8 +723,10 @@ describe("read_file tool XML output structure", () => {
const result = await executeReadFileTool({}, { totalLines: 0 })
// Verify
- expect(result).toBe(
- `\n${testFilePath}\nFile is empty\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\nFile is empty\\n{.*}\\n\\n`,
+ ),
)
})
})
@@ -638,8 +765,10 @@ describe("read_file tool XML output structure", () => {
)
// Verify
- expect(result).toBe(
- `\n${testFilePath}\n\n${numberedContent}\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\n\\n${numberedContent}\\n{.*}\\n\\n`,
+ ),
)
})
@@ -674,8 +803,10 @@ describe("read_file tool XML output structure", () => {
)
// Verify
- expect(result).toBe(
- `\n${testFilePath}\n\n${numberedContent}\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\n\\n${numberedContent}\\n{.*}\\n\\n`,
+ ),
)
})
@@ -763,8 +894,10 @@ describe("read_file tool XML output structure", () => {
)
// Should adjust to actual file length
- expect(result).toBe(
- `\n${testFilePath}\n\n${numberedContent}\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\n\\n${numberedContent}\\n{.*}\\n\\n`,
+ ),
)
// Verify
@@ -978,29 +1111,27 @@ describe("read_file tool XML output structure", () => {
describe("Multiple Files Tests", () => {
it("should handle multiple file entries correctly", async () => {
// Setup
- const file1Path = "test/file1.txt"
- const file2Path = "test/file2.txt"
- const file1Numbered = "1 | File 1 content"
- const file2Numbered = "1 | File 2 content"
-
- // Mock path resolution
- mockedPathResolve.mockImplementation((_, filePath) => {
- if (filePath === file1Path) return "/test/file1.txt"
- if (filePath === file2Path) return "/test/file2.txt"
- return filePath
- })
+ const file1Path = path.join(testDir, "file1.txt")
+ const file2Path = path.join(testDir, "file2.txt")
+ const file1Content = "File 1 content"
+ const file2Content = "File 2 content"
+ await fsp.writeFile(file1Path, file1Content)
+ await fsp.writeFile(file2Path, file2Content)
+
+ const file1Numbered = `1 | ${file1Content}`
+ const file2Numbered = `1 | ${file2Content}`
// Mock content for each file
mockedCountFileLines.mockResolvedValue(1)
mockProvider.getState.mockResolvedValue({ maxReadFileLine: -1 })
mockedExtractTextFromFile.mockImplementation((filePath) => {
- if (filePath === "/test/file1.txt") {
+ if (filePath === file1Path) {
return Promise.resolve(file1Numbered)
}
- if (filePath === "/test/file2.txt") {
+ if (filePath === file2Path) {
return Promise.resolve(file2Numbered)
}
- throw new Error("Unexpected file path")
+ throw new Error(`Unexpected file path: ${filePath}`)
})
// Execute
@@ -1012,40 +1143,37 @@ describe("read_file tool XML output structure", () => {
)
// Verify
- expect(result).toBe(
- `\n${file1Path}\n\n${file1Numbered}\n\n${file2Path}\n\n${file2Numbered}\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${file1Path.replace(/\\/g, "\\\\")}\\n\\n${file1Numbered}\\n{.*}\\n\\n${file2Path.replace(/\\/g, "\\\\")}\\n\\n${file2Numbered}\\n{.*}\\n\\n`,
+ ),
)
})
it("should handle errors in multiple file entries independently", async () => {
// Setup
- const validPath = "test/valid.txt"
- const invalidPath = "test/invalid.txt"
- const numberedContent = "1 | Valid file content"
-
- // Mock path resolution
- mockedPathResolve.mockImplementation((_, filePath) => {
- if (filePath === validPath) return "/test/valid.txt"
- if (filePath === invalidPath) return "/test/invalid.txt"
- return filePath
- })
+ const validPath = path.join(testDir, "valid.txt")
+ const invalidPath = path.join(testDir, "invalid.txt") // This file won't be created, RooIgnore will block
+ const validContent = "Valid file content"
+ await fsp.writeFile(validPath, validContent)
+ const numberedContent = `1 | ${validContent}`
// Mock RooIgnore to block invalid file and track validation order
const validationOrder: string[] = []
mockCline.rooIgnoreController = {
- validateAccess: jest.fn().mockImplementation((path) => {
- validationOrder.push(`validate:${path}`)
- const isValid = path !== invalidPath
- if (!isValid) {
- validationOrder.push(`error:${path}`)
+ validateAccess: jest.fn().mockImplementation((p) => {
+ // p is absolute path
+ validationOrder.push(`validate:${p}`)
+ const isPathValid = p === validPath
+ if (!isPathValid) {
+ validationOrder.push(`error:${p}`)
}
- return isValid
+ return isPathValid
}),
}
// Mock say to track RooIgnore error
mockCline.say = jest.fn().mockImplementation((_type, _path) => {
- // Don't add error to validationOrder here since validateAccess already does it
return Promise.resolve()
})
@@ -1054,36 +1182,37 @@ describe("read_file tool XML output structure", () => {
// Mock file operations to track operation order
mockedCountFileLines.mockImplementation((filePath) => {
- const relPath = filePath === "/test/valid.txt" ? validPath : invalidPath
- validationOrder.push(`countLines:${relPath}`)
- if (filePath.includes(validPath)) {
+ validationOrder.push(`countLines:${filePath}`)
+ if (filePath === validPath) {
return Promise.resolve(1)
}
- throw new Error("File not found")
+ // This should not be reached for invalidPath due to RooIgnore
+ throw new Error("File not found or access denied by mock")
})
mockedIsBinaryFile.mockImplementation((filePath) => {
- const relPath = filePath === "/test/valid.txt" ? validPath : invalidPath
- validationOrder.push(`isBinary:${relPath}`)
- if (filePath.includes(validPath)) {
+ validationOrder.push(`isBinary:${filePath}`)
+ if (filePath === validPath) {
return Promise.resolve(false)
}
- throw new Error("File not found")
+ // This should not be reached for invalidPath
+ throw new Error("File not found or access denied by mock")
})
mockedExtractTextFromFile.mockImplementation((filePath) => {
- if (filePath === "/test/valid.txt") {
+ if (filePath === validPath) {
validationOrder.push(`extract:${validPath}`)
return Promise.resolve(numberedContent)
}
- return Promise.reject(new Error("File not found"))
+ // This should not be reached for invalidPath
+ return Promise.reject(new Error("File not found or access denied by mock"))
})
// Mock approval for both files
mockCline.ask = jest
.fn()
.mockResolvedValueOnce({ response: "yesButtonClicked" }) // First file approved
- .mockResolvedValueOnce({ response: "noButtonClicked" }) // Second file denied
+ .mockResolvedValueOnce({ response: "noButtonClicked" }) // Second file denied - this won't be hit due to RooIgnore
// Execute - Skip the default validateAccess mock
const { readFileTool } = require("../readFileTool")
@@ -1117,44 +1246,52 @@ describe("read_file tool XML output structure", () => {
expect(validationOrder).toEqual([
`validate:${validPath}`,
`validate:${invalidPath}`,
- `error:${invalidPath}`,
- `countLines:${validPath}`,
+ `error:${invalidPath}`, // RooIgnore blocks invalidPath
+ `countLines:${validPath}`, // Operations proceed for validPath
`isBinary:${validPath}`,
`extract:${validPath}`,
])
// Verify result
- expect(result).toBe(
- `\n${validPath}\n\n${numberedContent}\n\n${invalidPath}${formatResponse.rooIgnoreError(invalidPath)}\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${validPath.replace(/\\/g, "\\\\")}\\n\\n${numberedContent}\\n{.*}\\n\\n${invalidPath.replace(/\\/g, "\\\\")}${formatResponse.rooIgnoreError(invalidPath)}\\n`,
+ ),
)
})
it("should handle mixed binary and text files", async () => {
// Setup
- const textPath = "test/text.txt"
- const binaryPath = "test/binary.pdf"
- const numberedContent = "1 | Text file content"
- const pdfContent = "1 | PDF content extracted"
+ const textPath = path.join(testDir, "text.txt")
+ const binaryPath = path.join(testDir, "binary.pdf")
+ const textContent = "Text file content"
+ const pdfContentRaw = "PDF content extracted" // Raw content
+ await fsp.writeFile(textPath, textContent)
+ await fsp.writeFile(binaryPath, "dummy binary data") // Actual content doesn't matter for this mock setup
- // Mock path.resolve to return the expected paths
- mockedPathResolve.mockImplementation((cwd, relPath) => `/${relPath}`)
+ const numberedTextContent = addLineNumbersMock(textContent) // "1 | Text file content"
+ const numberedPdfContent = addLineNumbersMock(pdfContentRaw) // "1 | PDF content extracted"
// Mock binary file detection
- mockedIsBinaryFile.mockImplementation((path) => {
- if (path.includes("text.txt")) return Promise.resolve(false)
- if (path.includes("binary.pdf")) return Promise.resolve(true)
+ mockedIsBinaryFile.mockImplementation((p) => {
+ if (p === textPath) return Promise.resolve(false)
+ if (p === binaryPath) return Promise.resolve(true)
return Promise.resolve(false)
})
- mockedCountFileLines.mockImplementation((path) => {
+ mockedCountFileLines.mockImplementation((p) => {
return Promise.resolve(1)
})
- mockedExtractTextFromFile.mockImplementation((path) => {
- if (path.includes("binary.pdf")) {
- return Promise.resolve(pdfContent)
+ // Specific mock for this test to ensure correct content is numbered and returned
+ mockedExtractTextFromFile.mockImplementation((p) => {
+ if (p === binaryPath) {
+ return Promise.resolve(numberedPdfContent) // Use pre-calculated numbered content
}
- return Promise.resolve(numberedContent)
+ if (p === textPath) {
+ return Promise.resolve(numberedTextContent) // Use pre-calculated numbered content
+ }
+ throw new Error(`Unexpected path in mixed binary/text test mock: ${p}`)
})
// Configure mocks for the test
@@ -1188,17 +1325,25 @@ describe("read_file tool XML output structure", () => {
// Check the result
expect(mockPushToolResult).toHaveBeenCalledWith(
- `\n${textPath}\n\n${numberedContent}\n\n${binaryPath}\n\n${pdfContent}\n\n`,
+ expect.stringMatching(
+ new RegExp(
+ `\\n${textPath.replace(/\\/g, "\\\\")}\\n\\n${numberedTextContent}\\n{.*}\\n\\n${binaryPath.replace(/\\/g, "\\\\")}\\n\\n${numberedPdfContent}\\n{.*}\\n\\n`,
+ ),
+ ),
)
})
it("should block unsupported binary files", async () => {
// Setup
- const unsupportedBinaryPath = "test/binary.exe"
+ const unsupportedBinaryPath = path.join(testDir, "binary.exe")
+ await fsp.writeFile(unsupportedBinaryPath, "dummy binary data")
mockedIsBinaryFile.mockImplementation(() => Promise.resolve(true))
mockedCountFileLines.mockImplementation(() => Promise.resolve(1))
mockProvider.getState.mockResolvedValue({ maxReadFileLine: -1 })
+ // Ensure getSupportedBinaryFormats is mocked correctly for this test
+ const originalGetSupported = extractTextModule.getSupportedBinaryFormats
+ extractTextModule.getSupportedBinaryFormats = jest.fn(() => [".pdf", ".docx"])
// Create standalone mock functions
const mockAskApproval = jest.fn().mockResolvedValue({ response: "yesButtonClicked" })
@@ -1230,12 +1375,15 @@ describe("read_file tool XML output structure", () => {
expect(mockPushToolResult).toHaveBeenCalledWith(
`\n${unsupportedBinaryPath}\nBinary file\n\n`,
)
+ // Restore original mock
+ extractTextModule.getSupportedBinaryFormats = originalGetSupported
})
})
describe("Edge Cases Tests", () => {
it("should handle empty files correctly with maxReadFileLine=-1", async () => {
// Setup - use empty string
+ await fsp.writeFile(testFilePath, "") // Ensure file is actually empty
mockInputContent = ""
const maxReadFileLine = -1
const totalLines = 0
@@ -1246,13 +1394,16 @@ describe("read_file tool XML output structure", () => {
const result = await executeReadFileTool({}, { maxReadFileLine, totalLines })
// Verify
- expect(result).toBe(
- `\n${testFilePath}\nFile is empty\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\nFile is empty\\n{.*}\\n\\n`,
+ ),
)
})
it("should handle empty files correctly with maxReadFileLine=0", async () => {
// Setup
+ await fsp.writeFile(testFilePath, "") // Ensure file is actually empty
mockedCountFileLines.mockResolvedValue(0)
mockedExtractTextFromFile.mockResolvedValue("")
mockedReadLines.mockResolvedValue("")
@@ -1264,48 +1415,72 @@ describe("read_file tool XML output structure", () => {
const result = await executeReadFileTool({}, { totalLines: 0 })
// Verify
- expect(result).toBe(
- `\n${testFilePath}\nFile is empty\n\n`,
+ expect(result).toMatch(
+ new RegExp(
+ `\\n${testFilePath.replace(/\\/g, "\\\\")}\\nFile is empty\\n{.*}\\n\\n`,
+ ),
)
})
it("should handle binary files with custom content correctly", async () => {
// Setup
+ const exePath = testFilePath.replace(".txt", ".exe")
+ await fsp.writeFile(exePath, "dummy binary data for exe") // Create the dummy .exe file
+
mockedIsBinaryFile.mockResolvedValue(true)
- mockedExtractTextFromFile.mockResolvedValue("")
+ mockedExtractTextFromFile.mockResolvedValue("") // extractTextFromFile returns empty for unsupported binary
mockedReadLines.mockResolvedValue("")
+ // Ensure getSupportedBinaryFormats is mocked correctly for this test
+ const originalGetSupported = extractTextModule.getSupportedBinaryFormats
+ extractTextModule.getSupportedBinaryFormats = jest.fn(() => [".pdf", ".docx"]) // .exe is not supported
// Execute
- const result = await executeReadFileTool({}, { isBinary: true })
+ const result = await executeReadFileTool(
+ { args: `${exePath}` },
+ { isBinary: true },
+ )
// Verify
expect(result).toBe(
- `\n${testFilePath}\nBinary file\n\n`,
+ `\n${exePath}\nBinary file\n\n`,
)
expect(mockedReadLines).not.toHaveBeenCalled()
+ // Restore original mock
+ extractTextModule.getSupportedBinaryFormats = originalGetSupported
})
it("should handle file read errors correctly", async () => {
// Setup
- const errorMessage = "File not found"
- // For error cases, we need to override the mock to simulate a failure
- mockedExtractTextFromFile.mockRejectedValue(new Error(errorMessage))
+ // To test this, ensure the file does not exist when validateAccessAndExistence is called.
+ if (fs.existsSync(testFilePath)) {
+ await fsp.rm(testFilePath) // Delete the file created by beforeEach
+ }
+
+ // This mock will not be reached if validateAccessAndExistence fails first due to ENOENT
+ mockedExtractTextFromFile.mockRejectedValue(
+ new Error(`ENOENT: no such file or directory, open '${testFilePath}'`),
+ )
// Execute
const result = await executeReadFileTool({})
// Verify
- expect(result).toBe(
- `\n${testFilePath}Error reading file: ${errorMessage}\n`,
- )
+ // If file doesn't exist, validateAccessAndExistence causes an error with 'stat'
+ // The readFileTool catches this and formats the error.
+ expect(result).toContain(`Error reading file: ENOENT: no such file or directory, open '${testFilePath}'`)
expect(result).not.toContain(` {
// Setup
const xmlContent = "Test"
- mockInputContent = xmlContent
- mockedExtractTextFromFile.mockResolvedValue(`1 | ${xmlContent}`)
+ await fsp.writeFile(testFilePath, xmlContent) // Write actual XML content
+ mockInputContent = xmlContent // This mock might still be used by addLineNumbersMock
+ // extractTextFromFile will now read the actual file content
+ mockedExtractTextFromFile.mockImplementation(async (filePath) => {
+ const actualContent = await fsp.readFile(filePath, "utf-8")
+ return addLineNumbersMock(actualContent)
+ })
// Execute
const result = await executeReadFileTool()
@@ -1316,7 +1491,44 @@ describe("read_file tool XML output structure", () => {
it("should handle files with very long paths", async () => {
// Setup
- const longPath = "very/long/path/".repeat(10) + "file.txt"
+ const longPathDir = path.join(
+ testDir,
+ "very",
+ "long",
+ "path",
+ "very",
+ "long",
+ "path",
+ "very",
+ "long",
+ "path",
+ "very",
+ "long",
+ "path",
+ "very",
+ "long",
+ "path",
+ )
+ const longFileName = "file.txt"
+ const longPath = path.join(longPathDir, longFileName)
+ const longFileActualContent = "content for long path file"
+
+ await fsp.mkdir(longPathDir, { recursive: true })
+ await fsp.writeFile(longPath, longFileActualContent)
+
+ // Specific mock for this test to read actual content and number it
+ const originalExtractMock = mockedExtractTextFromFile.getMockImplementation()
+ mockedExtractTextFromFile.mockImplementation(async (p) => {
+ if (p === longPath) {
+ const actualContent = await fsp.readFile(p, "utf-8")
+ return addLineNumbersMock(actualContent)
+ }
+ // Fallback for other paths if any (should not happen in this specific test call)
+ if (originalExtractMock) {
+ return originalExtractMock(p)
+ }
+ throw new Error(`Unexpected path in longPath test mock: ${p}`)
+ })
// Execute
const result = await executeReadFileTool({
@@ -1325,6 +1537,12 @@ describe("read_file tool XML output structure", () => {
// Verify long path is handled correctly
expect(result).toContain(`${longPath}`)
+ expect(result).toContain(addLineNumbersMock(longFileActualContent)) // Expect numbered actual content
+
+ // Restore original mock if necessary (though beforeEach will reset it)
+ if (originalExtractMock) {
+ mockedExtractTextFromFile.mockImplementation(originalExtractMock)
+ }
})
})
})
diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts
index 500c7a92c3..83ea99bc62 100644
--- a/src/core/tools/applyDiffTool.ts
+++ b/src/core/tools/applyDiffTool.ts
@@ -184,10 +184,36 @@ export async function applyDiffTool(
// Get the formatted response message
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
+ const stats = await fs.stat(absolutePath)
+ const newMtime = stats.mtime.toISOString()
+
+ const lineRanges: { start: number; end: number }[] = []
+ const diffBlocks = diffContent.split("<<<<<<< SEARCH")
+ for (const block of diffBlocks) {
+ if (!block.trim()) continue
+ const startLineMatch = block.match(/:start_line:(\d+)/)
+ if (startLineMatch) {
+ const startLine = parseInt(startLineMatch[1], 10)
+ const parts = block.split("=======")
+ if (parts.length > 1) {
+ const replacement = parts[1].split(">>>>>>> REPLACE")[0]
+ const lineCount = replacement.trim().split("\n").length
+ lineRanges.push({ start: startLine, end: startLine + lineCount - 1 })
+ }
+ }
+ }
+
+ const metadata = {
+ fileName: relPath,
+ mtime: newMtime,
+ lineRanges,
+ }
+ const metadataXml = `${JSON.stringify(metadata)}`
+
if (partFailHint) {
- pushToolResult(partFailHint + message)
+ pushToolResult(partFailHint + message + "\n" + metadataXml)
} else {
- pushToolResult(message)
+ pushToolResult(message + "\n" + metadataXml)
}
await cline.diffViewProvider.reset()
diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts
index e49ac43d7b..bc73b025b9 100644
--- a/src/core/tools/readFileTool.ts
+++ b/src/core/tools/readFileTool.ts
@@ -1,4 +1,5 @@
import path from "path"
+import fs from "fs" // Added fs import
import { isBinaryFile } from "isbinaryfile"
import { Task } from "../task/Task"
@@ -14,6 +15,8 @@ import { readLines } from "../../integrations/misc/read-lines"
import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text"
import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter"
import { parseXml } from "../../utils/xml"
+import { processAndFilterReadRequest } from "../services/fileReadCacheService"
+import { ConversationMessage } from "../services/fileReadCacheService"
export function getReadFileToolDescription(blockName: string, blockParams: any): string {
// Handle both single path and multiple files via args
@@ -57,7 +60,6 @@ interface FileEntry {
lineRanges?: LineRange[]
}
-// New interface to track file processing state
interface FileResult {
path: string
status: "approved" | "denied" | "blocked" | "error" | "pending"
@@ -431,7 +433,29 @@ export async function readFileTool(
const fullPath = path.resolve(cline.cwd, relPath)
const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {}
- // Process approved files
+ // INTEGRATION WITH fileReadCacheService
+ const cacheResult = await processAndFilterReadRequest(
+ fullPath,
+ fileResult.lineRanges ?? [],
+ (cline.apiConversationHistory as ConversationMessage[]) ?? [],
+ )
+
+ if (cacheResult.status === "REJECT_ALL") {
+ updateFileResult(relPath, {
+ notice: "File content is already up-to-date in the conversation history.",
+ xmlContent: `${relPath}File content is already up-to-date in the conversation history.`,
+ })
+ continue // Move to the next file
+ }
+
+ if (cacheResult.status === "ALLOW_PARTIAL") {
+ fileResult.lineRanges = cacheResult.rangesToRead
+ }
+ // For ALLOW_ALL, we proceed with the original fileResult.lineRanges
+
+ let fileContent: string | undefined // To store content read from file
+ let linesRead: LineRange[] = [] // To store ranges actually read
+
try {
const [totalLines, isBinary] = await Promise.all([countFileLines(fullPath), isBinaryFile(fullPath)])
@@ -458,17 +482,26 @@ export async function readFileTool(
await readLines(fullPath, range.end - 1, range.start - 1),
range.start,
)
+ fileContent = content // Store content for caching
+ linesRead.push(range) // Store the specific range read
+
const lineRangeAttr = ` lines="${range.start}-${range.end}"`
rangeResults.push(`\n${content}`)
}
+ const stats = await fs.promises.stat(fullPath)
+ const metadata = {
+ fileName: relPath,
+ mtime: stats.mtime.toISOString(),
+ lineRanges: linesRead,
+ }
+ const metadataXml = `${JSON.stringify(metadata)}\n`
+
updateFileResult(relPath, {
- xmlContent: `${relPath}\n${rangeResults.join("\n")}\n`,
+ xmlContent: `${relPath}\n${rangeResults.join("\n")}\n${metadataXml}`,
})
- continue
}
-
// Handle definitions-only mode
- if (maxReadFileLine === 0) {
+ else if (maxReadFileLine === 0) {
try {
const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
if (defResult) {
@@ -477,6 +510,8 @@ export async function readFileTool(
xmlContent: `${relPath}\n${defResult}\n${xmlInfo}`,
})
}
+ fileContent = "" // No content to cache for definitions only
+ linesRead = [] // No specific lines read
} catch (error) {
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
console.warn(`[read_file] Warning: ${error.message}`)
@@ -486,12 +521,13 @@ export async function readFileTool(
)
}
}
- continue
}
-
// Handle files exceeding line threshold
- if (maxReadFileLine > 0 && totalLines > maxReadFileLine) {
+ else if (maxReadFileLine > 0 && totalLines > maxReadFileLine) {
const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0))
+ fileContent = content // Store content for caching
+ linesRead = [{ start: 1, end: maxReadFileLine }] // Store the range read
+
const lineRangeAttr = ` lines="1-${maxReadFileLine}"`
let xmlInfo = `\n${content}\n`
@@ -513,24 +549,35 @@ export async function readFileTool(
)
}
}
- continue
}
-
// Handle normal file read
- const content = await extractTextFromFile(fullPath)
- const lineRangeAttr = ` lines="1-${totalLines}"`
- let xmlInfo = totalLines > 0 ? `\n${content}\n` : ``
+ else {
+ const content = await extractTextFromFile(fullPath)
+ fileContent = content // Store content for caching
+ linesRead = [{ start: 1, end: totalLines }] // Store the full range read
- if (totalLines === 0) {
- xmlInfo += `File is empty\n`
- }
+ const lineRangeAttr = ` lines="1-${totalLines}"`
+ let xmlInfo = totalLines > 0 ? `\n${content}\n` : ``
- // Track file read
- await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
+ if (totalLines === 0) {
+ xmlInfo += `File is empty\n`
+ }
- updateFileResult(relPath, {
- xmlContent: `${relPath}\n${xmlInfo}`,
- })
+ // Track file read
+ await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
+
+ const stats = fs.statSync(fullPath)
+ const metadata = {
+ fileName: relPath,
+ mtime: stats.mtime.toISOString(),
+ lineRanges: linesRead,
+ }
+ xmlInfo += `${JSON.stringify(metadata)}\n`
+
+ updateFileResult(relPath, {
+ xmlContent: `${relPath}\n${xmlInfo}`,
+ })
+ }
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error)
updateFileResult(relPath, {
diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts
index 63191acb7e..a31ada26b7 100644
--- a/src/core/tools/writeToFileTool.ts
+++ b/src/core/tools/writeToFileTool.ts
@@ -1,4 +1,5 @@
import path from "path"
+import fs from "fs/promises"
import delay from "delay"
import * as vscode from "vscode"
@@ -221,7 +222,18 @@ export async function writeToFileTool(
// Get the formatted response message
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
- pushToolResult(message)
+ const stats = await fs.stat(path.resolve(cline.cwd, relPath))
+ const newMtime = stats.mtime.toISOString()
+ const lineCount = newContent.split("\n").length
+
+ const metadata = {
+ fileName: relPath,
+ mtime: newMtime,
+ lineRanges: [{ start: 1, end: lineCount }],
+ }
+ const metadataXml = `${JSON.stringify(metadata)}`
+
+ pushToolResult(message + "\n" + metadataXml)
await cline.diffViewProvider.reset()
diff --git a/src/esbuild.mjs b/src/esbuild.mjs
index 178ff9eb07..e6983f058b 100644
--- a/src/esbuild.mjs
+++ b/src/esbuild.mjs
@@ -39,6 +39,12 @@ async function main() {
fs.rmSync(distDir, { recursive: true, force: true })
}
+ const assetsMaterialIconsDir = path.join(srcDir, "assets", "vscode-material-icons")
+ if (fs.existsSync(assetsMaterialIconsDir)) {
+ console.log(`[${name}] Cleaning assets directory: ${assetsMaterialIconsDir}`)
+ fs.rmSync(assetsMaterialIconsDir, { recursive: true, force: true })
+ }
+
/**
* @type {import('esbuild').Plugin[]}
*/
diff --git a/webview-ui/package.json b/webview-ui/package.json
index df074c2d7b..2833e86ab0 100644
--- a/webview-ui/package.json
+++ b/webview-ui/package.json
@@ -5,7 +5,6 @@
"scripts": {
"lint": "eslint src --ext=ts,tsx --max-warnings=0",
"check-types": "tsc",
- "pretest": "turbo run bundle --cwd ..",
"test": "jest -w=40%",
"format": "prettier --write src",
"dev": "vite",
@@ -17,56 +16,58 @@
"clean": "rimraf ../src/webview-ui/build ../apps/vscode-nightly/build/webview-ui tsconfig.tsbuildinfo .turbo"
},
"dependencies": {
- "@radix-ui/react-alert-dialog": "^1.1.6",
- "@radix-ui/react-checkbox": "^1.1.5",
- "@radix-ui/react-collapsible": "^1.1.3",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.5",
+ "@anthropic-ai/sdk": "^0.37.0",
+ "@radix-ui/react-alert-dialog": "^1.1.13",
+ "@radix-ui/react-checkbox": "^1.3.1",
+ "@radix-ui/react-collapsible": "^1.1.10",
+ "@radix-ui/react-dialog": "^1.1.13",
+ "@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-icons": "^1.3.2",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-portal": "^1.1.5",
- "@radix-ui/react-progress": "^1.1.2",
- "@radix-ui/react-select": "^2.1.6",
- "@radix-ui/react-separator": "^1.1.2",
- "@radix-ui/react-slider": "^1.2.3",
- "@radix-ui/react-slot": "^1.1.2",
+ "@radix-ui/react-popover": "^1.1.13",
+ "@radix-ui/react-portal": "^1.1.8",
+ "@radix-ui/react-progress": "^1.1.6",
+ "@radix-ui/react-select": "^2.2.4",
+ "@radix-ui/react-separator": "^1.1.6",
+ "@radix-ui/react-slider": "^1.3.4",
+ "@radix-ui/react-slot": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@roo-code/types": "workspace:^",
"@tailwindcss/vite": "^4.0.0",
- "@tanstack/react-query": "^5.68.0",
+ "@tanstack/react-query": "^5.76.1",
"@vscode/codicons": "^0.0.36",
"@vscode/webview-ui-toolkit": "^1.4.0",
"axios": "^1.7.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "cmdk": "^1.0.0",
+ "cmdk": "^1.1.1",
"date-fns": "^4.1.0",
- "debounce": "^2.1.1",
+ "debounce": "^2.2.0",
"fast-deep-equal": "^3.1.3",
"fzf": "^0.5.2",
- "i18next": "^24.2.2",
+ "i18next": "^24.2.3",
"i18next-http-backend": "^3.0.2",
"knuth-shuffle-seeded": "^1.0.6",
"lru-cache": "^11.1.0",
"lucide-react": "^0.513.0",
- "mermaid": "^11.4.1",
+ "mammoth": "^1.8.0",
+ "mermaid": "^11.6.0",
"posthog-js": "^1.227.2",
"pretty-bytes": "^6.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
- "react-i18next": "^15.4.1",
+ "react-i18next": "^15.5.1",
"react-markdown": "^9.0.3",
"react-remark": "^2.1.0",
- "react-textarea-autosize": "^8.5.3",
- "react-use": "^17.5.1",
- "react-virtuoso": "^4.7.13",
+ "react-textarea-autosize": "^8.5.9",
+ "react-use": "^17.6.0",
+ "react-virtuoso": "^4.12.7",
"rehype-highlight": "^7.0.0",
"remark-gfm": "^4.0.1",
- "remove-markdown": "^0.6.0",
+ "remove-markdown": "^0.6.2",
"shell-quote": "^1.8.2",
- "shiki": "^3.2.1",
+ "shiki": "^3.4.1",
"source-map": "^0.7.4",
- "styled-components": "^6.1.13",
+ "styled-components": "^6.1.18",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^4.0.0",
"tailwindcss-animate": "^1.0.7",
@@ -74,7 +75,7 @@
"use-sound": "^5.0.0",
"vscode-material-icons": "^0.1.1",
"vscrui": "^0.2.2",
- "zod": "^3.24.2"
+ "zod": "^3.24.4"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
@@ -82,15 +83,15 @@
"@roo-code/config-typescript": "workspace:^",
"@storybook/addon-essentials": "^8.5.6",
"@storybook/blocks": "^8.5.6",
- "@storybook/react": "^8.5.6",
+ "@storybook/react": "^8.6.12",
"@storybook/react-vite": "^8.5.6",
"@testing-library/jest-dom": "^6.6.3",
- "@testing-library/react": "^16.2.0",
+ "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^29.0.0",
"@types/node": "^18.0.0",
"@types/react": "^18.3.23",
- "@types/react-dom": "^18.3.5",
+ "@types/react-dom": "^18.3.7",
"@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/vscode-webview": "^1.57.5",