mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Merge remote-tracking branch 'origin/main' into fix-o3-formatting
This commit is contained in:
5
.changeset/blue-masks-camp.md
Normal file
5
.changeset/blue-masks-camp.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"roo-cline": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add shortcuts to the currently open tabs in the "Add File" section of @-mentions (thanks @olup!)
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"roo-cline": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Use an exponential backoff for API retries
|
|
||||||
1
.env.integration.example
Normal file
1
.env.integration.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
OPENROUTER_API_KEY=sk-or-v1-...
|
||||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1,2 +1,2 @@
|
|||||||
# These owners will be the default owners for everything in the repo
|
# These owners will be the default owners for everything in the repo
|
||||||
* @stea9499 @ColemanRoo @mrubens
|
* @stea9499 @ColemanRoo @mrubens @cte
|
||||||
|
|||||||
47
.github/workflows/code-qa.yml
vendored
47
.github/workflows/code-qa.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
name: Code QA Roo Code
|
name: Code QA Roo Code
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -13,33 +14,65 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm run install:all
|
run: npm run install:all
|
||||||
|
- name: Compile
|
||||||
- name: Compile TypeScript
|
|
||||||
run: npm run compile
|
run: npm run compile
|
||||||
|
- name: Check types
|
||||||
|
run: npm run check-types
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
unit-test:
|
unit-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm run install:all
|
run: npm run install:all
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: npm test
|
run: npm test
|
||||||
|
|
||||||
|
check-openrouter-api-key:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
exists: ${{ steps.openrouter-api-key-check.outputs.defined }}
|
||||||
|
steps:
|
||||||
|
- name: Check if OpenRouter API key exists
|
||||||
|
id: openrouter-api-key-check
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ "${{ secrets.OPENROUTER_API_KEY }}" != '' ]; then
|
||||||
|
echo "defined=true" >> $GITHUB_OUTPUT;
|
||||||
|
else
|
||||||
|
echo "defined=false" >> $GITHUB_OUTPUT;
|
||||||
|
fi
|
||||||
|
|
||||||
|
integration-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [check-openrouter-api-key]
|
||||||
|
if: needs.check-openrouter-api-key.outputs.exists == 'true'
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
- name: Create env.integration file
|
||||||
|
run: echo "OPENROUTER_API_KEY=${{ secrets.OPENROUTER_API_KEY }}" > .env.integration
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm run install:all
|
||||||
|
- name: Run integration tests
|
||||||
|
run: xvfb-run -a npm run test:integration
|
||||||
|
|||||||
22
.github/workflows/discord-pr-notify.yml
vendored
Normal file
22
.github/workflows/discord-pr-notify.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Discord PR Notifier
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
notify:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.head_ref != 'changeset-release/main'
|
||||||
|
steps:
|
||||||
|
- name: Send Discord Notification
|
||||||
|
uses: Ilshidur/action-discord@master
|
||||||
|
with:
|
||||||
|
args: |
|
||||||
|
🚀 **New Pull Request Opened!**
|
||||||
|
📝 **Title:** ${{ github.event.pull_request.title }}
|
||||||
|
🔗 <${{ github.event.pull_request.html_url }}>
|
||||||
|
👤 **Author:** ${{ github.event.pull_request.user.login }}
|
||||||
|
env:
|
||||||
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
44
.github/workflows/pages.yml
vendored
Normal file
44
.github/workflows/pages.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Deploy Jekyll site to Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
- name: Build with Jekyll
|
||||||
|
uses: actions/jekyll-build-pages@v1
|
||||||
|
with:
|
||||||
|
source: ./docs/
|
||||||
|
destination: ./_site
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
|
||||||
|
# Deployment job
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
out
|
|
||||||
dist
|
dist
|
||||||
|
out
|
||||||
|
out-integration
|
||||||
node_modules
|
node_modules
|
||||||
coverage/
|
coverage/
|
||||||
|
|
||||||
@@ -15,3 +16,9 @@ roo-cline-*.vsix
|
|||||||
# Test environment
|
# Test environment
|
||||||
.test_env
|
.test_env
|
||||||
.vscode-test/
|
.vscode-test/
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
docs/_site/
|
||||||
|
|
||||||
|
# Dotenv
|
||||||
|
.env.integration
|
||||||
|
|||||||
@@ -6,4 +6,7 @@ if [ "$branch" = "main" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|
||||||
npm run compile
|
npm run compile
|
||||||
|
npm run lint
|
||||||
|
npm run check-types
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* See: https://code.visualstudio.com/api/working-with-extensions/testing-extension
|
||||||
|
*/
|
||||||
|
|
||||||
import { defineConfig } from '@vscode/test-cli';
|
import { defineConfig } from '@vscode/test-cli';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
files: 'src/test/extension.test.ts',
|
label: 'integrationTest',
|
||||||
|
files: 'out-integration/test/**/*.test.js',
|
||||||
workspaceFolder: '.',
|
workspaceFolder: '.',
|
||||||
mocha: {
|
mocha: {
|
||||||
|
ui: 'tdd',
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
ui: 'tdd'
|
|
||||||
},
|
},
|
||||||
launchArgs: [
|
launchArgs: [
|
||||||
'--enable-proposed-api=RooVeterinaryInc.roo-cline',
|
'--enable-proposed-api=RooVeterinaryInc.roo-cline',
|
||||||
|
|||||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
|||||||
# Roo Code Changelog
|
# Roo Code Changelog
|
||||||
|
|
||||||
|
## [3.3.9]
|
||||||
|
|
||||||
|
- Add o3-mini-high and o3-mini-low
|
||||||
|
|
||||||
|
## [3.3.8]
|
||||||
|
|
||||||
|
- Fix o3-mini in the Glama provider (thanks @Punkpeye!)
|
||||||
|
- Add the option to omit instructions for creating MCP servers from the system prompt (thanks @samhvw8!)
|
||||||
|
- Fix a bug where renaming API profiles without actually changing the name would delete them (thanks @samhvw8!)
|
||||||
|
|
||||||
|
## [3.3.7]
|
||||||
|
|
||||||
|
- Support for o3-mini (thanks @shpigunov!)
|
||||||
|
- Code Action improvements to allow selecting code and adding it to context, plus bug fixes (thanks @samhvw8!)
|
||||||
|
- Ability to include a message when approving or rejecting tool use (thanks @napter!)
|
||||||
|
- Improvements to chat input box styling (thanks @psv2522!)
|
||||||
|
- Capture reasoning from more variants of DeepSeek R1 (thanks @Szpadel!)
|
||||||
|
- Use an exponential backoff for API retries (if delay after first error is 5s, delay after second consecutive error will be 10s, then 20s, etc)
|
||||||
|
- Add a slider in advanced settings to enable rate limiting requests to avoid overloading providers (i.e. wait at least 10 seconds between API requests)
|
||||||
|
- Prompt tweaks to make Roo better at creating new custom modes for you
|
||||||
|
|
||||||
## [3.3.6]
|
## [3.3.6]
|
||||||
|
|
||||||
- Add a "new task" tool that allows Roo to start new tasks with an initial message and mode
|
- Add a "new task" tool that allows Roo to start new tasks with an initial message and mode
|
||||||
|
|||||||
2
docs/Gemfile
Normal file
2
docs/Gemfile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
gem 'github-pages', group: :jekyll_plugins
|
||||||
308
docs/Gemfile.lock
Normal file
308
docs/Gemfile.lock
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
activesupport (8.0.1)
|
||||||
|
base64
|
||||||
|
benchmark (>= 0.3)
|
||||||
|
bigdecimal
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
|
connection_pool (>= 2.2.5)
|
||||||
|
drb
|
||||||
|
i18n (>= 1.6, < 2)
|
||||||
|
logger (>= 1.4.2)
|
||||||
|
minitest (>= 5.1)
|
||||||
|
securerandom (>= 0.3)
|
||||||
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
|
uri (>= 0.13.1)
|
||||||
|
addressable (2.8.7)
|
||||||
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
|
base64 (0.2.0)
|
||||||
|
benchmark (0.4.0)
|
||||||
|
bigdecimal (3.1.9)
|
||||||
|
coffee-script (2.4.1)
|
||||||
|
coffee-script-source
|
||||||
|
execjs
|
||||||
|
coffee-script-source (1.12.2)
|
||||||
|
colorator (1.1.0)
|
||||||
|
commonmarker (0.23.11)
|
||||||
|
concurrent-ruby (1.3.5)
|
||||||
|
connection_pool (2.5.0)
|
||||||
|
csv (3.3.2)
|
||||||
|
dnsruby (1.72.3)
|
||||||
|
base64 (~> 0.2.0)
|
||||||
|
simpleidn (~> 0.2.1)
|
||||||
|
drb (2.2.1)
|
||||||
|
em-websocket (0.5.3)
|
||||||
|
eventmachine (>= 0.12.9)
|
||||||
|
http_parser.rb (~> 0)
|
||||||
|
ethon (0.16.0)
|
||||||
|
ffi (>= 1.15.0)
|
||||||
|
eventmachine (1.2.7)
|
||||||
|
execjs (2.10.0)
|
||||||
|
faraday (2.12.2)
|
||||||
|
faraday-net_http (>= 2.0, < 3.5)
|
||||||
|
json
|
||||||
|
logger
|
||||||
|
faraday-net_http (3.4.0)
|
||||||
|
net-http (>= 0.5.0)
|
||||||
|
ffi (1.17.1-aarch64-linux-gnu)
|
||||||
|
ffi (1.17.1-aarch64-linux-musl)
|
||||||
|
ffi (1.17.1-arm-linux-gnu)
|
||||||
|
ffi (1.17.1-arm-linux-musl)
|
||||||
|
ffi (1.17.1-arm64-darwin)
|
||||||
|
ffi (1.17.1-x86_64-darwin)
|
||||||
|
ffi (1.17.1-x86_64-linux-gnu)
|
||||||
|
ffi (1.17.1-x86_64-linux-musl)
|
||||||
|
forwardable-extended (2.6.0)
|
||||||
|
gemoji (4.1.0)
|
||||||
|
github-pages (232)
|
||||||
|
github-pages-health-check (= 1.18.2)
|
||||||
|
jekyll (= 3.10.0)
|
||||||
|
jekyll-avatar (= 0.8.0)
|
||||||
|
jekyll-coffeescript (= 1.2.2)
|
||||||
|
jekyll-commonmark-ghpages (= 0.5.1)
|
||||||
|
jekyll-default-layout (= 0.1.5)
|
||||||
|
jekyll-feed (= 0.17.0)
|
||||||
|
jekyll-gist (= 1.5.0)
|
||||||
|
jekyll-github-metadata (= 2.16.1)
|
||||||
|
jekyll-include-cache (= 0.2.1)
|
||||||
|
jekyll-mentions (= 1.6.0)
|
||||||
|
jekyll-optional-front-matter (= 0.3.2)
|
||||||
|
jekyll-paginate (= 1.1.0)
|
||||||
|
jekyll-readme-index (= 0.3.0)
|
||||||
|
jekyll-redirect-from (= 0.16.0)
|
||||||
|
jekyll-relative-links (= 0.6.1)
|
||||||
|
jekyll-remote-theme (= 0.4.3)
|
||||||
|
jekyll-sass-converter (= 1.5.2)
|
||||||
|
jekyll-seo-tag (= 2.8.0)
|
||||||
|
jekyll-sitemap (= 1.4.0)
|
||||||
|
jekyll-swiss (= 1.0.0)
|
||||||
|
jekyll-theme-architect (= 0.2.0)
|
||||||
|
jekyll-theme-cayman (= 0.2.0)
|
||||||
|
jekyll-theme-dinky (= 0.2.0)
|
||||||
|
jekyll-theme-hacker (= 0.2.0)
|
||||||
|
jekyll-theme-leap-day (= 0.2.0)
|
||||||
|
jekyll-theme-merlot (= 0.2.0)
|
||||||
|
jekyll-theme-midnight (= 0.2.0)
|
||||||
|
jekyll-theme-minimal (= 0.2.0)
|
||||||
|
jekyll-theme-modernist (= 0.2.0)
|
||||||
|
jekyll-theme-primer (= 0.6.0)
|
||||||
|
jekyll-theme-slate (= 0.2.0)
|
||||||
|
jekyll-theme-tactile (= 0.2.0)
|
||||||
|
jekyll-theme-time-machine (= 0.2.0)
|
||||||
|
jekyll-titles-from-headings (= 0.5.3)
|
||||||
|
jemoji (= 0.13.0)
|
||||||
|
kramdown (= 2.4.0)
|
||||||
|
kramdown-parser-gfm (= 1.1.0)
|
||||||
|
liquid (= 4.0.4)
|
||||||
|
mercenary (~> 0.3)
|
||||||
|
minima (= 2.5.1)
|
||||||
|
nokogiri (>= 1.16.2, < 2.0)
|
||||||
|
rouge (= 3.30.0)
|
||||||
|
terminal-table (~> 1.4)
|
||||||
|
webrick (~> 1.8)
|
||||||
|
github-pages-health-check (1.18.2)
|
||||||
|
addressable (~> 2.3)
|
||||||
|
dnsruby (~> 1.60)
|
||||||
|
octokit (>= 4, < 8)
|
||||||
|
public_suffix (>= 3.0, < 6.0)
|
||||||
|
typhoeus (~> 1.3)
|
||||||
|
html-pipeline (2.14.3)
|
||||||
|
activesupport (>= 2)
|
||||||
|
nokogiri (>= 1.4)
|
||||||
|
http_parser.rb (0.8.0)
|
||||||
|
i18n (1.14.7)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
jekyll (3.10.0)
|
||||||
|
addressable (~> 2.4)
|
||||||
|
colorator (~> 1.0)
|
||||||
|
csv (~> 3.0)
|
||||||
|
em-websocket (~> 0.5)
|
||||||
|
i18n (>= 0.7, < 2)
|
||||||
|
jekyll-sass-converter (~> 1.0)
|
||||||
|
jekyll-watch (~> 2.0)
|
||||||
|
kramdown (>= 1.17, < 3)
|
||||||
|
liquid (~> 4.0)
|
||||||
|
mercenary (~> 0.3.3)
|
||||||
|
pathutil (~> 0.9)
|
||||||
|
rouge (>= 1.7, < 4)
|
||||||
|
safe_yaml (~> 1.0)
|
||||||
|
webrick (>= 1.0)
|
||||||
|
jekyll-avatar (0.8.0)
|
||||||
|
jekyll (>= 3.0, < 5.0)
|
||||||
|
jekyll-coffeescript (1.2.2)
|
||||||
|
coffee-script (~> 2.2)
|
||||||
|
coffee-script-source (~> 1.12)
|
||||||
|
jekyll-commonmark (1.4.0)
|
||||||
|
commonmarker (~> 0.22)
|
||||||
|
jekyll-commonmark-ghpages (0.5.1)
|
||||||
|
commonmarker (>= 0.23.7, < 1.1.0)
|
||||||
|
jekyll (>= 3.9, < 4.0)
|
||||||
|
jekyll-commonmark (~> 1.4.0)
|
||||||
|
rouge (>= 2.0, < 5.0)
|
||||||
|
jekyll-default-layout (0.1.5)
|
||||||
|
jekyll (>= 3.0, < 5.0)
|
||||||
|
jekyll-feed (0.17.0)
|
||||||
|
jekyll (>= 3.7, < 5.0)
|
||||||
|
jekyll-gist (1.5.0)
|
||||||
|
octokit (~> 4.2)
|
||||||
|
jekyll-github-metadata (2.16.1)
|
||||||
|
jekyll (>= 3.4, < 5.0)
|
||||||
|
octokit (>= 4, < 7, != 4.4.0)
|
||||||
|
jekyll-include-cache (0.2.1)
|
||||||
|
jekyll (>= 3.7, < 5.0)
|
||||||
|
jekyll-mentions (1.6.0)
|
||||||
|
html-pipeline (~> 2.3)
|
||||||
|
jekyll (>= 3.7, < 5.0)
|
||||||
|
jekyll-optional-front-matter (0.3.2)
|
||||||
|
jekyll (>= 3.0, < 5.0)
|
||||||
|
jekyll-paginate (1.1.0)
|
||||||
|
jekyll-readme-index (0.3.0)
|
||||||
|
jekyll (>= 3.0, < 5.0)
|
||||||
|
jekyll-redirect-from (0.16.0)
|
||||||
|
jekyll (>= 3.3, < 5.0)
|
||||||
|
jekyll-relative-links (0.6.1)
|
||||||
|
jekyll (>= 3.3, < 5.0)
|
||||||
|
jekyll-remote-theme (0.4.3)
|
||||||
|
addressable (~> 2.0)
|
||||||
|
jekyll (>= 3.5, < 5.0)
|
||||||
|
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||||
|
rubyzip (>= 1.3.0, < 3.0)
|
||||||
|
jekyll-sass-converter (1.5.2)
|
||||||
|
sass (~> 3.4)
|
||||||
|
jekyll-seo-tag (2.8.0)
|
||||||
|
jekyll (>= 3.8, < 5.0)
|
||||||
|
jekyll-sitemap (1.4.0)
|
||||||
|
jekyll (>= 3.7, < 5.0)
|
||||||
|
jekyll-swiss (1.0.0)
|
||||||
|
jekyll-theme-architect (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-cayman (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-dinky (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-hacker (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-leap-day (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-merlot (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-midnight (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-minimal (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-modernist (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-primer (0.6.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-github-metadata (~> 2.9)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-slate (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-tactile (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-theme-time-machine (0.2.0)
|
||||||
|
jekyll (> 3.5, < 5.0)
|
||||||
|
jekyll-seo-tag (~> 2.0)
|
||||||
|
jekyll-titles-from-headings (0.5.3)
|
||||||
|
jekyll (>= 3.3, < 5.0)
|
||||||
|
jekyll-watch (2.2.1)
|
||||||
|
listen (~> 3.0)
|
||||||
|
jemoji (0.13.0)
|
||||||
|
gemoji (>= 3, < 5)
|
||||||
|
html-pipeline (~> 2.2)
|
||||||
|
jekyll (>= 3.0, < 5.0)
|
||||||
|
json (2.9.1)
|
||||||
|
kramdown (2.4.0)
|
||||||
|
rexml
|
||||||
|
kramdown-parser-gfm (1.1.0)
|
||||||
|
kramdown (~> 2.0)
|
||||||
|
liquid (4.0.4)
|
||||||
|
listen (3.9.0)
|
||||||
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
|
logger (1.6.5)
|
||||||
|
mercenary (0.3.6)
|
||||||
|
minima (2.5.1)
|
||||||
|
jekyll (>= 3.5, < 5.0)
|
||||||
|
jekyll-feed (~> 0.9)
|
||||||
|
jekyll-seo-tag (~> 2.1)
|
||||||
|
minitest (5.25.4)
|
||||||
|
net-http (0.6.0)
|
||||||
|
uri
|
||||||
|
nokogiri (1.18.2-aarch64-linux-gnu)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-aarch64-linux-musl)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-arm-linux-gnu)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-arm-linux-musl)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-arm64-darwin)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-x86_64-darwin)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-x86_64-linux-gnu)
|
||||||
|
racc (~> 1.4)
|
||||||
|
nokogiri (1.18.2-x86_64-linux-musl)
|
||||||
|
racc (~> 1.4)
|
||||||
|
octokit (4.25.1)
|
||||||
|
faraday (>= 1, < 3)
|
||||||
|
sawyer (~> 0.9)
|
||||||
|
pathutil (0.16.2)
|
||||||
|
forwardable-extended (~> 2.6)
|
||||||
|
public_suffix (5.1.1)
|
||||||
|
racc (1.8.1)
|
||||||
|
rb-fsevent (0.11.2)
|
||||||
|
rb-inotify (0.11.1)
|
||||||
|
ffi (~> 1.0)
|
||||||
|
rexml (3.4.0)
|
||||||
|
rouge (3.30.0)
|
||||||
|
rubyzip (2.4.1)
|
||||||
|
safe_yaml (1.0.5)
|
||||||
|
sass (3.7.4)
|
||||||
|
sass-listen (~> 4.0.0)
|
||||||
|
sass-listen (4.0.0)
|
||||||
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
|
sawyer (0.9.2)
|
||||||
|
addressable (>= 2.3.5)
|
||||||
|
faraday (>= 0.17.3, < 3)
|
||||||
|
securerandom (0.4.1)
|
||||||
|
simpleidn (0.2.3)
|
||||||
|
terminal-table (1.8.0)
|
||||||
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
|
typhoeus (1.4.1)
|
||||||
|
ethon (>= 0.9.0)
|
||||||
|
tzinfo (2.0.6)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
unicode-display_width (1.8.0)
|
||||||
|
uri (1.0.2)
|
||||||
|
webrick (1.9.1)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
aarch64-linux-gnu
|
||||||
|
aarch64-linux-musl
|
||||||
|
arm-linux-gnu
|
||||||
|
arm-linux-musl
|
||||||
|
arm64-darwin
|
||||||
|
x86_64-darwin
|
||||||
|
x86_64-linux-gnu
|
||||||
|
x86_64-linux-musl
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
github-pages
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.5.18
|
||||||
15
docs/_config.yml
Normal file
15
docs/_config.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
title: Roo Code Documentation
|
||||||
|
description: Documentation for the Roo Code project
|
||||||
|
remote_theme: just-the-docs/just-the-docs
|
||||||
|
|
||||||
|
url: https://docs.roocode.com
|
||||||
|
|
||||||
|
aux_links:
|
||||||
|
"Roo Code on GitHub":
|
||||||
|
- "//github.com/RooVetGit/Roo-Code"
|
||||||
|
|
||||||
|
# Enable search
|
||||||
|
search_enabled: true
|
||||||
|
|
||||||
|
# Enable dark mode
|
||||||
|
color_scheme: dark
|
||||||
10
docs/getting-started/index.md
Normal file
10
docs/getting-started/index.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
layout: default
|
||||||
|
nav_order: 2
|
||||||
|
has_children: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Getting Started with Roo Code
|
||||||
|
|
||||||
|
This section will help you get up and running with Roo Code quickly.
|
||||||
9
docs/index.md
Normal file
9
docs/index.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Home
|
||||||
|
layout: home
|
||||||
|
nav_order: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Welcome to Roo Code Documentation
|
||||||
|
|
||||||
|
This is the documentation for Roo Code. Choose a section from the navigation menu to get started.
|
||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1737569578,
|
||||||
|
"narHash": "sha256-6qY0pk2QmUtBT9Mywdvif0i/CLVgpCjMUn6g9vB+f3M=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "47addd76727f42d351590c905d9d1905ca895b82",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-24.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
33
flake.nix
Normal file
33
flake.nix
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
description = "Roo Code development environment";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, ... }: let
|
||||||
|
systems = [ "aarch64-darwin" "x86_64-linux" ];
|
||||||
|
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||||
|
|
||||||
|
mkDevShell = system: let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in pkgs.mkShell {
|
||||||
|
name = "roo-code";
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
zsh
|
||||||
|
nodejs_18
|
||||||
|
corepack_18
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
exec zsh
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
devShells = forAllSystems (system: {
|
||||||
|
default = mkDevShell system;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
180
package-lock.json
generated
180
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "roo-cline",
|
"name": "roo-cline",
|
||||||
"version": "3.3.6",
|
"version": "3.3.9",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "roo-cline",
|
"name": "roo-cline",
|
||||||
"version": "3.3.6",
|
"version": "3.3.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||||
"@anthropic-ai/sdk": "^0.26.0",
|
"@anthropic-ai/sdk": "^0.26.0",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.10",
|
"@changesets/cli": "^2.27.10",
|
||||||
"@changesets/types": "^6.0.0",
|
"@changesets/types": "^6.0.0",
|
||||||
|
"@dotenvx/dotenvx": "^1.34.0",
|
||||||
"@types/diff": "^5.2.1",
|
"@types/diff": "^5.2.1",
|
||||||
"@types/diff-match-patch": "^1.0.36",
|
"@types/diff-match-patch": "^1.0.36",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
@@ -65,7 +66,6 @@
|
|||||||
"@typescript-eslint/parser": "^7.11.0",
|
"@typescript-eslint/parser": "^7.11.0",
|
||||||
"@vscode/test-cli": "^0.0.9",
|
"@vscode/test-cli": "^0.0.9",
|
||||||
"@vscode/test-electron": "^2.4.0",
|
"@vscode/test-electron": "^2.4.0",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
@@ -3030,6 +3030,110 @@
|
|||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx": {
|
||||||
|
"version": "1.34.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.34.0.tgz",
|
||||||
|
"integrity": "sha512-+Dp/xaI3IZ4eKv+b2vg4V89VnqLKbmJ7UZ7unnZxMu9SNLOSc2jYaXey1YHCJM+67T0pOr2Gbej3TewnuoqTWQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^11.1.0",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"eciesjs": "^0.4.10",
|
||||||
|
"execa": "^5.1.1",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"ignore": "^5.3.0",
|
||||||
|
"object-treeify": "1.1.33",
|
||||||
|
"picomatch": "^4.0.2",
|
||||||
|
"which": "^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"dotenvx": "src/cli/dotenvx.js",
|
||||||
|
"git-dotenvx": "src/cli/dotenvx.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx/node_modules/commander": {
|
||||||
|
"version": "11.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||||
|
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx/node_modules/fdir": {
|
||||||
|
"version": "6.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
|
||||||
|
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"picomatch": "^3 || ^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"picomatch": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx/node_modules/isexe": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx/node_modules/picomatch": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dotenvx/dotenvx/node_modules/which": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^3.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"node-which": "bin/which.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.13.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ecies/ciphers": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1",
|
||||||
|
"deno": ">=2",
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@noble/ciphers": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/darwin-arm64": {
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.24.0",
|
"version": "0.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
|
||||||
@@ -3964,6 +4068,48 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/ciphers": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/curves": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.7.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz",
|
||||||
|
"integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.21.3 || >=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -7772,6 +7918,24 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eciesjs": {
|
||||||
|
"version": "0.4.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.13.tgz",
|
||||||
|
"integrity": "sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@ecies/ciphers": "^0.2.2",
|
||||||
|
"@noble/ciphers": "^1.0.0",
|
||||||
|
"@noble/curves": "^1.6.0",
|
||||||
|
"@noble/hashes": "^1.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1",
|
||||||
|
"deno": ">=2",
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eight-colors": {
|
"node_modules/eight-colors": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/eight-colors/-/eight-colors-1.3.1.tgz",
|
||||||
@@ -12247,6 +12411,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-treeify": {
|
||||||
|
"version": "1.1.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz",
|
||||||
|
"integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object.assign": {
|
"node_modules/object.assign": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -3,7 +3,7 @@
|
|||||||
"displayName": "Roo Code (prev. Roo Cline)",
|
"displayName": "Roo Code (prev. Roo Cline)",
|
||||||
"description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.",
|
"description": "A VS Code plugin that enhances coding with AI-powered automation, multi-model support, and experimental features.",
|
||||||
"publisher": "RooVeterinaryInc",
|
"publisher": "RooVeterinaryInc",
|
||||||
"version": "3.3.6",
|
"version": "3.3.9",
|
||||||
"icon": "assets/icons/rocket.png",
|
"icon": "assets/icons/rocket.png",
|
||||||
"galleryBanner": {
|
"galleryBanner": {
|
||||||
"color": "#617A91",
|
"color": "#617A91",
|
||||||
@@ -221,16 +221,16 @@
|
|||||||
"build:webview": "cd webview-ui && npm run build",
|
"build:webview": "cd webview-ui && npm run build",
|
||||||
"changeset": "changeset",
|
"changeset": "changeset",
|
||||||
"check-types": "tsc --noEmit",
|
"check-types": "tsc --noEmit",
|
||||||
"compile": "npm run check-types && npm run lint && node esbuild.js",
|
"compile": "tsc -p . --outDir out && node esbuild.js",
|
||||||
"compile-tests": "tsc -p . --outDir out",
|
"compile:integration": "tsc -p tsconfig.integration.json",
|
||||||
"install:all": "npm install && cd webview-ui && npm install",
|
"install:all": "npm install && cd webview-ui && npm install",
|
||||||
"lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
|
"lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
|
||||||
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
|
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
|
||||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
"pretest": "npm run compile && npm run compile:integration",
|
||||||
"dev": "cd webview-ui && npm run dev",
|
"dev": "cd webview-ui && npm run dev",
|
||||||
"test": "jest && npm run test:webview",
|
"test": "jest && npm run test:webview",
|
||||||
"test:webview": "cd webview-ui && npm run test",
|
"test:webview": "cd webview-ui && npm run test",
|
||||||
"test:extension": "vscode-test",
|
"test:integration": "npm run build && npm run compile:integration && npx dotenvx run -f .env.integration -- vscode-test",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"publish:marketplace": "vsce publish && ovsx publish",
|
"publish:marketplace": "vsce publish && ovsx publish",
|
||||||
"publish": "npm run build && changeset publish && npm install --package-lock-only",
|
"publish": "npm run build && changeset publish && npm install --package-lock-only",
|
||||||
@@ -245,6 +245,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.10",
|
"@changesets/cli": "^2.27.10",
|
||||||
"@changesets/types": "^6.0.0",
|
"@changesets/types": "^6.0.0",
|
||||||
|
"@dotenvx/dotenvx": "^1.34.0",
|
||||||
"@types/diff": "^5.2.1",
|
"@types/diff": "^5.2.1",
|
||||||
"@types/diff-match-patch": "^1.0.36",
|
"@types/diff-match-patch": "^1.0.36",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
@@ -255,7 +256,6 @@
|
|||||||
"@typescript-eslint/parser": "^7.11.0",
|
"@typescript-eslint/parser": "^7.11.0",
|
||||||
"@vscode/test-cli": "^0.0.9",
|
"@vscode/test-cli": "^0.0.9",
|
||||||
"@vscode/test-electron": "^2.4.0",
|
"@vscode/test-electron": "^2.4.0",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.24.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
|
|||||||
@@ -5,9 +5,25 @@ const vscode = {
|
|||||||
createTextEditorDecorationType: jest.fn().mockReturnValue({
|
createTextEditorDecorationType: jest.fn().mockReturnValue({
|
||||||
dispose: jest.fn(),
|
dispose: jest.fn(),
|
||||||
}),
|
}),
|
||||||
|
tabGroups: {
|
||||||
|
onDidChangeTabs: jest.fn(() => {
|
||||||
|
return {
|
||||||
|
dispose: jest.fn(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
all: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
onDidSaveTextDocument: jest.fn(),
|
onDidSaveTextDocument: jest.fn(),
|
||||||
|
createFileSystemWatcher: jest.fn().mockReturnValue({
|
||||||
|
onDidCreate: jest.fn().mockReturnValue({ dispose: jest.fn() }),
|
||||||
|
onDidDelete: jest.fn().mockReturnValue({ dispose: jest.fn() }),
|
||||||
|
dispose: jest.fn(),
|
||||||
|
}),
|
||||||
|
fs: {
|
||||||
|
stat: jest.fn(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Disposable: class {
|
Disposable: class {
|
||||||
dispose() {}
|
dispose() {}
|
||||||
@@ -57,6 +73,17 @@ const vscode = {
|
|||||||
Development: 2,
|
Development: 2,
|
||||||
Test: 3,
|
Test: 3,
|
||||||
},
|
},
|
||||||
|
FileType: {
|
||||||
|
Unknown: 0,
|
||||||
|
File: 1,
|
||||||
|
Directory: 2,
|
||||||
|
SymbolicLink: 64,
|
||||||
|
},
|
||||||
|
TabInputText: class {
|
||||||
|
constructor(uri) {
|
||||||
|
this.uri = uri
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = vscode
|
module.exports = vscode
|
||||||
|
|||||||
32
src/activate/handleUri.ts
Normal file
32
src/activate/handleUri.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import * as vscode from "vscode"
|
||||||
|
|
||||||
|
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||||
|
|
||||||
|
export const handleUri = async (uri: vscode.Uri) => {
|
||||||
|
const path = uri.path
|
||||||
|
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
||||||
|
const visibleProvider = ClineProvider.getVisibleInstance()
|
||||||
|
|
||||||
|
if (!visibleProvider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (path) {
|
||||||
|
case "/glama": {
|
||||||
|
const code = query.get("code")
|
||||||
|
if (code) {
|
||||||
|
await visibleProvider.handleGlamaCallback(code)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case "/openrouter": {
|
||||||
|
const code = query.get("code")
|
||||||
|
if (code) {
|
||||||
|
await visibleProvider.handleOpenRouterCallback(code)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/activate/index.ts
Normal file
3
src/activate/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { handleUri } from "./handleUri"
|
||||||
|
export { registerCommands } from "./registerCommands"
|
||||||
|
export { registerCodeActions } from "./registerCodeActions"
|
||||||
91
src/activate/registerCodeActions.ts
Normal file
91
src/activate/registerCodeActions.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import * as vscode from "vscode"
|
||||||
|
|
||||||
|
import { ACTION_NAMES, COMMAND_IDS } from "../core/CodeActionProvider"
|
||||||
|
import { EditorUtils } from "../core/EditorUtils"
|
||||||
|
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||||
|
|
||||||
|
export const registerCodeActions = (context: vscode.ExtensionContext) => {
|
||||||
|
registerCodeActionPair(
|
||||||
|
context,
|
||||||
|
COMMAND_IDS.EXPLAIN,
|
||||||
|
"EXPLAIN",
|
||||||
|
"What would you like Roo to explain?",
|
||||||
|
"E.g. How does the error handling work?",
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCodeActionPair(
|
||||||
|
context,
|
||||||
|
COMMAND_IDS.FIX,
|
||||||
|
"FIX",
|
||||||
|
"What would you like Roo to fix?",
|
||||||
|
"E.g. Maintain backward compatibility",
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCodeActionPair(
|
||||||
|
context,
|
||||||
|
COMMAND_IDS.IMPROVE,
|
||||||
|
"IMPROVE",
|
||||||
|
"What would you like Roo to improve?",
|
||||||
|
"E.g. Focus on performance optimization",
|
||||||
|
)
|
||||||
|
|
||||||
|
registerCodeAction(context, COMMAND_IDS.ADD_TO_CONTEXT, "ADD_TO_CONTEXT")
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerCodeAction = (
|
||||||
|
context: vscode.ExtensionContext,
|
||||||
|
command: string,
|
||||||
|
promptType: keyof typeof ACTION_NAMES,
|
||||||
|
inputPrompt?: string,
|
||||||
|
inputPlaceholder?: string,
|
||||||
|
) => {
|
||||||
|
let userInput: string | undefined
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(command, async (...args: any[]) => {
|
||||||
|
if (inputPrompt) {
|
||||||
|
userInput = await vscode.window.showInputBox({
|
||||||
|
prompt: inputPrompt,
|
||||||
|
placeHolder: inputPlaceholder,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle both code action and direct command cases.
|
||||||
|
let filePath: string
|
||||||
|
let selectedText: string
|
||||||
|
let diagnostics: any[] | undefined
|
||||||
|
|
||||||
|
if (args.length > 1) {
|
||||||
|
// Called from code action.
|
||||||
|
;[filePath, selectedText, diagnostics] = args
|
||||||
|
} else {
|
||||||
|
// Called directly from command palette.
|
||||||
|
const context = EditorUtils.getEditorContext()
|
||||||
|
if (!context) return
|
||||||
|
;({ filePath, selectedText, diagnostics } = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
...{ filePath, selectedText },
|
||||||
|
...(diagnostics ? { diagnostics } : {}),
|
||||||
|
...(userInput ? { userInput } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
await ClineProvider.handleCodeAction(command, promptType, params)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerCodeActionPair = (
|
||||||
|
context: vscode.ExtensionContext,
|
||||||
|
baseCommand: string,
|
||||||
|
promptType: keyof typeof ACTION_NAMES,
|
||||||
|
inputPrompt?: string,
|
||||||
|
inputPlaceholder?: string,
|
||||||
|
) => {
|
||||||
|
// Register new task version.
|
||||||
|
registerCodeAction(context, baseCommand, promptType, inputPrompt, inputPlaceholder)
|
||||||
|
|
||||||
|
// Register current task version.
|
||||||
|
registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, inputPrompt, inputPlaceholder)
|
||||||
|
}
|
||||||
83
src/activate/registerCommands.ts
Normal file
83
src/activate/registerCommands.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import * as vscode from "vscode"
|
||||||
|
import delay from "delay"
|
||||||
|
|
||||||
|
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||||
|
|
||||||
|
export type RegisterCommandOptions = {
|
||||||
|
context: vscode.ExtensionContext
|
||||||
|
outputChannel: vscode.OutputChannel
|
||||||
|
provider: ClineProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerCommands = (options: RegisterCommandOptions) => {
|
||||||
|
const { context, outputChannel } = options
|
||||||
|
|
||||||
|
for (const [command, callback] of Object.entries(getCommandsMap(options))) {
|
||||||
|
context.subscriptions.push(vscode.commands.registerCommand(command, callback))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => {
|
||||||
|
return {
|
||||||
|
"roo-cline.plusButtonClicked": async () => {
|
||||||
|
await provider.clearTask()
|
||||||
|
await provider.postStateToWebview()
|
||||||
|
await provider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
||||||
|
},
|
||||||
|
"roo-cline.mcpButtonClicked": () => {
|
||||||
|
provider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
|
||||||
|
},
|
||||||
|
"roo-cline.promptsButtonClicked": () => {
|
||||||
|
provider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
|
||||||
|
},
|
||||||
|
"roo-cline.popoutButtonClicked": () => openClineInNewTab({ context, outputChannel }),
|
||||||
|
"roo-cline.openInNewTab": () => openClineInNewTab({ context, outputChannel }),
|
||||||
|
"roo-cline.settingsButtonClicked": () => {
|
||||||
|
provider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
|
||||||
|
},
|
||||||
|
"roo-cline.historyButtonClicked": () => {
|
||||||
|
provider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
|
||||||
|
outputChannel.appendLine("Opening Roo Code in new tab")
|
||||||
|
|
||||||
|
// (This example uses webviewProvider activation event which is necessary to
|
||||||
|
// deserialize cached webview, but since we use retainContextWhenHidden, we
|
||||||
|
// don't need to use that event).
|
||||||
|
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
|
||||||
|
const tabProvider = new ClineProvider(context, outputChannel)
|
||||||
|
// const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
|
||||||
|
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
|
||||||
|
|
||||||
|
// Check if there are any visible text editors, otherwise open a new group
|
||||||
|
// to the right.
|
||||||
|
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
|
||||||
|
|
||||||
|
if (!hasVisibleEditors) {
|
||||||
|
await vscode.commands.executeCommand("workbench.action.newGroupRight")
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
|
||||||
|
|
||||||
|
const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
|
||||||
|
enableScripts: true,
|
||||||
|
retainContextWhenHidden: true,
|
||||||
|
localResourceRoots: [context.extensionUri],
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: use better svg icon with light and dark variants (see
|
||||||
|
// https://stackoverflow.com/questions/58365687/vscode-extension-iconpath).
|
||||||
|
panel.iconPath = {
|
||||||
|
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
||||||
|
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
||||||
|
}
|
||||||
|
|
||||||
|
tabProvider.resolveWebviewView(panel)
|
||||||
|
|
||||||
|
// Lock the editor group so clicking on files doesn't open them over the panel
|
||||||
|
await delay(100)
|
||||||
|
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
|
||||||
|
}
|
||||||
@@ -313,6 +313,21 @@ describe("OpenAiNativeHandler", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should complete prompt successfully with o3-mini model", async () => {
|
||||||
|
handler = new OpenAiNativeHandler({
|
||||||
|
apiModelId: "o3-mini",
|
||||||
|
openAiNativeApiKey: "test-api-key",
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await handler.completePrompt("Test prompt")
|
||||||
|
expect(result).toBe("Test response")
|
||||||
|
expect(mockCreate).toHaveBeenCalledWith({
|
||||||
|
model: "o3-mini",
|
||||||
|
messages: [{ role: "user", content: "Test prompt" }],
|
||||||
|
reasoning_effort: "medium",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("should handle API errors", async () => {
|
it("should handle API errors", async () => {
|
||||||
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
mockCreate.mockRejectedValueOnce(new Error("API Error"))
|
||||||
await expect(handler.completePrompt("Test prompt")).rejects.toThrow(
|
await expect(handler.completePrompt("Test prompt")).rejects.toThrow(
|
||||||
|
|||||||
@@ -72,16 +72,19 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||||||
maxTokens = 8_192
|
maxTokens = 8_192
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: completion, response } = await this.client.chat.completions
|
const requestOptions: OpenAI.Chat.ChatCompletionCreateParams = {
|
||||||
.create(
|
|
||||||
{
|
|
||||||
model: this.getModel().id,
|
model: this.getModel().id,
|
||||||
max_tokens: maxTokens,
|
max_tokens: maxTokens,
|
||||||
temperature: 0,
|
|
||||||
messages: openAiMessages,
|
messages: openAiMessages,
|
||||||
stream: true,
|
stream: true,
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
if (this.supportsTemperature()) {
|
||||||
|
requestOptions.temperature = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: completion, response } = await this.client.chat.completions
|
||||||
|
.create(requestOptions, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Glama-Metadata": JSON.stringify({
|
"X-Glama-Metadata": JSON.stringify({
|
||||||
labels: [
|
labels: [
|
||||||
@@ -92,8 +95,7 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.withResponse()
|
.withResponse()
|
||||||
|
|
||||||
const completionRequestId = response.headers.get("x-completion-request-id")
|
const completionRequestId = response.headers.get("x-completion-request-id")
|
||||||
@@ -148,6 +150,10 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private supportsTemperature(): boolean {
|
||||||
|
return !this.getModel().id.startsWith("openai/o3-mini")
|
||||||
|
}
|
||||||
|
|
||||||
getModel(): { id: string; info: ModelInfo } {
|
getModel(): { id: string; info: ModelInfo } {
|
||||||
const modelId = this.options.glamaModelId
|
const modelId = this.options.glamaModelId
|
||||||
const modelInfo = this.options.glamaModelInfo
|
const modelInfo = this.options.glamaModelInfo
|
||||||
@@ -164,7 +170,10 @@ export class GlamaHandler implements ApiHandler, SingleCompletionHandler {
|
|||||||
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
|
||||||
model: this.getModel().id,
|
model: this.getModel().id,
|
||||||
messages: [{ role: "user", content: prompt }],
|
messages: [{ role: "user", content: prompt }],
|
||||||
temperature: 0,
|
}
|
||||||
|
|
||||||
|
if (this.supportsTemperature()) {
|
||||||
|
requestOptions.temperature = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.getModel().id.startsWith("anthropic/")) {
|
if (this.getModel().id.startsWith("anthropic/")) {
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export class Cline {
|
|||||||
didFinishAborting = false
|
didFinishAborting = false
|
||||||
abandoned = false
|
abandoned = false
|
||||||
private diffViewProvider: DiffViewProvider
|
private diffViewProvider: DiffViewProvider
|
||||||
|
private lastApiRequestTime?: number
|
||||||
|
|
||||||
// streaming
|
// streaming
|
||||||
private currentStreamingContentIndex = 0
|
private currentStreamingContentIndex = 0
|
||||||
@@ -796,9 +797,40 @@ export class Cline {
|
|||||||
async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream {
|
async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream {
|
||||||
let mcpHub: McpHub | undefined
|
let mcpHub: McpHub | undefined
|
||||||
|
|
||||||
const { mcpEnabled, alwaysApproveResubmit, requestDelaySeconds } =
|
const { mcpEnabled, alwaysApproveResubmit, requestDelaySeconds, rateLimitSeconds } =
|
||||||
(await this.providerRef.deref()?.getState()) ?? {}
|
(await this.providerRef.deref()?.getState()) ?? {}
|
||||||
|
|
||||||
|
let finalDelay = 0
|
||||||
|
|
||||||
|
// Only apply rate limiting if this isn't the first request
|
||||||
|
if (this.lastApiRequestTime) {
|
||||||
|
const now = Date.now()
|
||||||
|
const timeSinceLastRequest = now - this.lastApiRequestTime
|
||||||
|
const rateLimit = rateLimitSeconds || 0
|
||||||
|
const rateLimitDelay = Math.max(0, rateLimit * 1000 - timeSinceLastRequest)
|
||||||
|
finalDelay = rateLimitDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add exponential backoff delay for retries
|
||||||
|
if (retryAttempt > 0) {
|
||||||
|
const baseDelay = requestDelaySeconds || 5
|
||||||
|
const exponentialDelay = Math.ceil(baseDelay * Math.pow(2, retryAttempt)) * 1000
|
||||||
|
finalDelay = Math.max(finalDelay, exponentialDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalDelay > 0) {
|
||||||
|
// Show countdown timer
|
||||||
|
for (let i = Math.ceil(finalDelay / 1000); i > 0; i--) {
|
||||||
|
const delayMessage =
|
||||||
|
retryAttempt > 0 ? `Retrying in ${i} seconds...` : `Rate limiting for ${i} seconds...`
|
||||||
|
await this.say("api_req_retry_delayed", delayMessage, undefined, true)
|
||||||
|
await delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last request time before making the request
|
||||||
|
this.lastApiRequestTime = Date.now()
|
||||||
|
|
||||||
if (mcpEnabled ?? true) {
|
if (mcpEnabled ?? true) {
|
||||||
mcpHub = this.providerRef.deref()?.mcpHub
|
mcpHub = this.providerRef.deref()?.mcpHub
|
||||||
if (!mcpHub) {
|
if (!mcpHub) {
|
||||||
@@ -810,8 +842,14 @@ export class Cline {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const { browserViewportSize, mode, customModePrompts, preferredLanguage, experiments } =
|
const {
|
||||||
(await this.providerRef.deref()?.getState()) ?? {}
|
browserViewportSize,
|
||||||
|
mode,
|
||||||
|
customModePrompts,
|
||||||
|
preferredLanguage,
|
||||||
|
experiments,
|
||||||
|
enableMcpServerCreation,
|
||||||
|
} = (await this.providerRef.deref()?.getState()) ?? {}
|
||||||
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
|
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
|
||||||
const systemPrompt = await (async () => {
|
const systemPrompt = await (async () => {
|
||||||
const provider = this.providerRef.deref()
|
const provider = this.providerRef.deref()
|
||||||
@@ -832,6 +870,7 @@ export class Cline {
|
|||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
this.diffEnabled,
|
this.diffEnabled,
|
||||||
experiments,
|
experiments,
|
||||||
|
enableMcpServerCreation,
|
||||||
)
|
)
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const ACTION_NAMES = {
|
|||||||
ADD_TO_CONTEXT: "Roo Code: Add to Context",
|
ADD_TO_CONTEXT: "Roo Code: Add to Context",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const COMMAND_IDS = {
|
export const COMMAND_IDS = {
|
||||||
EXPLAIN: "roo-cline.explainCode",
|
EXPLAIN: "roo-cline.explainCode",
|
||||||
FIX: "roo-cline.fixCode",
|
FIX: "roo-cline.fixCode",
|
||||||
IMPROVE: "roo-cline.improveCode",
|
IMPROVE: "roo-cline.improveCode",
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ jest.mock("vscode", () => {
|
|||||||
visibleTextEditors: [mockTextEditor],
|
visibleTextEditors: [mockTextEditor],
|
||||||
tabGroups: {
|
tabGroups: {
|
||||||
all: [mockTabGroup],
|
all: [mockTabGroup],
|
||||||
|
onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
@@ -750,8 +751,11 @@ describe("Cline", () => {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify delay was called correctly
|
// Calculate expected delay calls based on exponential backoff
|
||||||
expect(mockDelay).toHaveBeenCalledTimes(baseDelay)
|
const exponentialDelay = Math.ceil(baseDelay * Math.pow(2, 1)) // retryAttempt = 1
|
||||||
|
const rateLimitDelay = baseDelay // Initial rate limit delay
|
||||||
|
const totalExpectedDelays = exponentialDelay + rateLimitDelay
|
||||||
|
expect(mockDelay).toHaveBeenCalledTimes(totalExpectedDelays)
|
||||||
expect(mockDelay).toHaveBeenCalledWith(1000)
|
expect(mockDelay).toHaveBeenCalledWith(1000)
|
||||||
|
|
||||||
// Verify error message content
|
// Verify error message content
|
||||||
|
|||||||
@@ -1,37 +1,9 @@
|
|||||||
import { DiffStrategy, DiffResult } from "../types"
|
import { DiffStrategy, DiffResult } from "../types"
|
||||||
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
|
import { addLineNumbers, everyLineHasLineNumbers, stripLineNumbers } from "../../../integrations/misc/extract-text"
|
||||||
|
import { distance } from "fastest-levenshtein"
|
||||||
|
|
||||||
const BUFFER_LINES = 20 // Number of extra context lines to show before and after matches
|
const BUFFER_LINES = 20 // Number of extra context lines to show before and after matches
|
||||||
|
|
||||||
function levenshteinDistance(a: string, b: string): number {
|
|
||||||
const matrix: number[][] = []
|
|
||||||
|
|
||||||
// Initialize matrix
|
|
||||||
for (let i = 0; i <= a.length; i++) {
|
|
||||||
matrix[i] = [i]
|
|
||||||
}
|
|
||||||
for (let j = 0; j <= b.length; j++) {
|
|
||||||
matrix[0][j] = j
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill matrix
|
|
||||||
for (let i = 1; i <= a.length; i++) {
|
|
||||||
for (let j = 1; j <= b.length; j++) {
|
|
||||||
if (a[i - 1] === b[j - 1]) {
|
|
||||||
matrix[i][j] = matrix[i - 1][j - 1]
|
|
||||||
} else {
|
|
||||||
matrix[i][j] = Math.min(
|
|
||||||
matrix[i - 1][j - 1] + 1, // substitution
|
|
||||||
matrix[i][j - 1] + 1, // insertion
|
|
||||||
matrix[i - 1][j] + 1, // deletion
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matrix[a.length][b.length]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSimilarity(original: string, search: string): number {
|
function getSimilarity(original: string, search: string): number {
|
||||||
if (search === "") {
|
if (search === "") {
|
||||||
return 1
|
return 1
|
||||||
@@ -47,12 +19,12 @@ function getSimilarity(original: string, search: string): number {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate Levenshtein distance
|
// Calculate Levenshtein distance using fastest-levenshtein's distance function
|
||||||
const distance = levenshteinDistance(normalizedOriginal, normalizedSearch)
|
const dist = distance(normalizedOriginal, normalizedSearch)
|
||||||
|
|
||||||
// Calculate similarity ratio (0 to 1, where 1 is exact match)
|
// Calculate similarity ratio (0 to 1, where 1 is an exact match)
|
||||||
const maxLength = Math.max(normalizedOriginal.length, normalizedSearch.length)
|
const maxLength = Math.max(normalizedOriginal.length, normalizedSearch.length)
|
||||||
return 1 - distance / maxLength
|
return 1 - dist / maxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SearchReplaceDiffStrategy implements DiffStrategy {
|
export class SearchReplaceDiffStrategy implements DiffStrategy {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -174,6 +174,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -194,6 +195,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -216,6 +218,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -236,6 +239,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -256,6 +260,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -276,6 +281,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
true, // diffEnabled
|
true, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toContain("apply_diff")
|
expect(prompt).toContain("apply_diff")
|
||||||
@@ -297,6 +303,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
false, // diffEnabled
|
false, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).not.toContain("apply_diff")
|
expect(prompt).not.toContain("apply_diff")
|
||||||
@@ -318,6 +325,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).not.toContain("apply_diff")
|
expect(prompt).not.toContain("apply_diff")
|
||||||
@@ -339,6 +347,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
"Spanish", // preferredLanguage
|
"Spanish", // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toContain("Language Preference:")
|
expect(prompt).toContain("Language Preference:")
|
||||||
@@ -371,6 +380,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Role definition should be at the top
|
// Role definition should be at the top
|
||||||
@@ -406,6 +416,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Role definition from promptComponent should be at the top
|
// Role definition from promptComponent should be at the top
|
||||||
@@ -436,6 +447,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Should use the default mode's role definition
|
// Should use the default mode's role definition
|
||||||
@@ -458,6 +470,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments, // experiments - undefined should disable all experimental tools
|
experiments, // experiments - undefined should disable all experimental tools
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify experimental tools are not included in the prompt
|
// Verify experimental tools are not included in the prompt
|
||||||
@@ -485,6 +498,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify experimental tools are included in the prompt when enabled
|
// Verify experimental tools are included in the prompt when enabled
|
||||||
@@ -512,6 +526,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
undefined, // diffEnabled
|
undefined, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify only enabled experimental tools are included
|
// Verify only enabled experimental tools are included
|
||||||
@@ -539,6 +554,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
true, // diffEnabled
|
true, // diffEnabled
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify base instruction lists all available tools
|
// Verify base instruction lists all available tools
|
||||||
@@ -568,6 +584,7 @@ describe("SYSTEM_PROMPT", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
true,
|
true,
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify detailed instructions for each tool
|
// Verify detailed instructions for each tool
|
||||||
@@ -623,6 +640,7 @@ describe("addCustomInstructions", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
@@ -643,11 +661,60 @@ describe("addCustomInstructions", () => {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
experiments,
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(prompt).toMatchSnapshot()
|
expect(prompt).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should include MCP server creation info when enabled", async () => {
|
||||||
|
const mockMcpHub = createMockMcpHub()
|
||||||
|
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
mockContext,
|
||||||
|
"/test/path",
|
||||||
|
false, // supportsComputerUse
|
||||||
|
mockMcpHub, // mcpHub
|
||||||
|
undefined, // diffStrategy
|
||||||
|
undefined, // browserViewportSize
|
||||||
|
defaultModeSlug, // mode
|
||||||
|
undefined, // customModePrompts
|
||||||
|
undefined, // customModes,
|
||||||
|
undefined, // globalCustomInstructions
|
||||||
|
undefined, // preferredLanguage
|
||||||
|
undefined, // diffEnabled
|
||||||
|
experiments,
|
||||||
|
true, // enableMcpServerCreation
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).toContain("Creating an MCP Server")
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should exclude MCP server creation info when disabled", async () => {
|
||||||
|
const mockMcpHub = createMockMcpHub()
|
||||||
|
|
||||||
|
const prompt = await SYSTEM_PROMPT(
|
||||||
|
mockContext,
|
||||||
|
"/test/path",
|
||||||
|
false, // supportsComputerUse
|
||||||
|
mockMcpHub, // mcpHub
|
||||||
|
undefined, // diffStrategy
|
||||||
|
undefined, // browserViewportSize
|
||||||
|
defaultModeSlug, // mode
|
||||||
|
undefined, // customModePrompts
|
||||||
|
undefined, // customModes,
|
||||||
|
undefined, // globalCustomInstructions
|
||||||
|
undefined, // preferredLanguage
|
||||||
|
undefined, // diffEnabled
|
||||||
|
experiments,
|
||||||
|
false, // enableMcpServerCreation
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(prompt).not.toContain("Creating an MCP Server")
|
||||||
|
expect(prompt).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
it("should prioritize mode-specific rules for code mode", async () => {
|
it("should prioritize mode-specific rules for code mode", async () => {
|
||||||
const instructions = await addCustomInstructions("", "", "/test/path", defaultModeSlug)
|
const instructions = await addCustomInstructions("", "", "/test/path", defaultModeSlug)
|
||||||
expect(instructions).toMatchSnapshot()
|
expect(instructions).toMatchSnapshot()
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { DiffStrategy } from "../../diff/DiffStrategy"
|
import { DiffStrategy } from "../../diff/DiffStrategy"
|
||||||
import { McpHub } from "../../../services/mcp/McpHub"
|
import { McpHub } from "../../../services/mcp/McpHub"
|
||||||
|
|
||||||
export async function getMcpServersSection(mcpHub?: McpHub, diffStrategy?: DiffStrategy): Promise<string> {
|
export async function getMcpServersSection(
|
||||||
|
mcpHub?: McpHub,
|
||||||
|
diffStrategy?: DiffStrategy,
|
||||||
|
enableMcpServerCreation?: boolean,
|
||||||
|
): Promise<string> {
|
||||||
if (!mcpHub) {
|
if (!mcpHub) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ export async function getMcpServersSection(mcpHub?: McpHub, diffStrategy?: DiffS
|
|||||||
.join("\n\n")}`
|
.join("\n\n")}`
|
||||||
: "(No MCP servers currently connected)"
|
: "(No MCP servers currently connected)"
|
||||||
|
|
||||||
return `MCP SERVERS
|
const baseSection = `MCP SERVERS
|
||||||
|
|
||||||
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
||||||
|
|
||||||
@@ -51,7 +55,15 @@ The Model Context Protocol (MCP) enables communication between the system and lo
|
|||||||
|
|
||||||
When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.
|
When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.
|
||||||
|
|
||||||
${connectedServers}
|
${connectedServers}`
|
||||||
|
|
||||||
|
if (!enableMcpServerCreation) {
|
||||||
|
return baseSection
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
baseSection +
|
||||||
|
`
|
||||||
|
|
||||||
## Creating an MCP Server
|
## Creating an MCP Server
|
||||||
|
|
||||||
@@ -411,4 +423,5 @@ However some MCP servers may be running from installed packages rather than a lo
|
|||||||
The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that...").
|
The user may not always request the use or creation of MCP servers. Instead, they might provide tasks that can be completed with existing tools. While using the MCP SDK to extend your capabilities can be useful, it's important to understand that this is just one specialized type of task you can accomplish. You should only implement MCP servers when the user explicitly requests it (e.g., "add a tool that...").
|
||||||
|
|
||||||
Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.`
|
Remember: The MCP documentation and example provided above are to help you understand and work with existing MCP servers or create new ones when requested by the user. You already have access to tools and capabilities that can be used to accomplish a wide range of tasks.`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ async function generatePrompt(
|
|||||||
preferredLanguage?: string,
|
preferredLanguage?: string,
|
||||||
diffEnabled?: boolean,
|
diffEnabled?: boolean,
|
||||||
experiments?: Record<string, boolean>,
|
experiments?: Record<string, boolean>,
|
||||||
|
enableMcpServerCreation?: boolean,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Extension context is required for generating system prompt")
|
throw new Error("Extension context is required for generating system prompt")
|
||||||
@@ -49,7 +50,7 @@ async function generatePrompt(
|
|||||||
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined
|
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined
|
||||||
|
|
||||||
const [mcpServersSection, modesSection] = await Promise.all([
|
const [mcpServersSection, modesSection] = await Promise.all([
|
||||||
getMcpServersSection(mcpHub, effectiveDiffStrategy),
|
getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation),
|
||||||
getModesSection(context),
|
getModesSection(context),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -105,6 +106,7 @@ export const SYSTEM_PROMPT = async (
|
|||||||
preferredLanguage?: string,
|
preferredLanguage?: string,
|
||||||
diffEnabled?: boolean,
|
diffEnabled?: boolean,
|
||||||
experiments?: Record<string, boolean>,
|
experiments?: Record<string, boolean>,
|
||||||
|
enableMcpServerCreation?: boolean,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Extension context is required for generating system prompt")
|
throw new Error("Extension context is required for generating system prompt")
|
||||||
@@ -139,5 +141,6 @@ export const SYSTEM_PROMPT = async (
|
|||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
diffEnabled,
|
diffEnabled,
|
||||||
experiments,
|
experiments,
|
||||||
|
enableMcpServerCreation,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,7 @@ import { findLast } from "../../shared/array"
|
|||||||
import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
|
import { ApiConfigMeta, ExtensionMessage } from "../../shared/ExtensionMessage"
|
||||||
import { HistoryItem } from "../../shared/HistoryItem"
|
import { HistoryItem } from "../../shared/HistoryItem"
|
||||||
import { WebviewMessage } from "../../shared/WebviewMessage"
|
import { WebviewMessage } from "../../shared/WebviewMessage"
|
||||||
import {
|
import { Mode, CustomModePrompts, PromptComponent, defaultModeSlug } from "../../shared/modes"
|
||||||
Mode,
|
|
||||||
modes,
|
|
||||||
CustomModePrompts,
|
|
||||||
PromptComponent,
|
|
||||||
ModeConfig,
|
|
||||||
defaultModeSlug,
|
|
||||||
getModeBySlug,
|
|
||||||
} from "../../shared/modes"
|
|
||||||
import { SYSTEM_PROMPT } from "../prompts/system"
|
import { SYSTEM_PROMPT } from "../prompts/system"
|
||||||
import { fileExistsAtPath } from "../../utils/fs"
|
import { fileExistsAtPath } from "../../utils/fs"
|
||||||
import { Cline } from "../Cline"
|
import { Cline } from "../Cline"
|
||||||
@@ -37,7 +29,7 @@ import { getUri } from "./getUri"
|
|||||||
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
|
import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
|
||||||
import { checkExistKey } from "../../shared/checkExistApiConfig"
|
import { checkExistKey } from "../../shared/checkExistApiConfig"
|
||||||
import { singleCompletionHandler } from "../../utils/single-completion-handler"
|
import { singleCompletionHandler } from "../../utils/single-completion-handler"
|
||||||
import { getCommitInfo, searchCommits, getWorkingState } from "../../utils/git"
|
import { searchCommits } from "../../utils/git"
|
||||||
import { ConfigManager } from "../config/ConfigManager"
|
import { ConfigManager } from "../config/ConfigManager"
|
||||||
import { CustomModesManager } from "../config/CustomModesManager"
|
import { CustomModesManager } from "../config/CustomModesManager"
|
||||||
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
|
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
|
||||||
@@ -110,8 +102,10 @@ type GlobalStateKey =
|
|||||||
| "writeDelayMs"
|
| "writeDelayMs"
|
||||||
| "terminalOutputLineLimit"
|
| "terminalOutputLineLimit"
|
||||||
| "mcpEnabled"
|
| "mcpEnabled"
|
||||||
|
| "enableMcpServerCreation"
|
||||||
| "alwaysApproveResubmit"
|
| "alwaysApproveResubmit"
|
||||||
| "requestDelaySeconds"
|
| "requestDelaySeconds"
|
||||||
|
| "rateLimitSeconds"
|
||||||
| "currentApiConfigName"
|
| "currentApiConfigName"
|
||||||
| "listApiConfigMeta"
|
| "listApiConfigMeta"
|
||||||
| "vsCodeLmModelSelector"
|
| "vsCodeLmModelSelector"
|
||||||
@@ -139,6 +133,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
private static activeInstances: Set<ClineProvider> = new Set()
|
private static activeInstances: Set<ClineProvider> = new Set()
|
||||||
private disposables: vscode.Disposable[] = []
|
private disposables: vscode.Disposable[] = []
|
||||||
private view?: vscode.WebviewView | vscode.WebviewPanel
|
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||||
|
private isViewLaunched = false
|
||||||
private cline?: Cline
|
private cline?: Cline
|
||||||
private workspaceTracker?: WorkspaceTracker
|
private workspaceTracker?: WorkspaceTracker
|
||||||
mcpHub?: McpHub
|
mcpHub?: McpHub
|
||||||
@@ -651,6 +646,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.isViewLaunched = true
|
||||||
break
|
break
|
||||||
case "newTask":
|
case "newTask":
|
||||||
// Code that should run in response to the hello message command
|
// Code that should run in response to the hello message command
|
||||||
@@ -846,6 +842,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
await this.updateGlobalState("mcpEnabled", mcpEnabled)
|
await this.updateGlobalState("mcpEnabled", mcpEnabled)
|
||||||
await this.postStateToWebview()
|
await this.postStateToWebview()
|
||||||
break
|
break
|
||||||
|
case "enableMcpServerCreation":
|
||||||
|
await this.updateGlobalState("enableMcpServerCreation", message.bool ?? true)
|
||||||
|
await this.postStateToWebview()
|
||||||
|
break
|
||||||
case "playSound":
|
case "playSound":
|
||||||
if (message.audioType) {
|
if (message.audioType) {
|
||||||
const soundPath = path.join(this.context.extensionPath, "audio", `${message.audioType}.wav`)
|
const soundPath = path.join(this.context.extensionPath, "audio", `${message.audioType}.wav`)
|
||||||
@@ -886,6 +886,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
await this.updateGlobalState("requestDelaySeconds", message.value ?? 5)
|
await this.updateGlobalState("requestDelaySeconds", message.value ?? 5)
|
||||||
await this.postStateToWebview()
|
await this.postStateToWebview()
|
||||||
break
|
break
|
||||||
|
case "rateLimitSeconds":
|
||||||
|
await this.updateGlobalState("rateLimitSeconds", message.value ?? 0)
|
||||||
|
await this.postStateToWebview()
|
||||||
|
break
|
||||||
case "preferredLanguage":
|
case "preferredLanguage":
|
||||||
await this.updateGlobalState("preferredLanguage", message.text)
|
await this.updateGlobalState("preferredLanguage", message.text)
|
||||||
await this.postStateToWebview()
|
await this.postStateToWebview()
|
||||||
@@ -1130,6 +1134,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
mcpEnabled,
|
mcpEnabled,
|
||||||
fuzzyMatchThreshold,
|
fuzzyMatchThreshold,
|
||||||
experiments,
|
experiments,
|
||||||
|
enableMcpServerCreation,
|
||||||
} = await this.getState()
|
} = await this.getState()
|
||||||
|
|
||||||
// Create diffStrategy based on current model and settings
|
// Create diffStrategy based on current model and settings
|
||||||
@@ -1158,6 +1163,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
diffEnabled,
|
diffEnabled,
|
||||||
experiments,
|
experiments,
|
||||||
|
enableMcpServerCreation,
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.postMessageToWebview({
|
await this.postMessageToWebview({
|
||||||
@@ -1216,6 +1222,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
try {
|
try {
|
||||||
const { oldName, newName } = message.values
|
const { oldName, newName } = message.values
|
||||||
|
|
||||||
|
if (oldName === newName) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
await this.configManager.saveConfig(newName, message.apiConfiguration)
|
await this.configManager.saveConfig(newName, message.apiConfiguration)
|
||||||
await this.configManager.deleteConfig(oldName)
|
await this.configManager.deleteConfig(oldName)
|
||||||
|
|
||||||
@@ -1995,8 +2005,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
terminalOutputLineLimit,
|
terminalOutputLineLimit,
|
||||||
fuzzyMatchThreshold,
|
fuzzyMatchThreshold,
|
||||||
mcpEnabled,
|
mcpEnabled,
|
||||||
|
enableMcpServerCreation,
|
||||||
alwaysApproveResubmit,
|
alwaysApproveResubmit,
|
||||||
requestDelaySeconds,
|
requestDelaySeconds,
|
||||||
|
rateLimitSeconds,
|
||||||
currentApiConfigName,
|
currentApiConfigName,
|
||||||
listApiConfigMeta,
|
listApiConfigMeta,
|
||||||
mode,
|
mode,
|
||||||
@@ -2036,8 +2048,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
|
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
|
||||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||||
mcpEnabled: mcpEnabled ?? true,
|
mcpEnabled: mcpEnabled ?? true,
|
||||||
|
enableMcpServerCreation: enableMcpServerCreation ?? true,
|
||||||
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
||||||
requestDelaySeconds: requestDelaySeconds ?? 10,
|
requestDelaySeconds: requestDelaySeconds ?? 10,
|
||||||
|
rateLimitSeconds: rateLimitSeconds ?? 0,
|
||||||
currentApiConfigName: currentApiConfigName ?? "default",
|
currentApiConfigName: currentApiConfigName ?? "default",
|
||||||
listApiConfigMeta: listApiConfigMeta ?? [],
|
listApiConfigMeta: listApiConfigMeta ?? [],
|
||||||
mode: mode ?? defaultModeSlug,
|
mode: mode ?? defaultModeSlug,
|
||||||
@@ -2159,8 +2173,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
screenshotQuality,
|
screenshotQuality,
|
||||||
terminalOutputLineLimit,
|
terminalOutputLineLimit,
|
||||||
mcpEnabled,
|
mcpEnabled,
|
||||||
|
enableMcpServerCreation,
|
||||||
alwaysApproveResubmit,
|
alwaysApproveResubmit,
|
||||||
requestDelaySeconds,
|
requestDelaySeconds,
|
||||||
|
rateLimitSeconds,
|
||||||
currentApiConfigName,
|
currentApiConfigName,
|
||||||
listApiConfigMeta,
|
listApiConfigMeta,
|
||||||
vsCodeLmModelSelector,
|
vsCodeLmModelSelector,
|
||||||
@@ -2231,8 +2247,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
this.getGlobalState("screenshotQuality") as Promise<number | undefined>,
|
this.getGlobalState("screenshotQuality") as Promise<number | undefined>,
|
||||||
this.getGlobalState("terminalOutputLineLimit") as Promise<number | undefined>,
|
this.getGlobalState("terminalOutputLineLimit") as Promise<number | undefined>,
|
||||||
this.getGlobalState("mcpEnabled") as Promise<boolean | undefined>,
|
this.getGlobalState("mcpEnabled") as Promise<boolean | undefined>,
|
||||||
|
this.getGlobalState("enableMcpServerCreation") as Promise<boolean | undefined>,
|
||||||
this.getGlobalState("alwaysApproveResubmit") as Promise<boolean | undefined>,
|
this.getGlobalState("alwaysApproveResubmit") as Promise<boolean | undefined>,
|
||||||
this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
|
this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
|
||||||
|
this.getGlobalState("rateLimitSeconds") as Promise<number | undefined>,
|
||||||
this.getGlobalState("currentApiConfigName") as Promise<string | undefined>,
|
this.getGlobalState("currentApiConfigName") as Promise<string | undefined>,
|
||||||
this.getGlobalState("listApiConfigMeta") as Promise<ApiConfigMeta[] | undefined>,
|
this.getGlobalState("listApiConfigMeta") as Promise<ApiConfigMeta[] | undefined>,
|
||||||
this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
this.getGlobalState("vsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||||
@@ -2353,8 +2371,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
return langMap[vscodeLang.split("-")[0]] ?? "English"
|
return langMap[vscodeLang.split("-")[0]] ?? "English"
|
||||||
})(),
|
})(),
|
||||||
mcpEnabled: mcpEnabled ?? true,
|
mcpEnabled: mcpEnabled ?? true,
|
||||||
|
enableMcpServerCreation: enableMcpServerCreation ?? true,
|
||||||
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
||||||
requestDelaySeconds: Math.max(5, requestDelaySeconds ?? 10),
|
requestDelaySeconds: Math.max(5, requestDelaySeconds ?? 10),
|
||||||
|
rateLimitSeconds: rateLimitSeconds ?? 0,
|
||||||
currentApiConfigName: currentApiConfigName ?? "default",
|
currentApiConfigName: currentApiConfigName ?? "default",
|
||||||
listApiConfigMeta: listApiConfigMeta ?? [],
|
listApiConfigMeta: listApiConfigMeta ?? [],
|
||||||
modeApiConfigs: modeApiConfigs ?? ({} as Record<Mode, string>),
|
modeApiConfigs: modeApiConfigs ?? ({} as Record<Mode, string>),
|
||||||
@@ -2412,7 +2432,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
|
|
||||||
// secrets
|
// secrets
|
||||||
|
|
||||||
private async storeSecret(key: SecretKey, value?: string) {
|
public async storeSecret(key: SecretKey, value?: string) {
|
||||||
if (value) {
|
if (value) {
|
||||||
await this.context.secrets.store(key, value)
|
await this.context.secrets.store(key, value)
|
||||||
} else {
|
} else {
|
||||||
@@ -2466,4 +2486,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
await this.postStateToWebview()
|
await this.postStateToWebview()
|
||||||
await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// integration tests
|
||||||
|
|
||||||
|
get viewLaunched() {
|
||||||
|
return this.isViewLaunched
|
||||||
|
}
|
||||||
|
|
||||||
|
get messages() {
|
||||||
|
return this.cline?.clineMessages || []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,7 +323,9 @@ describe("ClineProvider", () => {
|
|||||||
browserViewportSize: "900x600",
|
browserViewportSize: "900x600",
|
||||||
fuzzyMatchThreshold: 1.0,
|
fuzzyMatchThreshold: 1.0,
|
||||||
mcpEnabled: true,
|
mcpEnabled: true,
|
||||||
|
enableMcpServerCreation: false,
|
||||||
requestDelaySeconds: 5,
|
requestDelaySeconds: 5,
|
||||||
|
rateLimitSeconds: 0,
|
||||||
mode: defaultModeSlug,
|
mode: defaultModeSlug,
|
||||||
customModes: [],
|
customModes: [],
|
||||||
experiments: experimentDefault,
|
experiments: experimentDefault,
|
||||||
@@ -894,6 +896,7 @@ describe("ClineProvider", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mcpEnabled: true,
|
mcpEnabled: true,
|
||||||
|
enableMcpServerCreation: false,
|
||||||
mode: "code" as const,
|
mode: "code" as const,
|
||||||
experiments: experimentDefault,
|
experiments: experimentDefault,
|
||||||
} as any)
|
} as any)
|
||||||
@@ -926,6 +929,7 @@ describe("ClineProvider", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mcpEnabled: false,
|
mcpEnabled: false,
|
||||||
|
enableMcpServerCreation: false,
|
||||||
mode: "code" as const,
|
mode: "code" as const,
|
||||||
experiments: experimentDefault,
|
experiments: experimentDefault,
|
||||||
} as any)
|
} as any)
|
||||||
@@ -990,6 +994,7 @@ describe("ClineProvider", () => {
|
|||||||
},
|
},
|
||||||
customModePrompts: {},
|
customModePrompts: {},
|
||||||
mode: "code",
|
mode: "code",
|
||||||
|
enableMcpServerCreation: true,
|
||||||
mcpEnabled: false,
|
mcpEnabled: false,
|
||||||
browserViewportSize: "900x600",
|
browserViewportSize: "900x600",
|
||||||
experimentalDiffStrategy: true,
|
experimentalDiffStrategy: true,
|
||||||
@@ -1024,6 +1029,7 @@ describe("ClineProvider", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
true, // diffEnabled
|
true, // diffEnabled
|
||||||
experimentDefault,
|
experimentDefault,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run the test again to verify it's consistent
|
// Run the test again to verify it's consistent
|
||||||
@@ -1047,6 +1053,7 @@ describe("ClineProvider", () => {
|
|||||||
diffEnabled: false,
|
diffEnabled: false,
|
||||||
fuzzyMatchThreshold: 0.8,
|
fuzzyMatchThreshold: 0.8,
|
||||||
experiments: experimentDefault,
|
experiments: experimentDefault,
|
||||||
|
enableMcpServerCreation: true,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
// Mock SYSTEM_PROMPT to verify diffEnabled is passed as false
|
// Mock SYSTEM_PROMPT to verify diffEnabled is passed as false
|
||||||
@@ -1075,6 +1082,7 @@ describe("ClineProvider", () => {
|
|||||||
undefined, // preferredLanguage
|
undefined, // preferredLanguage
|
||||||
false, // diffEnabled
|
false, // diffEnabled
|
||||||
experimentDefault,
|
experimentDefault,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1089,6 +1097,7 @@ describe("ClineProvider", () => {
|
|||||||
architect: { customInstructions: "Architect mode instructions" },
|
architect: { customInstructions: "Architect mode instructions" },
|
||||||
},
|
},
|
||||||
mode: "architect",
|
mode: "architect",
|
||||||
|
enableMcpServerCreation: false,
|
||||||
mcpEnabled: false,
|
mcpEnabled: false,
|
||||||
browserViewportSize: "900x600",
|
browserViewportSize: "900x600",
|
||||||
experiments: experimentDefault,
|
experiments: experimentDefault,
|
||||||
|
|||||||
245
src/extension.ts
245
src/extension.ts
@@ -1,37 +1,33 @@
|
|||||||
// The module 'vscode' contains the VS Code extensibility API
|
|
||||||
// Import the module and reference it with the alias vscode in your code below
|
|
||||||
import delay from "delay"
|
|
||||||
import * as vscode from "vscode"
|
import * as vscode from "vscode"
|
||||||
|
|
||||||
import { ClineProvider } from "./core/webview/ClineProvider"
|
import { ClineProvider } from "./core/webview/ClineProvider"
|
||||||
import { createClineAPI } from "./exports"
|
import { createClineAPI } from "./exports"
|
||||||
import "./utils/path" // necessary to have access to String.prototype.toPosix
|
import "./utils/path" // Necessary to have access to String.prototype.toPosix.
|
||||||
import { ACTION_NAMES, CodeActionProvider } from "./core/CodeActionProvider"
|
import { CodeActionProvider } from "./core/CodeActionProvider"
|
||||||
import { EditorUtils } from "./core/EditorUtils"
|
|
||||||
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
|
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
|
||||||
|
import { handleUri, registerCommands, registerCodeActions } from "./activate"
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
* Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||||
|
*
|
||||||
Inspired by
|
* Inspired by:
|
||||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/weather-webview
|
* - https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/weather-webview
|
||||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-cra
|
* - https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-cra
|
||||||
|
*/
|
||||||
*/
|
|
||||||
|
|
||||||
let outputChannel: vscode.OutputChannel
|
let outputChannel: vscode.OutputChannel
|
||||||
|
|
||||||
// This method is called when your extension is activated
|
// This method is called when your extension is activated.
|
||||||
// Your extension is activated the very first time the command is executed
|
// Your extension is activated the very first time the command is executed.
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
outputChannel = vscode.window.createOutputChannel("Roo-Code")
|
outputChannel = vscode.window.createOutputChannel("Roo-Code")
|
||||||
context.subscriptions.push(outputChannel)
|
context.subscriptions.push(outputChannel)
|
||||||
|
|
||||||
outputChannel.appendLine("Roo-Code extension activated")
|
outputChannel.appendLine("Roo-Code extension activated")
|
||||||
|
|
||||||
// Get default commands from configuration
|
// Get default commands from configuration.
|
||||||
const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
|
const defaultCommands = vscode.workspace.getConfiguration("roo-cline").get<string[]>("allowedCommands") || []
|
||||||
|
|
||||||
// Initialize global state if not already set
|
// Initialize global state if not already set.
|
||||||
if (!context.globalState.get("allowedCommands")) {
|
if (!context.globalState.get("allowedCommands")) {
|
||||||
context.globalState.update("allowedCommands", defaultCommands)
|
context.globalState.update("allowedCommands", defaultCommands)
|
||||||
}
|
}
|
||||||
@@ -44,220 +40,49 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
context.subscriptions.push(
|
registerCommands({ context, outputChannel, provider: sidebarProvider })
|
||||||
vscode.commands.registerCommand("roo-cline.plusButtonClicked", async () => {
|
|
||||||
outputChannel.appendLine("Plus button Clicked")
|
|
||||||
await sidebarProvider.clearTask()
|
|
||||||
await sidebarProvider.postStateToWebview()
|
|
||||||
await sidebarProvider.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
/**
|
||||||
vscode.commands.registerCommand("roo-cline.mcpButtonClicked", () => {
|
* We use the text document content provider API to show the left side for diff
|
||||||
sidebarProvider.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
|
* view by creating a virtual document for the original content. This makes it
|
||||||
}),
|
* readonly so users know to edit the right side if they want to keep their changes.
|
||||||
)
|
*
|
||||||
|
* This API allows you to create readonly documents in VSCode from arbitrary
|
||||||
context.subscriptions.push(
|
* sources, and works by claiming an uri-scheme for which your provider then
|
||||||
vscode.commands.registerCommand("roo-cline.promptsButtonClicked", () => {
|
* returns text contents. The scheme must be provided when registering a
|
||||||
sidebarProvider.postMessageToWebview({ type: "action", action: "promptsButtonClicked" })
|
* provider and cannot change afterwards.
|
||||||
}),
|
*
|
||||||
)
|
* Note how the provider doesn't create uris for virtual documents - its role
|
||||||
|
* is to provide contents given such an uri. In return, content providers are
|
||||||
const openClineInNewTab = async () => {
|
* wired into the open document logic so that providers are always considered.
|
||||||
outputChannel.appendLine("Opening Roo Code in new tab")
|
*
|
||||||
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
|
* https://code.visualstudio.com/api/extension-guides/virtual-documents
|
||||||
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
|
|
||||||
const tabProvider = new ClineProvider(context, outputChannel)
|
|
||||||
//const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
|
|
||||||
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
|
|
||||||
|
|
||||||
// Check if there are any visible text editors, otherwise open a new group to the right
|
|
||||||
const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0
|
|
||||||
if (!hasVisibleEditors) {
|
|
||||||
await vscode.commands.executeCommand("workbench.action.newGroupRight")
|
|
||||||
}
|
|
||||||
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
|
|
||||||
|
|
||||||
const panel = vscode.window.createWebviewPanel(ClineProvider.tabPanelId, "Roo Code", targetCol, {
|
|
||||||
enableScripts: true,
|
|
||||||
retainContextWhenHidden: true,
|
|
||||||
localResourceRoots: [context.extensionUri],
|
|
||||||
})
|
|
||||||
// TODO: use better svg icon with light and dark variants (see https://stackoverflow.com/questions/58365687/vscode-extension-iconpath)
|
|
||||||
|
|
||||||
panel.iconPath = {
|
|
||||||
light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
|
||||||
dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "rocket.png"),
|
|
||||||
}
|
|
||||||
tabProvider.resolveWebviewView(panel)
|
|
||||||
|
|
||||||
// Lock the editor group so clicking on files doesn't open them over the panel
|
|
||||||
await delay(100)
|
|
||||||
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
|
|
||||||
}
|
|
||||||
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand("roo-cline.popoutButtonClicked", openClineInNewTab))
|
|
||||||
context.subscriptions.push(vscode.commands.registerCommand("roo-cline.openInNewTab", openClineInNewTab))
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.commands.registerCommand("roo-cline.settingsButtonClicked", () => {
|
|
||||||
//vscode.window.showInformationMessage(message)
|
|
||||||
sidebarProvider.postMessageToWebview({ type: "action", action: "settingsButtonClicked" })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.commands.registerCommand("roo-cline.historyButtonClicked", () => {
|
|
||||||
sidebarProvider.postMessageToWebview({ type: "action", action: "historyButtonClicked" })
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
We use the text document content provider API to show the left side for diff view by creating a virtual document for the original content. This makes it readonly so users know to edit the right side if they want to keep their changes.
|
|
||||||
|
|
||||||
- This API allows you to create readonly documents in VSCode from arbitrary sources, and works by claiming an uri-scheme for which your provider then returns text contents. The scheme must be provided when registering a provider and cannot change afterwards.
|
|
||||||
- Note how the provider doesn't create uris for virtual documents - its role is to provide contents given such an uri. In return, content providers are wired into the open document logic so that providers are always considered.
|
|
||||||
https://code.visualstudio.com/api/extension-guides/virtual-documents
|
|
||||||
*/
|
*/
|
||||||
const diffContentProvider = new (class implements vscode.TextDocumentContentProvider {
|
const diffContentProvider = new (class implements vscode.TextDocumentContentProvider {
|
||||||
provideTextDocumentContent(uri: vscode.Uri): string {
|
provideTextDocumentContent(uri: vscode.Uri): string {
|
||||||
return Buffer.from(uri.query, "base64").toString("utf-8")
|
return Buffer.from(uri.query, "base64").toString("utf-8")
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider),
|
vscode.workspace.registerTextDocumentContentProvider(DIFF_VIEW_URI_SCHEME, diffContentProvider),
|
||||||
)
|
)
|
||||||
|
|
||||||
// URI Handler
|
|
||||||
const handleUri = async (uri: vscode.Uri) => {
|
|
||||||
const path = uri.path
|
|
||||||
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
|
|
||||||
const visibleProvider = ClineProvider.getVisibleInstance()
|
|
||||||
if (!visibleProvider) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch (path) {
|
|
||||||
case "/glama": {
|
|
||||||
const code = query.get("code")
|
|
||||||
if (code) {
|
|
||||||
await visibleProvider.handleGlamaCallback(code)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case "/openrouter": {
|
|
||||||
const code = query.get("code")
|
|
||||||
if (code) {
|
|
||||||
await visibleProvider.handleOpenRouterCallback(code)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
|
context.subscriptions.push(vscode.window.registerUriHandler({ handleUri }))
|
||||||
|
|
||||||
// Register code actions provider
|
// Register code actions provider.
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.languages.registerCodeActionsProvider({ pattern: "**/*" }, new CodeActionProvider(), {
|
vscode.languages.registerCodeActionsProvider({ pattern: "**/*" }, new CodeActionProvider(), {
|
||||||
providedCodeActionKinds: CodeActionProvider.providedCodeActionKinds,
|
providedCodeActionKinds: CodeActionProvider.providedCodeActionKinds,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to handle code actions
|
registerCodeActions(context)
|
||||||
const registerCodeAction = (
|
|
||||||
context: vscode.ExtensionContext,
|
|
||||||
command: string,
|
|
||||||
promptType: keyof typeof ACTION_NAMES,
|
|
||||||
inputPrompt?: string,
|
|
||||||
inputPlaceholder?: string,
|
|
||||||
) => {
|
|
||||||
let userInput: string | undefined
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
|
||||||
vscode.commands.registerCommand(command, async (...args: any[]) => {
|
|
||||||
if (inputPrompt) {
|
|
||||||
userInput = await vscode.window.showInputBox({
|
|
||||||
prompt: inputPrompt,
|
|
||||||
placeHolder: inputPlaceholder,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle both code action and direct command cases
|
|
||||||
let filePath: string
|
|
||||||
let selectedText: string
|
|
||||||
let diagnostics: any[] | undefined
|
|
||||||
|
|
||||||
if (args.length > 1) {
|
|
||||||
// Called from code action
|
|
||||||
;[filePath, selectedText, diagnostics] = args
|
|
||||||
} else {
|
|
||||||
// Called directly from command palette
|
|
||||||
const context = EditorUtils.getEditorContext()
|
|
||||||
if (!context) return
|
|
||||||
;({ filePath, selectedText, diagnostics } = context)
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
...{ filePath, selectedText },
|
|
||||||
...(diagnostics ? { diagnostics } : {}),
|
|
||||||
...(userInput ? { userInput } : {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
await ClineProvider.handleCodeAction(command, promptType, params)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to register both versions of a code action
|
|
||||||
const registerCodeActionPair = (
|
|
||||||
context: vscode.ExtensionContext,
|
|
||||||
baseCommand: string,
|
|
||||||
promptType: keyof typeof ACTION_NAMES,
|
|
||||||
inputPrompt?: string,
|
|
||||||
inputPlaceholder?: string,
|
|
||||||
) => {
|
|
||||||
// Register new task version
|
|
||||||
registerCodeAction(context, baseCommand, promptType, inputPrompt, inputPlaceholder)
|
|
||||||
|
|
||||||
// Register current task version
|
|
||||||
registerCodeAction(context, `${baseCommand}InCurrentTask`, promptType, inputPrompt, inputPlaceholder)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register code action commands
|
|
||||||
registerCodeActionPair(
|
|
||||||
context,
|
|
||||||
"roo-cline.explainCode",
|
|
||||||
"EXPLAIN",
|
|
||||||
"What would you like Roo to explain?",
|
|
||||||
"E.g. How does the error handling work?",
|
|
||||||
)
|
|
||||||
|
|
||||||
registerCodeActionPair(
|
|
||||||
context,
|
|
||||||
"roo-cline.fixCode",
|
|
||||||
"FIX",
|
|
||||||
"What would you like Roo to fix?",
|
|
||||||
"E.g. Maintain backward compatibility",
|
|
||||||
)
|
|
||||||
|
|
||||||
registerCodeActionPair(
|
|
||||||
context,
|
|
||||||
"roo-cline.improveCode",
|
|
||||||
"IMPROVE",
|
|
||||||
"What would you like Roo to improve?",
|
|
||||||
"E.g. Focus on performance optimization",
|
|
||||||
)
|
|
||||||
|
|
||||||
registerCodeAction(context, "roo-cline.addToContext", "ADD_TO_CONTEXT")
|
|
||||||
|
|
||||||
return createClineAPI(outputChannel, sidebarProvider)
|
return createClineAPI(outputChannel, sidebarProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is called when your extension is deactivated
|
// This method is called when your extension is deactivated.
|
||||||
export function deactivate() {
|
export function deactivate() {
|
||||||
outputChannel.appendLine("Roo-Code extension deactivated")
|
outputChannel.appendLine("Roo-Code extension deactivated")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as vscode from "vscode"
|
|||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { listFiles } from "../../services/glob/list-files"
|
import { listFiles } from "../../services/glob/list-files"
|
||||||
import { ClineProvider } from "../../core/webview/ClineProvider"
|
import { ClineProvider } from "../../core/webview/ClineProvider"
|
||||||
|
import { toRelativePath } from "../../utils/path"
|
||||||
|
|
||||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||||
const MAX_INITIAL_FILES = 1_000
|
const MAX_INITIAL_FILES = 1_000
|
||||||
@@ -48,6 +49,23 @@ class WorkspaceTracker {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.disposables.push(watcher)
|
this.disposables.push(watcher)
|
||||||
|
|
||||||
|
this.disposables.push(vscode.window.tabGroups.onDidChangeTabs(() => this.workspaceDidUpdate()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOpenedTabsInfo() {
|
||||||
|
return vscode.window.tabGroups.all.flatMap((group) =>
|
||||||
|
group.tabs
|
||||||
|
.filter((tab) => tab.input instanceof vscode.TabInputText)
|
||||||
|
.map((tab) => {
|
||||||
|
const path = (tab.input as vscode.TabInputText).uri.fsPath
|
||||||
|
return {
|
||||||
|
label: tab.label,
|
||||||
|
isActive: tab.isActive,
|
||||||
|
path: toRelativePath(path, cwd || ""),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private workspaceDidUpdate() {
|
private workspaceDidUpdate() {
|
||||||
@@ -59,12 +77,12 @@ class WorkspaceTracker {
|
|||||||
if (!cwd) {
|
if (!cwd) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const relativeFilePaths = Array.from(this.filePaths).map((file) => toRelativePath(file, cwd))
|
||||||
this.providerRef.deref()?.postMessageToWebview({
|
this.providerRef.deref()?.postMessageToWebview({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: Array.from(this.filePaths).map((file) => {
|
filePaths: relativeFilePaths,
|
||||||
const relativePath = path.relative(cwd, file).toPosix()
|
openedTabs: this.getOpenedTabsInfo(),
|
||||||
return file.endsWith("/") ? relativePath + "/" : relativePath
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
this.updateTimer = null
|
this.updateTimer = null
|
||||||
}, 300) // Debounce for 300ms
|
}, 300) // Debounce for 300ms
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ const mockWatcher = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jest.mock("vscode", () => ({
|
jest.mock("vscode", () => ({
|
||||||
|
window: {
|
||||||
|
tabGroups: {
|
||||||
|
onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })),
|
||||||
|
all: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
workspaceFolders: [
|
workspaceFolders: [
|
||||||
{
|
{
|
||||||
@@ -61,6 +67,7 @@ describe("WorkspaceTracker", () => {
|
|||||||
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: expect.arrayContaining(["file1.ts", "file2.ts"]),
|
filePaths: expect.arrayContaining(["file1.ts", "file2.ts"]),
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
expect((mockProvider.postMessageToWebview as jest.Mock).mock.calls[0][0].filePaths).toHaveLength(2)
|
expect((mockProvider.postMessageToWebview as jest.Mock).mock.calls[0][0].filePaths).toHaveLength(2)
|
||||||
})
|
})
|
||||||
@@ -74,6 +81,7 @@ describe("WorkspaceTracker", () => {
|
|||||||
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: ["newfile.ts"],
|
filePaths: ["newfile.ts"],
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -92,6 +100,7 @@ describe("WorkspaceTracker", () => {
|
|||||||
expect(mockProvider.postMessageToWebview).toHaveBeenLastCalledWith({
|
expect(mockProvider.postMessageToWebview).toHaveBeenLastCalledWith({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -106,6 +115,7 @@ describe("WorkspaceTracker", () => {
|
|||||||
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: expect.arrayContaining(["newdir"]),
|
filePaths: expect.arrayContaining(["newdir"]),
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
const lastCall = (mockProvider.postMessageToWebview as jest.Mock).mock.calls.slice(-1)[0]
|
const lastCall = (mockProvider.postMessageToWebview as jest.Mock).mock.calls.slice(-1)[0]
|
||||||
expect(lastCall[0].filePaths).toHaveLength(1)
|
expect(lastCall[0].filePaths).toHaveLength(1)
|
||||||
@@ -126,6 +136,7 @@ describe("WorkspaceTracker", () => {
|
|||||||
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
|
||||||
type: "workspaceUpdated",
|
type: "workspaceUpdated",
|
||||||
filePaths: expect.arrayContaining(expectedFiles),
|
filePaths: expect.arrayContaining(expectedFiles),
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
expect(calls[0][0].filePaths).toHaveLength(1000)
|
expect(calls[0][0].filePaths).toHaveLength(1000)
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ export interface ExtensionMessage {
|
|||||||
lmStudioModels?: string[]
|
lmStudioModels?: string[]
|
||||||
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
|
||||||
filePaths?: string[]
|
filePaths?: string[]
|
||||||
|
openedTabs?: Array<{
|
||||||
|
label: string
|
||||||
|
isActive: boolean
|
||||||
|
path?: string
|
||||||
|
}>
|
||||||
partialMessage?: ClineMessage
|
partialMessage?: ClineMessage
|
||||||
glamaModels?: Record<string, ModelInfo>
|
glamaModels?: Record<string, ModelInfo>
|
||||||
openRouterModels?: Record<string, ModelInfo>
|
openRouterModels?: Record<string, ModelInfo>
|
||||||
@@ -94,6 +99,7 @@ export interface ExtensionState {
|
|||||||
alwaysApproveResubmit?: boolean
|
alwaysApproveResubmit?: boolean
|
||||||
alwaysAllowModeSwitch?: boolean
|
alwaysAllowModeSwitch?: boolean
|
||||||
requestDelaySeconds: number
|
requestDelaySeconds: number
|
||||||
|
rateLimitSeconds: number // Minimum time between successive requests (0 = disabled)
|
||||||
uriScheme?: string
|
uriScheme?: string
|
||||||
allowedCommands?: string[]
|
allowedCommands?: string[]
|
||||||
soundEnabled?: boolean
|
soundEnabled?: boolean
|
||||||
@@ -106,6 +112,7 @@ export interface ExtensionState {
|
|||||||
writeDelayMs: number
|
writeDelayMs: number
|
||||||
terminalOutputLineLimit?: number
|
terminalOutputLineLimit?: number
|
||||||
mcpEnabled: boolean
|
mcpEnabled: boolean
|
||||||
|
enableMcpServerCreation: boolean
|
||||||
mode: Mode
|
mode: Mode
|
||||||
modeApiConfigs?: Record<Mode, string>
|
modeApiConfigs?: Record<Mode, string>
|
||||||
enhancementApiConfigId?: string
|
enhancementApiConfigId?: string
|
||||||
|
|||||||
@@ -62,10 +62,12 @@ export interface WebviewMessage {
|
|||||||
| "deleteMessage"
|
| "deleteMessage"
|
||||||
| "terminalOutputLineLimit"
|
| "terminalOutputLineLimit"
|
||||||
| "mcpEnabled"
|
| "mcpEnabled"
|
||||||
|
| "enableMcpServerCreation"
|
||||||
| "searchCommits"
|
| "searchCommits"
|
||||||
| "refreshGlamaModels"
|
| "refreshGlamaModels"
|
||||||
| "alwaysApproveResubmit"
|
| "alwaysApproveResubmit"
|
||||||
| "requestDelaySeconds"
|
| "requestDelaySeconds"
|
||||||
|
| "rateLimitSeconds"
|
||||||
| "setApiConfigPassword"
|
| "setApiConfigPassword"
|
||||||
| "requestVsCodeLmModels"
|
| "requestVsCodeLmModels"
|
||||||
| "mode"
|
| "mode"
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export interface ModelInfo {
|
|||||||
cacheWritesPrice?: number
|
cacheWritesPrice?: number
|
||||||
cacheReadsPrice?: number
|
cacheReadsPrice?: number
|
||||||
description?: string
|
description?: string
|
||||||
|
reasoningEffort?: "low" | "medium" | "high"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anthropic
|
// Anthropic
|
||||||
@@ -510,6 +511,33 @@ export type OpenAiNativeModelId = keyof typeof openAiNativeModels
|
|||||||
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-4o"
|
export const openAiNativeDefaultModelId: OpenAiNativeModelId = "gpt-4o"
|
||||||
export const openAiNativeModels = {
|
export const openAiNativeModels = {
|
||||||
// don't support tool use yet
|
// don't support tool use yet
|
||||||
|
"o3-mini": {
|
||||||
|
maxTokens: 100_000,
|
||||||
|
contextWindow: 200_000,
|
||||||
|
supportsImages: false,
|
||||||
|
supportsPromptCache: false,
|
||||||
|
inputPrice: 1.1,
|
||||||
|
outputPrice: 4.4,
|
||||||
|
reasoningEffort: "medium",
|
||||||
|
},
|
||||||
|
"o3-mini-high": {
|
||||||
|
maxTokens: 100_000,
|
||||||
|
contextWindow: 200_000,
|
||||||
|
supportsImages: false,
|
||||||
|
supportsPromptCache: false,
|
||||||
|
inputPrice: 1.1,
|
||||||
|
outputPrice: 4.4,
|
||||||
|
reasoningEffort: "high",
|
||||||
|
},
|
||||||
|
"o3-mini-low": {
|
||||||
|
maxTokens: 100_000,
|
||||||
|
contextWindow: 200_000,
|
||||||
|
supportsImages: false,
|
||||||
|
supportsPromptCache: false,
|
||||||
|
inputPrice: 1.1,
|
||||||
|
outputPrice: 4.4,
|
||||||
|
reasoningEffort: "low",
|
||||||
|
},
|
||||||
o1: {
|
o1: {
|
||||||
maxTokens: 100_000,
|
maxTokens: 100_000,
|
||||||
contextWindow: 200_000,
|
contextWindow: 200_000,
|
||||||
@@ -531,8 +559,8 @@ export const openAiNativeModels = {
|
|||||||
contextWindow: 128_000,
|
contextWindow: 128_000,
|
||||||
supportsImages: true,
|
supportsImages: true,
|
||||||
supportsPromptCache: false,
|
supportsPromptCache: false,
|
||||||
inputPrice: 3,
|
inputPrice: 1.1,
|
||||||
outputPrice: 12,
|
outputPrice: 4.4,
|
||||||
},
|
},
|
||||||
"gpt-4o": {
|
"gpt-4o": {
|
||||||
maxTokens: 4_096,
|
maxTokens: 4_096,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface ApiMetrics {
|
|||||||
totalCacheWrites?: number
|
totalCacheWrites?: number
|
||||||
totalCacheReads?: number
|
totalCacheReads?: number
|
||||||
totalCost: number
|
totalCost: number
|
||||||
contextTokens: number // Total tokens in conversation (last message's tokensIn + tokensOut)
|
contextTokens: number // Total tokens in conversation (last message's tokensIn + tokensOut + cacheWrites + cacheReads)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +17,7 @@ interface ApiMetrics {
|
|||||||
* It extracts and sums up the tokensIn, tokensOut, cacheWrites, cacheReads, and cost from these messages.
|
* It extracts and sums up the tokensIn, tokensOut, cacheWrites, cacheReads, and cost from these messages.
|
||||||
*
|
*
|
||||||
* @param messages - An array of ClineMessage objects to process.
|
* @param messages - An array of ClineMessage objects to process.
|
||||||
* @returns An ApiMetrics object containing totalTokensIn, totalTokensOut, totalCacheWrites, totalCacheReads, and totalCost.
|
* @returns An ApiMetrics object containing totalTokensIn, totalTokensOut, totalCacheWrites, totalCacheReads, totalCost, and contextTokens.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const messages = [
|
* const messages = [
|
||||||
@@ -36,27 +36,30 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
|
|||||||
contextTokens: 0,
|
contextTokens: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the last api_req_started message that has valid token information
|
// Helper function to get total tokens from a message
|
||||||
const lastApiReq = [...messages].reverse().find((message) => {
|
const getTotalTokensFromMessage = (message: ClineMessage): number => {
|
||||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
if (!message.text) return 0
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(message.text)
|
const { tokensIn, tokensOut, cacheWrites, cacheReads } = JSON.parse(message.text)
|
||||||
return typeof parsedData.tokensIn === "number" && typeof parsedData.tokensOut === "number"
|
return (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the last api_req_started message that has any tokens
|
||||||
|
const lastApiReq = [...messages].reverse().find((message) => {
|
||||||
|
if (message.type === "say" && message.say === "api_req_started") {
|
||||||
|
return getTotalTokensFromMessage(message) > 0
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// Keep track of the last valid context tokens
|
// Calculate running totals
|
||||||
let lastValidContextTokens = 0
|
|
||||||
|
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
if (message.type === "say" && message.say === "api_req_started" && message.text) {
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(message.text)
|
const { tokensIn, tokensOut, cacheWrites, cacheReads, cost } = JSON.parse(message.text)
|
||||||
const { tokensIn, tokensOut, cacheWrites, cacheReads, cost } = parsedData
|
|
||||||
|
|
||||||
if (typeof tokensIn === "number") {
|
if (typeof tokensIn === "number") {
|
||||||
result.totalTokensIn += tokensIn
|
result.totalTokensIn += tokensIn
|
||||||
@@ -74,15 +77,9 @@ export function getApiMetrics(messages: ClineMessage[]): ApiMetrics {
|
|||||||
result.totalCost += cost
|
result.totalCost += cost
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last valid context tokens whenever we have valid input and output tokens
|
// If this is the last api request with tokens, use its total for context size
|
||||||
if (tokensIn > 0 && tokensOut > 0) {
|
|
||||||
lastValidContextTokens = tokensIn + tokensOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the last api request, use its tokens for context size
|
|
||||||
if (message === lastApiReq) {
|
if (message === lastApiReq) {
|
||||||
// Use the last valid context tokens if the current request doesn't have valid tokens
|
result.contextTokens = getTotalTokensFromMessage(message)
|
||||||
result.contextTokens = tokensIn > 0 && tokensOut > 0 ? tokensIn + tokensOut : lastValidContextTokens
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing JSON:", error)
|
console.error("Error parsing JSON:", error)
|
||||||
|
|||||||
@@ -1,121 +1,18 @@
|
|||||||
const assert = require("assert")
|
import * as assert from "assert"
|
||||||
const vscode = require("vscode")
|
import * as vscode from "vscode"
|
||||||
const path = require("path")
|
|
||||||
const fs = require("fs")
|
|
||||||
const dotenv = require("dotenv")
|
|
||||||
|
|
||||||
// Load test environment variables
|
suite("Roo Code Extension", () => {
|
||||||
const testEnvPath = path.join(__dirname, ".test_env")
|
test("OPENROUTER_API_KEY environment variable is set", () => {
|
||||||
dotenv.config({ path: testEnvPath })
|
if (!process.env.OPENROUTER_API_KEY) {
|
||||||
|
assert.fail("OPENROUTER_API_KEY environment variable is not set")
|
||||||
suite("Roo Code Extension Test Suite", () => {
|
|
||||||
vscode.window.showInformationMessage("Starting Roo Code extension tests.")
|
|
||||||
|
|
||||||
test("Extension should be present", () => {
|
|
||||||
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
|
|
||||||
assert.notStrictEqual(extension, undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Extension should activate", async () => {
|
|
||||||
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
|
|
||||||
if (!extension) {
|
|
||||||
assert.fail("Extension not found")
|
|
||||||
}
|
}
|
||||||
await extension.activate()
|
|
||||||
assert.strictEqual(extension.isActive, true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("OpenRouter API key and models should be configured correctly", function (done) {
|
|
||||||
// @ts-ignore
|
|
||||||
this.timeout(60000) // Increase timeout to 60s for network requests
|
|
||||||
;(async () => {
|
|
||||||
try {
|
|
||||||
// Get extension instance
|
|
||||||
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
|
|
||||||
if (!extension) {
|
|
||||||
done(new Error("Extension not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify API key is set and valid
|
|
||||||
const apiKey = process.env.OPEN_ROUTER_API_KEY
|
|
||||||
if (!apiKey) {
|
|
||||||
done(new Error("OPEN_ROUTER_API_KEY environment variable is not set"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!apiKey.startsWith("sk-or-v1-")) {
|
|
||||||
done(new Error("OpenRouter API key should have correct format"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate extension and get provider
|
|
||||||
const api = await extension.activate()
|
|
||||||
if (!api) {
|
|
||||||
done(new Error("Extension API not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the provider from the extension's exports
|
|
||||||
const provider = api.sidebarProvider
|
|
||||||
if (!provider) {
|
|
||||||
done(new Error("Provider not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up the API configuration
|
|
||||||
await provider.updateGlobalState("apiProvider", "openrouter")
|
|
||||||
await provider.storeSecret("openRouterApiKey", apiKey)
|
|
||||||
|
|
||||||
// Set up timeout to fail test if models don't load
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
done(new Error("Timeout waiting for models to load"))
|
|
||||||
}, 30000)
|
|
||||||
|
|
||||||
// Wait for models to be loaded
|
|
||||||
const checkModels = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const models = await provider.readOpenRouterModels()
|
|
||||||
if (!models) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clearInterval(checkModels)
|
|
||||||
clearTimeout(timeout)
|
|
||||||
|
|
||||||
// Verify expected Claude models are available
|
|
||||||
const expectedModels = [
|
|
||||||
"anthropic/claude-3.5-sonnet:beta",
|
|
||||||
"anthropic/claude-3-sonnet:beta",
|
|
||||||
"anthropic/claude-3.5-sonnet",
|
|
||||||
"anthropic/claude-3.5-sonnet-20240620",
|
|
||||||
"anthropic/claude-3.5-sonnet-20240620:beta",
|
|
||||||
"anthropic/claude-3.5-haiku:beta",
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const modelId of expectedModels) {
|
|
||||||
assert.strictEqual(modelId in models, true, `Model ${modelId} should be available`)
|
|
||||||
}
|
|
||||||
|
|
||||||
done()
|
|
||||||
} catch (error) {
|
|
||||||
clearInterval(checkModels)
|
|
||||||
clearTimeout(timeout)
|
|
||||||
done(error)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
// Trigger model loading
|
|
||||||
await provider.refreshOpenRouterModels()
|
|
||||||
} catch (error) {
|
|
||||||
done(error)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Commands should be registered", async () => {
|
test("Commands should be registered", async () => {
|
||||||
const commands = await vscode.commands.getCommands(true)
|
const timeout = 10 * 1_000
|
||||||
|
const interval = 1_000
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
// Test core commands are registered
|
|
||||||
const expectedCommands = [
|
const expectedCommands = [
|
||||||
"roo-cline.plusButtonClicked",
|
"roo-cline.plusButtonClicked",
|
||||||
"roo-cline.mcpButtonClicked",
|
"roo-cline.mcpButtonClicked",
|
||||||
@@ -128,204 +25,39 @@ suite("Roo Code Extension Test Suite", () => {
|
|||||||
"roo-cline.improveCode",
|
"roo-cline.improveCode",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
const commands = await vscode.commands.getCommands(true)
|
||||||
|
const missingCommands = []
|
||||||
|
|
||||||
for (const cmd of expectedCommands) {
|
for (const cmd of expectedCommands) {
|
||||||
assert.strictEqual(commands.includes(cmd), true, `Command ${cmd} should be registered`)
|
if (!commands.includes(cmd)) {
|
||||||
|
missingCommands.push(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingCommands.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = await vscode.commands.getCommands(true)
|
||||||
|
|
||||||
|
for (const cmd of expectedCommands) {
|
||||||
|
assert.ok(commands.includes(cmd), `Command ${cmd} should be registered`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Views should be registered", () => {
|
test("Webview panel can be created", () => {
|
||||||
const view = vscode.window.createWebviewPanel(
|
const view = vscode.window.createWebviewPanel(
|
||||||
"roo-cline.SidebarProvider",
|
"roo-cline.SidebarProvider",
|
||||||
"Roo Code",
|
"Roo Code",
|
||||||
vscode.ViewColumn.One,
|
vscode.ViewColumn.One,
|
||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
assert.notStrictEqual(view, undefined)
|
|
||||||
|
assert.ok(view, "Failed to create webview panel")
|
||||||
view.dispose()
|
view.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Should handle prompt and response correctly", async function () {
|
|
||||||
// @ts-ignore
|
|
||||||
this.timeout(60000) // Increase timeout for API request
|
|
||||||
|
|
||||||
const timeout = 30000
|
|
||||||
const interval = 1000
|
|
||||||
|
|
||||||
// Get extension instance
|
|
||||||
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
|
|
||||||
if (!extension) {
|
|
||||||
assert.fail("Extension not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Activate extension and get API
|
|
||||||
const api = await extension.activate()
|
|
||||||
if (!api) {
|
|
||||||
assert.fail("Extension API not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get provider
|
|
||||||
const provider = api.sidebarProvider
|
|
||||||
if (!provider) {
|
|
||||||
assert.fail("Provider not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up API configuration
|
|
||||||
await provider.updateGlobalState("apiProvider", "openrouter")
|
|
||||||
await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
|
|
||||||
const apiKey = process.env.OPEN_ROUTER_API_KEY
|
|
||||||
if (!apiKey) {
|
|
||||||
assert.fail("OPEN_ROUTER_API_KEY environment variable is not set")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await provider.storeSecret("openRouterApiKey", apiKey)
|
|
||||||
|
|
||||||
// Create webview panel with development options
|
|
||||||
const extensionUri = extension.extensionUri
|
|
||||||
const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
|
|
||||||
enableScripts: true,
|
|
||||||
enableCommandUris: true,
|
|
||||||
retainContextWhenHidden: true,
|
|
||||||
localResourceRoots: [extensionUri],
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialize webview with development context
|
|
||||||
panel.webview.options = {
|
|
||||||
enableScripts: true,
|
|
||||||
enableCommandUris: true,
|
|
||||||
localResourceRoots: [extensionUri],
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize provider with panel
|
|
||||||
provider.resolveWebviewView(panel)
|
|
||||||
|
|
||||||
// Set up message tracking
|
|
||||||
let webviewReady = false
|
|
||||||
let messagesReceived = false
|
|
||||||
const originalPostMessage = provider.postMessageToWebview.bind(provider)
|
|
||||||
// @ts-ignore
|
|
||||||
provider.postMessageToWebview = async (message) => {
|
|
||||||
if (message.type === "state") {
|
|
||||||
webviewReady = true
|
|
||||||
console.log("Webview state received:", message)
|
|
||||||
if (message.state?.clineMessages?.length > 0) {
|
|
||||||
messagesReceived = true
|
|
||||||
console.log("Messages in state:", message.state.clineMessages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await originalPostMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for webview to launch and receive initial state
|
|
||||||
let startTime = Date.now()
|
|
||||||
while (Date.now() - startTime < timeout) {
|
|
||||||
if (webviewReady) {
|
|
||||||
// Wait an additional second for webview to fully initialize
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!webviewReady) {
|
|
||||||
throw new Error("Timeout waiting for webview to be ready")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send webviewDidLaunch to initialize chat
|
|
||||||
await provider.postMessageToWebview({ type: "webviewDidLaunch" })
|
|
||||||
console.log("Sent webviewDidLaunch")
|
|
||||||
|
|
||||||
// Wait for webview to fully initialize
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// Restore original postMessage
|
|
||||||
provider.postMessageToWebview = originalPostMessage
|
|
||||||
|
|
||||||
// Wait for OpenRouter models to be fully loaded
|
|
||||||
startTime = Date.now()
|
|
||||||
while (Date.now() - startTime < timeout) {
|
|
||||||
const models = await provider.readOpenRouterModels()
|
|
||||||
if (models && Object.keys(models).length > 0) {
|
|
||||||
console.log("OpenRouter models loaded")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send prompt
|
|
||||||
const prompt = "Hello world, what is your name?"
|
|
||||||
console.log("Sending prompt:", prompt)
|
|
||||||
|
|
||||||
// Start task
|
|
||||||
try {
|
|
||||||
await api.startNewTask(prompt)
|
|
||||||
console.log("Task started")
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error starting task:", error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for task to appear in history with tokens
|
|
||||||
startTime = Date.now()
|
|
||||||
while (Date.now() - startTime < timeout) {
|
|
||||||
const state = await provider.getState()
|
|
||||||
const task = state.taskHistory?.[0]
|
|
||||||
if (task && task.tokensOut > 0) {
|
|
||||||
console.log("Task completed with tokens:", task)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for messages to be processed
|
|
||||||
startTime = Date.now()
|
|
||||||
let responseReceived = false
|
|
||||||
while (Date.now() - startTime < timeout) {
|
|
||||||
// Check provider.clineMessages
|
|
||||||
const messages = provider.clineMessages
|
|
||||||
if (messages && messages.length > 0) {
|
|
||||||
console.log("Provider messages:", JSON.stringify(messages, null, 2))
|
|
||||||
// @ts-ignore
|
|
||||||
const hasResponse = messages.some(
|
|
||||||
(m: { type: string; text: string }) =>
|
|
||||||
m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
|
|
||||||
)
|
|
||||||
if (hasResponse) {
|
|
||||||
console.log('Found response containing "Cline" in provider messages')
|
|
||||||
responseReceived = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check provider.cline.clineMessages
|
|
||||||
const clineMessages = provider.cline?.clineMessages
|
|
||||||
if (clineMessages && clineMessages.length > 0) {
|
|
||||||
console.log("Cline messages:", JSON.stringify(clineMessages, null, 2))
|
|
||||||
// @ts-ignore
|
|
||||||
const hasResponse = clineMessages.some(
|
|
||||||
(m: { type: string; text: string }) =>
|
|
||||||
m.type === "say" && m.text && m.text.toLowerCase().includes("cline"),
|
|
||||||
)
|
|
||||||
if (hasResponse) {
|
|
||||||
console.log('Found response containing "Cline" in cline messages')
|
|
||||||
responseReceived = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseReceived) {
|
|
||||||
console.log("Final provider state:", await provider.getState())
|
|
||||||
console.log("Final cline messages:", provider.cline?.clineMessages)
|
|
||||||
throw new Error('Did not receive expected response containing "Cline"')
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
panel.dispose()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
77
src/test/task.test.ts
Normal file
77
src/test/task.test.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import * as assert from "assert"
|
||||||
|
import * as vscode from "vscode"
|
||||||
|
|
||||||
|
import { ClineAPI } from "../exports/cline"
|
||||||
|
import { ClineProvider } from "../core/webview/ClineProvider"
|
||||||
|
|
||||||
|
suite("Roo Code Task", () => {
|
||||||
|
test("Should handle prompt and response correctly", async function () {
|
||||||
|
const timeout = 30000
|
||||||
|
const interval = 1000
|
||||||
|
|
||||||
|
const extension = vscode.extensions.getExtension("RooVeterinaryInc.roo-cline")
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
assert.fail("Extension not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const api: ClineAPI = await extension.activate()
|
||||||
|
const provider = api.sidebarProvider as ClineProvider
|
||||||
|
await provider.updateGlobalState("apiProvider", "openrouter")
|
||||||
|
await provider.updateGlobalState("openRouterModelId", "anthropic/claude-3.5-sonnet")
|
||||||
|
await provider.storeSecret("openRouterApiKey", process.env.OPENROUTER_API_KEY || "sk-or-v1-fake-api-key")
|
||||||
|
|
||||||
|
// Create webview panel with development options.
|
||||||
|
const panel = vscode.window.createWebviewPanel("roo-cline.SidebarProvider", "Roo Code", vscode.ViewColumn.One, {
|
||||||
|
enableScripts: true,
|
||||||
|
enableCommandUris: true,
|
||||||
|
retainContextWhenHidden: true,
|
||||||
|
localResourceRoots: [extension.extensionUri],
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize provider with panel.
|
||||||
|
provider.resolveWebviewView(panel)
|
||||||
|
|
||||||
|
// Wait for webview to launch.
|
||||||
|
let startTime = Date.now()
|
||||||
|
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
if (provider.viewLaunched) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
await api.startNewTask("Hello world, what is your name? Respond with 'My name is ...'")
|
||||||
|
|
||||||
|
// Wait for task to appear in history with tokens.
|
||||||
|
startTime = Date.now()
|
||||||
|
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
const state = await provider.getState()
|
||||||
|
const task = state.taskHistory?.[0]
|
||||||
|
|
||||||
|
if (task && task.tokensOut > 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.messages.length === 0) {
|
||||||
|
assert.fail("No messages received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("Provider messages:", JSON.stringify(provider.messages, null, 2))
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
provider.messages.some(({ type, text }) => type === "say" && text?.includes("My name is Roo")),
|
||||||
|
"Did not receive expected response containing 'My name is Roo'",
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
panel.dispose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ES2020",
|
|
||||||
"lib": ["ES2020"],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "../..",
|
|
||||||
"strict": false,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"noImplicitThis": false,
|
|
||||||
"alwaysStrict": false,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"baseUrl": "../..",
|
|
||||||
"paths": {
|
|
||||||
"*": ["*", "src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules", ".vscode-test"]
|
|
||||||
}
|
|
||||||
@@ -99,3 +99,8 @@ export function getReadablePath(cwd: string, relPath?: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toRelativePath = (filePath: string, cwd: string) => {
|
||||||
|
const relativePath = path.relative(cwd, filePath).toPosix()
|
||||||
|
return filePath.endsWith("/") ? relativePath + "/" : relativePath
|
||||||
|
}
|
||||||
|
|||||||
17
tsconfig.integration.json
Normal file
17
tsconfig.integration.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "ESNext.Disposable", "DOM"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"useUnknownInCatchVariables": false,
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "out-integration"
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts"],
|
||||||
|
"exclude": [".vscode-test", "benchmark", "dist", "**/node_modules/**", "out", "out-integration", "webview-ui"]
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@ export const ChatRowContent = ({
|
|||||||
}
|
}
|
||||||
}, [isLast, message.say])
|
}, [isLast, message.say])
|
||||||
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
|
||||||
if (message.text && message.say === "api_req_started") {
|
if (message.text != null && message.say === "api_req_started") {
|
||||||
const info: ClineApiReqInfo = JSON.parse(message.text)
|
const info: ClineApiReqInfo = JSON.parse(message.text)
|
||||||
return [info.cost, info.cancelReason, info.streamingFailedMessage]
|
return [info.cost, info.cancelReason, info.streamingFailedMessage]
|
||||||
}
|
}
|
||||||
@@ -183,28 +183,26 @@ export const ChatRowContent = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
return [
|
return [
|
||||||
apiReqCancelReason ? (
|
apiReqCancelReason != null ? (
|
||||||
apiReqCancelReason === "user_cancelled" ? (
|
apiReqCancelReason === "user_cancelled" ? (
|
||||||
getIconSpan("error", cancelledColor)
|
getIconSpan("error", cancelledColor)
|
||||||
) : (
|
) : (
|
||||||
getIconSpan("error", errorColor)
|
getIconSpan("error", errorColor)
|
||||||
)
|
)
|
||||||
) : cost ? (
|
) : cost != null ? (
|
||||||
getIconSpan("check", successColor)
|
getIconSpan("check", successColor)
|
||||||
) : apiRequestFailedMessage ? (
|
) : apiRequestFailedMessage ? (
|
||||||
getIconSpan("error", errorColor)
|
getIconSpan("error", errorColor)
|
||||||
) : (
|
) : (
|
||||||
<ProgressIndicator />
|
<ProgressIndicator />
|
||||||
),
|
),
|
||||||
apiReqCancelReason ? (
|
apiReqCancelReason != null ? (
|
||||||
apiReqCancelReason === "user_cancelled" ? (
|
apiReqCancelReason === "user_cancelled" ? (
|
||||||
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Cancelled</span>
|
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Cancelled</span>
|
||||||
) : (
|
) : (
|
||||||
<span style={{ color: errorColor, fontWeight: "bold" }}>
|
<span style={{ color: errorColor, fontWeight: "bold" }}>API Streaming Failed</span>
|
||||||
API Streaming Failed ({JSON.stringify(apiReqCancelReason)})
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
) : cost ? (
|
) : cost != null ? (
|
||||||
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request</span>
|
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request</span>
|
||||||
) : apiRequestFailedMessage ? (
|
) : apiRequestFailedMessage ? (
|
||||||
<span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span>
|
<span style={{ color: errorColor, fontWeight: "bold" }}>API Request Failed</span>
|
||||||
@@ -512,7 +510,9 @@ export const ChatRowContent = ({
|
|||||||
style={{
|
style={{
|
||||||
...headerStyle,
|
...headerStyle,
|
||||||
marginBottom:
|
marginBottom:
|
||||||
(!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage ? 10 : 0,
|
(cost == null && apiRequestFailedMessage) || apiReqStreamingFailedMessage
|
||||||
|
? 10
|
||||||
|
: 0,
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
@@ -524,13 +524,13 @@ export const ChatRowContent = ({
|
|||||||
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
|
||||||
{icon}
|
{icon}
|
||||||
{title}
|
{title}
|
||||||
<VSCodeBadge style={{ opacity: cost ? 1 : 0 }}>
|
<VSCodeBadge style={{ opacity: cost != null && cost > 0 ? 1 : 0 }}>
|
||||||
${Number(cost || 0)?.toFixed(4)}
|
${Number(cost || 0)?.toFixed(4)}
|
||||||
</VSCodeBadge>
|
</VSCodeBadge>
|
||||||
</div>
|
</div>
|
||||||
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
|
||||||
</div>
|
</div>
|
||||||
{((!cost && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
|
{((cost == null && apiRequestFailedMessage) || apiReqStreamingFailedMessage) && (
|
||||||
<>
|
<>
|
||||||
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
|
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
|
||||||
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
|
{apiRequestFailedMessage || apiReqStreamingFailedMessage}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { filePaths, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState()
|
const { filePaths, openedTabs, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState()
|
||||||
const [gitCommits, setGitCommits] = useState<any[]>([])
|
const [gitCommits, setGitCommits] = useState<any[]>([])
|
||||||
const [showDropdown, setShowDropdown] = useState(false)
|
const [showDropdown, setShowDropdown] = useState(false)
|
||||||
|
|
||||||
@@ -138,14 +138,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||||||
return [
|
return [
|
||||||
{ type: ContextMenuOptionType.Problems, value: "problems" },
|
{ type: ContextMenuOptionType.Problems, value: "problems" },
|
||||||
...gitCommits,
|
...gitCommits,
|
||||||
|
...openedTabs
|
||||||
|
.filter((tab) => tab.path)
|
||||||
|
.map((tab) => ({
|
||||||
|
type: ContextMenuOptionType.OpenedFile,
|
||||||
|
value: "/" + tab.path,
|
||||||
|
})),
|
||||||
...filePaths
|
...filePaths
|
||||||
.map((file) => "/" + file)
|
.map((file) => "/" + file)
|
||||||
|
.filter((path) => !openedTabs.some((tab) => tab.path && "/" + tab.path === path)) // Filter out paths that are already in openedTabs
|
||||||
.map((path) => ({
|
.map((path) => ({
|
||||||
type: path.endsWith("/") ? ContextMenuOptionType.Folder : ContextMenuOptionType.File,
|
type: path.endsWith("/") ? ContextMenuOptionType.Folder : ContextMenuOptionType.File,
|
||||||
value: path,
|
value: path,
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
}, [filePaths, gitCommits])
|
}, [filePaths, gitCommits, openedTabs])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
const lastApiReqStarted = findLast(modifiedMessages, (message) => message.say === "api_req_started")
|
const lastApiReqStarted = findLast(modifiedMessages, (message) => message.say === "api_req_started")
|
||||||
if (lastApiReqStarted && lastApiReqStarted.text && lastApiReqStarted.say === "api_req_started") {
|
if (lastApiReqStarted && lastApiReqStarted.text != null && lastApiReqStarted.say === "api_req_started") {
|
||||||
const cost = JSON.parse(lastApiReqStarted.text).cost
|
const cost = JSON.parse(lastApiReqStarted.text).cost
|
||||||
if (cost === undefined) {
|
if (cost === undefined) {
|
||||||
// api request has not finished yet
|
// api request has not finished yet
|
||||||
@@ -718,9 +718,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
if (message.say === "api_req_started") {
|
if (message.say === "api_req_started") {
|
||||||
// get last api_req_started in currentGroup to check if it's cancelled. If it is then this api req is not part of the current browser session
|
// get last api_req_started in currentGroup to check if it's cancelled. If it is then this api req is not part of the current browser session
|
||||||
const lastApiReqStarted = [...currentGroup].reverse().find((m) => m.say === "api_req_started")
|
const lastApiReqStarted = [...currentGroup].reverse().find((m) => m.say === "api_req_started")
|
||||||
if (lastApiReqStarted?.text) {
|
if (lastApiReqStarted?.text != null) {
|
||||||
const info = JSON.parse(lastApiReqStarted.text)
|
const info = JSON.parse(lastApiReqStarted.text)
|
||||||
const isCancelled = info.cancelReason !== null
|
const isCancelled = info.cancelReason != null
|
||||||
if (isCancelled) {
|
if (isCancelled) {
|
||||||
endBrowserSession()
|
endBrowserSession()
|
||||||
result.push(message)
|
result.push(message)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||||||
return <span>Git Commits</span>
|
return <span>Git Commits</span>
|
||||||
}
|
}
|
||||||
case ContextMenuOptionType.File:
|
case ContextMenuOptionType.File:
|
||||||
|
case ContextMenuOptionType.OpenedFile:
|
||||||
case ContextMenuOptionType.Folder:
|
case ContextMenuOptionType.Folder:
|
||||||
if (option.value) {
|
if (option.value) {
|
||||||
return (
|
return (
|
||||||
@@ -100,6 +101,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||||||
|
|
||||||
const getIconForOption = (option: ContextMenuQueryItem): string => {
|
const getIconForOption = (option: ContextMenuQueryItem): string => {
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
|
case ContextMenuOptionType.OpenedFile:
|
||||||
|
return "window"
|
||||||
case ContextMenuOptionType.File:
|
case ContextMenuOptionType.File:
|
||||||
return "file"
|
return "file"
|
||||||
case ContextMenuOptionType.Folder:
|
case ContextMenuOptionType.Folder:
|
||||||
@@ -194,6 +197,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
|||||||
{(option.type === ContextMenuOptionType.Problems ||
|
{(option.type === ContextMenuOptionType.Problems ||
|
||||||
((option.type === ContextMenuOptionType.File ||
|
((option.type === ContextMenuOptionType.File ||
|
||||||
option.type === ContextMenuOptionType.Folder ||
|
option.type === ContextMenuOptionType.Folder ||
|
||||||
|
option.type === ContextMenuOptionType.OpenedFile ||
|
||||||
option.type === ContextMenuOptionType.Git) &&
|
option.type === ContextMenuOptionType.Git) &&
|
||||||
option.value)) && (
|
option.value)) && (
|
||||||
<i
|
<i
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
import { render, fireEvent, screen } from "@testing-library/react"
|
|
||||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
|
||||||
import AutoApproveMenu from "../AutoApproveMenu"
|
|
||||||
import { defaultModeSlug, defaultPrompts } from "../../../../../src/shared/modes"
|
|
||||||
import { experimentDefault } from "../../../../../src/shared/experiments"
|
|
||||||
|
|
||||||
// Mock the ExtensionStateContext hook
|
|
||||||
jest.mock("../../../context/ExtensionStateContext")
|
|
||||||
|
|
||||||
const mockUseExtensionState = useExtensionState as jest.MockedFunction<typeof useExtensionState>
|
|
||||||
|
|
||||||
describe("AutoApproveMenu", () => {
|
|
||||||
const defaultMockState = {
|
|
||||||
// Required state properties
|
|
||||||
version: "1.0.0",
|
|
||||||
clineMessages: [],
|
|
||||||
taskHistory: [],
|
|
||||||
shouldShowAnnouncement: false,
|
|
||||||
allowedCommands: [],
|
|
||||||
soundEnabled: false,
|
|
||||||
soundVolume: 0.5,
|
|
||||||
diffEnabled: false,
|
|
||||||
fuzzyMatchThreshold: 1.0,
|
|
||||||
preferredLanguage: "English",
|
|
||||||
writeDelayMs: 1000,
|
|
||||||
browserViewportSize: "900x600",
|
|
||||||
screenshotQuality: 75,
|
|
||||||
terminalOutputLineLimit: 500,
|
|
||||||
mcpEnabled: true,
|
|
||||||
requestDelaySeconds: 5,
|
|
||||||
currentApiConfigName: "default",
|
|
||||||
listApiConfigMeta: [],
|
|
||||||
mode: defaultModeSlug,
|
|
||||||
customModePrompts: defaultPrompts,
|
|
||||||
customSupportPrompts: {},
|
|
||||||
enhancementApiConfigId: "",
|
|
||||||
didHydrateState: true,
|
|
||||||
showWelcome: false,
|
|
||||||
theme: {},
|
|
||||||
glamaModels: {},
|
|
||||||
openRouterModels: {},
|
|
||||||
openAiModels: [],
|
|
||||||
mcpServers: [],
|
|
||||||
filePaths: [],
|
|
||||||
experiments: experimentDefault,
|
|
||||||
customModes: [],
|
|
||||||
|
|
||||||
// Auto-approve specific properties
|
|
||||||
alwaysAllowReadOnly: false,
|
|
||||||
alwaysAllowWrite: false,
|
|
||||||
alwaysAllowExecute: false,
|
|
||||||
alwaysAllowBrowser: false,
|
|
||||||
alwaysAllowMcp: false,
|
|
||||||
alwaysApproveResubmit: false,
|
|
||||||
alwaysAllowModeSwitch: false,
|
|
||||||
autoApprovalEnabled: false,
|
|
||||||
|
|
||||||
// Required setter functions
|
|
||||||
setApiConfiguration: jest.fn(),
|
|
||||||
setCustomInstructions: jest.fn(),
|
|
||||||
setAlwaysAllowReadOnly: jest.fn(),
|
|
||||||
setAlwaysAllowWrite: jest.fn(),
|
|
||||||
setAlwaysAllowExecute: jest.fn(),
|
|
||||||
setAlwaysAllowBrowser: jest.fn(),
|
|
||||||
setAlwaysAllowMcp: jest.fn(),
|
|
||||||
setAlwaysAllowModeSwitch: jest.fn(),
|
|
||||||
setShowAnnouncement: jest.fn(),
|
|
||||||
setAllowedCommands: jest.fn(),
|
|
||||||
setSoundEnabled: jest.fn(),
|
|
||||||
setSoundVolume: jest.fn(),
|
|
||||||
setDiffEnabled: jest.fn(),
|
|
||||||
setBrowserViewportSize: jest.fn(),
|
|
||||||
setFuzzyMatchThreshold: jest.fn(),
|
|
||||||
setPreferredLanguage: jest.fn(),
|
|
||||||
setWriteDelayMs: jest.fn(),
|
|
||||||
setScreenshotQuality: jest.fn(),
|
|
||||||
setTerminalOutputLineLimit: jest.fn(),
|
|
||||||
setMcpEnabled: jest.fn(),
|
|
||||||
setAlwaysApproveResubmit: jest.fn(),
|
|
||||||
setRequestDelaySeconds: jest.fn(),
|
|
||||||
setCurrentApiConfigName: jest.fn(),
|
|
||||||
setListApiConfigMeta: jest.fn(),
|
|
||||||
onUpdateApiConfig: jest.fn(),
|
|
||||||
setMode: jest.fn(),
|
|
||||||
setCustomModePrompts: jest.fn(),
|
|
||||||
setCustomSupportPrompts: jest.fn(),
|
|
||||||
setEnhancementApiConfigId: jest.fn(),
|
|
||||||
setAutoApprovalEnabled: jest.fn(),
|
|
||||||
setExperimentEnabled: jest.fn(),
|
|
||||||
handleInputChange: jest.fn(),
|
|
||||||
setCustomModes: jest.fn(),
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockUseExtensionState.mockReturnValue(defaultMockState)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("renders with initial collapsed state", () => {
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
// Check for main checkbox and label
|
|
||||||
expect(screen.getByText("Auto-approve:")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("None")).toBeInTheDocument()
|
|
||||||
|
|
||||||
// Verify the menu is collapsed (actions not visible)
|
|
||||||
expect(screen.queryByText("Read files and directories")).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("expands menu when clicked", () => {
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
// Click to expand
|
|
||||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
|
||||||
|
|
||||||
// Verify menu items are visible
|
|
||||||
expect(screen.getByText("Read files and directories")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Edit files")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Execute approved commands")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Use the browser")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Use MCP servers")).toBeInTheDocument()
|
|
||||||
expect(screen.getByText("Retry failed requests")).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("toggles main auto-approval checkbox", () => {
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
const mainCheckbox = screen.getByRole("checkbox")
|
|
||||||
fireEvent.click(mainCheckbox)
|
|
||||||
|
|
||||||
expect(defaultMockState.setAutoApprovalEnabled).toHaveBeenCalledWith(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("toggles individual permissions", () => {
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
// Expand menu
|
|
||||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
|
||||||
|
|
||||||
// Click read files checkbox
|
|
||||||
fireEvent.click(screen.getByText("Read files and directories"))
|
|
||||||
expect(defaultMockState.setAlwaysAllowReadOnly).toHaveBeenCalledWith(true)
|
|
||||||
|
|
||||||
// Click edit files checkbox
|
|
||||||
fireEvent.click(screen.getByText("Edit files"))
|
|
||||||
expect(defaultMockState.setAlwaysAllowWrite).toHaveBeenCalledWith(true)
|
|
||||||
|
|
||||||
// Click execute commands checkbox
|
|
||||||
fireEvent.click(screen.getByText("Execute approved commands"))
|
|
||||||
expect(defaultMockState.setAlwaysAllowExecute).toHaveBeenCalledWith(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("displays enabled actions in summary", () => {
|
|
||||||
mockUseExtensionState.mockReturnValue({
|
|
||||||
...defaultMockState,
|
|
||||||
alwaysAllowReadOnly: true,
|
|
||||||
alwaysAllowWrite: true,
|
|
||||||
autoApprovalEnabled: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
// Check that enabled actions are shown in summary
|
|
||||||
expect(screen.getByText("Read, Edit")).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("preserves checkbox states", () => {
|
|
||||||
// Mock state with some permissions enabled
|
|
||||||
const mockState = {
|
|
||||||
...defaultMockState,
|
|
||||||
alwaysAllowReadOnly: true,
|
|
||||||
alwaysAllowWrite: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mock to return our state
|
|
||||||
mockUseExtensionState.mockReturnValue(mockState)
|
|
||||||
|
|
||||||
render(<AutoApproveMenu />)
|
|
||||||
|
|
||||||
// Expand menu
|
|
||||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
|
||||||
|
|
||||||
// Verify read and edit checkboxes are checked
|
|
||||||
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
|
|
||||||
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
|
|
||||||
|
|
||||||
// Verify the setters haven't been called yet
|
|
||||||
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
|
|
||||||
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
|
|
||||||
|
|
||||||
// Collapse menu
|
|
||||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
|
||||||
|
|
||||||
// Expand again
|
|
||||||
fireEvent.click(screen.getByText("Auto-approve:"))
|
|
||||||
|
|
||||||
// Verify checkboxes are still present
|
|
||||||
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
|
|
||||||
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
|
|
||||||
|
|
||||||
// Verify the setters still haven't been called
|
|
||||||
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
|
|
||||||
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -41,6 +41,7 @@ describe("ChatTextArea", () => {
|
|||||||
// Default mock implementation for useExtensionState
|
// Default mock implementation for useExtensionState
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
apiConfiguration: {
|
apiConfiguration: {
|
||||||
apiProvider: "anthropic",
|
apiProvider: "anthropic",
|
||||||
},
|
},
|
||||||
@@ -51,6 +52,7 @@ describe("ChatTextArea", () => {
|
|||||||
it("should be disabled when textAreaDisabled is true", () => {
|
it("should be disabled when textAreaDisabled is true", () => {
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<ChatTextArea {...defaultProps} textAreaDisabled={true} />)
|
render(<ChatTextArea {...defaultProps} textAreaDisabled={true} />)
|
||||||
@@ -68,6 +70,7 @@ describe("ChatTextArea", () => {
|
|||||||
|
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -85,6 +88,7 @@ describe("ChatTextArea", () => {
|
|||||||
it("should not send message when input is empty", () => {
|
it("should not send message when input is empty", () => {
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
apiConfiguration: {
|
apiConfiguration: {
|
||||||
apiProvider: "openrouter",
|
apiProvider: "openrouter",
|
||||||
},
|
},
|
||||||
@@ -101,6 +105,7 @@ describe("ChatTextArea", () => {
|
|||||||
it("should show loading state while enhancing", () => {
|
it("should show loading state while enhancing", () => {
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
apiConfiguration: {
|
apiConfiguration: {
|
||||||
apiProvider: "openrouter",
|
apiProvider: "openrouter",
|
||||||
},
|
},
|
||||||
@@ -123,6 +128,7 @@ describe("ChatTextArea", () => {
|
|||||||
// Update apiConfiguration
|
// Update apiConfiguration
|
||||||
;(useExtensionState as jest.Mock).mockReturnValue({
|
;(useExtensionState as jest.Mock).mockReturnValue({
|
||||||
filePaths: [],
|
filePaths: [],
|
||||||
|
openedTabs: [],
|
||||||
apiConfiguration: {
|
apiConfiguration: {
|
||||||
apiProvider: "openrouter",
|
apiProvider: "openrouter",
|
||||||
newSetting: "test",
|
newSetting: "test",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
VSCodeButton,
|
VSCodeButton,
|
||||||
|
VSCodeCheckbox,
|
||||||
VSCodeLink,
|
VSCodeLink,
|
||||||
VSCodePanels,
|
VSCodePanels,
|
||||||
VSCodePanelTab,
|
VSCodePanelTab,
|
||||||
@@ -18,7 +19,13 @@ type McpViewProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const McpView = ({ onDone }: McpViewProps) => {
|
const McpView = ({ onDone }: McpViewProps) => {
|
||||||
const { mcpServers: servers, alwaysAllowMcp, mcpEnabled } = useExtensionState()
|
const {
|
||||||
|
mcpServers: servers,
|
||||||
|
alwaysAllowMcp,
|
||||||
|
mcpEnabled,
|
||||||
|
enableMcpServerCreation,
|
||||||
|
setEnableMcpServerCreation,
|
||||||
|
} = useExtensionState()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -67,6 +74,27 @@ const McpView = ({ onDone }: McpViewProps) => {
|
|||||||
|
|
||||||
{mcpEnabled && (
|
{mcpEnabled && (
|
||||||
<>
|
<>
|
||||||
|
<div style={{ marginBottom: 15 }}>
|
||||||
|
<VSCodeCheckbox
|
||||||
|
checked={enableMcpServerCreation}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setEnableMcpServerCreation(e.target.checked)
|
||||||
|
vscode.postMessage({ type: "enableMcpServerCreation", bool: e.target.checked })
|
||||||
|
}}>
|
||||||
|
<span style={{ fontWeight: "500" }}>Enable MCP Server Creation</span>
|
||||||
|
</VSCodeCheckbox>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: "12px",
|
||||||
|
marginTop: "5px",
|
||||||
|
color: "var(--vscode-descriptionForeground)",
|
||||||
|
}}>
|
||||||
|
When enabled, Roo can help you create new MCP servers via commands like "add a new tool
|
||||||
|
to...". If you don't need to create MCP servers you can disable this to reduce Roo's
|
||||||
|
token usage.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Server List */}
|
{/* Server List */}
|
||||||
{servers.length > 0 && (
|
{servers.length > 0 && (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ const ApiConfigManager = ({
|
|||||||
if (editState === "new") {
|
if (editState === "new") {
|
||||||
onUpsertConfig(trimmedValue)
|
onUpsertConfig(trimmedValue)
|
||||||
} else if (editState === "rename" && currentApiConfigName) {
|
} else if (editState === "rename" && currentApiConfigName) {
|
||||||
|
if (currentApiConfigName === trimmedValue) {
|
||||||
|
setEditState(null)
|
||||||
|
setInputValue("")
|
||||||
|
return
|
||||||
|
}
|
||||||
onRenameConfig(currentApiConfigName, trimmedValue)
|
onRenameConfig(currentApiConfigName, trimmedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
setAlwaysApproveResubmit,
|
setAlwaysApproveResubmit,
|
||||||
requestDelaySeconds,
|
requestDelaySeconds,
|
||||||
setRequestDelaySeconds,
|
setRequestDelaySeconds,
|
||||||
|
rateLimitSeconds,
|
||||||
|
setRateLimitSeconds,
|
||||||
currentApiConfigName,
|
currentApiConfigName,
|
||||||
listApiConfigMeta,
|
listApiConfigMeta,
|
||||||
experiments,
|
experiments,
|
||||||
@@ -92,6 +94,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
|
vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
|
||||||
vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
|
vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
|
||||||
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
|
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
|
||||||
|
vscode.postMessage({ type: "rateLimitSeconds", value: rateLimitSeconds })
|
||||||
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
|
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
type: "upsertApiConfiguration",
|
type: "upsertApiConfiguration",
|
||||||
@@ -572,6 +575,26 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
|
|
||||||
<div style={{ marginBottom: 40 }}>
|
<div style={{ marginBottom: 40 }}>
|
||||||
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Advanced Settings</h3>
|
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 15px 0" }}>Advanced Settings</h3>
|
||||||
|
<div style={{ marginBottom: 15 }}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
||||||
|
<span style={{ fontWeight: "500" }}>Rate limit</span>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="60"
|
||||||
|
step="1"
|
||||||
|
value={rateLimitSeconds}
|
||||||
|
onChange={(e) => setRateLimitSeconds(parseInt(e.target.value))}
|
||||||
|
style={{ ...sliderStyle }}
|
||||||
|
/>
|
||||||
|
<span style={{ ...sliderLabelStyle }}>{rateLimitSeconds}s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
||||||
|
Minimum time between API requests.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div style={{ marginBottom: 15 }}>
|
<div style={{ marginBottom: 15 }}>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
||||||
<span style={{ fontWeight: "500" }}>Terminal output limit</span>
|
<span style={{ fontWeight: "500" }}>Terminal output limit</span>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const WelcomeView = () => {
|
|||||||
|
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
const disableLetsGoButton = !!apiErrorMessage
|
const disableLetsGoButton = apiErrorMessage != null
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||||||
openAiModels: string[]
|
openAiModels: string[]
|
||||||
mcpServers: McpServer[]
|
mcpServers: McpServer[]
|
||||||
filePaths: string[]
|
filePaths: string[]
|
||||||
|
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
|
||||||
setApiConfiguration: (config: ApiConfiguration) => void
|
setApiConfiguration: (config: ApiConfiguration) => void
|
||||||
setCustomInstructions: (value?: string) => void
|
setCustomInstructions: (value?: string) => void
|
||||||
setAlwaysAllowReadOnly: (value: boolean) => void
|
setAlwaysAllowReadOnly: (value: boolean) => void
|
||||||
@@ -51,10 +52,14 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||||||
setTerminalOutputLineLimit: (value: number) => void
|
setTerminalOutputLineLimit: (value: number) => void
|
||||||
mcpEnabled: boolean
|
mcpEnabled: boolean
|
||||||
setMcpEnabled: (value: boolean) => void
|
setMcpEnabled: (value: boolean) => void
|
||||||
|
enableMcpServerCreation: boolean
|
||||||
|
setEnableMcpServerCreation: (value: boolean) => void
|
||||||
alwaysApproveResubmit?: boolean
|
alwaysApproveResubmit?: boolean
|
||||||
setAlwaysApproveResubmit: (value: boolean) => void
|
setAlwaysApproveResubmit: (value: boolean) => void
|
||||||
requestDelaySeconds: number
|
requestDelaySeconds: number
|
||||||
setRequestDelaySeconds: (value: number) => void
|
setRequestDelaySeconds: (value: number) => void
|
||||||
|
rateLimitSeconds: number
|
||||||
|
setRateLimitSeconds: (value: number) => void
|
||||||
setCurrentApiConfigName: (value: string) => void
|
setCurrentApiConfigName: (value: string) => void
|
||||||
setListApiConfigMeta: (value: ApiConfigMeta[]) => void
|
setListApiConfigMeta: (value: ApiConfigMeta[]) => void
|
||||||
onUpdateApiConfig: (apiConfig: ApiConfiguration) => void
|
onUpdateApiConfig: (apiConfig: ApiConfiguration) => void
|
||||||
@@ -90,8 +95,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
screenshotQuality: 75,
|
screenshotQuality: 75,
|
||||||
terminalOutputLineLimit: 500,
|
terminalOutputLineLimit: 500,
|
||||||
mcpEnabled: true,
|
mcpEnabled: true,
|
||||||
|
enableMcpServerCreation: true,
|
||||||
alwaysApproveResubmit: false,
|
alwaysApproveResubmit: false,
|
||||||
requestDelaySeconds: 5,
|
requestDelaySeconds: 5,
|
||||||
|
rateLimitSeconds: 0, // Minimum time between successive requests (0 = disabled)
|
||||||
currentApiConfigName: "default",
|
currentApiConfigName: "default",
|
||||||
listApiConfigMeta: [],
|
listApiConfigMeta: [],
|
||||||
mode: defaultModeSlug,
|
mode: defaultModeSlug,
|
||||||
@@ -110,6 +117,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
const [glamaModels, setGlamaModels] = useState<Record<string, ModelInfo>>({
|
const [glamaModels, setGlamaModels] = useState<Record<string, ModelInfo>>({
|
||||||
[glamaDefaultModelId]: glamaDefaultModelInfo,
|
[glamaDefaultModelId]: glamaDefaultModelInfo,
|
||||||
})
|
})
|
||||||
|
const [openedTabs, setOpenedTabs] = useState<Array<{ label: string; isActive: boolean; path?: string }>>([])
|
||||||
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
|
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
|
||||||
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
|
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
|
||||||
})
|
})
|
||||||
@@ -170,7 +178,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "workspaceUpdated": {
|
case "workspaceUpdated": {
|
||||||
setFilePaths(message.filePaths ?? [])
|
const paths = message.filePaths ?? []
|
||||||
|
const tabs = message.openedTabs ?? []
|
||||||
|
|
||||||
|
setFilePaths(paths)
|
||||||
|
setOpenedTabs(tabs)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "partialMessage": {
|
case "partialMessage": {
|
||||||
@@ -237,6 +249,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
openAiModels,
|
openAiModels,
|
||||||
mcpServers,
|
mcpServers,
|
||||||
filePaths,
|
filePaths,
|
||||||
|
openedTabs,
|
||||||
soundVolume: state.soundVolume,
|
soundVolume: state.soundVolume,
|
||||||
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
||||||
writeDelayMs: state.writeDelayMs,
|
writeDelayMs: state.writeDelayMs,
|
||||||
@@ -269,8 +282,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
setTerminalOutputLineLimit: (value) =>
|
setTerminalOutputLineLimit: (value) =>
|
||||||
setState((prevState) => ({ ...prevState, terminalOutputLineLimit: value })),
|
setState((prevState) => ({ ...prevState, terminalOutputLineLimit: value })),
|
||||||
setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })),
|
setMcpEnabled: (value) => setState((prevState) => ({ ...prevState, mcpEnabled: value })),
|
||||||
|
setEnableMcpServerCreation: (value) =>
|
||||||
|
setState((prevState) => ({ ...prevState, enableMcpServerCreation: value })),
|
||||||
setAlwaysApproveResubmit: (value) => setState((prevState) => ({ ...prevState, alwaysApproveResubmit: value })),
|
setAlwaysApproveResubmit: (value) => setState((prevState) => ({ ...prevState, alwaysApproveResubmit: value })),
|
||||||
setRequestDelaySeconds: (value) => setState((prevState) => ({ ...prevState, requestDelaySeconds: value })),
|
setRequestDelaySeconds: (value) => setState((prevState) => ({ ...prevState, requestDelaySeconds: value })),
|
||||||
|
setRateLimitSeconds: (value) => setState((prevState) => ({ ...prevState, rateLimitSeconds: value })),
|
||||||
setCurrentApiConfigName: (value) => setState((prevState) => ({ ...prevState, currentApiConfigName: value })),
|
setCurrentApiConfigName: (value) => setState((prevState) => ({ ...prevState, currentApiConfigName: value })),
|
||||||
setListApiConfigMeta,
|
setListApiConfigMeta,
|
||||||
onUpdateApiConfig,
|
onUpdateApiConfig,
|
||||||
|
|||||||
@@ -264,3 +264,11 @@ vscode-dropdown::part(listbox) {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--vscode-badge-foreground) 30%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vscrui Overrides / Hacks
|
||||||
|
*/
|
||||||
|
|
||||||
|
.vscrui-checkbox__listbox > ul {
|
||||||
|
max-height: unset !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export function removeMention(text: string, position: number): { newText: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ContextMenuOptionType {
|
export enum ContextMenuOptionType {
|
||||||
|
OpenedFile = "openedFile",
|
||||||
File = "file",
|
File = "file",
|
||||||
Folder = "folder",
|
Folder = "folder",
|
||||||
Problems = "problems",
|
Problems = "problems",
|
||||||
@@ -80,8 +81,14 @@ export function getContextMenuOptions(
|
|||||||
if (query === "") {
|
if (query === "") {
|
||||||
if (selectedType === ContextMenuOptionType.File) {
|
if (selectedType === ContextMenuOptionType.File) {
|
||||||
const files = queryItems
|
const files = queryItems
|
||||||
.filter((item) => item.type === ContextMenuOptionType.File)
|
.filter(
|
||||||
.map((item) => ({ type: ContextMenuOptionType.File, value: item.value }))
|
(item) =>
|
||||||
|
item.type === ContextMenuOptionType.File || item.type === ContextMenuOptionType.OpenedFile,
|
||||||
|
)
|
||||||
|
.map((item) => ({
|
||||||
|
type: item.type,
|
||||||
|
value: item.value,
|
||||||
|
}))
|
||||||
return files.length > 0 ? files : [{ type: ContextMenuOptionType.NoResults }]
|
return files.length > 0 ? files : [{ type: ContextMenuOptionType.NoResults }]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +169,16 @@ export function getContextMenuOptions(
|
|||||||
|
|
||||||
// Separate matches by type
|
// Separate matches by type
|
||||||
const fileMatches = matchingItems.filter(
|
const fileMatches = matchingItems.filter(
|
||||||
(item) => item.type === ContextMenuOptionType.File || item.type === ContextMenuOptionType.Folder,
|
(item) =>
|
||||||
|
item.type === ContextMenuOptionType.File ||
|
||||||
|
item.type === ContextMenuOptionType.OpenedFile ||
|
||||||
|
item.type === ContextMenuOptionType.Folder,
|
||||||
)
|
)
|
||||||
const gitMatches = matchingItems.filter((item) => item.type === ContextMenuOptionType.Git)
|
const gitMatches = matchingItems.filter((item) => item.type === ContextMenuOptionType.Git)
|
||||||
const otherMatches = matchingItems.filter(
|
const otherMatches = matchingItems.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.type !== ContextMenuOptionType.File &&
|
item.type !== ContextMenuOptionType.File &&
|
||||||
|
item.type !== ContextMenuOptionType.OpenedFile &&
|
||||||
item.type !== ContextMenuOptionType.Folder &&
|
item.type !== ContextMenuOptionType.Folder &&
|
||||||
item.type !== ContextMenuOptionType.Git,
|
item.type !== ContextMenuOptionType.Git,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user