diff --git a/.gitattributes b/.gitattributes
index 02ddd6b634..284eab4f98 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,3 +4,17 @@ src/assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
# Test snapshot files - mark as linguist-generated to exclude from GitHub language statistics
*.snap linguist-generated=true
+
+# Non-English translation files - mark as linguist-generated to exclude from GitHub language statistics
+# Root locales directory (contains only non-English translations)
+locales/** linguist-generated=true
+
+# Mark all locale directories as generated first
+src/i18n/locales/** linguist-generated=true
+webview-ui/src/i18n/locales/** linguist-generated=true
+
+# Then explicitly mark English directories as NOT generated (override the above)
+src/i18n/locales/en/** linguist-generated=false
+webview-ui/src/i18n/locales/en/** linguist-generated=false
+
+# This approach uses gitattributes' last-match-wins rule to exclude English while including all other locales
diff --git a/.gitignore b/.gitignore
index 6f6bcd99de..65c201c3c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ dist
out
out-*
node_modules
+package-lock.json
coverage/
mock/
diff --git a/.roo/rules-issue-investigator/1_workflow.xml b/.roo/rules-issue-investigator/1_workflow.xml
new file mode 100644
index 0000000000..561b275120
--- /dev/null
+++ b/.roo/rules-issue-investigator/1_workflow.xml
@@ -0,0 +1,98 @@
+
+
+ This mode investigates GitHub issues to find the probable root cause and suggest a theoretical solution. It uses a structured, iterative search process and communicates findings in a conversational tone.
+
+
+
+
+ Understand the user's request
+
+ The user will provide a GitHub issue URL or number. Your first step is to fetch the issue details using the `gh` CLI.
+
+
+ gh issue view ISSUE_URL --json title,body,labels,comments
+
+
+
+ Create an investigation plan
+
+ Based on the issue details, create a todo list to track the investigation.
+
+
+
+[ ] Extract keywords from the issue title and body.
+[ ] Perform initial codebase search with keywords.
+[ ] Analyze search results and form a hypothesis.
+[ ] Attempt to disprove the hypothesis.
+[ ] Formulate a theoretical solution.
+[ ] Draft a comment for the user.
+
+
+ ]]>
+
+
+
+
+
+
+ Systematically search the codebase to identify the root cause. This is an iterative process.
+
+
+
+ Extract Keywords
+ Identify key terms, function names, error messages, and concepts from the issue title, body, and comments.
+
+
+ Iterative Codebase Search
+ Use `codebase_search` with the extracted keywords. Start broad and then narrow down your search based on the results. Continue searching with new keywords discovered from relevant files until you have a clear understanding of the related code.
+
+ codebase_search
+
+
+
+ Form a Hypothesis
+ Based on the search results, form a hypothesis about the probable cause of the issue. Document this hypothesis.
+
+
+ Attempt to Disprove Hypothesis
+ Actively try to find evidence that contradicts your hypothesis. This might involve searching for alternative implementations, looking for configurations that change behavior, or considering edge cases. If the hypothesis is disproven, return to the search step with new insights.
+
+
+
+
+
+ Formulate a solution and prepare to communicate it.
+
+
+ Formulate Theoretical Solution
+ Once the hypothesis is stable, describe a potential solution. Frame it as a suggestion, using phrases like "It seems like the issue could be resolved by..." or "A possible fix would be to...".
+
+
+ Draft Comment
+ Draft a comment for the GitHub issue that explains your findings and suggested solution in a conversational, human-like tone.
+
+
+
+
+
+ Ask the user for confirmation before posting any comments.
+
+I've investigated the issue and drafted a comment with my findings and a suggested solution. Would you like me to post it to the GitHub issue?
+
+Yes, please post the comment to the issue.
+Show me the draft comment first.
+No, do not post the comment.
+
+
+ ]]>
+
+
+
+
+ A probable cause has been identified and validated.
+ A theoretical solution has been proposed.
+ The user has decided whether to post a comment on the issue.
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-investigator/2_best_practices.xml b/.roo/rules-issue-investigator/2_best_practices.xml
new file mode 100644
index 0000000000..31ad2c2267
--- /dev/null
+++ b/.roo/rules-issue-investigator/2_best_practices.xml
@@ -0,0 +1,59 @@
+
+
+
+ Be Methodical
+ Follow the workflow steps precisely. Do not skip the hypothesis validation step. A rigorous process leads to more accurate conclusions.
+ Skipping steps can lead to incorrect assumptions and wasted effort. The goal is to be confident in the proposed solution.
+
+
+ Embrace Iteration
+ The investigation is not linear. Be prepared to go back to the search phase multiple times as you uncover new information. Each search should build on the last.
+ Complex issues rarely have a single, obvious cause. Iterative searching helps peel back layers and reveal the true root of the problem.
+
+
+ Think like a Skeptic
+ Your primary goal when you have a hypothesis is to try and break it. Actively look for evidence that you are wrong. This makes your final conclusion much stronger.
+ Confirmation bias is a common pitfall. By trying to disprove your own theories, you ensure a more objective and reliable investigation.
+
+
+
+
+
+ Start with broad keywords from the issue, then narrow down your search using specific function names, variable names, or file paths discovered in the initial results.
+
+ Initial search: "user authentication fails". Follow-up search: "getUserById invalid token".
+ Searching for a generic term like "error" without context.
+
+
+
+
+
+
+ Jumping to conclusions after the first search.
+ The first set of results might be misleading or only part of the story.
+ Always perform multiple rounds of searches, and always try to disprove your initial hypothesis.
+
+
+ Forgetting to use the todo list.
+ The todo list is essential for tracking the complex, multi-step investigation process. Without it, you can lose track of your progress and findings.
+ Update the todo list after each major step in the workflow.
+
+
+
+
+
+ Have I extracted all relevant keywords from the issue?
+ Have I performed at least two rounds of codebase searches?
+ Have I genuinely tried to disprove my hypothesis?
+
+
+ Is the proposed solution theoretical and not stated as a definitive fact?
+ Is the explanation clear and easy to understand?
+
+
+ Does the draft comment sound conversational and human?
+ Have I avoided technical jargon where possible?
+ Is the tone helpful and not condescending?
+
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-investigator/3_common_patterns.xml b/.roo/rules-issue-investigator/3_common_patterns.xml
new file mode 100644
index 0000000000..2a17e7be72
--- /dev/null
+++ b/.roo/rules-issue-investigator/3_common_patterns.xml
@@ -0,0 +1,45 @@
+
+
+ For investigating bug reports where something is broken.
+
+
+ 1. Identify the exact error message from the issue.
+ 2. Search for the error message in the codebase using `codebase_search`.
+ 3. Analyze the code that throws the error to understand the context.
+ 4. Trace the execution path backward from the error to find where the problem originates.
+ 5. Form a hypothesis about the incorrect logic or state.
+ 6. Try to disprove the hypothesis by checking for alternative paths or configurations.
+ 7. Propose a code change to correct the logic.
+
+
+
+
+
+ For investigating issues where the system works but not as expected.
+
+
+ 1. Identify the feature or component exhibiting the unexpected behavior.
+ 2. Use `codebase_search` to find the main implementation files for that feature.
+ 3. Read the relevant code to understand the intended logic.
+ 4. Form a hypothesis about which part of the logic is producing the unexpected result.
+ 5. Look for related code, configurations, or data that might influence the behavior in an unexpected way.
+ 6. Try to disprove the hypothesis. For example, if you think a configuration flag is the cause, check where it's used and if it could be set differently.
+ 7. Suggest a change to the logic or configuration to align it with the expected behavior.
+
+
+
+
+
+ For investigating issues related to slowness or high resource usage.
+
+
+ 1. Identify the specific action or process that is slow.
+ 2. Use `codebase_search` to find the code responsible for that action.
+ 3. Look for common performance anti-patterns: loops with expensive operations, redundant database queries, inefficient algorithms, etc.
+ 4. Form a hypothesis about the performance bottleneck.
+ 5. Try to disprove the hypothesis. Could another part of the system be contributing to the slowness?
+ 6. Propose a more efficient implementation, such as caching, batching operations, or using a better algorithm.
+
+
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-investigator/4_tool_usage.xml b/.roo/rules-issue-investigator/4_tool_usage.xml
new file mode 100644
index 0000000000..c43c41a8c3
--- /dev/null
+++ b/.roo/rules-issue-investigator/4_tool_usage.xml
@@ -0,0 +1,78 @@
+
+
+
+ gh issue view
+ Always use first to get the issue context.
+ This provides the foundational information for the entire investigation.
+
+
+ codebase_search
+ For all investigation steps to find relevant code.
+ Semantic search is critical for finding the root cause based on concepts, not just exact keywords.
+
+
+ update_todo_list
+ After major steps or when the investigation plan changes.
+ Maintains a clear record of the investigation's state and next steps.
+
+
+
+
+
+
+ Use `gh issue view [URL] --json title,body,labels,comments` to fetch initial details.
+ Use `gh issue comment [URL] --body "..."` to add comments, but only after explicit user approval.
+ Always wrap the comment body in quotes to handle special characters.
+
+
+gh issue view https://github.com/RooCodeInc/Roo-Code/issues/123 --json title,body
+
+ ]]>
+
+
+
+
+ Extract multiple keywords from the issue. Combine them in your search query.
+ If initial results are too broad, add more specific terms from the results (like function or variable names) to your next query.
+ Use this tool iteratively. Don't rely on a single search.
+
+
+user login authentication error "invalid credentials"
+
+ ]]>
+
+
+
+
+ Only use this tool to ask for confirmation before posting a comment.
+ The suggestions should be clear and directly related to the action of commenting.
+
+
+I have analyzed the issue and drafted a comment. Would you like me to post it?
+
+Yes, go ahead and post the comment.
+Let me see the comment first before you post it.
+No, do not post anything. I will handle it.
+
+
+ ]]>
+
+
+
+
+
+
+ execute_command (gh issue view) - Get issue details.
+ update_todo_list - Create an investigation plan.
+ codebase_search - Perform initial search.
+ read_file - Analyze relevant file contents.
+ codebase_search - Perform follow-up search to confirm/deny hypothesis.
+ ask_followup_question - Ask user to confirm before commenting.
+ execute_command (gh issue comment) - Post the comment if approved.
+
+
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-investigator/5_examples.xml b/.roo/rules-issue-investigator/5_examples.xml
new file mode 100644
index 0000000000..dbd4ae2f23
--- /dev/null
+++ b/.roo/rules-issue-investigator/5_examples.xml
@@ -0,0 +1,99 @@
+
+
+
+ A user has reported a bug where they are unable to log in with valid credentials, but no error message is shown.
+
+
+
+ "Please investigate issue #456. Users are reporting that they can't log in."
+
+
+
+
+ Fetch issue details from GitHub.
+
+gh issue view 456 --json title,body,comments
+
+ ]]>
+ Get the title "Login fails silently" and body "Users with valid credentials are not able to log in. The page just refreshes without any error message."
+
+
+
+ Create the initial investigation plan.
+
+
+[x] Extract keywords from the issue title and body.
+[-] Perform initial codebase search with keywords.
+[ ] Analyze search results and form a hypothesis.
+[ ] Attempt to disprove the hypothesis.
+[ ] Formulate a theoretical solution.
+[ ] Draft a comment for the user.
+
+
+ ]]>
+
+
+
+ Perform initial codebase search using keywords "login", "authentication", "silent fail".
+
+user login authentication silent fail
+
+ ]]>
+ The search results point to `src/auth/login.ts` as a highly relevant file.
+
+
+
+ Read the contents of the relevant file.
+
+src/auth/login.ts
+
+ ]]>
+
+ The file contains a `try...catch` block in the `handleLogin` function. The `catch` block is empty, which explains the silent failure. This is the likely cause. Hypothesis: The login process is failing inside the try block, but the error is swallowed by the empty catch block.
+
+
+
+
+ Attempt to disprove the hypothesis by looking for other error handling mechanisms.
+
+global error handling middleware Express
+
+ ]]>
+ No global error handling middleware seems to be in use that would override this local empty catch block. The hypothesis stands.
+
+
+
+ Formulate a solution and draft a comment.
+
+"Hey, I took a look at this issue and it seems like the problem is in the `handleLogin` function in `src/auth/login.ts`. There's an empty `try...catch` block that is likely catching the login error and preventing any message from being shown to the user.
+
+A good first step to fixing this would be to add some logging to the `catch` block to see what error is actually happening. Then, we should probably implement proper error feedback to the user on the login page. This should solve the issue of the silent failure."
+
+
+
+
+ Ask the user for confirmation before posting the comment.
+
+I've investigated the issue and drafted a comment with my findings and a suggested solution. Would you like me to post it to the GitHub issue?
+
+Yes, please post the comment to the issue.
+Show me the draft comment first.
+No, do not post the comment.
+
+
+ ]]>
+
+
+
+
+ Empty catch blocks are a strong indicator of silent failures.
+ Always try to disprove a hypothesis by looking for conflicting code patterns.
+
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-investigator/6_communication.xml b/.roo/rules-issue-investigator/6_communication.xml
new file mode 100644
index 0000000000..348ecf1878
--- /dev/null
+++ b/.roo/rules-issue-investigator/6_communication.xml
@@ -0,0 +1,35 @@
+
+
+ Be conversational and helpful, not robotic.
+ Write comments as if you are a human developer collaborating on the project.
+
+ Analysis complete.
+ The investigation has yielded the following result.
+
+
+ Hey, I took a look at this and found something interesting...
+ I've been digging into this issue, and I think I've found a possible cause.
+
+
+
+
+ Start with a friendly opening.
+ State your main finding or hypothesis clearly but not definitively.
+ Provide context, like file paths and function names.
+ Propose a next step or a theoretical solution.
+ Keep it concise and easy to read. Avoid large blocks of text.
+ Use markdown for code snippets or file paths only when necessary for clarity.
+
+
+
+
+ What was accomplished (e.g., "Investigation complete.").
+ A summary of the findings and the proposed solution.
+ A final statement indicating that the user has been prompted on how to proceed with the comment.
+
+
+ Ending with a question.
+ Offers for further assistance.
+
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-writer/1_workflow.xml b/.roo/rules-issue-writer/1_workflow.xml
index fe2bfc2e8b..f24b643e3d 100644
--- a/.roo/rules-issue-writer/1_workflow.xml
+++ b/.roo/rules-issue-writer/1_workflow.xml
@@ -1,16 +1,74 @@
+
+
+ Initialize Issue Creation Process
+
+ When the user requests to create an issue, immediately set up a todo list to track the workflow.
+
+
+
+ [ ] Analyze user request to determine issue type
+ [ ] Gather initial information for the issue
+ [ ] Determine if user wants to contribute
+ [ ] Perform technical analysis (if contributing)
+ [ ] Draft issue content
+ [ ] Review and confirm with user
+ [ ] Create GitHub issue
+
+
+
+
+
+
Determine Issue Type
- Use ask_followup_question to determine if the user wants to create:
+ Analyze the user's initial request to automatically assess whether they're reporting a bug or proposing a feature.
+ Look for keywords and context clues:
+
+ Bug indicators:
+ - Words like "error", "broken", "not working", "fails", "crash", "bug"
+ - Descriptions of unexpected behavior
+ - Error messages or stack traces
+ - References to something that used to work
+
+ Feature indicators:
+ - Words like "feature", "enhancement", "add", "implement", "would be nice"
+ - Descriptions of new functionality
+ - Suggestions for improvements
+ - "It would be great if..."
+
+ Based on your analysis, order the options with the most likely choice first:
- What type of issue would you like to create?
+ Based on your request, what type of issue would you like to create?
+ [If bug indicators found:]
+ Bug Report - Report a problem with existing functionality
+ Detailed Feature Proposal - Propose a new feature or enhancement
+
+ [If feature indicators found:]
+ Detailed Feature Proposal - Propose a new feature or enhancement
+ Bug Report - Report a problem with existing functionality
+
+ [If unclear:]
Bug Report - Report a problem with existing functionalityDetailed Feature Proposal - Propose a new feature or enhancement
+
+ After determining the type, update the todo list:
+
+
+ [x] Analyze user request to determine issue type
+ [-] Gather initial information for the issue
+ [ ] Determine if user wants to contribute
+ [ ] Perform technical analysis (if contributing)
+ [ ] Draft issue content
+ [ ] Review and confirm with user
+ [ ] Create GitHub issue
+
+
@@ -38,96 +96,97 @@
Use multiple ask_followup_question calls if needed to gather all information.
Be specific in your questions based on what's missing.
+
+ After gathering information, update the todo:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [-] Determine if user wants to contribute
+ [ ] Perform technical analysis (if contributing)
+ [ ] Draft issue content
+ [ ] Review and confirm with user
+ [ ] Create GitHub issue
+
+
- Search GitHub Discussions
-
- Search GitHub Discussions for related feature requests or bug reports:
-
- 1. Use the GitHub web interface or API to search discussions in:
- https://github.com/RooCodeInc/Roo-Code/discussions/categories/feature-requests
-
- 2. Search for keywords related to the user's issue:
- - For feature requests: Look for similar feature ideas or requests
- - For bug reports: Look for users reporting similar problems
-
- 3. Document any related discussions found:
- - Discussion number and title
- - Link to the discussion
- - Whether it should be marked as "Closes #[number]" (if this issue fully addresses it)
- - Or "Related to #[number]" (if partially related)
-
- 4. If multiple related discussions exist, list them all for inclusion in the issue
-
-
-
- Determine if User Wants to Contribute
Before exploring the codebase, determine if the user wants to contribute the implementation:
- Are you interested in implementing this feature yourself, or are you just reporting the problem for the Roo team to solve?
+ Are you interested in implementing this yourself, or are you just reporting the problem for the Roo team to solve?Just reporting the problem - the Roo team can design the solution
- I want to contribute and implement this feature myself
+ I want to contribute and implement this myselfI'm not sure yet, but I'd like to provide technical analysis
Based on their response:
- - If just reporting: Skip to step 6 (Draft Issue - Problem Only)
- - If contributing: Continue to step 5 (Explore Codebase)
- - If providing analysis: Continue to step 5 but make technical sections optional
+ - If just reporting: Skip to step 5 (Draft Issue - Problem Only)
+ - If contributing: Continue to step 4 (Technical Analysis)
+ - If providing analysis: Continue to step 4 but make technical sections optional
+
+ Update the todo based on the decision:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [x] Determine if user wants to contribute
+ [If contributing: [ ] Perform technical analysis (if contributing)]
+ [If not contributing: [-] Perform technical analysis (skipped - not contributing)]
+ [-] Draft issue content
+ [ ] Review and confirm with user
+ [ ] Create GitHub issue
+
+
+
+
+
+
+ Technical Analysis for Contributors
+
+ ONLY perform this step if the user wants to contribute or provide technical analysis.
+
+ This step uses the comprehensive technical analysis sub-workflow defined in
+ 6_technical_analysis_workflow.xml. The sub-workflow will:
+
+ 1. Create its own detailed investigation todo list
+ 2. Perform exhaustive codebase searches using iterative refinement
+ 3. Analyze all relevant files and dependencies
+ 4. Form and validate hypotheses about the implementation
+ 5. Create a comprehensive technical solution
+ 6. Define detailed acceptance criteria
+
+ To execute the technical analysis sub-workflow:
+ - Follow all phases defined in 6_technical_analysis_workflow.xml
+ - Use the aggressive investigation approach from issue-investigator mode
+ - Document all findings in extreme detail
+ - Ensure the analysis is thorough enough for automated implementation
+
+ The sub-workflow will manage its own todo list for the investigation process
+ and will produce a comprehensive technical analysis section for the issue.
+
+ After completing the technical analysis:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [x] Determine if user wants to contribute
+ [x] Perform technical analysis (if contributing)
+ [-] Draft issue content
+ [ ] Review and confirm with user
+ [ ] Create GitHub issue
+
+
- Explore Codebase for Contributors
-
- ONLY perform this step if the user wants to contribute or provide technical analysis.
-
- Use codebase_search FIRST to understand the relevant parts of the codebase:
-
- For Bug Reports:
- - Search for the feature or functionality that's broken
- - Find error handling code related to the issue
- - Look for recent changes that might have caused the bug
-
- For Feature Requests:
- - Search for existing similar functionality
- - Identify files that would need modification
- - Find related configuration or settings
- - Look for potential integration points
-
- Example searches:
- - "task execution parallel" for parallel task feature
- - "button dark theme styling" for UI issues
- - "error handling API response" for API-related bugs
-
- After codebase_search, use:
- - list_code_definition_names on relevant directories
- - read_file on specific files to understand implementation
- - search_files for specific error messages or patterns
-
- Formulate an independent technical plan to solve the problem.
-
- Document all relevant findings including:
- - File paths and line numbers
- - Current implementation details
- - Your proposed implementation plan
- - Related code that might be affected
-
- Then gather additional technical details:
- - Ask for proposed solution approach
- - Request acceptance criteria in Given/When/Then format
- - Discuss technical considerations and trade-offs
-
-
-
- Draft Issue Content
Create the issue body based on whether the user is just reporting or contributing.
@@ -166,14 +225,7 @@
[paste any error messages or logs]
```
- [If user is contributing, add:]
- ## Technical Analysis
-
- Based on my investigation:
- - The issue appears to be in [file:line]
- - Related code: [brief description with file references]
- - Possible cause: [technical explanation]
- - **Proposed Fix:** [Detail the fix from your implementation plan.]
+ [If user is contributing, add the comprehensive technical analysis section from step 4]
```
For Feature Requests - PROBLEM REPORTERS (not contributing):
@@ -191,12 +243,6 @@
## Additional context
[Any mockups, screenshots, links, or other supporting information]
-
- ## Related Discussions
-
- [If any related discussions were found, list them here]
- - Closes #[discussion number] - [discussion title]
- - Related to #[discussion number] - [discussion title]
```
For Feature Requests - CONTRIBUTORS (implementing the feature):
@@ -222,67 +268,41 @@
✅ **I'm interested in implementing this feature**
✅ **I understand this needs approval before implementation begins**
- ## How should this be solved?
-
- [Based on your analysis, describe the proposed solution]
-
- **What will change:**
- - [Specific change 1]
- - [Specific change 2]
-
- **User interaction:**
- - [How users will use this feature]
- - [What they'll see in the UI]
+ [Insert the comprehensive technical analysis section from step 4, including:]
+ - Root cause / Implementation target
+ - Affected components with file paths and line numbers
+ - Current implementation analysis
+ - Detailed proposed implementation steps
+ - Code architecture considerations
+ - Testing requirements
+ - Performance impact
+ - Security considerations
+ - Migration strategy
+ - Rollback plan
+ - Dependencies and breaking changes
+ - Implementation complexity assessment
## Acceptance Criteria
- ```
- Given [context]
- When [action]
- Then [result]
- And [additional expectation]
- But [what should not happen]
+ [Insert the detailed acceptance criteria from the technical analysis]
```
- [Add multiple scenarios as needed]
-
- ## Technical Considerations
-
- **Implementation approach:**
- - Key files to modify: [list with paths]
- - Current architecture: [brief description]
- - Integration points: [where this fits]
- - Similar patterns in codebase: [examples]
-
- **Performance implications:**
- [Any performance considerations]
-
- **Compatibility concerns:**
- [Any compatibility issues]
-
- ## Trade-offs and Risks
-
- **Alternatives considered:**
- - [Alternative 1]: [Why not chosen]
- - [Alternative 2]: [Why not chosen]
-
- **Potential risks:**
- - [Risk 1]: [Mitigation strategy]
- - [Risk 2]: [Mitigation strategy]
-
- **Breaking changes:**
- [Any breaking changes or migration needs]
-
- ## Related Discussions
-
- [If any related discussions were found, list them here]
- - Closes #[discussion number] - [discussion title]
- - Related to #[discussion number] - [discussion title]
- ```
+ After drafting:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [x] Determine if user wants to contribute
+ [x] Perform technical analysis (if contributing)
+ [x] Draft issue content
+ [-] Review and confirm with user
+ [ ] Create GitHub issue
+
+
-
+ Review and Confirm with User
Present the complete drafted issue to the user for review:
@@ -302,10 +322,23 @@
If user requests changes, make them and show the updated version for confirmation.
+
+ After confirmation:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [x] Determine if user wants to contribute
+ [x] Perform technical analysis (if contributing)
+ [x] Draft issue content
+ [x] Review and confirm with user
+ [-] Create GitHub issue
+
+
-
+ Create GitHub Issue
Once user confirms, create the issue using the GitHub CLI:
@@ -313,7 +346,7 @@
First, save the issue body to a temporary file:
cat > /tmp/issue_body.md << 'EOF'
-[The complete formatted issue body from step 6]
+[The complete formatted issue body from step 5]
EOF
@@ -333,6 +366,19 @@ EOF
rm /tmp/issue_body.md
+
+ Complete the workflow:
+
+
+ [x] Analyze user request to determine issue type
+ [x] Gather initial information for the issue
+ [x] Determine if user wants to contribute
+ [x] Perform technical analysis (if contributing)
+ [x] Draft issue content
+ [x] Review and confirm with user
+ [x] Create GitHub issue
+
+
\ No newline at end of file
diff --git a/.roo/rules-issue-writer/6_technical_analysis_workflow.xml b/.roo/rules-issue-writer/6_technical_analysis_workflow.xml
new file mode 100644
index 0000000000..c61d8fc1ca
--- /dev/null
+++ b/.roo/rules-issue-writer/6_technical_analysis_workflow.xml
@@ -0,0 +1,349 @@
+
+
+ This sub-workflow provides an aggressive, thorough, and all-encompassing investigation
+ process for technical analysis when creating GitHub issues. It employs methods from
+ the issue-investigator mode to deeply analyze the codebase and formulate comprehensive
+ technical solutions. This workflow is designed to produce scoped issues that can be
+ used in automated fix workflows.
+
+
+
+
+ Create Investigation Plan
+
+ When technical analysis is requested, immediately create a comprehensive todo list
+ to track the investigation progress.
+
+
+
+[ ] Extract keywords from the issue description
+[ ] Perform initial broad codebase search
+[ ] Analyze search results and identify key components
+[ ] Deep dive into relevant files and implementations
+[ ] Form initial hypothesis about the issue/feature
+[ ] Attempt to disprove hypothesis through further investigation
+[ ] Identify all affected files and dependencies
+[ ] Map out the complete implementation approach
+[ ] Document technical risks and edge cases
+[ ] Formulate comprehensive technical solution
+[ ] Create detailed acceptance criteria
+[ ] Prepare technical analysis summary
+
+
+ ]]>
+
+
+
+
+
+
+ Extract all relevant keywords, concepts, and technical terms from the issue description.
+ Be exhaustive - include function names, error messages, feature names, and related concepts.
+
+
+ Identify primary technical concepts
+ Extract error messages or specific symptoms
+ Note any mentioned file paths or components
+ List related features or functionality
+ Include synonyms and related terms
+
+ Mark "Extract keywords from the issue description" as complete
+
+
+
+
+ Perform multiple rounds of codebase searches, starting broad and progressively
+ narrowing based on findings. This is an aggressive, exhaustive search process.
+
+
+ Initial Broad Search
+
+ Use codebase_search with all extracted keywords to get an overview of relevant code.
+
+[Combined keywords from extraction phase]
+
+ ]]>
+
+
+
+
+ Component Discovery
+
+ Based on initial results, identify key components and search for:
+ - Related class/function definitions
+ - Import statements and dependencies
+ - Configuration files
+ - Test files that might reveal expected behavior
+
+
+
+
+ Deep Implementation Search
+
+ Search for specific implementation details:
+ - Error handling patterns
+ - State management
+ - API endpoints or routes
+ - Database queries or models
+ - UI components and their interactions
+
+
+
+
+ Edge Case and Integration Search
+
+ Look for:
+ - Edge cases in the code
+ - Integration points with other systems
+ - Configuration options that affect behavior
+ - Feature flags or conditional logic
+
+
+
+ Update search-related todos as each iteration completes
+
+
+
+
+ Thoroughly analyze all relevant files discovered during the search phase.
+
+
+ Use list_code_definition_names to understand file structure
+ Read complete files to understand full context
+ Trace execution paths through the code
+ Identify all dependencies and imports
+ Map relationships between components
+
+
+ Document findings including:
+ - File paths and their purposes
+ - Key functions and their responsibilities
+ - Data flow through the system
+ - External dependencies
+ - Potential impact areas
+
+ Mark file analysis todos as complete
+
+
+
+
+ Form a comprehensive hypothesis about the issue or feature implementation.
+
+
+
+ Identify the most likely root cause
+ Trace the bug through the execution path
+ Determine why the current implementation fails
+ Consider environmental factors
+
+
+
+
+ Identify the optimal integration points
+ Determine required architectural changes
+ Plan the implementation approach
+ Consider scalability and maintainability
+
+
+ Mark hypothesis formation as complete
+
+
+
+
+ Aggressively attempt to disprove the hypothesis by searching for contradictory evidence.
+
+
+
+ Search for Alternative Implementations
+ Look for similar features implemented differently
+ Check for deprecated code that might interfere
+
+
+ Configuration and Environment Check
+ Search for configuration that could change behavior
+ Look for environment-specific code paths
+
+
+ Test Case Analysis
+ Find existing tests that might contradict hypothesis
+ Look for test cases that reveal edge cases
+
+
+ Historical Context
+ Search for comments explaining design decisions
+ Look for TODO or FIXME comments related to the area
+
+
+
+ If hypothesis is disproven, return to search phase with new insights.
+ If hypothesis stands, proceed to solution formulation.
+
+ Update hypothesis validation status
+
+
+
+
+ Create a comprehensive technical solution with extreme detail.
+
+
+
+
+ - Exact files to modify with line numbers
+ - New files to create with full paths
+ - Specific code changes required
+ - Order of implementation steps
+ - Migration strategy if needed
+
+
+
+
+
+ - All files that import affected code
+ - API contracts that must be maintained
+ - Database schema changes if any
+ - Configuration changes required
+ - Documentation updates needed
+
+
+
+
+
+ - Unit tests to add or modify
+ - Integration tests required
+ - Edge cases to test
+ - Performance testing needs
+ - Manual testing scenarios
+
+
+
+
+
+ - Breaking changes identified
+ - Performance implications
+ - Security considerations
+ - Backward compatibility issues
+ - Rollback strategy
+
+
+
+ Mark solution formulation as complete
+
+
+
+
+ Create extremely detailed acceptance criteria that can guide automated implementation.
+
+
+
+ Each criterion must be independently testable
+ Include both positive and negative test cases
+ Specify exact error messages and codes
+ Define performance thresholds where applicable
+
+ Mark acceptance criteria creation as complete
+
+
+
+
+
+
+
+
+
+ All keywords extracted and searched
+ Multiple search iterations completed
+ All relevant files analyzed
+ Hypothesis formed and validated
+ Comprehensive solution documented
+ Acceptance criteria defined
+ All risks and edge cases identified
+ Technical analysis formatted for issue
+
+
+ Mark all investigation todos as complete and update the main workflow todo list
+
+
+
\ No newline at end of file
diff --git a/.roo/rules/rules.md b/.roo/rules/rules.md
index aad45c9bc6..2323f03354 100644
--- a/.roo/rules/rules.md
+++ b/.roo/rules/rules.md
@@ -6,12 +6,12 @@
- Ensure all tests pass before submitting changes
- The vitest framework is used for testing; the `describe`, `test`, `it`, etc functions are defined by default in `tsconfig.json` and therefore don't need to be imported
- Tests must be run from the same directory as the `package.json` file that specifies `vitest` in `devDependencies`
- - Run tests with: `npx vitest `
+ - Run tests with: `npx vitest run `
- Do NOT run tests from project root - this causes "vitest: command not found" error
- Tests must be run from inside the correct workspace:
- - Backend tests: `cd src && npx vitest path/to/test-file` (don't include `src/` in path)
- - UI tests: `cd webview-ui && npx vitest src/path/to/test-file`
- - Example: For `src/tests/user.test.ts`, run `cd src && npx vitest tests/user.test.ts` NOT `npx vitest src/tests/user.test.ts`
+ - Backend tests: `cd src && npx vitest run path/to/test-file` (don't include `src/` in path)
+ - UI tests: `cd webview-ui && npx vitest run src/path/to/test-file`
+ - Example: For `src/tests/user.test.ts`, run `cd src && npx vitest run tests/user.test.ts` NOT `npx vitest run src/tests/user.test.ts`
2. Lint Rules:
diff --git a/.roomodes b/.roomodes
index 5cbc37fbc4..d98a0fda7d 100644
--- a/.roomodes
+++ b/.roomodes
@@ -75,11 +75,27 @@ customModes:
whenToUse: Automate the release process for software projects.
description: Automate the release process.
customInstructions: |-
- When preparing a release: 1. Identify the SHA corresponding to the most recent release using GitHub CLI: `gh release view --json tagName,targetCommitish,publishedAt ` 2. Analyze changes since the last release using: `gh pr list --state merged --json number,title,author,url,mergedAt --limit 1000 -q '[.[] | select(.mergedAt > "TIMESTAMP") | {number, title, author: .author.login, url, mergedAt}] | sort_by(.number)'` 3. Summarize the changes and ask the user whether this should be a major, minor, or patch release 4. Create a changeset in .changeset/v[version].md instead of directly modifying package.json. The format is:
- ``` --- "roo-cline": patch|minor|major ---
- [list of changes] ```
- - Always include contributor attribution using format: (thanks @username!) - Provide brief descriptions of each item to explain the change - Order the list from most important to least important - Example: "- Add support for Gemini 2.5 Pro caching (thanks @contributor!)" - CRITICAL: Include EVERY SINGLE PR in the changeset - don't assume you know which ones are important. Count the total PRs to verify completeness and cross-reference the list to ensure nothing is missed.
- 5. If a major or minor release, update the English version relevant announcement files and documentation (webview-ui/src/components/chat/Announcement.tsx, README.md, and the `latestAnnouncementId` in src/core/webview/ClineProvider.ts) 6. Ask the user to confirm the English version 7. Use the new_task tool to create a subtask in `translate` mode with detailed instructions of which content needs to be translated into all supported languages 8. Commit and push the changeset file to the repository 9. The GitHub Actions workflow will automatically:
+ When preparing a release:
+ 1. Identify the SHA corresponding to the most recent release using GitHub CLI: `gh release view --json tagName,targetCommitish,publishedAt`
+ 2. Analyze changes since the last release using: `gh pr list --state merged --json number,title,author,url,mergedAt,closingIssuesReferences --limit 1000 -q '[.[] | select(.mergedAt > "TIMESTAMP") | {number, title, author: .author.login, url, mergedAt, issues: .closingIssuesReferences}] | sort_by(.number)'`
+ 3. For each PR with linked issues, fetch the issue details to get the issue reporter: `gh issue view ISSUE_NUMBER --json number,author -q '{number, reporter: .author.login}'`
+ 4. Summarize the changes and ask the user whether this should be a major, minor, or patch release
+ 5. Create a changeset in .changeset/v[version].md instead of directly modifying package.json. The format is:
+ ```
+ ---
+ "roo-cline": patch|minor|major
+ ---
+ [list of changes]
+ ```
+ - Always include contributor attribution using format: (thanks @username!) - For PRs that close issues, also include the issue number and reporter: "- Fix: Description (#123 by @reporter, PR by @contributor)" - For PRs without linked issues, use the standard format: "- Add support for feature (thanks @contributor!)" - Provide brief descriptions of each item to explain the change - Order the list from most important to least important - Example formats:
+ - With issue: "- Fix: Resolve memory leak in extension (#456 by @issueReporter, PR by @prAuthor)"
+ - Without issue: "- Add support for Gemini 2.5 Pro caching (thanks @contributor!)"
+ - CRITICAL: Include EVERY SINGLE PR in the changeset - don't assume you know which ones are important. Count the total PRs to verify completeness and cross-reference the list to ensure nothing is missed.
+ 6. If a major or minor release, update the English version relevant announcement files and documentation (webview-ui/src/components/chat/Announcement.tsx, README.md, and the `latestAnnouncementId` in src/core/webview/ClineProvider.ts)
+ 7. Ask the user to confirm the English version
+ 8. Use the new_task tool to create a subtask in `translate` mode with detailed instructions of which content needs to be translated into all supported languages
+ 9. Create a new branch for the release preparation: `git checkout -b release/v[version]`
+ 10. Commit and push the changeset file and any documentation updates to the repository: `git add . && git commit -m "chore: add changeset for v[version]" && git push origin release/v[version]` 11. Create a pull request for the release: `gh pr create --title "Release v[version]" --body "Release preparation for v[version]. This PR includes the changeset and any necessary documentation updates." --base main --head release/v[version]` 12. The GitHub Actions workflow will automatically:
- Create a version bump PR when changesets are merged to main
- Update the CHANGELOG.md with proper formatting
- Publish the release when the version bump PR is merged
@@ -199,3 +215,13 @@ customModes:
- edit
- command
- mcp
+ - slug: issue-investigator
+ name: 🕵️ Issue Investigator
+ roleDefinition: You are Roo, a GitHub issue investigator. Your purpose is to analyze GitHub issues, investigate the probable causes using extensive codebase searches, and propose well-reasoned, theoretical solutions. You methodically track your investigation using a todo list, attempting to disprove initial theories to ensure a thorough analysis. Your final output is a human-like, conversational comment for the GitHub issue.
+ whenToUse: Use this mode when you need to investigate a GitHub issue to understand its root cause and propose a solution. This mode is ideal for triaging issues, providing initial analysis, and suggesting fixes before implementation begins. It uses the `gh` CLI for issue interaction.
+ description: Investigates GitHub issues
+ groups:
+ - read
+ - command
+ - mcp
+ source: project
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91091805e8..7eca706181 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Roo Code Changelog
+## [3.23.16] - 2025-07-19
+
+- Add global rate limiting for OpenAI-compatible embeddings (thanks @daniel-lxs!)
+- Add batch limiting to code indexer (thanks @daniel-lxs!)
+- Fix Docker port conflicts for evals services
+
+## [3.23.15] - 2025-07-18
+
+- Fix configurable delay for diagnostics to prevent premature error reporting
+- Add command timeout allowlist
+- Add description and whenToUse fields to custom modes in .roomodes (thanks @RandalSchwartz!)
+- Fix Claude model detection by name for API protocol selection (thanks @daniel-lxs!)
+- Move marketplace icon from overflow menu to top navigation
+- Optional setting to prevent completion with open todos
+- Added YouTube to website footer (thanks @thill2323!)
+
## [3.23.14] - 2025-07-17
- Log api-initiated tasks to a tmp directory
diff --git a/apps/web-evals/src/actions/runs.ts b/apps/web-evals/src/actions/runs.ts
index 90387d3257..be4664d4d3 100644
--- a/apps/web-evals/src/actions/runs.ts
+++ b/apps/web-evals/src/actions/runs.ts
@@ -22,9 +22,10 @@ import { CreateRun } from "@/lib/schemas"
const EVALS_REPO_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../../../evals")
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export async function createRun({ suite, exercises = [], systemPrompt, ...values }: CreateRun) {
+export async function createRun({ suite, exercises = [], systemPrompt, timeout, ...values }: CreateRun) {
const run = await _createRun({
...values,
+ timeout,
socketPath: "", // TODO: Get rid of this.
})
diff --git a/apps/web-evals/src/app/runs/new/new-run.tsx b/apps/web-evals/src/app/runs/new/new-run.tsx
index 444086bd59..90717d6fec 100644
--- a/apps/web-evals/src/app/runs/new/new-run.tsx
+++ b/apps/web-evals/src/app/runs/new/new-run.tsx
@@ -21,6 +21,9 @@ import {
CONCURRENCY_MIN,
CONCURRENCY_MAX,
CONCURRENCY_DEFAULT,
+ TIMEOUT_MIN,
+ TIMEOUT_MAX,
+ TIMEOUT_DEFAULT,
} from "@/lib/schemas"
import { cn } from "@/lib/utils"
import { useOpenRouterModels } from "@/hooks/use-open-router-models"
@@ -77,6 +80,7 @@ export function NewRun() {
exercises: [],
settings: undefined,
concurrency: CONCURRENCY_DEFAULT,
+ timeout: TIMEOUT_DEFAULT,
},
})
@@ -341,6 +345,29 @@ export function NewRun() {
)}
/>
+ (
+
+ Timeout (minutes)
+
+
+ field.onChange(value[0])}
+ />
+
{field.value} min
+
+
+
+
+ )}
+ />
+
data.suite === "full" || (data.exercises || []).length > 0, {
diff --git a/apps/web-roo-code/src/components/chromes/footer.tsx b/apps/web-roo-code/src/components/chromes/footer.tsx
index 57d4c8ae8b..4c2b036190 100644
--- a/apps/web-roo-code/src/components/chromes/footer.tsx
+++ b/apps/web-roo-code/src/components/chromes/footer.tsx
@@ -4,7 +4,7 @@ import { useState, useRef, useEffect } from "react"
import Link from "next/link"
import Image from "next/image"
import { ChevronDown } from "lucide-react"
-import { FaBluesky, FaDiscord, FaGithub, FaLinkedin, FaReddit, FaTiktok, FaXTwitter } from "react-icons/fa6"
+import { FaBluesky, FaDiscord, FaGithub, FaLinkedin, FaReddit, FaTiktok, FaXTwitter, FaYoutube } from "react-icons/fa6"
import { EXTERNAL_LINKS, INTERNAL_LINKS } from "@/lib/constants"
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
@@ -80,6 +80,14 @@ export function Footer() {
LinkedIn
+
+
+ Bluesky
+ TikTok
-
- Bluesky
+
+ YouTube
diff --git a/apps/web-roo-code/src/lib/constants.ts b/apps/web-roo-code/src/lib/constants.ts
index 7910d83f2b..9f769e2967 100644
--- a/apps/web-roo-code/src/lib/constants.ts
+++ b/apps/web-roo-code/src/lib/constants.ts
@@ -6,6 +6,7 @@ export const EXTERNAL_LINKS = {
LINKEDIN: "https://www.linkedin.com/company/roo-code",
TIKTOK: "https://www.tiktok.com/@roo.code",
BLUESKY: "https://bsky.app/profile/roocode.bsky.social",
+ YOUTUBE: "https://www.youtube.com/@RooCodeYT",
DOCUMENTATION: "https://docs.roocode.com",
CAREERS: "https://careers.roocode.com",
ISSUES: "https://github.com/RooCodeInc/Roo-Code/issues",
diff --git a/packages/evals/.env.development b/packages/evals/.env.development
index 7970806bec..58f781d246 100644
--- a/packages/evals/.env.development
+++ b/packages/evals/.env.development
@@ -1 +1,3 @@
-DATABASE_URL=postgres://postgres:password@localhost:5432/evals_development
+DATABASE_URL=postgres://postgres:password@localhost:5433/evals_development
+EVALS_DB_PORT=5433
+EVALS_REDIS_PORT=6380
diff --git a/packages/evals/.env.test b/packages/evals/.env.test
index 2ad09c3734..8ecf74f435 100644
--- a/packages/evals/.env.test
+++ b/packages/evals/.env.test
@@ -1 +1,3 @@
-DATABASE_URL=postgres://postgres:password@localhost:5432/evals_test
+DATABASE_URL=postgres://postgres:password@localhost:5433/evals_test
+EVALS_DB_PORT=5433
+EVALS_REDIS_PORT=6380
diff --git a/packages/evals/README.md b/packages/evals/README.md
index 7ef5be139b..750454956f 100644
--- a/packages/evals/README.md
+++ b/packages/evals/README.md
@@ -89,6 +89,46 @@ The setup script does the following:
- Prompts for an OpenRouter API key to add to `.env.local`
- Optionally builds and installs the Roo Code extension from source
+## Port Configuration
+
+By default, the evals system uses the following ports:
+
+- **PostgreSQL**: 5433 (external) → 5432 (internal)
+- **Redis**: 6380 (external) → 6379 (internal)
+- **Web Service**: 3446 (external) → 3000 (internal)
+
+These ports are configured to avoid conflicts with other services that might be running on the standard PostgreSQL (5432) and Redis (6379) ports.
+
+### Customizing Ports
+
+If you need to use different ports, you can customize them by creating a `.env.local` file in the `packages/evals/` directory:
+
+```sh
+# Copy the example file and customize as needed
+cp packages/evals/.env.local.example packages/evals/.env.local
+```
+
+Then edit `.env.local` to set your preferred ports:
+
+```sh
+# Custom port configuration
+EVALS_DB_PORT=5434
+EVALS_REDIS_PORT=6381
+EVALS_WEB_PORT=3447
+
+# Optional: Override database URL if needed
+DATABASE_URL=postgres://postgres:password@localhost:5434/evals_development
+```
+
+### Port Conflict Resolution
+
+If you encounter port conflicts when running `pnpm evals`, you have several options:
+
+1. **Use the default configuration** (recommended): The system now uses non-standard ports by default
+2. **Stop conflicting services**: Temporarily stop other PostgreSQL/Redis services
+3. **Customize ports**: Use the `.env.local` file to set different ports
+4. **Use Docker networks**: Run services in isolated Docker networks
+
## Troubleshooting
Here are some errors that you might encounter along with potential fixes:
diff --git a/packages/evals/src/cli/redis.ts b/packages/evals/src/cli/redis.ts
index 8f2c164e49..7e6fa77da5 100644
--- a/packages/evals/src/cli/redis.ts
+++ b/packages/evals/src/cli/redis.ts
@@ -1,7 +1,5 @@
import { createClient, type RedisClientType } from "redis"
-import { EVALS_TIMEOUT } from "@roo-code/types"
-
let redis: RedisClientType | undefined
export const redisClient = async () => {
@@ -18,11 +16,19 @@ export const getPubSubKey = (runId: number) => `evals:${runId}`
export const getRunnersKey = (runId: number) => `runners:${runId}`
export const getHeartbeatKey = (runId: number) => `heartbeat:${runId}`
-export const registerRunner = async ({ runId, taskId }: { runId: number; taskId: number }) => {
+export const registerRunner = async ({
+ runId,
+ taskId,
+ timeoutSeconds,
+}: {
+ runId: number
+ taskId: number
+ timeoutSeconds: number
+}) => {
const redis = await redisClient()
const runnersKey = getRunnersKey(runId)
await redis.sAdd(runnersKey, `task-${taskId}:${process.env.HOSTNAME ?? process.pid}`)
- await redis.expire(runnersKey, EVALS_TIMEOUT / 1_000)
+ await redis.expire(runnersKey, timeoutSeconds)
}
export const deregisterRunner = async ({ runId, taskId }: { runId: number; taskId: number }) => {
diff --git a/packages/evals/src/cli/runTask.ts b/packages/evals/src/cli/runTask.ts
index 507d614ea5..0683cd7238 100644
--- a/packages/evals/src/cli/runTask.ts
+++ b/packages/evals/src/cli/runTask.ts
@@ -5,14 +5,7 @@ import * as os from "node:os"
import pWaitFor from "p-wait-for"
import { execa } from "execa"
-import {
- type TaskEvent,
- TaskCommandName,
- RooCodeEventName,
- IpcMessageType,
- EVALS_SETTINGS,
- EVALS_TIMEOUT,
-} from "@roo-code/types"
+import { type TaskEvent, TaskCommandName, RooCodeEventName, IpcMessageType, EVALS_SETTINGS } from "@roo-code/types"
import { IpcClient } from "@roo-code/ipc"
import {
@@ -42,7 +35,7 @@ export const processTask = async ({ taskId, logger }: { taskId: number; logger?:
const task = await findTask(taskId)
const { language, exercise } = task
const run = await findRun(task.runId)
- await registerRunner({ runId: run.id, taskId })
+ await registerRunner({ runId: run.id, taskId, timeoutSeconds: (run.timeout || 5) * 60 })
const containerized = isDockerContainer()
@@ -304,9 +297,10 @@ export const runTask = async ({ run, task, publish, logger }: RunTaskOptions) =>
})
try {
+ const timeoutMs = (run.timeout || 5) * 60 * 1_000 // Convert minutes to milliseconds
await pWaitFor(() => !!taskFinishedAt || !!taskAbortedAt || isClientDisconnected, {
interval: 1_000,
- timeout: EVALS_TIMEOUT,
+ timeout: timeoutMs,
})
} catch (_error) {
taskTimedOut = true
diff --git a/packages/evals/src/db/migrations/0001_add_timeout_to_runs.sql b/packages/evals/src/db/migrations/0001_add_timeout_to_runs.sql
new file mode 100644
index 0000000000..16d3cc1bdd
--- /dev/null
+++ b/packages/evals/src/db/migrations/0001_add_timeout_to_runs.sql
@@ -0,0 +1 @@
+ALTER TABLE "runs" ADD COLUMN "timeout" integer DEFAULT 5 NOT NULL;
\ No newline at end of file
diff --git a/packages/evals/src/db/queries/__tests__/copyRun.spec.ts b/packages/evals/src/db/queries/__tests__/copyRun.spec.ts
index c693e471db..079373d568 100644
--- a/packages/evals/src/db/queries/__tests__/copyRun.spec.ts
+++ b/packages/evals/src/db/queries/__tests__/copyRun.spec.ts
@@ -23,6 +23,7 @@ describe("copyRun", () => {
socketPath: "/tmp/roo.sock",
description: "Test run for copying",
concurrency: 4,
+ timeout: 5,
})
sourceRunId = run.id
@@ -271,7 +272,7 @@ describe("copyRun", () => {
})
it("should copy run without task metrics", async () => {
- const minimalRun = await createRun({ model: "gpt-3.5-turbo", socketPath: "/tmp/minimal.sock" })
+ const minimalRun = await createRun({ model: "gpt-3.5-turbo", socketPath: "/tmp/minimal.sock", timeout: 5 })
const newRunId = await copyRun({ sourceDb: db, targetDb: db, runId: minimalRun.id })
diff --git a/packages/evals/src/db/schema.ts b/packages/evals/src/db/schema.ts
index 0338b812e2..73705ac054 100644
--- a/packages/evals/src/db/schema.ts
+++ b/packages/evals/src/db/schema.ts
@@ -18,6 +18,7 @@ export const runs = pgTable("runs", {
pid: integer(),
socketPath: text("socket_path").notNull(),
concurrency: integer().default(2).notNull(),
+ timeout: integer().default(5).notNull(),
passed: integer().default(0).notNull(),
failed: integer().default(0).notNull(),
createdAt: timestamp("created_at").notNull(),
diff --git a/packages/types/npm/package.json b/packages/types/npm/package.json
index 924e5251cb..7f364e3d4f 100644
--- a/packages/types/npm/package.json
+++ b/packages/types/npm/package.json
@@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
- "version": "1.35.0",
+ "version": "1.36.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",
diff --git a/packages/types/src/codebase-index.ts b/packages/types/src/codebase-index.ts
index 0ad19d8676..89d5b168d7 100644
--- a/packages/types/src/codebase-index.ts
+++ b/packages/types/src/codebase-index.ts
@@ -21,7 +21,7 @@ export const CODEBASE_INDEX_DEFAULTS = {
export const codebaseIndexConfigSchema = z.object({
codebaseIndexEnabled: z.boolean().optional(),
codebaseIndexQdrantUrl: z.string().optional(),
- codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini"]).optional(),
+ codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible", "gemini", "mistral"]).optional(),
codebaseIndexEmbedderBaseUrl: z.string().optional(),
codebaseIndexEmbedderModelId: z.string().optional(),
codebaseIndexEmbedderModelDimension: z.number().optional(),
@@ -47,6 +47,7 @@ export const codebaseIndexModelsSchema = z.object({
ollama: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
"openai-compatible": z.record(z.string(), z.object({ dimension: z.number() })).optional(),
gemini: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
+ mistral: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
})
export type CodebaseIndexModels = z.infer
@@ -62,6 +63,7 @@ export const codebaseIndexProviderSchema = z.object({
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
codebaseIndexGeminiApiKey: z.string().optional(),
+ codebaseIndexMistralApiKey: z.string().optional(),
})
export type CodebaseIndexProvider = z.infer
diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts
index 129ddebdd5..a30550dce1 100644
--- a/packages/types/src/global-settings.ts
+++ b/packages/types/src/global-settings.ts
@@ -15,6 +15,20 @@ import { modeConfigSchema } from "./mode.js"
import { customModePromptsSchema, customSupportPromptsSchema } from "./mode.js"
import { languagesSchema } from "./vscode.js"
+/**
+ * Default delay in milliseconds after writes to allow diagnostics to detect potential problems.
+ * This delay is particularly important for Go and other languages where tools like goimports
+ * need time to automatically clean up unused imports.
+ */
+export const DEFAULT_WRITE_DELAY_MS = 1000
+
+/**
+ * Default terminal output character limit constant.
+ * This provides a reasonable default that aligns with typical terminal usage
+ * while preventing context window explosions from extremely long lines.
+ */
+export const DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT = 50_000
+
/**
* GlobalSettings
*/
@@ -37,7 +51,7 @@ export const globalSettingsSchema = z.object({
alwaysAllowWrite: z.boolean().optional(),
alwaysAllowWriteOutsideWorkspace: z.boolean().optional(),
alwaysAllowWriteProtected: z.boolean().optional(),
- writeDelayMs: z.number().optional(),
+ writeDelayMs: z.number().min(0).optional(),
alwaysAllowBrowser: z.boolean().optional(),
alwaysApproveResubmit: z.boolean().optional(),
requestDelaySeconds: z.number().optional(),
@@ -51,6 +65,7 @@ export const globalSettingsSchema = z.object({
allowedCommands: z.array(z.string()).optional(),
deniedCommands: z.array(z.string()).optional(),
commandExecutionTimeout: z.number().optional(),
+ commandTimeoutAllowlist: z.array(z.string()).optional(),
preventCompletionWithOpenTodos: z.boolean().optional(),
allowedMaxRequests: z.number().nullish(),
autoCondenseContext: z.boolean().optional(),
@@ -77,6 +92,7 @@ export const globalSettingsSchema = z.object({
maxReadFileLine: z.number().optional(),
terminalOutputLineLimit: z.number().optional(),
+ terminalOutputCharacterLimit: z.number().optional(),
terminalShellIntegrationTimeout: z.number().optional(),
terminalShellIntegrationDisabled: z.boolean().optional(),
terminalCommandDelay: z.number().optional(),
@@ -87,6 +103,8 @@ export const globalSettingsSchema = z.object({
terminalZdotdir: z.boolean().optional(),
terminalCompressProgressBar: z.boolean().optional(),
+ diagnosticsEnabled: z.boolean().optional(),
+
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
fuzzyMatchThreshold: z.number().optional(),
@@ -152,6 +170,7 @@ export const SECRET_STATE_KEYS = [
"codeIndexQdrantApiKey",
"codebaseIndexOpenAiCompatibleApiKey",
"codebaseIndexGeminiApiKey",
+ "codebaseIndexMistralApiKey",
] as const satisfies readonly (keyof ProviderSettings)[]
export type SecretState = Pick
@@ -203,6 +222,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
followupAutoApproveTimeoutMs: 0,
allowedCommands: ["*"],
commandExecutionTimeout: 30_000,
+ commandTimeoutAllowlist: [],
preventCompletionWithOpenTodos: false,
browserToolEnabled: false,
@@ -216,6 +236,7 @@ export const EVALS_SETTINGS: RooCodeSettings = {
soundVolume: 0.5,
terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
terminalShellIntegrationTimeout: 30000,
terminalCommandDelay: 0,
terminalPowershellCounter: false,
@@ -226,6 +247,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
terminalCompressProgressBar: true,
terminalShellIntegrationDisabled: true,
+ diagnosticsEnabled: true,
+
diffEnabled: true,
fuzzyMatchThreshold: 1,
diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts
index 3da22d1be8..3e1ccb5571 100644
--- a/src/core/config/CustomModesManager.ts
+++ b/src/core/config/CustomModesManager.ts
@@ -558,48 +558,54 @@ export class CustomModesManager {
*/
public async checkRulesDirectoryHasContent(slug: string): Promise {
try {
- // Get workspace path
- const workspacePath = getWorkspacePath()
- if (!workspacePath) {
- return false
- }
+ // First, find the mode to determine its source
+ const allModes = await this.getCustomModes()
+ const mode = allModes.find((m) => m.slug === slug)
- // Check if .roomodes file exists and contains this mode
- // This ensures we can only consolidate rules for modes that have been customized
- const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME)
- try {
- const roomodesExists = await fileExistsAtPath(roomodesPath)
- if (roomodesExists) {
- const roomodesContent = await fs.readFile(roomodesPath, "utf-8")
- const roomodesData = yaml.parse(roomodesContent)
- const roomodesModes = roomodesData?.customModes || []
-
- // Check if this specific mode exists in .roomodes
- const modeInRoomodes = roomodesModes.find((m: any) => m.slug === slug)
- if (!modeInRoomodes) {
- return false // Mode not customized in .roomodes, cannot consolidate
- }
- } else {
- // If no .roomodes file exists, check if it's in global custom modes
- const allModes = await this.getCustomModes()
- const mode = allModes.find((m) => m.slug === slug)
-
- if (!mode) {
- return false // Not a custom mode, cannot consolidate
- }
+ if (!mode) {
+ // If not in custom modes, check if it's in .roomodes (project-specific)
+ const workspacePath = getWorkspacePath()
+ if (!workspacePath) {
+ return false
}
- } catch (error) {
- // If we can't read .roomodes, fall back to checking custom modes
- const allModes = await this.getCustomModes()
- const mode = allModes.find((m) => m.slug === slug)
- if (!mode) {
- return false // Not a custom mode, cannot consolidate
+ const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME)
+ try {
+ const roomodesExists = await fileExistsAtPath(roomodesPath)
+ if (roomodesExists) {
+ const roomodesContent = await fs.readFile(roomodesPath, "utf-8")
+ const roomodesData = yaml.parse(roomodesContent)
+ const roomodesModes = roomodesData?.customModes || []
+
+ // Check if this specific mode exists in .roomodes
+ const modeInRoomodes = roomodesModes.find((m: any) => m.slug === slug)
+ if (!modeInRoomodes) {
+ return false // Mode not found anywhere
+ }
+ } else {
+ return false // No .roomodes file and not in custom modes
+ }
+ } catch (error) {
+ return false // Cannot read .roomodes and not in custom modes
}
}
- // Check for .roo/rules-{slug}/ directory
- const modeRulesDir = path.join(workspacePath, ".roo", `rules-${slug}`)
+ // Determine the correct rules directory based on mode source
+ let modeRulesDir: string
+ const isGlobalMode = mode?.source === "global"
+
+ if (isGlobalMode) {
+ // For global modes, check in global .roo directory
+ const globalRooDir = getGlobalRooDirectory()
+ modeRulesDir = path.join(globalRooDir, `rules-${slug}`)
+ } else {
+ // For project modes, check in workspace .roo directory
+ const workspacePath = getWorkspacePath()
+ if (!workspacePath) {
+ return false
+ }
+ modeRulesDir = path.join(workspacePath, ".roo", `rules-${slug}`)
+ }
try {
const stats = await fs.stat(modeRulesDir)
@@ -655,24 +661,23 @@ export class CustomModesManager {
// If mode not found in custom modes, check if it's a built-in mode that has been customized
if (!mode) {
+ // Only check workspace-based modes if workspace is available
const workspacePath = getWorkspacePath()
- if (!workspacePath) {
- return { success: false, error: "No workspace found" }
- }
+ if (workspacePath) {
+ const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME)
+ try {
+ const roomodesExists = await fileExistsAtPath(roomodesPath)
+ if (roomodesExists) {
+ const roomodesContent = await fs.readFile(roomodesPath, "utf-8")
+ const roomodesData = yaml.parse(roomodesContent)
+ const roomodesModes = roomodesData?.customModes || []
- const roomodesPath = path.join(workspacePath, ROOMODES_FILENAME)
- try {
- const roomodesExists = await fileExistsAtPath(roomodesPath)
- if (roomodesExists) {
- const roomodesContent = await fs.readFile(roomodesPath, "utf-8")
- const roomodesData = yaml.parse(roomodesContent)
- const roomodesModes = roomodesData?.customModes || []
-
- // Find the mode in .roomodes
- mode = roomodesModes.find((m: any) => m.slug === slug)
+ // Find the mode in .roomodes
+ mode = roomodesModes.find((m: any) => m.slug === slug)
+ }
+ } catch (error) {
+ // Continue to check built-in modes
}
- } catch (error) {
- // Continue to check built-in modes
}
// If still not found, check if it's a built-in mode
@@ -687,14 +692,25 @@ export class CustomModesManager {
}
}
- // Get workspace path
- const workspacePath = getWorkspacePath()
- if (!workspacePath) {
- return { success: false, error: "No workspace found" }
+ // Determine the base directory based on mode source
+ const isGlobalMode = mode.source === "global"
+ let baseDir: string
+ if (isGlobalMode) {
+ // For global modes, use the global .roo directory
+ baseDir = getGlobalRooDirectory()
+ } else {
+ // For project modes, use the workspace directory
+ const workspacePath = getWorkspacePath()
+ if (!workspacePath) {
+ return { success: false, error: "No workspace found" }
+ }
+ baseDir = workspacePath
}
- // Check for .roo/rules-{slug}/ directory
- const modeRulesDir = path.join(workspacePath, ".roo", `rules-${slug}`)
+ // Check for .roo/rules-{slug}/ directory (or rules-{slug}/ for global)
+ const modeRulesDir = isGlobalMode
+ ? path.join(baseDir, `rules-${slug}`)
+ : path.join(baseDir, ".roo", `rules-${slug}`)
let rulesFiles: RuleFile[] = []
try {
@@ -709,8 +725,10 @@ export class CustomModesManager {
const filePath = path.join(modeRulesDir, entry.name)
const content = await fs.readFile(filePath, "utf-8")
if (content.trim()) {
- // Calculate relative path from .roo directory
- const relativePath = path.relative(path.join(workspacePath, ".roo"), filePath)
+ // Calculate relative path based on mode source
+ const relativePath = isGlobalMode
+ ? path.relative(baseDir, filePath)
+ : path.relative(path.join(baseDir, ".roo"), filePath)
rulesFiles.push({ relativePath, content: content.trim() })
}
}
@@ -755,6 +773,77 @@ export class CustomModesManager {
}
}
+ /**
+ * Helper method to import rules files for a mode
+ * @param importMode - The mode being imported
+ * @param rulesFiles - The rules files to import
+ * @param source - The import source ("global" or "project")
+ */
+ private async importRulesFiles(
+ importMode: ExportedModeConfig,
+ rulesFiles: RuleFile[],
+ source: "global" | "project",
+ ): Promise {
+ // Determine base directory and rules folder path based on source
+ let baseDir: string
+ let rulesFolderPath: string
+
+ if (source === "global") {
+ baseDir = getGlobalRooDirectory()
+ rulesFolderPath = path.join(baseDir, `rules-${importMode.slug}`)
+ } else {
+ const workspacePath = getWorkspacePath()
+ baseDir = path.join(workspacePath, ".roo")
+ rulesFolderPath = path.join(baseDir, `rules-${importMode.slug}`)
+ }
+
+ // Always remove the existing rules folder for this mode if it exists
+ // This ensures that if the imported mode has no rules, the folder is cleaned up
+ try {
+ await fs.rm(rulesFolderPath, { recursive: true, force: true })
+ logger.info(`Removed existing ${source} rules folder for mode ${importMode.slug}`)
+ } catch (error) {
+ // It's okay if the folder doesn't exist
+ logger.debug(`No existing ${source} rules folder to remove for mode ${importMode.slug}`)
+ }
+
+ // Only proceed with file creation if there are rules files to import
+ if (!rulesFiles || !Array.isArray(rulesFiles) || rulesFiles.length === 0) {
+ return
+ }
+
+ // Import the new rules files with path validation
+ for (const ruleFile of rulesFiles) {
+ if (ruleFile.relativePath && ruleFile.content) {
+ // Validate the relative path to prevent path traversal attacks
+ const normalizedRelativePath = path.normalize(ruleFile.relativePath)
+
+ // Ensure the path doesn't contain traversal sequences
+ if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) {
+ logger.error(`Invalid file path detected: ${ruleFile.relativePath}`)
+ continue // Skip this file but continue with others
+ }
+
+ const targetPath = path.join(baseDir, normalizedRelativePath)
+ const normalizedTargetPath = path.normalize(targetPath)
+ const expectedBasePath = path.normalize(baseDir)
+
+ // Ensure the resolved path stays within the base directory
+ if (!normalizedTargetPath.startsWith(expectedBasePath)) {
+ logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`)
+ continue // Skip this file but continue with others
+ }
+
+ // Ensure directory exists
+ const targetDir = path.dirname(targetPath)
+ await fs.mkdir(targetDir, { recursive: true })
+
+ // Write the file
+ await fs.writeFile(targetPath, ruleFile.content, "utf-8")
+ }
+ }
+ }
+
/**
* Imports modes from YAML content, including their associated rules files
* @param yamlContent - The YAML content containing mode configurations
@@ -821,100 +910,8 @@ export class CustomModesManager {
source: source, // Use the provided source parameter
})
- // Handle project-level imports
- if (source === "project") {
- const workspacePath = getWorkspacePath()
-
- // Always remove the existing rules folder for this mode if it exists
- // This ensures that if the imported mode has no rules, the folder is cleaned up
- const rulesFolderPath = path.join(workspacePath, ".roo", `rules-${importMode.slug}`)
- try {
- await fs.rm(rulesFolderPath, { recursive: true, force: true })
- logger.info(`Removed existing rules folder for mode ${importMode.slug}`)
- } catch (error) {
- // It's okay if the folder doesn't exist
- logger.debug(`No existing rules folder to remove for mode ${importMode.slug}`)
- }
-
- // Only create new rules files if they exist in the import
- if (rulesFiles && Array.isArray(rulesFiles) && rulesFiles.length > 0) {
- // Import the new rules files with path validation
- for (const ruleFile of rulesFiles) {
- if (ruleFile.relativePath && ruleFile.content) {
- // Validate the relative path to prevent path traversal attacks
- const normalizedRelativePath = path.normalize(ruleFile.relativePath)
-
- // Ensure the path doesn't contain traversal sequences
- if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) {
- logger.error(`Invalid file path detected: ${ruleFile.relativePath}`)
- continue // Skip this file but continue with others
- }
-
- const targetPath = path.join(workspacePath, ".roo", normalizedRelativePath)
- const normalizedTargetPath = path.normalize(targetPath)
- const expectedBasePath = path.normalize(path.join(workspacePath, ".roo"))
-
- // Ensure the resolved path stays within the .roo directory
- if (!normalizedTargetPath.startsWith(expectedBasePath)) {
- logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`)
- continue // Skip this file but continue with others
- }
-
- // Ensure directory exists
- const targetDir = path.dirname(targetPath)
- await fs.mkdir(targetDir, { recursive: true })
-
- // Write the file
- await fs.writeFile(targetPath, ruleFile.content, "utf-8")
- }
- }
- }
- } else if (source === "global" && rulesFiles && Array.isArray(rulesFiles)) {
- // For global imports, preserve the rules files structure in the global .roo directory
- const globalRooDir = getGlobalRooDirectory()
-
- // Always remove the existing rules folder for this mode if it exists
- // This ensures that if the imported mode has no rules, the folder is cleaned up
- const rulesFolderPath = path.join(globalRooDir, `rules-${importMode.slug}`)
- try {
- await fs.rm(rulesFolderPath, { recursive: true, force: true })
- logger.info(`Removed existing global rules folder for mode ${importMode.slug}`)
- } catch (error) {
- // It's okay if the folder doesn't exist
- logger.debug(`No existing global rules folder to remove for mode ${importMode.slug}`)
- }
-
- // Import the new rules files with path validation
- for (const ruleFile of rulesFiles) {
- if (ruleFile.relativePath && ruleFile.content) {
- // Validate the relative path to prevent path traversal attacks
- const normalizedRelativePath = path.normalize(ruleFile.relativePath)
-
- // Ensure the path doesn't contain traversal sequences
- if (normalizedRelativePath.includes("..") || path.isAbsolute(normalizedRelativePath)) {
- logger.error(`Invalid file path detected: ${ruleFile.relativePath}`)
- continue // Skip this file but continue with others
- }
-
- const targetPath = path.join(globalRooDir, normalizedRelativePath)
- const normalizedTargetPath = path.normalize(targetPath)
- const expectedBasePath = path.normalize(globalRooDir)
-
- // Ensure the resolved path stays within the global .roo directory
- if (!normalizedTargetPath.startsWith(expectedBasePath)) {
- logger.error(`Path traversal attempt detected: ${ruleFile.relativePath}`)
- continue // Skip this file but continue with others
- }
-
- // Ensure directory exists
- const targetDir = path.dirname(targetPath)
- await fs.mkdir(targetDir, { recursive: true })
-
- // Write the file
- await fs.writeFile(targetPath, ruleFile.content, "utf-8")
- }
- }
- }
+ // Import rules files (this also handles cleanup of existing rules folders)
+ await this.importRulesFiles(importMode, rulesFiles || [], source)
}
// Refresh the modes after import
diff --git a/src/core/config/__tests__/CustomModesManager.spec.ts b/src/core/config/__tests__/CustomModesManager.spec.ts
index c325a27f75..682696fd03 100644
--- a/src/core/config/__tests__/CustomModesManager.spec.ts
+++ b/src/core/config/__tests__/CustomModesManager.spec.ts
@@ -1373,7 +1373,7 @@ describe("CustomModesManager", () => {
})
describe("exportModeWithRules", () => {
- it("should return error when no workspace is available", async () => {
+ it("should return error when mode is not found and no workspace is available", async () => {
// Create a fresh manager instance to avoid cache issues
const freshManager = new CustomModesManager(mockContext, mockOnUpdate)
@@ -1391,7 +1391,7 @@ describe("CustomModesManager", () => {
const result = await freshManager.exportModeWithRules("test-mode")
expect(result.success).toBe(false)
- expect(result.error).toBe("No workspace found")
+ expect(result.error).toBe("Mode not found")
})
it("should return error when mode is not found", async () => {
@@ -1571,5 +1571,133 @@ describe("CustomModesManager", () => {
expect(result.success).toBe(true)
expect(result.yaml).toContain("test-mode")
})
+
+ it("should successfully export global mode with rules from global .roo directory", async () => {
+ // Mock a global mode
+ const globalMode = {
+ slug: "global-test-mode",
+ name: "Global Test Mode",
+ roleDefinition: "Global Test Role",
+ groups: ["read"],
+ source: "global",
+ }
+
+ // Create a fresh manager instance to avoid cache issues
+ const freshManager = new CustomModesManager(mockContext, mockOnUpdate)
+
+ ;(fs.readFile as Mock).mockImplementation(async (path: string) => {
+ if (path === mockSettingsPath) {
+ return yaml.stringify({ customModes: [globalMode] })
+ }
+ if (path.includes("rules-global-test-mode") && path.includes("rule1.md")) {
+ return "Global rule content"
+ }
+ throw new Error("File not found")
+ })
+ ;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
+ return path === mockSettingsPath
+ })
+ ;(fs.stat as Mock).mockImplementation(async (path: string) => {
+ if (path.includes("rules-global-test-mode")) {
+ return { isDirectory: () => true }
+ }
+ throw new Error("Directory not found")
+ })
+ ;(fs.readdir as Mock).mockImplementation(async (path: string) => {
+ if (path.includes("rules-global-test-mode")) {
+ return [{ name: "rule1.md", isFile: () => true }]
+ }
+ return []
+ })
+
+ const result = await freshManager.exportModeWithRules("global-test-mode")
+
+ expect(result.success).toBe(true)
+ expect(result.yaml).toContain("global-test-mode")
+ expect(result.yaml).toContain("Global Test Mode")
+ expect(result.yaml).toContain("Global rule content")
+ })
+
+ it("should successfully export global mode without rules when global rules directory doesn't exist", async () => {
+ // Mock a global mode
+ const globalMode = {
+ slug: "global-test-mode",
+ name: "Global Test Mode",
+ roleDefinition: "Global Test Role",
+ groups: ["read"],
+ source: "global",
+ }
+
+ // Create a fresh manager instance to avoid cache issues
+ const freshManager = new CustomModesManager(mockContext, mockOnUpdate)
+
+ ;(fs.readFile as Mock).mockImplementation(async (path: string) => {
+ if (path === mockSettingsPath) {
+ return yaml.stringify({ customModes: [globalMode] })
+ }
+ throw new Error("File not found")
+ })
+ ;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
+ return path === mockSettingsPath
+ })
+ ;(fs.stat as Mock).mockRejectedValue(new Error("Directory not found"))
+
+ const result = await freshManager.exportModeWithRules("global-test-mode")
+
+ expect(result.success).toBe(true)
+ expect(result.yaml).toContain("global-test-mode")
+ expect(result.yaml).toContain("Global Test Mode")
+ // Should not contain rulesFiles since no rules directory exists
+ expect(result.yaml).not.toContain("rulesFiles")
+ })
+
+ it("should handle global mode export when workspace is not available", async () => {
+ // Mock a global mode
+ const globalMode = {
+ slug: "global-test-mode",
+ name: "Global Test Mode",
+ roleDefinition: "Global Test Role",
+ groups: ["read"],
+ source: "global",
+ }
+
+ // Create a fresh manager instance to avoid cache issues
+ const freshManager = new CustomModesManager(mockContext, mockOnUpdate)
+
+ // Mock no workspace folders
+ ;(vscode.workspace as any).workspaceFolders = []
+ ;(getWorkspacePath as Mock).mockReturnValue(null)
+ ;(fs.readFile as Mock).mockImplementation(async (path: string) => {
+ if (path === mockSettingsPath) {
+ return yaml.stringify({ customModes: [globalMode] })
+ }
+ if (path.includes("rules-global-test-mode") && path.includes("rule1.md")) {
+ return "Global rule content"
+ }
+ throw new Error("File not found")
+ })
+ ;(fileExistsAtPath as Mock).mockImplementation(async (path: string) => {
+ return path === mockSettingsPath
+ })
+ ;(fs.stat as Mock).mockImplementation(async (path: string) => {
+ if (path.includes("rules-global-test-mode")) {
+ return { isDirectory: () => true }
+ }
+ throw new Error("Directory not found")
+ })
+ ;(fs.readdir as Mock).mockImplementation(async (path: string) => {
+ if (path.includes("rules-global-test-mode")) {
+ return [{ name: "rule1.md", isFile: () => true }]
+ }
+ return []
+ })
+
+ const result = await freshManager.exportModeWithRules("global-test-mode")
+
+ // Should succeed even without workspace since it's a global mode
+ expect(result.success).toBe(true)
+ expect(result.yaml).toContain("global-test-mode")
+ expect(result.yaml).toContain("Global rule content")
+ })
})
})
diff --git a/src/core/environment/getEnvironmentDetails.ts b/src/core/environment/getEnvironmentDetails.ts
index 6f0c9fe2bf..b83b37c75b 100644
--- a/src/core/environment/getEnvironmentDetails.ts
+++ b/src/core/environment/getEnvironmentDetails.ts
@@ -6,6 +6,7 @@ import pWaitFor from "p-wait-for"
import delay from "delay"
import type { ExperimentId } from "@roo-code/types"
+import { DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT } from "@roo-code/types"
import { EXPERIMENT_IDS, experiments as Experiments } from "../../shared/experiments"
import { formatLanguage } from "../../shared/language"
@@ -25,7 +26,11 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
const clineProvider = cline.providerRef.deref()
const state = await clineProvider?.getState()
- const { terminalOutputLineLimit = 500, maxWorkspaceFiles = 200 } = state ?? {}
+ const {
+ terminalOutputLineLimit = 500,
+ terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
+ maxWorkspaceFiles = 200,
+ } = state ?? {}
// It could be useful for cline to know if the user went from one or no
// file to another between messages, so we always include this context.
@@ -111,7 +116,11 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
let newOutput = TerminalRegistry.getUnretrievedOutput(busyTerminal.id)
if (newOutput) {
- newOutput = Terminal.compressTerminalOutput(newOutput, terminalOutputLineLimit)
+ newOutput = Terminal.compressTerminalOutput(
+ newOutput,
+ terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
+ )
terminalDetails += `\n### New Output\n${newOutput}`
}
}
@@ -139,7 +148,11 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
let output = process.getUnretrievedOutput()
if (output) {
- output = Terminal.compressTerminalOutput(output, terminalOutputLineLimit)
+ output = Terminal.compressTerminalOutput(
+ output,
+ terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
+ )
terminalOutputs.push(`Command: \`${process.command}\`\n${output}`)
}
}
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
index 8563bef9cc..e80857d354 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-disabled.snap
@@ -269,55 +269,6 @@ Examples:
true
-## use_mcp_tool
-Description: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.
-Parameters:
-- server_name: (required) The name of the MCP server providing the tool
-- tool_name: (required) The name of the tool to execute
-- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
-Usage:
-
-server name here
-tool name here
-
-{
- "param1": "value1",
- "param2": "value2"
-}
-
-
-
-Example: Requesting to use an MCP tool
-
-
-weather-server
-get_forecast
-
-{
- "city": "San Francisco",
- "days": 5
-}
-
-
-
-## access_mcp_resource
-Description: Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.
-Parameters:
-- server_name: (required) The name of the MCP server providing the resource
-- uri: (required) The URI identifying the specific resource to access
-Usage:
-
-server name here
-resource URI here
-
-
-Example: Requesting to access an MCP resource
-
-
-weather-server
-weather://san-francisco/current
-
-
## ask_followup_question
Description: Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.
Parameters:
@@ -508,18 +459,7 @@ It is crucial to proceed step-by-step, waiting for the user's message after each
By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.
-MCP SERVERS
-The Model Context Protocol (MCP) enables communication between the system and MCP servers that provide additional tools and resources to extend your capabilities. MCP servers can be one of two types:
-
-1. Local (Stdio-based) servers: These run locally on the user's machine and communicate via standard input/output
-2. Remote (SSE-based) servers: These run on remote machines and communicate via Server-Sent Events (SSE) over HTTP/HTTPS
-
-# Connected MCP Servers
-
-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.
-
-(No MCP servers currently connected)
====
@@ -531,8 +471,6 @@ CAPABILITIES
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to apply the changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.
-- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
-
====
diff --git a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
index fd8ef4dc98..4dce0f264a 100644
--- a/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
+++ b/src/core/prompts/__tests__/__snapshots__/add-custom-instructions/mcp-server-creation-enabled.snap
@@ -519,7 +519,7 @@ The Model Context Protocol (MCP) enables communication between the system and MC
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.
-(No MCP servers currently connected)
+
## Creating an MCP Server
The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. If they do, you should obtain detailed instructions on this topic using the fetch_instructions tool, like this:
diff --git a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
index fd8ef4dc98..4dce0f264a 100644
--- a/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
+++ b/src/core/prompts/__tests__/__snapshots__/system-prompt/with-mcp-hub-provided.snap
@@ -519,7 +519,7 @@ The Model Context Protocol (MCP) enables communication between the system and MC
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.
-(No MCP servers currently connected)
+
## Creating an MCP Server
The user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. If they do, you should obtain detailed instructions on this topic using the fetch_instructions tool, like this:
diff --git a/src/core/prompts/__tests__/add-custom-instructions.spec.ts b/src/core/prompts/__tests__/add-custom-instructions.spec.ts
index b2ca5589f9..5097685e3b 100644
--- a/src/core/prompts/__tests__/add-custom-instructions.spec.ts
+++ b/src/core/prompts/__tests__/add-custom-instructions.spec.ts
@@ -168,9 +168,9 @@ const mockContext = {
} as unknown as vscode.ExtensionContext
// Instead of extending McpHub, create a mock that implements just what we need
-const createMockMcpHub = (): McpHub =>
+const createMockMcpHub = (withServers: boolean = false): McpHub =>
({
- getServers: () => [],
+ getServers: () => (withServers ? [{ name: "test-server", disabled: false }] : []),
getMcpServersPath: async () => "/mock/mcp/path",
getMcpSettingsFilePath: async () => "/mock/settings/path",
dispose: async () => {},
@@ -236,7 +236,7 @@ describe("addCustomInstructions", () => {
})
it("should include MCP server creation info when enabled", async () => {
- const mockMcpHub = createMockMcpHub()
+ const mockMcpHub = createMockMcpHub(true)
const prompt = await SYSTEM_PROMPT(
mockContext,
@@ -262,7 +262,7 @@ describe("addCustomInstructions", () => {
})
it("should exclude MCP server creation info when disabled", async () => {
- const mockMcpHub = createMockMcpHub()
+ const mockMcpHub = createMockMcpHub(false)
const prompt = await SYSTEM_PROMPT(
mockContext,
diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts
index e6af6eaf5a..175f3ba265 100644
--- a/src/core/prompts/__tests__/system-prompt.spec.ts
+++ b/src/core/prompts/__tests__/system-prompt.spec.ts
@@ -168,9 +168,9 @@ const mockContext = {
} as unknown as vscode.ExtensionContext
// Instead of extending McpHub, create a mock that implements just what we need
-const createMockMcpHub = (): McpHub =>
+const createMockMcpHub = (withServers: boolean = false): McpHub =>
({
- getServers: () => [],
+ getServers: () => (withServers ? [{ name: "test-server", disabled: false }] : []),
getMcpServersPath: async () => "/mock/mcp/path",
getMcpSettingsFilePath: async () => "/mock/settings/path",
dispose: async () => {},
@@ -250,7 +250,7 @@ describe("SYSTEM_PROMPT", () => {
})
it("should include MCP server info when mcpHub is provided", async () => {
- mockMcpHub = createMockMcpHub()
+ mockMcpHub = createMockMcpHub(true)
const prompt = await SYSTEM_PROMPT(
mockContext,
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index bfc12930a0..92653cafd2 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -71,9 +71,14 @@ async function generatePrompt(
const modeConfig = getModeBySlug(mode, customModeConfigs) || modes.find((m) => m.slug === mode) || modes[0]
const { roleDefinition, baseInstructions } = getModeSelection(mode, promptComponent, customModeConfigs)
+ // Check if MCP functionality should be included
+ const hasMcpGroup = modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "mcp")
+ const hasMcpServers = mcpHub && mcpHub.getServers().length > 0
+ const shouldIncludeMcp = hasMcpGroup && hasMcpServers
+
const [modesSection, mcpServersSection] = await Promise.all([
getModesSection(context),
- modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "mcp")
+ shouldIncludeMcp
? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation)
: Promise.resolve(""),
])
@@ -93,7 +98,7 @@ ${getToolDescriptionsForMode(
codeIndexManager,
effectiveDiffStrategy,
browserViewportSize,
- mcpHub,
+ shouldIncludeMcp ? mcpHub : undefined,
customModeConfigs,
experiments,
partialReadsEnabled,
@@ -104,7 +109,7 @@ ${getToolUseGuidelinesSection(codeIndexManager)}
${mcpServersSection}
-${getCapabilitiesSection(cwd, supportsComputerUse, mcpHub, effectiveDiffStrategy, codeIndexManager)}
+${getCapabilitiesSection(cwd, supportsComputerUse, shouldIncludeMcp ? mcpHub : undefined, effectiveDiffStrategy, codeIndexManager)}
${modesSection}
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index 53b8ef5b87..ccd24b7d71 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -1442,16 +1442,18 @@ export class Task extends EventEmitter {
// could be in (i.e. could have streamed some tools the user
// may have executed), so we just resort to replicating a
// cancel task.
- this.abortTask()
- // Check if this was a user-initiated cancellation
- // If this.abort is true, it means the user clicked cancel, so we should
+ // Check if this was a user-initiated cancellation BEFORE calling abortTask
+ // If this.abort is already true, it means the user clicked cancel, so we should
// treat this as "user_cancelled" rather than "streaming_failed"
const cancelReason = this.abort ? "user_cancelled" : "streaming_failed"
const streamingFailedMessage = this.abort
? undefined
: (error.message ?? JSON.stringify(serializeError(error), null, 2))
+ // Now call abortTask after determining the cancel reason
+ await this.abortTask()
+
await abortStream(cancelReason, streamingFailedMessage)
const history = await provider?.getTaskWithId(this.taskId)
diff --git a/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts b/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts
index de98c9df20..b9e0af3a8a 100644
--- a/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts
+++ b/src/core/tools/__tests__/executeCommandTimeout.integration.spec.ts
@@ -3,7 +3,7 @@
import * as vscode from "vscode"
import * as fs from "fs/promises"
-import { executeCommand, ExecuteCommandOptions } from "../executeCommandTool"
+import { executeCommand, executeCommandTool, ExecuteCommandOptions } from "../executeCommandTool"
import { Task } from "../../task/Task"
import { TerminalRegistry } from "../../../integrations/terminal/TerminalRegistry"
@@ -17,6 +17,20 @@ vitest.mock("vscode", () => ({
vitest.mock("fs/promises")
vitest.mock("../../../integrations/terminal/TerminalRegistry")
vitest.mock("../../task/Task")
+vitest.mock("../../prompts/responses", () => ({
+ formatResponse: {
+ toolError: vitest.fn((msg) => `Tool Error: ${msg}`),
+ rooIgnoreError: vitest.fn((msg) => `RooIgnore Error: ${msg}`),
+ },
+}))
+vitest.mock("../../../utils/text-normalization", () => ({
+ unescapeHtmlEntities: vitest.fn((text) => text),
+}))
+vitest.mock("../../../shared/package", () => ({
+ Package: {
+ name: "roo-cline",
+ },
+}))
describe("Command Execution Timeout Integration", () => {
let mockTask: any
@@ -186,4 +200,213 @@ describe("Command Execution Timeout Integration", () => {
expect(result[0]).toBe(false) // Not rejected
expect(result[1]).not.toContain("terminated after exceeding")
})
+
+ describe("Command Timeout Allowlist", () => {
+ let mockBlock: any
+ let mockAskApproval: any
+ let mockHandleError: any
+ let mockPushToolResult: any
+ let mockRemoveClosingTag: any
+
+ beforeEach(() => {
+ // Reset mocks for allowlist tests
+ vitest.clearAllMocks()
+ ;(fs.access as any).mockResolvedValue(undefined)
+ ;(TerminalRegistry.getOrCreateTerminal as any).mockResolvedValue(mockTerminal)
+
+ // Mock the executeCommandTool parameters
+ mockBlock = {
+ params: {
+ command: "",
+ cwd: undefined,
+ },
+ partial: false,
+ }
+
+ mockAskApproval = vitest.fn().mockResolvedValue(true) // Always approve
+ mockHandleError = vitest.fn()
+ mockPushToolResult = vitest.fn()
+ mockRemoveClosingTag = vitest.fn()
+
+ // Mock task with additional properties needed by executeCommandTool
+ mockTask = {
+ cwd: "/test/directory",
+ terminalProcess: undefined,
+ providerRef: {
+ deref: vitest.fn().mockResolvedValue({
+ postMessageToWebview: vitest.fn(),
+ getState: vitest.fn().mockResolvedValue({
+ terminalOutputLineLimit: 500,
+ terminalShellIntegrationDisabled: false,
+ }),
+ }),
+ },
+ say: vitest.fn().mockResolvedValue(undefined),
+ consecutiveMistakeCount: 0,
+ recordToolError: vitest.fn(),
+ sayAndCreateMissingParamError: vitest.fn(),
+ rooIgnoreController: {
+ validateCommand: vitest.fn().mockReturnValue(null),
+ },
+ lastMessageTs: Date.now(),
+ ask: vitest.fn(),
+ didRejectTool: false,
+ }
+ })
+
+ it("should skip timeout for commands in allowlist", async () => {
+ // Mock VSCode configuration with timeout and allowlist
+ const mockGetConfiguration = vitest.fn().mockReturnValue({
+ get: vitest.fn().mockImplementation((key: string) => {
+ if (key === "commandExecutionTimeout") return 1 // 1 second timeout
+ if (key === "commandTimeoutAllowlist") return ["npm", "git"]
+ return undefined
+ }),
+ })
+ ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
+
+ mockBlock.params.command = "npm install"
+
+ // Create a process that would timeout if not allowlisted
+ const longRunningProcess = new Promise((resolve) => {
+ setTimeout(resolve, 2000) // 2 seconds, longer than 1 second timeout
+ })
+ mockTerminal.runCommand.mockReturnValue(longRunningProcess)
+
+ await executeCommandTool(
+ mockTask as Task,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should complete successfully without timeout because "npm" is in allowlist
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const result = mockPushToolResult.mock.calls[0][0]
+ expect(result).not.toContain("terminated after exceeding")
+ }, 3000)
+
+ it("should apply timeout for commands not in allowlist", async () => {
+ // Mock VSCode configuration with timeout and allowlist
+ const mockGetConfiguration = vitest.fn().mockReturnValue({
+ get: vitest.fn().mockImplementation((key: string) => {
+ if (key === "commandExecutionTimeout") return 1 // 1 second timeout
+ if (key === "commandTimeoutAllowlist") return ["npm", "git"]
+ return undefined
+ }),
+ })
+ ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
+
+ mockBlock.params.command = "sleep 10" // Not in allowlist
+
+ // Create a process that never resolves
+ const neverResolvingProcess = new Promise(() => {})
+ ;(neverResolvingProcess as any).abort = vitest.fn()
+ mockTerminal.runCommand.mockReturnValue(neverResolvingProcess)
+
+ await executeCommandTool(
+ mockTask as Task,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should timeout because "sleep" is not in allowlist
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const result = mockPushToolResult.mock.calls[0][0]
+ expect(result).toContain("terminated after exceeding")
+ }, 3000)
+
+ it("should handle empty allowlist", async () => {
+ // Mock VSCode configuration with timeout and empty allowlist
+ const mockGetConfiguration = vitest.fn().mockReturnValue({
+ get: vitest.fn().mockImplementation((key: string) => {
+ if (key === "commandExecutionTimeout") return 1 // 1 second timeout
+ if (key === "commandTimeoutAllowlist") return []
+ return undefined
+ }),
+ })
+ ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
+
+ mockBlock.params.command = "npm install"
+
+ // Create a process that never resolves
+ const neverResolvingProcess = new Promise(() => {})
+ ;(neverResolvingProcess as any).abort = vitest.fn()
+ mockTerminal.runCommand.mockReturnValue(neverResolvingProcess)
+
+ await executeCommandTool(
+ mockTask as Task,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ // Should timeout because allowlist is empty
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const result = mockPushToolResult.mock.calls[0][0]
+ expect(result).toContain("terminated after exceeding")
+ }, 3000)
+
+ it("should match command prefixes correctly", async () => {
+ // Mock VSCode configuration with timeout and allowlist
+ const mockGetConfiguration = vitest.fn().mockReturnValue({
+ get: vitest.fn().mockImplementation((key: string) => {
+ if (key === "commandExecutionTimeout") return 1 // 1 second timeout
+ if (key === "commandTimeoutAllowlist") return ["git log", "npm run"]
+ return undefined
+ }),
+ })
+ ;(vscode.workspace.getConfiguration as any).mockReturnValue(mockGetConfiguration())
+
+ const longRunningProcess = new Promise((resolve) => {
+ setTimeout(resolve, 2000) // 2 seconds
+ })
+ const neverResolvingProcess = new Promise(() => {})
+ ;(neverResolvingProcess as any).abort = vitest.fn()
+
+ // Test exact prefix match - should not timeout
+ mockBlock.params.command = "git log --oneline"
+ mockTerminal.runCommand.mockReturnValueOnce(longRunningProcess)
+
+ await executeCommandTool(
+ mockTask as Task,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const result1 = mockPushToolResult.mock.calls[0][0]
+ expect(result1).not.toContain("terminated after exceeding")
+
+ // Reset mocks for second test
+ mockPushToolResult.mockClear()
+
+ // Test partial prefix match (should not match) - should timeout
+ mockBlock.params.command = "git status" // "git" alone is not in allowlist, only "git log"
+ mockTerminal.runCommand.mockReturnValueOnce(neverResolvingProcess)
+
+ await executeCommandTool(
+ mockTask as Task,
+ mockBlock,
+ mockAskApproval,
+ mockHandleError,
+ mockPushToolResult,
+ mockRemoveClosingTag,
+ )
+
+ expect(mockPushToolResult).toHaveBeenCalled()
+ const result2 = mockPushToolResult.mock.calls[0][0]
+ expect(result2).toContain("terminated after exceeding")
+ }, 5000)
+ })
})
diff --git a/src/core/tools/__tests__/insertContentTool.spec.ts b/src/core/tools/__tests__/insertContentTool.spec.ts
index c980ee17ec..e23d7aaa33 100644
--- a/src/core/tools/__tests__/insertContentTool.spec.ts
+++ b/src/core/tools/__tests__/insertContentTool.spec.ts
@@ -71,6 +71,14 @@ describe("insertContentTool", () => {
cwd: "/",
consecutiveMistakeCount: 0,
didEditFile: false,
+ providerRef: {
+ deref: vi.fn().mockReturnValue({
+ getState: vi.fn().mockResolvedValue({
+ diagnosticsEnabled: true,
+ writeDelayMs: 1000,
+ }),
+ }),
+ },
rooIgnoreController: {
validateAccess: vi.fn().mockReturnValue(true),
},
diff --git a/src/core/tools/__tests__/writeToFileTool.spec.ts b/src/core/tools/__tests__/writeToFileTool.spec.ts
index f223d4b0fc..1b8582c9cc 100644
--- a/src/core/tools/__tests__/writeToFileTool.spec.ts
+++ b/src/core/tools/__tests__/writeToFileTool.spec.ts
@@ -132,6 +132,14 @@ describe("writeToFileTool", () => {
mockCline.consecutiveMistakeCount = 0
mockCline.didEditFile = false
mockCline.diffStrategy = undefined
+ mockCline.providerRef = {
+ deref: vi.fn().mockReturnValue({
+ getState: vi.fn().mockResolvedValue({
+ diagnosticsEnabled: true,
+ writeDelayMs: 1000,
+ }),
+ }),
+ }
mockCline.rooIgnoreController = {
validateAccess: vi.fn().mockReturnValue(true),
}
@@ -376,7 +384,7 @@ describe("writeToFileTool", () => {
userEdits: userEditsValue,
finalContent: "modified content",
})
- // Manually set the property on the mock instance because the original saveChanges is not called
+ // Set the userEdits property on the diffViewProvider mock to simulate user edits
mockCline.diffViewProvider.userEdits = userEditsValue
await executeWriteFileTool({}, { fileExists: true })
diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts
index f5b4ab7dd3..ad4bb0590f 100644
--- a/src/core/tools/applyDiffTool.ts
+++ b/src/core/tools/applyDiffTool.ts
@@ -2,6 +2,7 @@ import path from "path"
import fs from "fs/promises"
import { TelemetryService } from "@roo-code/telemetry"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { getReadablePath } from "../../utils/path"
@@ -170,7 +171,11 @@ export async function applyDiffToolLegacy(
}
// Call saveChanges to update the DiffViewProvider properties
- await cline.diffViewProvider.saveChanges()
+ const provider = cline.providerRef.deref()
+ const state = await provider?.getState()
+ const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
+ const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
+ await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Track file edit operation
if (relPath) {
diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts
index 407dc283b5..81dc1993b2 100644
--- a/src/core/tools/executeCommandTool.ts
+++ b/src/core/tools/executeCommandTool.ts
@@ -4,7 +4,7 @@ import * as vscode from "vscode"
import delay from "delay"
-import { CommandExecutionStatus } from "@roo-code/types"
+import { CommandExecutionStatus, DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { Task } from "../task/Task"
@@ -63,15 +63,27 @@ export async function executeCommandTool(
const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString()
const clineProvider = await cline.providerRef.deref()
const clineProviderState = await clineProvider?.getState()
- const { terminalOutputLineLimit = 500, terminalShellIntegrationDisabled = false } = clineProviderState ?? {}
+ const {
+ terminalOutputLineLimit = 500,
+ terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
+ terminalShellIntegrationDisabled = false,
+ } = clineProviderState ?? {}
// Get command execution timeout from VSCode configuration (in seconds)
const commandExecutionTimeoutSeconds = vscode.workspace
.getConfiguration(Package.name)
.get("commandExecutionTimeout", 0)
- // Convert seconds to milliseconds for internal use
- const commandExecutionTimeout = commandExecutionTimeoutSeconds * 1000
+ // Get command timeout allowlist from VSCode configuration
+ const commandTimeoutAllowlist = vscode.workspace
+ .getConfiguration(Package.name)
+ .get("commandTimeoutAllowlist", [])
+
+ // Check if command matches any prefix in the allowlist
+ const isCommandAllowlisted = commandTimeoutAllowlist.some((prefix) => command!.startsWith(prefix.trim()))
+
+ // Convert seconds to milliseconds for internal use, but skip timeout if command is allowlisted
+ const commandExecutionTimeout = isCommandAllowlisted ? 0 : commandExecutionTimeoutSeconds * 1000
const options: ExecuteCommandOptions = {
executionId,
@@ -79,6 +91,7 @@ export async function executeCommandTool(
customCwd,
terminalShellIntegrationDisabled,
terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
commandExecutionTimeout,
}
@@ -125,6 +138,7 @@ export type ExecuteCommandOptions = {
customCwd?: string
terminalShellIntegrationDisabled?: boolean
terminalOutputLineLimit?: number
+ terminalOutputCharacterLimit?: number
commandExecutionTimeout?: number
}
@@ -136,6 +150,7 @@ export async function executeCommand(
customCwd,
terminalShellIntegrationDisabled = false,
terminalOutputLineLimit = 500,
+ terminalOutputCharacterLimit = DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
commandExecutionTimeout = 0,
}: ExecuteCommandOptions,
): Promise<[boolean, ToolResponse]> {
@@ -171,7 +186,11 @@ export async function executeCommand(
const callbacks: RooTerminalCallbacks = {
onLine: async (lines: string, process: RooTerminalProcess) => {
accumulatedOutput += lines
- const compressedOutput = Terminal.compressTerminalOutput(accumulatedOutput, terminalOutputLineLimit)
+ const compressedOutput = Terminal.compressTerminalOutput(
+ accumulatedOutput,
+ terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
+ )
const status: CommandExecutionStatus = { executionId, status: "output", output: compressedOutput }
clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
@@ -190,7 +209,11 @@ export async function executeCommand(
} catch (_error) {}
},
onCompleted: (output: string | undefined) => {
- result = Terminal.compressTerminalOutput(output ?? "", terminalOutputLineLimit)
+ result = Terminal.compressTerminalOutput(
+ output ?? "",
+ terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
+ )
cline.say("command_output", result)
completed = true
},
diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts
index b76769fcf0..2b31224400 100644
--- a/src/core/tools/insertContentTool.ts
+++ b/src/core/tools/insertContentTool.ts
@@ -10,6 +10,7 @@ import { ClineSayTool } from "../../shared/ExtensionMessage"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { fileExistsAtPath } from "../../utils/fs"
import { insertGroups } from "../diff/insert-groups"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
export async function insertContentTool(
cline: Task,
@@ -155,7 +156,11 @@ export async function insertContentTool(
}
// Call saveChanges to update the DiffViewProvider properties
- await cline.diffViewProvider.saveChanges()
+ const provider = cline.providerRef.deref()
+ const state = await provider?.getState()
+ const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
+ const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
+ await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Track file edit operation
if (relPath) {
diff --git a/src/core/tools/multiApplyDiffTool.ts b/src/core/tools/multiApplyDiffTool.ts
index 8057f77949..b41d409dbb 100644
--- a/src/core/tools/multiApplyDiffTool.ts
+++ b/src/core/tools/multiApplyDiffTool.ts
@@ -2,6 +2,7 @@ import path from "path"
import fs from "fs/promises"
import { TelemetryService } from "@roo-code/telemetry"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { getReadablePath } from "../../utils/path"
@@ -553,7 +554,11 @@ ${errorDetails ? `\nTechnical details:\n${errorDetails}\n` : ""}
}
// Call saveChanges to update the DiffViewProvider properties
- await cline.diffViewProvider.saveChanges()
+ const provider = cline.providerRef.deref()
+ const state = await provider?.getState()
+ const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
+ const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
+ await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Track file edit operation
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts
index 967d5339ba..b6ec3ed39b 100644
--- a/src/core/tools/searchAndReplaceTool.ts
+++ b/src/core/tools/searchAndReplaceTool.ts
@@ -11,6 +11,7 @@ import { ClineSayTool } from "../../shared/ExtensionMessage"
import { getReadablePath } from "../../utils/path"
import { fileExistsAtPath } from "../../utils/fs"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
/**
* Tool for performing search and replace operations on files
@@ -227,7 +228,11 @@ export async function searchAndReplaceTool(
}
// Call saveChanges to update the DiffViewProvider properties
- await cline.diffViewProvider.saveChanges()
+ const provider = cline.providerRef.deref()
+ const state = await provider?.getState()
+ const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
+ const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
+ await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Track file edit operation
if (relPath) {
diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts
index 84f8ef807e..fd9d158f3f 100644
--- a/src/core/tools/writeToFileTool.ts
+++ b/src/core/tools/writeToFileTool.ts
@@ -13,6 +13,7 @@ import { getReadablePath } from "../../utils/path"
import { isPathOutsideWorkspace } from "../../utils/pathUtils"
import { detectCodeOmission } from "../../integrations/editor/detect-omission"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
export async function writeToFileTool(
cline: Task,
@@ -213,7 +214,11 @@ export async function writeToFileTool(
}
// Call saveChanges to update the DiffViewProvider properties
- await cline.diffViewProvider.saveChanges()
+ const provider = cline.providerRef.deref()
+ const state = await provider?.getState()
+ const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
+ const writeDelayMs = state?.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS
+ await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
// Track file edit operation
if (relPath) {
diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts
index 107122dcb4..6231f08167 100644
--- a/src/core/webview/ClineProvider.ts
+++ b/src/core/webview/ClineProvider.ts
@@ -28,6 +28,7 @@ import {
openRouterDefaultModelId,
glamaDefaultModelId,
ORGANIZATION_ALLOW_ALL,
+ DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService, getRooCodeApiUrl } from "@roo-code/cloud"
@@ -42,6 +43,7 @@ import { ExtensionMessage, MarketplaceInstalledMetadata } from "../../shared/Ext
import { Mode, defaultModeSlug } from "../../shared/modes"
import { experimentDefault, experiments, EXPERIMENT_IDS } from "../../shared/experiments"
import { formatLanguage } from "../../shared/language"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { Terminal } from "../../integrations/terminal/Terminal"
import { downloadTask } from "../../integrations/misc/export-markdown"
import { getTheme } from "../../integrations/theme/getTheme"
@@ -1392,6 +1394,7 @@ export class ClineProvider
cachedChromeHostUrl,
writeDelayMs,
terminalOutputLineLimit,
+ terminalOutputCharacterLimit,
terminalShellIntegrationTimeout,
terminalShellIntegrationDisabled,
terminalCommandDelay,
@@ -1436,6 +1439,7 @@ export class ClineProvider
profileThresholds,
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
+ diagnosticsEnabled,
} = await this.getState()
const telemetryKey = process.env.POSTHOG_API_KEY
@@ -1489,8 +1493,9 @@ export class ClineProvider
remoteBrowserHost,
remoteBrowserEnabled: remoteBrowserEnabled ?? false,
cachedChromeHostUrl: cachedChromeHostUrl,
- writeDelayMs: writeDelayMs ?? 1000,
+ writeDelayMs: writeDelayMs ?? DEFAULT_WRITE_DELAY_MS,
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
+ terminalOutputCharacterLimit: terminalOutputCharacterLimit ?? DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout,
terminalShellIntegrationDisabled: terminalShellIntegrationDisabled ?? false,
terminalCommandDelay: terminalCommandDelay ?? 0,
@@ -1555,6 +1560,7 @@ export class ClineProvider
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
+ diagnosticsEnabled: diagnosticsEnabled ?? true,
}
}
@@ -1638,6 +1644,7 @@ export class ClineProvider
alwaysAllowFollowupQuestions: stateValues.alwaysAllowFollowupQuestions ?? false,
alwaysAllowUpdateTodoList: stateValues.alwaysAllowUpdateTodoList ?? false,
followupAutoApproveTimeoutMs: stateValues.followupAutoApproveTimeoutMs ?? 60000,
+ diagnosticsEnabled: stateValues.diagnosticsEnabled ?? true,
allowedMaxRequests: stateValues.allowedMaxRequests,
autoCondenseContext: stateValues.autoCondenseContext ?? true,
autoCondenseContextPercent: stateValues.autoCondenseContextPercent ?? 100,
@@ -1656,8 +1663,10 @@ export class ClineProvider
remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false,
cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined,
fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0,
- writeDelayMs: stateValues.writeDelayMs ?? 1000,
+ writeDelayMs: stateValues.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS,
terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
+ terminalOutputCharacterLimit:
+ stateValues.terminalOutputCharacterLimit ?? DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT,
terminalShellIntegrationTimeout:
stateValues.terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout,
terminalShellIntegrationDisabled: stateValues.terminalShellIntegrationDisabled ?? false,
diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts
index dd9ee12bfc..5272c33451 100644
--- a/src/core/webview/__tests__/ClineProvider.spec.ts
+++ b/src/core/webview/__tests__/ClineProvider.spec.ts
@@ -540,6 +540,7 @@ describe("ClineProvider", () => {
sharingEnabled: false,
profileThresholds: {},
hasOpenedModeSelector: false,
+ diagnosticsEnabled: true,
}
const message: ExtensionMessage = {
diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts
index 75e2eabfe9..fc9bac4f93 100644
--- a/src/core/webview/webviewMessageHandler.ts
+++ b/src/core/webview/webviewMessageHandler.ts
@@ -1044,10 +1044,35 @@ export const webviewMessageHandler = async (
await updateGlobalState("writeDelayMs", message.value)
await provider.postStateToWebview()
break
- case "terminalOutputLineLimit":
- await updateGlobalState("terminalOutputLineLimit", message.value)
+ case "diagnosticsEnabled":
+ await updateGlobalState("diagnosticsEnabled", message.bool ?? true)
await provider.postStateToWebview()
break
+ case "terminalOutputLineLimit":
+ // Validate that the line limit is a positive number
+ const lineLimit = message.value
+ if (typeof lineLimit === "number" && lineLimit > 0) {
+ await updateGlobalState("terminalOutputLineLimit", lineLimit)
+ await provider.postStateToWebview()
+ } else {
+ vscode.window.showErrorMessage(
+ t("common:errors.invalid_line_limit") || "Terminal output line limit must be a positive number",
+ )
+ }
+ break
+ case "terminalOutputCharacterLimit":
+ // Validate that the character limit is a positive number
+ const charLimit = message.value
+ if (typeof charLimit === "number" && charLimit > 0) {
+ await updateGlobalState("terminalOutputCharacterLimit", charLimit)
+ await provider.postStateToWebview()
+ } else {
+ vscode.window.showErrorMessage(
+ t("common:errors.invalid_character_limit") ||
+ "Terminal output character limit must be a positive number",
+ )
+ }
+ break
case "terminalShellIntegrationTimeout":
await updateGlobalState("terminalShellIntegrationTimeout", message.value)
await provider.postStateToWebview()
@@ -1257,6 +1282,11 @@ export const webviewMessageHandler = async (
await provider.postStateToWebview()
break
case "updateCondensingPrompt":
+ // Store the condensing prompt in customSupportPrompts["CONDENSE"] instead of customCondensingPrompt
+ const currentSupportPrompts = getGlobalState("customSupportPrompts") ?? {}
+ const updatedSupportPrompts = { ...currentSupportPrompts, CONDENSE: message.text }
+ await updateGlobalState("customSupportPrompts", updatedSupportPrompts)
+ // Also update the old field for backward compatibility during migration
await updateGlobalState("customCondensingPrompt", message.text)
await provider.postStateToWebview()
break
@@ -2051,6 +2081,12 @@ export const webviewMessageHandler = async (
settings.codebaseIndexGeminiApiKey,
)
}
+ if (settings.codebaseIndexMistralApiKey !== undefined) {
+ await provider.contextProxy.storeSecret(
+ "codebaseIndexMistralApiKey",
+ settings.codebaseIndexMistralApiKey,
+ )
+ }
// Send success response first - settings are saved regardless of validation
await provider.postMessageToWebview({
@@ -2143,6 +2179,7 @@ export const webviewMessageHandler = async (
"codebaseIndexOpenAiCompatibleApiKey",
))
const hasGeminiApiKey = !!(await provider.context.secrets.get("codebaseIndexGeminiApiKey"))
+ const hasMistralApiKey = !!(await provider.context.secrets.get("codebaseIndexMistralApiKey"))
provider.postMessageToWebview({
type: "codeIndexSecretStatus",
@@ -2151,6 +2188,7 @@ export const webviewMessageHandler = async (
hasQdrantApiKey,
hasOpenAiCompatibleApiKey,
hasGeminiApiKey,
+ hasMistralApiKey,
},
})
break
diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json
index 5deed252bf..651bc2b80f 100644
--- a/src/i18n/locales/ca/embeddings.json
+++ b/src/i18n/locales/ca/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Falta la configuració d'Ollama per crear l'embedder",
"openAiCompatibleConfigMissing": "Falta la configuració compatible amb OpenAI per crear l'embedder",
"geminiConfigMissing": "Falta la configuració de Gemini per crear l'embedder",
+ "mistralConfigMissing": "Falta la configuració de Mistral per crear l'embedder",
"invalidEmbedderType": "Tipus d'embedder configurat no vàlid: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Assegura't que la 'Dimensió d'incrustació' estigui configurada correctament als paràmetres del proveïdor compatible amb OpenAI.",
"vectorDimensionNotDetermined": "No s'ha pogut determinar la dimensió del vector per al model '{{modelId}}' amb el proveïdor '{{provider}}'. Comprova els perfils del model o la configuració.",
diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json
index 74381747e1..167abc516c 100644
--- a/src/i18n/locales/de/embeddings.json
+++ b/src/i18n/locales/de/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Ollama-Konfiguration fehlt für die Erstellung des Embedders",
"openAiCompatibleConfigMissing": "OpenAI-kompatible Konfiguration fehlt für die Erstellung des Embedders",
"geminiConfigMissing": "Gemini-Konfiguration fehlt für die Erstellung des Embedders",
+ "mistralConfigMissing": "Mistral-Konfiguration fehlt für die Erstellung des Embedders",
"invalidEmbedderType": "Ungültiger Embedder-Typ konfiguriert: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Stelle sicher, dass die 'Embedding-Dimension' in den OpenAI-kompatiblen Anbietereinstellungen korrekt eingestellt ist.",
"vectorDimensionNotDetermined": "Konnte die Vektordimension für Modell '{{modelId}}' mit Anbieter '{{provider}}' nicht bestimmen. Überprüfe die Modellprofile oder Konfiguration.",
diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json
index 96b3b2dbea..270a8d193b 100644
--- a/src/i18n/locales/en/embeddings.json
+++ b/src/i18n/locales/en/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Ollama configuration missing for embedder creation",
"openAiCompatibleConfigMissing": "OpenAI Compatible configuration missing for embedder creation",
"geminiConfigMissing": "Gemini configuration missing for embedder creation",
+ "mistralConfigMissing": "Mistral configuration missing for embedder creation",
"invalidEmbedderType": "Invalid embedder type configured: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Please ensure the 'Embedding Dimension' is correctly set in the OpenAI-Compatible provider settings.",
"vectorDimensionNotDetermined": "Could not determine vector dimension for model '{{modelId}}' with provider '{{provider}}'. Check model profiles or configuration.",
diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json
index e47db420eb..06478f1d50 100644
--- a/src/i18n/locales/es/embeddings.json
+++ b/src/i18n/locales/es/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Falta la configuración de Ollama para crear el incrustador",
"openAiCompatibleConfigMissing": "Falta la configuración compatible con OpenAI para crear el incrustador",
"geminiConfigMissing": "Falta la configuración de Gemini para crear el incrustador",
+ "mistralConfigMissing": "Falta la configuración de Mistral para la creación del incrustador",
"invalidEmbedderType": "Tipo de incrustador configurado inválido: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Asegúrate de que la 'Dimensión de incrustación' esté configurada correctamente en los ajustes del proveedor compatible con OpenAI.",
"vectorDimensionNotDetermined": "No se pudo determinar la dimensión del vector para el modelo '{{modelId}}' con el proveedor '{{provider}}'. Verifica los perfiles del modelo o la configuración.",
diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json
index c63d3a7fbc..167d093e7a 100644
--- a/src/i18n/locales/fr/embeddings.json
+++ b/src/i18n/locales/fr/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Configuration Ollama manquante pour la création de l'embedder",
"openAiCompatibleConfigMissing": "Configuration compatible OpenAI manquante pour la création de l'embedder",
"geminiConfigMissing": "Configuration Gemini manquante pour la création de l'embedder",
+ "mistralConfigMissing": "Configuration Mistral manquante pour la création de l'embedder",
"invalidEmbedderType": "Type d'embedder configuré invalide : {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Assure-toi que la 'Dimension d'embedding' est correctement définie dans les paramètres du fournisseur compatible OpenAI.",
"vectorDimensionNotDetermined": "Impossible de déterminer la dimension du vecteur pour le modèle '{{modelId}}' avec le fournisseur '{{provider}}'. Vérifie les profils du modèle ou la configuration.",
diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json
index 15709fd700..ad24cfe9d1 100644
--- a/src/i18n/locales/hi/embeddings.json
+++ b/src/i18n/locales/hi/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "एम्बेडर बनाने के लिए Ollama कॉन्फ़िगरेशन गायब है",
"openAiCompatibleConfigMissing": "एम्बेडर बनाने के लिए OpenAI संगत कॉन्फ़िगरेशन गायब है",
"geminiConfigMissing": "एम्बेडर बनाने के लिए Gemini कॉन्फ़िगरेशन गायब है",
+ "mistralConfigMissing": "एम्बेडर निर्माण के लिए मिस्ट्रल कॉन्फ़िगरेशन गायब है",
"invalidEmbedderType": "अमान्य एम्बेडर प्रकार कॉन्फ़िगर किया गया: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। कृपया सुनिश्चित करें कि OpenAI-संगत प्रदाता सेटिंग्स में 'एम्बेडिंग आयाम' सही तरीके से सेट है।",
"vectorDimensionNotDetermined": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। मॉडल प्रोफ़ाइल या कॉन्फ़िगरेशन की जांच करें।",
diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json
index e78d39d1ab..997c6e8018 100644
--- a/src/i18n/locales/id/embeddings.json
+++ b/src/i18n/locales/id/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Konfigurasi Ollama tidak ada untuk membuat embedder",
"openAiCompatibleConfigMissing": "Konfigurasi yang kompatibel dengan OpenAI tidak ada untuk membuat embedder",
"geminiConfigMissing": "Konfigurasi Gemini tidak ada untuk membuat embedder",
+ "mistralConfigMissing": "Konfigurasi Mistral hilang untuk pembuatan embedder",
"invalidEmbedderType": "Tipe embedder yang dikonfigurasi tidak valid: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Pastikan 'Dimensi Embedding' diatur dengan benar di pengaturan penyedia yang kompatibel dengan OpenAI.",
"vectorDimensionNotDetermined": "Tidak dapat menentukan dimensi vektor untuk model '{{modelId}}' dengan penyedia '{{provider}}'. Periksa profil model atau konfigurasi.",
diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json
index 679b17a25e..1bc406aecb 100644
--- a/src/i18n/locales/it/embeddings.json
+++ b/src/i18n/locales/it/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Configurazione Ollama mancante per la creazione dell'embedder",
"openAiCompatibleConfigMissing": "Configurazione compatibile con OpenAI mancante per la creazione dell'embedder",
"geminiConfigMissing": "Configurazione Gemini mancante per la creazione dell'embedder",
+ "mistralConfigMissing": "Configurazione di Mistral mancante per la creazione dell'embedder",
"invalidEmbedderType": "Tipo di embedder configurato non valido: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Assicurati che la 'Dimensione di embedding' sia impostata correttamente nelle impostazioni del provider compatibile con OpenAI.",
"vectorDimensionNotDetermined": "Impossibile determinare la dimensione del vettore per il modello '{{modelId}}' con il provider '{{provider}}'. Controlla i profili del modello o la configurazione.",
diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json
index 89136eb1cc..7152eb52df 100644
--- a/src/i18n/locales/ja/embeddings.json
+++ b/src/i18n/locales/ja/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "エンベッダー作成のためのOllama設定がありません",
"openAiCompatibleConfigMissing": "エンベッダー作成のためのOpenAI互換設定がありません",
"geminiConfigMissing": "エンベッダー作成のためのGemini設定がありません",
+ "mistralConfigMissing": "エンベッダー作成のためのMistral設定がありません",
"invalidEmbedderType": "無効なエンベッダータイプが設定されています: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。OpenAI互換プロバイダー設定で「埋め込み次元」が正しく設定されていることを確認してください。",
"vectorDimensionNotDetermined": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。モデルプロファイルまたは設定を確認してください。",
diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json
index 7129883ad7..f1c40f66bc 100644
--- a/src/i18n/locales/ko/embeddings.json
+++ b/src/i18n/locales/ko/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "임베더 생성을 위한 Ollama 구성이 누락되었습니다",
"openAiCompatibleConfigMissing": "임베더 생성을 위한 OpenAI 호환 구성이 누락되었습니다",
"geminiConfigMissing": "임베더 생성을 위한 Gemini 구성이 누락되었습니다",
+ "mistralConfigMissing": "임베더 생성을 위한 Mistral 구성이 없습니다",
"invalidEmbedderType": "잘못된 임베더 유형이 구성되었습니다: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. OpenAI 호환 프로바이더 설정에서 '임베딩 차원'이 올바르게 설정되어 있는지 확인하세요.",
"vectorDimensionNotDetermined": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. 모델 프로필 또는 구성을 확인하세요.",
diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json
index ede20774ac..19b7bfeaa2 100644
--- a/src/i18n/locales/nl/embeddings.json
+++ b/src/i18n/locales/nl/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Ollama-configuratie ontbreekt voor het maken van embedder",
"openAiCompatibleConfigMissing": "OpenAI-compatibele configuratie ontbreekt voor het maken van embedder",
"geminiConfigMissing": "Gemini-configuratie ontbreekt voor het maken van embedder",
+ "mistralConfigMissing": "Mistral-configuratie ontbreekt voor het maken van de embedder",
"invalidEmbedderType": "Ongeldig embedder-type geconfigureerd: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Zorg ervoor dat de 'Embedding Dimensie' correct is ingesteld in de OpenAI-compatibele provider-instellingen.",
"vectorDimensionNotDetermined": "Kan de vectordimensie voor model '{{modelId}}' met provider '{{provider}}' niet bepalen. Controleer modelprofielen of configuratie.",
diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json
index 70279021bd..46e761cb8b 100644
--- a/src/i18n/locales/pl/embeddings.json
+++ b/src/i18n/locales/pl/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Brak konfiguracji Ollama do utworzenia embeddera",
"openAiCompatibleConfigMissing": "Brak konfiguracji kompatybilnej z OpenAI do utworzenia embeddera",
"geminiConfigMissing": "Brak konfiguracji Gemini do utworzenia embeddera",
+ "mistralConfigMissing": "Brak konfiguracji Mistral do utworzenia embeddera",
"invalidEmbedderType": "Skonfigurowano nieprawidłowy typ embeddera: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Upewnij się, że 'Wymiar osadzania' jest poprawnie ustawiony w ustawieniach dostawcy kompatybilnego z OpenAI.",
"vectorDimensionNotDetermined": "Nie można określić wymiaru wektora dla modelu '{{modelId}}' z dostawcą '{{provider}}'. Sprawdź profile modelu lub konfigurację.",
diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json
index aea1bb5007..816b1ecded 100644
--- a/src/i18n/locales/pt-BR/embeddings.json
+++ b/src/i18n/locales/pt-BR/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Configuração do Ollama ausente para criação do embedder",
"openAiCompatibleConfigMissing": "Configuração compatível com OpenAI ausente para criação do embedder",
"geminiConfigMissing": "Configuração do Gemini ausente para criação do embedder",
+ "mistralConfigMissing": "Configuração do Mistral ausente para a criação do embedder",
"invalidEmbedderType": "Tipo de embedder configurado inválido: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Certifique-se de que a 'Dimensão de Embedding' esteja configurada corretamente nas configurações do provedor compatível com OpenAI.",
"vectorDimensionNotDetermined": "Não foi possível determinar a dimensão do vetor para o modelo '{{modelId}}' com o provedor '{{provider}}'. Verifique os perfis do modelo ou a configuração.",
diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json
index a724539b76..fb1688e2ca 100644
--- a/src/i18n/locales/ru/embeddings.json
+++ b/src/i18n/locales/ru/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Отсутствует конфигурация Ollama для создания эмбеддера",
"openAiCompatibleConfigMissing": "Отсутствует конфигурация, совместимая с OpenAI, для создания эмбеддера",
"geminiConfigMissing": "Отсутствует конфигурация Gemini для создания эмбеддера",
+ "mistralConfigMissing": "Конфигурация Mistral отсутствует для создания эмбеддера",
"invalidEmbedderType": "Настроен недопустимый тип эмбеддера: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Убедитесь, что 'Размерность эмбеддинга' правильно установлена в настройках провайдера, совместимого с OpenAI.",
"vectorDimensionNotDetermined": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Проверьте профили модели или конфигурацию.",
diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json
index 3e115ce103..5023190929 100644
--- a/src/i18n/locales/tr/embeddings.json
+++ b/src/i18n/locales/tr/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Gömücü oluşturmak için Ollama yapılandırması eksik",
"openAiCompatibleConfigMissing": "Gömücü oluşturmak için OpenAI uyumlu yapılandırması eksik",
"geminiConfigMissing": "Gömücü oluşturmak için Gemini yapılandırması eksik",
+ "mistralConfigMissing": "Gömücü oluşturmak için Mistral yapılandırması eksik",
"invalidEmbedderType": "Geçersiz gömücü türü yapılandırıldı: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. OpenAI uyumlu sağlayıcı ayarlarında 'Gömme Boyutu'nun doğru ayarlandığından emin ol.",
"vectorDimensionNotDetermined": "'{{provider}}' sağlayıcısı ile '{{modelId}}' modeli için vektör boyutu belirlenemedi. Model profillerini veya yapılandırmayı kontrol et.",
diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json
index 9ef61105fa..626f0f6862 100644
--- a/src/i18n/locales/vi/embeddings.json
+++ b/src/i18n/locales/vi/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "Thiếu cấu hình Ollama để tạo embedder",
"openAiCompatibleConfigMissing": "Thiếu cấu hình tương thích OpenAI để tạo embedder",
"geminiConfigMissing": "Thiếu cấu hình Gemini để tạo embedder",
+ "mistralConfigMissing": "Thiếu cấu hình Mistral để tạo trình nhúng",
"invalidEmbedderType": "Loại embedder được cấu hình không hợp lệ: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Hãy đảm bảo 'Kích thước Embedding' được cài đặt đúng trong cài đặt nhà cung cấp tương thích OpenAI.",
"vectorDimensionNotDetermined": "Không thể xác định kích thước vector cho mô hình '{{modelId}}' với nhà cung cấp '{{provider}}'. Kiểm tra hồ sơ mô hình hoặc cấu hình.",
diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json
index d3ded6e5a2..3247631bb2 100644
--- a/src/i18n/locales/zh-CN/embeddings.json
+++ b/src/i18n/locales/zh-CN/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "创建嵌入器缺少 Ollama 配置",
"openAiCompatibleConfigMissing": "创建嵌入器缺少 OpenAI 兼容配置",
"geminiConfigMissing": "创建嵌入器缺少 Gemini 配置",
+ "mistralConfigMissing": "创建嵌入器时缺少 Mistral 配置",
"invalidEmbedderType": "配置的嵌入器类型无效:{{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请确保在 OpenAI 兼容提供商设置中正确设置了「嵌入维度」。",
"vectorDimensionNotDetermined": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请检查模型配置文件或配置。",
diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json
index 5ab5dcb292..b3b3231d4a 100644
--- a/src/i18n/locales/zh-TW/embeddings.json
+++ b/src/i18n/locales/zh-TW/embeddings.json
@@ -46,6 +46,7 @@
"ollamaConfigMissing": "建立嵌入器缺少 Ollama 設定",
"openAiCompatibleConfigMissing": "建立嵌入器缺少 OpenAI 相容設定",
"geminiConfigMissing": "建立嵌入器缺少 Gemini 設定",
+ "mistralConfigMissing": "建立嵌入器時缺少 Mistral 設定",
"invalidEmbedderType": "設定的嵌入器類型無效:{{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請確保在 OpenAI 相容提供商設定中正確設定了「嵌入維度」。",
"vectorDimensionNotDetermined": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請檢查模型設定檔或設定。",
diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts
index 225e076297..f4133029c9 100644
--- a/src/integrations/editor/DiffViewProvider.ts
+++ b/src/integrations/editor/DiffViewProvider.ts
@@ -4,6 +4,7 @@ import * as fs from "fs/promises"
import * as diff from "diff"
import stripBom from "strip-bom"
import { XMLBuilder } from "fast-xml-parser"
+import delay from "delay"
import { createDirectoriesForFile } from "../../utils/fs"
import { arePathsEqual, getReadablePath } from "../../utils/path"
@@ -11,6 +12,7 @@ import { formatResponse } from "../../core/prompts/responses"
import { diagnosticsToProblemsString, getNewDiagnostics } from "../diagnostics"
import { ClineSayTool } from "../../shared/ExtensionMessage"
import { Task } from "../../core/task/Task"
+import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { DecorationController } from "./DecorationController"
@@ -179,7 +181,7 @@ export class DiffViewProvider {
}
}
- async saveChanges(): Promise<{
+ async saveChanges(diagnosticsEnabled: boolean = true, writeDelayMs: number = DEFAULT_WRITE_DELAY_MS): Promise<{
newProblemsMessage: string | undefined
userEdits: string | undefined
finalContent: string | undefined
@@ -214,18 +216,35 @@ export class DiffViewProvider {
// and can address them accordingly. If problems don't change immediately after
// applying a fix, won't be notified, which is generally fine since the
// initial fix is usually correct and it may just take time for linters to catch up.
- const postDiagnostics = vscode.languages.getDiagnostics()
+
+ let newProblemsMessage = ""
+
+ if (diagnosticsEnabled) {
+ // Add configurable delay to allow linters time to process and clean up issues
+ // like unused imports (especially important for Go and other languages)
+ // Ensure delay is non-negative
+ const safeDelayMs = Math.max(0, writeDelayMs)
+
+ try {
+ await delay(safeDelayMs)
+ } catch (error) {
+ // Log error but continue - delay failure shouldn't break the save operation
+ console.warn(`Failed to apply write delay: ${error}`)
+ }
+
+ const postDiagnostics = vscode.languages.getDiagnostics()
- const newProblems = await diagnosticsToProblemsString(
- getNewDiagnostics(this.preDiagnostics, postDiagnostics),
- [
- vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
- ],
- this.cwd,
- ) // Will be empty string if no errors.
+ const newProblems = await diagnosticsToProblemsString(
+ getNewDiagnostics(this.preDiagnostics, postDiagnostics),
+ [
+ vscode.DiagnosticSeverity.Error, // only including errors since warnings can be distracting (if user wants to fix warnings they can use the @problems mention)
+ ],
+ this.cwd,
+ ) // Will be empty string if no errors.
- const newProblemsMessage =
- newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
+ newProblemsMessage =
+ newProblems.length > 0 ? `\n\nNew problems detected after saving the file:\n${newProblems}` : ""
+ }
// If the edited content has different EOL characters, we don't want to
// show a diff with all the EOL differences.
diff --git a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts
index ad1950345b..a4aded95bb 100644
--- a/src/integrations/editor/__tests__/DiffViewProvider.spec.ts
+++ b/src/integrations/editor/__tests__/DiffViewProvider.spec.ts
@@ -1,6 +1,12 @@
import { DiffViewProvider, DIFF_VIEW_URI_SCHEME, DIFF_VIEW_LABEL_CHANGES } from "../DiffViewProvider"
import * as vscode from "vscode"
import * as path from "path"
+import delay from "delay"
+
+// Mock delay
+vi.mock("delay", () => ({
+ default: vi.fn().mockResolvedValue(undefined),
+}))
// Mock fs/promises
vi.mock("fs/promises", () => ({
@@ -45,6 +51,12 @@ vi.mock("vscode", () => ({
languages: {
getDiagnostics: vi.fn(() => []),
},
+ DiagnosticSeverity: {
+ Error: 0,
+ Warning: 1,
+ Information: 2,
+ Hint: 3,
+ },
WorkspaceEdit: vi.fn().mockImplementation(() => ({
replace: vi.fn(),
delete: vi.fn(),
@@ -327,4 +339,83 @@ describe("DiffViewProvider", () => {
).toBeUndefined()
})
})
+
+ describe("saveChanges method with diagnostic settings", () => {
+ beforeEach(() => {
+ // Setup common mocks for saveChanges tests
+ ;(diffViewProvider as any).relPath = "test.ts"
+ ;(diffViewProvider as any).newContent = "new content"
+ ;(diffViewProvider as any).activeDiffEditor = {
+ document: {
+ getText: vi.fn().mockReturnValue("new content"),
+ isDirty: false,
+ save: vi.fn().mockResolvedValue(undefined),
+ },
+ }
+ ;(diffViewProvider as any).preDiagnostics = []
+
+ // Mock vscode functions
+ vi.mocked(vscode.window.showTextDocument).mockResolvedValue({} as any)
+ vi.mocked(vscode.languages.getDiagnostics).mockReturnValue([])
+ })
+
+ it("should apply diagnostic delay when diagnosticsEnabled is true", async () => {
+ const mockDelay = vi.mocked(delay)
+ mockDelay.mockClear()
+
+ // Mock closeAllDiffViews
+ ;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)
+
+ const result = await diffViewProvider.saveChanges(true, 3000)
+
+ // Verify delay was called with correct duration
+ expect(mockDelay).toHaveBeenCalledWith(3000)
+ expect(vscode.languages.getDiagnostics).toHaveBeenCalled()
+ expect(result.newProblemsMessage).toBe("")
+ })
+
+ it("should skip diagnostics when diagnosticsEnabled is false", async () => {
+ const mockDelay = vi.mocked(delay)
+ mockDelay.mockClear()
+
+ // Mock closeAllDiffViews
+ ;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)
+
+ const result = await diffViewProvider.saveChanges(false, 2000)
+
+ // Verify delay was NOT called and diagnostics were NOT checked
+ expect(mockDelay).not.toHaveBeenCalled()
+ expect(vscode.languages.getDiagnostics).not.toHaveBeenCalled()
+ expect(result.newProblemsMessage).toBe("")
+ })
+
+ it("should use default values when no parameters provided", async () => {
+ const mockDelay = vi.mocked(delay)
+ mockDelay.mockClear()
+
+ // Mock closeAllDiffViews
+ ;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)
+
+ const result = await diffViewProvider.saveChanges()
+
+ // Verify default behavior (enabled=true, delay=2000ms)
+ expect(mockDelay).toHaveBeenCalledWith(1000)
+ expect(vscode.languages.getDiagnostics).toHaveBeenCalled()
+ expect(result.newProblemsMessage).toBe("")
+ })
+
+ it("should handle custom delay values", async () => {
+ const mockDelay = vi.mocked(delay)
+ mockDelay.mockClear()
+
+ // Mock closeAllDiffViews
+ ;(diffViewProvider as any).closeAllDiffViews = vi.fn().mockResolvedValue(undefined)
+
+ const result = await diffViewProvider.saveChanges(true, 5000)
+
+ // Verify custom delay was used
+ expect(mockDelay).toHaveBeenCalledWith(5000)
+ expect(vscode.languages.getDiagnostics).toHaveBeenCalled()
+ })
+ })
})
diff --git a/src/integrations/misc/__tests__/extract-text.spec.ts b/src/integrations/misc/__tests__/extract-text.spec.ts
index 04b06cfa83..bb4b52fe93 100644
--- a/src/integrations/misc/__tests__/extract-text.spec.ts
+++ b/src/integrations/misc/__tests__/extract-text.spec.ts
@@ -306,6 +306,197 @@ describe("truncateOutput", () => {
const expectedLines = ["line1", "", "[...10 lines omitted...]", "", "line12", "line13", "line14", "line15"]
expect(resultLines).toEqual(expectedLines)
})
+
+ describe("character limit functionality", () => {
+ it("returns original content when no character limit provided", () => {
+ const content = "a".repeat(1000)
+ expect(truncateOutput(content, undefined, undefined)).toBe(content)
+ })
+
+ it("returns original content when characters are under limit", () => {
+ const content = "a".repeat(100)
+ expect(truncateOutput(content, undefined, 200)).toBe(content)
+ })
+
+ it("truncates content by character limit with 20/80 split", () => {
+ // Create content with 1000 characters
+ const content = "a".repeat(1000)
+
+ // Set character limit to 100
+ const result = truncateOutput(content, undefined, 100)
+
+ // Should keep:
+ // - First 20 characters (20% of 100)
+ // - Last 80 characters (80% of 100)
+ // - Omission indicator in between
+ const expectedStart = "a".repeat(20)
+ const expectedEnd = "a".repeat(80)
+ const expected = expectedStart + "\n[...900 characters omitted...]\n" + expectedEnd
+
+ expect(result).toBe(expected)
+ })
+
+ it("prioritizes character limit over line limit", () => {
+ // Create content with few lines but many characters per line
+ const longLine = "a".repeat(500)
+ const content = `${longLine}\n${longLine}\n${longLine}`
+
+ // Set both limits - character limit should take precedence
+ const result = truncateOutput(content, 10, 100)
+
+ // Should truncate by character limit, not line limit
+ const expectedStart = "a".repeat(20)
+ const expectedEnd = "a".repeat(80)
+ // Total content: 1502 chars, limit: 100, so 1402 chars omitted
+ const expected = expectedStart + "\n[...1402 characters omitted...]\n" + expectedEnd
+
+ expect(result).toBe(expected)
+ })
+
+ it("falls back to line limit when character limit is satisfied", () => {
+ // Create content with many short lines
+ const lines = Array.from({ length: 25 }, (_, i) => `line${i + 1}`)
+ const content = lines.join("\n")
+
+ // Character limit is high enough, so line limit should apply
+ const result = truncateOutput(content, 10, 10000)
+
+ // Should truncate by line limit
+ const expectedLines = [
+ "line1",
+ "line2",
+ "",
+ "[...15 lines omitted...]",
+ "",
+ "line18",
+ "line19",
+ "line20",
+ "line21",
+ "line22",
+ "line23",
+ "line24",
+ "line25",
+ ]
+ expect(result).toBe(expectedLines.join("\n"))
+ })
+
+ it("handles edge case where character limit equals content length", () => {
+ const content = "exactly100chars".repeat(6) + "1234" // exactly 100 chars
+ const result = truncateOutput(content, undefined, 100)
+ expect(result).toBe(content)
+ })
+
+ it("handles very small character limits", () => {
+ const content = "a".repeat(1000)
+ const result = truncateOutput(content, undefined, 10)
+
+ // 20% of 10 = 2, 80% of 10 = 8
+ const expected = "aa\n[...990 characters omitted...]\n" + "a".repeat(8)
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit with mixed content", () => {
+ const content = "Hello world! This is a test with mixed content including numbers 123 and symbols @#$%"
+ const result = truncateOutput(content, undefined, 50)
+
+ // 20% of 50 = 10, 80% of 50 = 40
+ const expectedStart = content.slice(0, 10) // "Hello worl"
+ const expectedEnd = content.slice(-40) // last 40 chars
+ const omittedChars = content.length - 50
+ const expected = expectedStart + `\n[...${omittedChars} characters omitted...]\n` + expectedEnd
+
+ expect(result).toBe(expected)
+ })
+
+ describe("edge cases with very small character limits", () => {
+ it("handles character limit of 1", () => {
+ const content = "abcdefghijklmnopqrstuvwxyz"
+ const result = truncateOutput(content, undefined, 1)
+
+ // 20% of 1 = 0.2 (floor = 0), so beforeLimit = 0
+ // afterLimit = 1 - 0 = 1
+ // Should keep 0 chars from start and 1 char from end
+ const expected = "\n[...25 characters omitted...]\nz"
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit of 2", () => {
+ const content = "abcdefghijklmnopqrstuvwxyz"
+ const result = truncateOutput(content, undefined, 2)
+
+ // 20% of 2 = 0.4 (floor = 0), so beforeLimit = 0
+ // afterLimit = 2 - 0 = 2
+ // Should keep 0 chars from start and 2 chars from end
+ const expected = "\n[...24 characters omitted...]\nyz"
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit of 5", () => {
+ const content = "abcdefghijklmnopqrstuvwxyz"
+ const result = truncateOutput(content, undefined, 5)
+
+ // 20% of 5 = 1, so beforeLimit = 1
+ // afterLimit = 5 - 1 = 4
+ // Should keep 1 char from start and 4 chars from end
+ const expected = "a\n[...21 characters omitted...]\nwxyz"
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit with multi-byte characters", () => {
+ const content = "🚀🎉🔥💻🌟🎨🎯🎪🎭🎬" // 10 emojis, each is multi-byte
+ const result = truncateOutput(content, undefined, 10)
+
+ // Character limit works on string length, not byte count
+ // 20% of 10 = 2, 80% of 10 = 8
+ // Note: In JavaScript, each emoji is actually 2 characters (surrogate pair)
+ // So the content is actually 20 characters long, not 10
+ const expected = "🚀\n[...10 characters omitted...]\n🎯🎪🎭🎬"
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit with newlines in content", () => {
+ const content = "line1\nline2\nline3\nline4\nline5"
+ const result = truncateOutput(content, undefined, 15)
+
+ // Total length is 29 chars (including newlines)
+ // 20% of 15 = 3, 80% of 15 = 12
+ // The slice will take first 3 chars: "lin"
+ // And last 12 chars: "e4\nline5" (counting backwards)
+ const expected = "lin\n[...14 characters omitted...]\n\nline4\nline5"
+ expect(result).toBe(expected)
+ })
+
+ it("handles character limit exactly matching content with omission message", () => {
+ // Edge case: when the omission message would make output longer than original
+ const content = "short"
+ const result = truncateOutput(content, undefined, 10)
+
+ // Content is 5 chars, limit is 10, so no truncation needed
+ expect(result).toBe(content)
+ })
+
+ it("handles character limit smaller than omission message", () => {
+ const content = "a".repeat(100)
+ const result = truncateOutput(content, undefined, 3)
+
+ // 20% of 3 = 0.6 (floor = 0), so beforeLimit = 0
+ // afterLimit = 3 - 0 = 3
+ const expected = "\n[...97 characters omitted...]\naaa"
+ expect(result).toBe(expected)
+ })
+
+ it("prioritizes character limit even with very high line limit", () => {
+ const content = "a".repeat(1000)
+ const result = truncateOutput(content, 999999, 50)
+
+ // Character limit should still apply despite high line limit
+ const expectedStart = "a".repeat(10) // 20% of 50
+ const expectedEnd = "a".repeat(40) // 80% of 50
+ const expected = expectedStart + "\n[...950 characters omitted...]\n" + expectedEnd
+ expect(result).toBe(expected)
+ })
+ })
+ })
})
describe("applyRunLengthEncoding", () => {
diff --git a/src/integrations/misc/extract-text.ts b/src/integrations/misc/extract-text.ts
index 8c7e7408a6..eb02f63b95 100644
--- a/src/integrations/misc/extract-text.ts
+++ b/src/integrations/misc/extract-text.ts
@@ -135,17 +135,58 @@ export function stripLineNumbers(content: string, aggressive: boolean = false):
* When truncation is needed, it keeps 20% of the lines from the start and 80% from the end,
* with a clear indicator of how many lines were omitted in between.
*
+ * IMPORTANT: Character limit takes precedence over line limit. This is because:
+ * 1. Character limit provides a hard cap on memory usage and context window consumption
+ * 2. A single line with millions of characters could bypass line limits and cause issues
+ * 3. Character limit ensures consistent behavior regardless of line structure
+ *
+ * When both limits are specified:
+ * - If content exceeds character limit, character-based truncation is applied (regardless of line count)
+ * - If content is within character limit but exceeds line limit, line-based truncation is applied
+ * - This prevents edge cases where extremely long lines could consume excessive resources
+ *
* @param content The multi-line string to truncate
- * @param lineLimit Optional maximum number of lines to keep. If not provided or 0, returns the original content
- * @returns The truncated string with an indicator of omitted lines, or the original content if no truncation needed
+ * @param lineLimit Optional maximum number of lines to keep. If not provided or 0, no line limit is applied
+ * @param characterLimit Optional maximum number of characters to keep. If not provided or 0, no character limit is applied
+ * @returns The truncated string with an indicator of omitted content, or the original content if no truncation needed
*
* @example
* // With 10 line limit on 25 lines of content:
* // - Keeps first 2 lines (20% of 10)
* // - Keeps last 8 lines (80% of 10)
* // - Adds "[...15 lines omitted...]" in between
+ *
+ * @example
+ * // With character limit on long single line:
+ * // - Keeps first 20% of characters
+ * // - Keeps last 80% of characters
+ * // - Adds "[...X characters omitted...]" in between
+ *
+ * @example
+ * // Character limit takes precedence:
+ * // content = "A".repeat(50000) + "\n" + "B".repeat(50000) // 2 lines, 100,002 chars
+ * // truncateOutput(content, 10, 40000) // Uses character limit, not line limit
+ * // Result: First ~8000 chars + "[...60002 characters omitted...]" + Last ~32000 chars
*/
-export function truncateOutput(content: string, lineLimit?: number): string {
+export function truncateOutput(content: string, lineLimit?: number, characterLimit?: number): string {
+ // If no limits are specified, return original content
+ if (!lineLimit && !characterLimit) {
+ return content
+ }
+
+ // Character limit takes priority over line limit
+ if (characterLimit && content.length > characterLimit) {
+ const beforeLimit = Math.floor(characterLimit * 0.2) // 20% of characters before
+ const afterLimit = characterLimit - beforeLimit // remaining 80% after
+
+ const startSection = content.slice(0, beforeLimit)
+ const endSection = content.slice(-afterLimit)
+ const omittedChars = content.length - characterLimit
+
+ return startSection + `\n[...${omittedChars} characters omitted...]\n` + endSection
+ }
+
+ // If character limit is not exceeded or not specified, check line limit
if (!lineLimit) {
return content
}
diff --git a/src/integrations/terminal/BaseTerminal.ts b/src/integrations/terminal/BaseTerminal.ts
index 8137881b8c..a79d417b07 100644
--- a/src/integrations/terminal/BaseTerminal.ts
+++ b/src/integrations/terminal/BaseTerminal.ts
@@ -1,4 +1,5 @@
import { truncateOutput, applyRunLengthEncoding, processBackspaces, processCarriageReturns } from "../misc/extract-text"
+import { DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT } from "@roo-code/types"
import type {
RooTerminalProvider,
@@ -262,11 +263,13 @@ export abstract class BaseTerminal implements RooTerminal {
}
/**
- * Compresses terminal output by applying run-length encoding and truncating to line limit
+ * Compresses terminal output by applying run-length encoding and truncating to line and character limits
* @param input The terminal output to compress
+ * @param lineLimit Maximum number of lines to keep
+ * @param characterLimit Optional maximum number of characters to keep (defaults to DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT)
* @returns The compressed terminal output
*/
- public static compressTerminalOutput(input: string, lineLimit: number): string {
+ public static compressTerminalOutput(input: string, lineLimit: number, characterLimit?: number): string {
let processedInput = input
if (BaseTerminal.compressProgressBar) {
@@ -274,7 +277,10 @@ export abstract class BaseTerminal implements RooTerminal {
processedInput = processBackspaces(processedInput)
}
- return truncateOutput(applyRunLengthEncoding(processedInput), lineLimit)
+ // Default character limit to prevent context window explosion
+ const effectiveCharLimit = characterLimit ?? DEFAULT_TERMINAL_OUTPUT_CHARACTER_LIMIT
+
+ return truncateOutput(applyRunLengthEncoding(processedInput), lineLimit, effectiveCharLimit)
}
/**
diff --git a/src/package.json b/src/package.json
index 448463b750..5e3cd3bc53 100644
--- a/src/package.json
+++ b/src/package.json
@@ -3,7 +3,7 @@
"displayName": "%extension.displayName%",
"description": "%extension.description%",
"publisher": "RooVeterinaryInc",
- "version": "3.23.14",
+ "version": "3.23.16",
"icon": "assets/icons/icon.png",
"galleryBanner": {
"color": "#617A91",
@@ -345,6 +345,14 @@
"maximum": 600,
"description": "%commands.commandExecutionTimeout.description%"
},
+ "roo-cline.commandTimeoutAllowlist": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": [],
+ "description": "%commands.commandTimeoutAllowlist.description%"
+ },
"roo-cline.preventCompletionWithOpenTodos": {
"type": "boolean",
"default": false,
diff --git a/src/package.nls.ca.json b/src/package.nls.ca.json
index 339e635f0d..f7910db978 100644
--- a/src/package.nls.ca.json
+++ b/src/package.nls.ca.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Ordres que es poden executar automàticament quan 'Aprova sempre les operacions d'execució' està activat",
"commands.deniedCommands.description": "Prefixos d'ordres que seran automàticament denegats sense demanar aprovació. En cas de conflictes amb ordres permeses, la coincidència de prefix més llarga té prioritat. Afegeix * per denegar totes les ordres.",
"commands.commandExecutionTimeout.description": "Temps màxim en segons per esperar que l'execució de l'ordre es completi abans d'esgotar el temps (0 = sense temps límit, 1-600s, per defecte: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Prefixos d'ordres que estan exclosos del temps límit d'execució d'ordres. Les ordres que coincideixin amb aquests prefixos s'executaran sense restriccions de temps límit.",
"settings.vsCodeLmModelSelector.description": "Configuració per a l'API del model de llenguatge VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "El proveïdor del model de llenguatge (p. ex. copilot)",
"settings.vsCodeLmModelSelector.family.description": "La família del model de llenguatge (p. ex. gpt-4)",
diff --git a/src/package.nls.de.json b/src/package.nls.de.json
index 5a6fe65b11..d25145616d 100644
--- a/src/package.nls.de.json
+++ b/src/package.nls.de.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Befehle, die automatisch ausgeführt werden können, wenn 'Ausführungsoperationen immer genehmigen' aktiviert ist",
"commands.deniedCommands.description": "Befehlspräfixe, die automatisch abgelehnt werden, ohne nach Genehmigung zu fragen. Bei Konflikten mit erlaubten Befehlen hat die längste Präfix-Übereinstimmung Vorrang. Füge * hinzu, um alle Befehle abzulehnen.",
"commands.commandExecutionTimeout.description": "Maximale Zeit in Sekunden, die auf den Abschluss der Befehlsausführung gewartet wird, bevor ein Timeout auftritt (0 = kein Timeout, 1-600s, Standard: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Befehlspräfixe, die vom Timeout der Befehlsausführung ausgeschlossen sind. Befehle, die diesen Präfixen entsprechen, werden ohne Timeout-Beschränkungen ausgeführt.",
"settings.vsCodeLmModelSelector.description": "Einstellungen für die VSCode-Sprachmodell-API",
"settings.vsCodeLmModelSelector.vendor.description": "Der Anbieter des Sprachmodells (z.B. copilot)",
"settings.vsCodeLmModelSelector.family.description": "Die Familie des Sprachmodells (z.B. gpt-4)",
diff --git a/src/package.nls.es.json b/src/package.nls.es.json
index 3e480550d8..057754dfb5 100644
--- a/src/package.nls.es.json
+++ b/src/package.nls.es.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Comandos que pueden ejecutarse automáticamente cuando 'Aprobar siempre operaciones de ejecución' está activado",
"commands.deniedCommands.description": "Prefijos de comandos que serán automáticamente denegados sin solicitar aprobación. En caso de conflictos con comandos permitidos, la coincidencia de prefijo más larga tiene prioridad. Añade * para denegar todos los comandos.",
"commands.commandExecutionTimeout.description": "Tiempo máximo en segundos para esperar que se complete la ejecución del comando antes de que expire (0 = sin tiempo límite, 1-600s, predeterminado: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Prefijos de comandos que están excluidos del tiempo límite de ejecución de comandos. Los comandos que coincidan con estos prefijos se ejecutarán sin restricciones de tiempo límite.",
"settings.vsCodeLmModelSelector.description": "Configuración para la API del modelo de lenguaje VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "El proveedor del modelo de lenguaje (ej. copilot)",
"settings.vsCodeLmModelSelector.family.description": "La familia del modelo de lenguaje (ej. gpt-4)",
diff --git a/src/package.nls.fr.json b/src/package.nls.fr.json
index 9e8fb83cc3..7f1193855f 100644
--- a/src/package.nls.fr.json
+++ b/src/package.nls.fr.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Commandes pouvant être exécutées automatiquement lorsque 'Toujours approuver les opérations d'exécution' est activé",
"commands.deniedCommands.description": "Préfixes de commandes qui seront automatiquement refusés sans demander d'approbation. En cas de conflit avec les commandes autorisées, la correspondance de préfixe la plus longue a la priorité. Ajouter * pour refuser toutes les commandes.",
"commands.commandExecutionTimeout.description": "Temps maximum en secondes pour attendre que l'exécution de la commande se termine avant expiration (0 = pas de délai, 1-600s, défaut : 0s)",
+ "commands.commandTimeoutAllowlist.description": "Préfixes de commandes qui sont exclus du délai d'exécution des commandes. Les commandes correspondant à ces préfixes s'exécuteront sans restrictions de délai.",
"settings.vsCodeLmModelSelector.description": "Paramètres pour l'API du modèle de langage VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "Le fournisseur du modèle de langage (ex: copilot)",
"settings.vsCodeLmModelSelector.family.description": "La famille du modèle de langage (ex: gpt-4)",
diff --git a/src/package.nls.hi.json b/src/package.nls.hi.json
index 88bc845969..de531a03a8 100644
--- a/src/package.nls.hi.json
+++ b/src/package.nls.hi.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "वे कमांड जो स्वचालित रूप से निष्पादित की जा सकती हैं जब 'हमेशा निष्पादन संचालन को स्वीकृत करें' सक्रिय हो",
"commands.deniedCommands.description": "कमांड प्रीफिक्स जो स्वचालित रूप से अस्वीकार कर दिए जाएंगे बिना अनुमोदन मांगे। अनुमतित कमांड के साथ संघर्ष की स्थिति में, सबसे लंबा प्रीफिक्स मैच प्राथमिकता लेता है। सभी कमांड को अस्वीकार करने के लिए * जोड़ें।",
"commands.commandExecutionTimeout.description": "कमांड निष्पादन पूरा होने का इंतजार करने के लिए अधिकतम समय सेकंड में, समय समाप्त होने से पहले (0 = कोई समय सीमा नहीं, 1-600s, डिफ़ॉल्ट: 0s)",
+ "commands.commandTimeoutAllowlist.description": "कमांड प्रीफिक्स जो कमांड निष्पादन टाइमआउट से बाहर रखे गए हैं। इन प्रीफिक्स से मेल खाने वाले कमांड बिना टाइमआउट प्रतिबंधों के चलेंगे।",
"settings.vsCodeLmModelSelector.description": "VSCode भाषा मॉडल API के लिए सेटिंग्स",
"settings.vsCodeLmModelSelector.vendor.description": "भाषा मॉडल का विक्रेता (उदा. copilot)",
"settings.vsCodeLmModelSelector.family.description": "भाषा मॉडल का परिवार (उदा. gpt-4)",
diff --git a/src/package.nls.id.json b/src/package.nls.id.json
index 1a2e038547..61a98cec1a 100644
--- a/src/package.nls.id.json
+++ b/src/package.nls.id.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Perintah yang dapat dijalankan secara otomatis ketika 'Selalu setujui operasi eksekusi' diaktifkan",
"commands.deniedCommands.description": "Awalan perintah yang akan otomatis ditolak tanpa meminta persetujuan. Jika terjadi konflik dengan perintah yang diizinkan, pencocokan awalan terpanjang akan diprioritaskan. Tambahkan * untuk menolak semua perintah.",
"commands.commandExecutionTimeout.description": "Waktu maksimum dalam detik untuk menunggu eksekusi perintah selesai sebelum timeout (0 = tanpa timeout, 1-600s, default: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Awalan perintah yang dikecualikan dari timeout eksekusi perintah. Perintah yang cocok dengan awalan ini akan berjalan tanpa batasan timeout.",
"settings.vsCodeLmModelSelector.description": "Pengaturan untuk API Model Bahasa VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "Vendor dari model bahasa (misalnya copilot)",
"settings.vsCodeLmModelSelector.family.description": "Keluarga dari model bahasa (misalnya gpt-4)",
diff --git a/src/package.nls.it.json b/src/package.nls.it.json
index 4d5ac4895d..383ea1041c 100644
--- a/src/package.nls.it.json
+++ b/src/package.nls.it.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Comandi che possono essere eseguiti automaticamente quando 'Approva sempre le operazioni di esecuzione' è attivato",
"commands.deniedCommands.description": "Prefissi di comandi che verranno automaticamente rifiutati senza richiedere approvazione. In caso di conflitti con comandi consentiti, la corrispondenza del prefisso più lungo ha la precedenza. Aggiungi * per rifiutare tutti i comandi.",
"commands.commandExecutionTimeout.description": "Tempo massimo in secondi per attendere il completamento dell'esecuzione del comando prima del timeout (0 = nessun timeout, 1-600s, predefinito: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Prefissi di comandi che sono esclusi dal timeout di esecuzione dei comandi. I comandi che corrispondono a questi prefissi verranno eseguiti senza restrizioni di timeout.",
"settings.vsCodeLmModelSelector.description": "Impostazioni per l'API del modello linguistico VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "Il fornitore del modello linguistico (es. copilot)",
"settings.vsCodeLmModelSelector.family.description": "La famiglia del modello linguistico (es. gpt-4)",
diff --git a/src/package.nls.ja.json b/src/package.nls.ja.json
index dcbc01d164..2e3a75930b 100644
--- a/src/package.nls.ja.json
+++ b/src/package.nls.ja.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "'常に実行操作を承認する'が有効な場合に自動実行できるコマンド",
"commands.deniedCommands.description": "承認を求めずに自動的に拒否されるコマンドプレフィックス。許可されたコマンドとの競合がある場合、最長プレフィックスマッチが優先されます。すべてのコマンドを拒否するには * を追加してください。",
"commands.commandExecutionTimeout.description": "コマンド実行の完了を待つ最大時間(秒)、タイムアウトまで(0 = タイムアウトなし、1-600秒、デフォルト: 0秒)",
+ "commands.commandTimeoutAllowlist.description": "コマンド実行タイムアウトから除外されるコマンドプレフィックス。これらのプレフィックスに一致するコマンドは、タイムアウト制限なしで実行されます。",
"settings.vsCodeLmModelSelector.description": "VSCode 言語モデル API の設定",
"settings.vsCodeLmModelSelector.vendor.description": "言語モデルのベンダー(例:copilot)",
"settings.vsCodeLmModelSelector.family.description": "言語モデルのファミリー(例:gpt-4)",
diff --git a/src/package.nls.json b/src/package.nls.json
index b52f2d11c2..1eb294ca44 100644
--- a/src/package.nls.json
+++ b/src/package.nls.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
"commands.commandExecutionTimeout.description": "Maximum time in seconds to wait for command execution to complete before timing out (0 = no timeout, 1-600s, default: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Command prefixes that are excluded from the command execution timeout. Commands matching these prefixes will run without timeout restrictions.",
"commands.preventCompletionWithOpenTodos.description": "Prevent task completion when there are incomplete todos in the todo list",
"settings.vsCodeLmModelSelector.description": "Settings for VSCode Language Model API",
"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
diff --git a/src/package.nls.ko.json b/src/package.nls.ko.json
index 6cb839e793..fc4142553c 100644
--- a/src/package.nls.ko.json
+++ b/src/package.nls.ko.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "'항상 실행 작업 승인' 이 활성화되어 있을 때 자동으로 실행할 수 있는 명령어",
"commands.deniedCommands.description": "승인을 요청하지 않고 자동으로 거부될 명령어 접두사. 허용된 명령어와 충돌하는 경우 가장 긴 접두사 일치가 우선됩니다. 모든 명령어를 거부하려면 *를 추가하세요.",
"commands.commandExecutionTimeout.description": "명령어 실행이 완료되기를 기다리는 최대 시간(초), 타임아웃 전까지 (0 = 타임아웃 없음, 1-600초, 기본값: 0초)",
+ "commands.commandTimeoutAllowlist.description": "명령어 실행 타임아웃에서 제외되는 명령어 접두사. 이러한 접두사와 일치하는 명령어는 타임아웃 제한 없이 실행됩니다.",
"settings.vsCodeLmModelSelector.description": "VSCode 언어 모델 API 설정",
"settings.vsCodeLmModelSelector.vendor.description": "언어 모델 공급자 (예: copilot)",
"settings.vsCodeLmModelSelector.family.description": "언어 모델 계열 (예: gpt-4)",
diff --git a/src/package.nls.nl.json b/src/package.nls.nl.json
index 51b23ec1a6..36efa6d1e4 100644
--- a/src/package.nls.nl.json
+++ b/src/package.nls.nl.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Commando's die automatisch kunnen worden uitgevoerd wanneer 'Altijd goedkeuren uitvoerbewerkingen' is ingeschakeld",
"commands.deniedCommands.description": "Commando-prefixen die automatisch worden geweigerd zonder om goedkeuring te vragen. Bij conflicten met toegestane commando's heeft de langste prefix-match voorrang. Voeg * toe om alle commando's te weigeren.",
"commands.commandExecutionTimeout.description": "Maximale tijd in seconden om te wachten tot commando-uitvoering voltooid is voordat er een timeout optreedt (0 = geen timeout, 1-600s, standaard: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Commando-prefixen die zijn uitgesloten van de commando-uitvoering timeout. Commando's die overeenkomen met deze prefixen worden uitgevoerd zonder timeout-beperkingen.",
"settings.vsCodeLmModelSelector.description": "Instellingen voor VSCode Language Model API",
"settings.vsCodeLmModelSelector.vendor.description": "De leverancier van het taalmodel (bijv. copilot)",
"settings.vsCodeLmModelSelector.family.description": "De familie van het taalmodel (bijv. gpt-4)",
diff --git a/src/package.nls.pl.json b/src/package.nls.pl.json
index 62daaae24b..3c500a166f 100644
--- a/src/package.nls.pl.json
+++ b/src/package.nls.pl.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Polecenia, które mogą być wykonywane automatycznie, gdy włączona jest opcja 'Zawsze zatwierdzaj operacje wykonania'",
"commands.deniedCommands.description": "Prefiksy poleceń, które będą automatycznie odrzucane bez pytania o zatwierdzenie. W przypadku konfliktów z dozwolonymi poleceniami, najdłuższe dopasowanie prefiksu ma pierwszeństwo. Dodaj * aby odrzucić wszystkie polecenia.",
"commands.commandExecutionTimeout.description": "Maksymalny czas w sekundach oczekiwania na zakończenie wykonania polecenia przed przekroczeniem limitu czasu (0 = brak limitu czasu, 1-600s, domyślnie: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Prefiksy poleceń, które są wykluczone z limitu czasu wykonania poleceń. Polecenia pasujące do tych prefiksów będą wykonywane bez ograniczeń czasowych.",
"settings.vsCodeLmModelSelector.description": "Ustawienia dla API modelu językowego VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "Dostawca modelu językowego (np. copilot)",
"settings.vsCodeLmModelSelector.family.description": "Rodzina modelu językowego (np. gpt-4)",
diff --git a/src/package.nls.pt-BR.json b/src/package.nls.pt-BR.json
index 7f3f7aece3..d5ac0b3b2c 100644
--- a/src/package.nls.pt-BR.json
+++ b/src/package.nls.pt-BR.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Comandos que podem ser executados automaticamente quando 'Sempre aprovar operações de execução' está ativado",
"commands.deniedCommands.description": "Prefixos de comandos que serão automaticamente negados sem solicitar aprovação. Em caso de conflitos com comandos permitidos, a correspondência de prefixo mais longa tem precedência. Adicione * para negar todos os comandos.",
"commands.commandExecutionTimeout.description": "Tempo máximo em segundos para aguardar a conclusão da execução do comando antes do timeout (0 = sem timeout, 1-600s, padrão: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Prefixos de comandos que são excluídos do timeout de execução de comandos. Comandos que correspondem a esses prefixos serão executados sem restrições de timeout.",
"settings.vsCodeLmModelSelector.description": "Configurações para a API do modelo de linguagem do VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "O fornecedor do modelo de linguagem (ex: copilot)",
"settings.vsCodeLmModelSelector.family.description": "A família do modelo de linguagem (ex: gpt-4)",
diff --git a/src/package.nls.ru.json b/src/package.nls.ru.json
index c1872a759e..282ff869e7 100644
--- a/src/package.nls.ru.json
+++ b/src/package.nls.ru.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Команды, которые могут быть автоматически выполнены, когда включена опция 'Всегда подтверждать операции выполнения'",
"commands.deniedCommands.description": "Префиксы команд, которые будут автоматически отклонены без запроса подтверждения. В случае конфликтов с разрешенными командами приоритет имеет самое длинное совпадение префикса. Добавьте * чтобы отклонить все команды.",
"commands.commandExecutionTimeout.description": "Максимальное время в секундах для ожидания завершения выполнения команды до истечения времени ожидания (0 = без тайм-аута, 1-600с, по умолчанию: 0с)",
+ "commands.commandTimeoutAllowlist.description": "Префиксы команд, которые исключены из тайм-аута выполнения команд. Команды, соответствующие этим префиксам, будут выполняться без ограничений по времени.",
"settings.vsCodeLmModelSelector.description": "Настройки для VSCode Language Model API",
"settings.vsCodeLmModelSelector.vendor.description": "Поставщик языковой модели (например, copilot)",
"settings.vsCodeLmModelSelector.family.description": "Семейство языковой модели (например, gpt-4)",
diff --git a/src/package.nls.tr.json b/src/package.nls.tr.json
index 589ce61912..7508c42c6e 100644
--- a/src/package.nls.tr.json
+++ b/src/package.nls.tr.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "'Her zaman yürütme işlemlerini onayla' etkinleştirildiğinde otomatik olarak yürütülebilen komutlar",
"commands.deniedCommands.description": "Onay istenmeden otomatik olarak reddedilecek komut önekleri. İzin verilen komutlarla çakışma durumunda en uzun önek eşleşmesi öncelik alır. Tüm komutları reddetmek için * ekleyin.",
"commands.commandExecutionTimeout.description": "Komut yürütmesinin tamamlanmasını beklemek için maksimum süre (saniye), zaman aşımından önce (0 = zaman aşımı yok, 1-600s, varsayılan: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Komut yürütme zaman aşımından hariç tutulan komut önekleri. Bu öneklerle eşleşen komutlar zaman aşımı kısıtlamaları olmadan çalışacaktır.",
"settings.vsCodeLmModelSelector.description": "VSCode dil modeli API'si için ayarlar",
"settings.vsCodeLmModelSelector.vendor.description": "Dil modelinin sağlayıcısı (örn: copilot)",
"settings.vsCodeLmModelSelector.family.description": "Dil modelinin ailesi (örn: gpt-4)",
diff --git a/src/package.nls.vi.json b/src/package.nls.vi.json
index 067738892d..58d386deac 100644
--- a/src/package.nls.vi.json
+++ b/src/package.nls.vi.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "Các lệnh có thể được thực thi tự động khi 'Luôn phê duyệt các thao tác thực thi' được bật",
"commands.deniedCommands.description": "Các tiền tố lệnh sẽ được tự động từ chối mà không yêu cầu phê duyệt. Trong trường hợp xung đột với các lệnh được phép, việc khớp tiền tố dài nhất sẽ được ưu tiên. Thêm * để từ chối tất cả các lệnh.",
"commands.commandExecutionTimeout.description": "Thời gian tối đa tính bằng giây để chờ việc thực thi lệnh hoàn thành trước khi hết thời gian chờ (0 = không có thời gian chờ, 1-600s, mặc định: 0s)",
+ "commands.commandTimeoutAllowlist.description": "Các tiền tố lệnh được loại trừ khỏi thời gian chờ thực thi lệnh. Các lệnh khớp với những tiền tố này sẽ chạy mà không có giới hạn thời gian chờ.",
"settings.vsCodeLmModelSelector.description": "Cài đặt cho API mô hình ngôn ngữ VSCode",
"settings.vsCodeLmModelSelector.vendor.description": "Nhà cung cấp mô hình ngôn ngữ (ví dụ: copilot)",
"settings.vsCodeLmModelSelector.family.description": "Họ mô hình ngôn ngữ (ví dụ: gpt-4)",
diff --git a/src/package.nls.zh-CN.json b/src/package.nls.zh-CN.json
index 3a69340f81..07f1877bc0 100644
--- a/src/package.nls.zh-CN.json
+++ b/src/package.nls.zh-CN.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "当启用'始终批准执行操作'时可以自动执行的命令",
"commands.deniedCommands.description": "将自动拒绝而无需请求批准的命令前缀。与允许命令冲突时,最长前缀匹配优先。添加 * 拒绝所有命令。",
"commands.commandExecutionTimeout.description": "等待命令执行完成的最大时间(秒),超时前(0 = 无超时,1-600秒,默认:0秒)",
+ "commands.commandTimeoutAllowlist.description": "从命令执行超时中排除的命令前缀。匹配这些前缀的命令将在没有超时限制的情况下运行。",
"settings.vsCodeLmModelSelector.description": "VSCode 语言模型 API 的设置",
"settings.vsCodeLmModelSelector.vendor.description": "语言模型的供应商(例如:copilot)",
"settings.vsCodeLmModelSelector.family.description": "语言模型的系列(例如:gpt-4)",
diff --git a/src/package.nls.zh-TW.json b/src/package.nls.zh-TW.json
index d6314420f1..237a2c2a6b 100644
--- a/src/package.nls.zh-TW.json
+++ b/src/package.nls.zh-TW.json
@@ -29,6 +29,7 @@
"commands.allowedCommands.description": "當啟用'始終批准執行操作'時可以自動執行的命令",
"commands.deniedCommands.description": "將自動拒絕而無需請求批准的命令前綴。與允許命令衝突時,最長前綴匹配優先。新增 * 拒絕所有命令。",
"commands.commandExecutionTimeout.description": "等待命令執行完成的最大時間(秒),逾時前(0 = 無逾時,1-600秒,預設:0秒)",
+ "commands.commandTimeoutAllowlist.description": "從命令執行逾時中排除的命令前綴。符合這些前綴的命令將在沒有逾時限制的情況下執行。",
"settings.vsCodeLmModelSelector.description": "VSCode 語言模型 API 的設定",
"settings.vsCodeLmModelSelector.vendor.description": "語言模型供應商(例如:copilot)",
"settings.vsCodeLmModelSelector.family.description": "語言模型系列(例如:gpt-4)",
diff --git a/src/services/code-index/config-manager.ts b/src/services/code-index/config-manager.ts
index 9958f456c3..1723f1c2a0 100644
--- a/src/services/code-index/config-manager.ts
+++ b/src/services/code-index/config-manager.ts
@@ -18,6 +18,7 @@ export class CodeIndexConfigManager {
private ollamaOptions?: ApiHandlerOptions
private openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
private geminiOptions?: { apiKey: string }
+ private mistralOptions?: { apiKey: string }
private qdrantUrl?: string = "http://localhost:6333"
private qdrantApiKey?: string
private searchMinScore?: number
@@ -67,6 +68,7 @@ export class CodeIndexConfigManager {
const openAiCompatibleBaseUrl = codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl ?? ""
const openAiCompatibleApiKey = this.contextProxy?.getSecret("codebaseIndexOpenAiCompatibleApiKey") ?? ""
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
+ const mistralApiKey = this.contextProxy?.getSecret("codebaseIndexMistralApiKey") ?? ""
// Update instance variables with configuration
this.codebaseIndexEnabled = codebaseIndexEnabled ?? true
@@ -100,6 +102,8 @@ export class CodeIndexConfigManager {
this.embedderProvider = "openai-compatible"
} else if (codebaseIndexEmbedderProvider === "gemini") {
this.embedderProvider = "gemini"
+ } else if (codebaseIndexEmbedderProvider === "mistral") {
+ this.embedderProvider = "mistral"
} else {
this.embedderProvider = "openai"
}
@@ -119,6 +123,7 @@ export class CodeIndexConfigManager {
: undefined
this.geminiOptions = geminiApiKey ? { apiKey: geminiApiKey } : undefined
+ this.mistralOptions = mistralApiKey ? { apiKey: mistralApiKey } : undefined
}
/**
@@ -135,6 +140,7 @@ export class CodeIndexConfigManager {
ollamaOptions?: ApiHandlerOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
+ mistralOptions?: { apiKey: string }
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
@@ -153,6 +159,7 @@ export class CodeIndexConfigManager {
openAiCompatibleBaseUrl: this.openAiCompatibleOptions?.baseUrl ?? "",
openAiCompatibleApiKey: this.openAiCompatibleOptions?.apiKey ?? "",
geminiApiKey: this.geminiOptions?.apiKey ?? "",
+ mistralApiKey: this.mistralOptions?.apiKey ?? "",
qdrantUrl: this.qdrantUrl ?? "",
qdrantApiKey: this.qdrantApiKey ?? "",
}
@@ -176,6 +183,7 @@ export class CodeIndexConfigManager {
ollamaOptions: this.ollamaOptions,
openAiCompatibleOptions: this.openAiCompatibleOptions,
geminiOptions: this.geminiOptions,
+ mistralOptions: this.mistralOptions,
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.currentSearchMinScore,
@@ -208,6 +216,11 @@ export class CodeIndexConfigManager {
const qdrantUrl = this.qdrantUrl
const isConfigured = !!(apiKey && qdrantUrl)
return isConfigured
+ } else if (this.embedderProvider === "mistral") {
+ const apiKey = this.mistralOptions?.apiKey
+ const qdrantUrl = this.qdrantUrl
+ const isConfigured = !!(apiKey && qdrantUrl)
+ return isConfigured
}
return false // Should not happen if embedderProvider is always set correctly
}
@@ -241,6 +254,7 @@ export class CodeIndexConfigManager {
const prevOpenAiCompatibleApiKey = prev?.openAiCompatibleApiKey ?? ""
const prevModelDimension = prev?.modelDimension
const prevGeminiApiKey = prev?.geminiApiKey ?? ""
+ const prevMistralApiKey = prev?.mistralApiKey ?? ""
const prevQdrantUrl = prev?.qdrantUrl ?? ""
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
@@ -277,6 +291,7 @@ export class CodeIndexConfigManager {
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentModelDimension = this.modelDimension
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
+ const currentMistralApiKey = this.mistralOptions?.apiKey ?? ""
const currentQdrantUrl = this.qdrantUrl ?? ""
const currentQdrantApiKey = this.qdrantApiKey ?? ""
@@ -295,6 +310,14 @@ export class CodeIndexConfigManager {
return true
}
+ if (prevGeminiApiKey !== currentGeminiApiKey) {
+ return true
+ }
+
+ if (prevMistralApiKey !== currentMistralApiKey) {
+ return true
+ }
+
// Check for model dimension changes (generic for all providers)
if (prevModelDimension !== currentModelDimension) {
return true
@@ -351,6 +374,7 @@ export class CodeIndexConfigManager {
ollamaOptions: this.ollamaOptions,
openAiCompatibleOptions: this.openAiCompatibleOptions,
geminiOptions: this.geminiOptions,
+ mistralOptions: this.mistralOptions,
qdrantUrl: this.qdrantUrl,
qdrantApiKey: this.qdrantApiKey,
searchMinScore: this.currentSearchMinScore,
diff --git a/src/services/code-index/constants/index.ts b/src/services/code-index/constants/index.ts
index 706a73935a..6f0e0fe7e6 100644
--- a/src/services/code-index/constants/index.ts
+++ b/src/services/code-index/constants/index.ts
@@ -20,6 +20,7 @@ export const BATCH_SEGMENT_THRESHOLD = 60 // Number of code segments to batch fo
export const MAX_BATCH_RETRIES = 3
export const INITIAL_RETRY_DELAY_MS = 500
export const PARSING_CONCURRENCY = 10
+export const MAX_PENDING_BATCHES = 20 // Maximum number of batches to accumulate before waiting
/**OpenAI Embedder */
export const MAX_BATCH_TOKENS = 100000
diff --git a/src/services/code-index/embedders/__tests__/mistral.spec.ts b/src/services/code-index/embedders/__tests__/mistral.spec.ts
new file mode 100644
index 0000000000..5085882503
--- /dev/null
+++ b/src/services/code-index/embedders/__tests__/mistral.spec.ts
@@ -0,0 +1,193 @@
+import { vitest, describe, it, expect, beforeEach } from "vitest"
+import type { MockedClass } from "vitest"
+import { MistralEmbedder } from "../mistral"
+import { OpenAICompatibleEmbedder } from "../openai-compatible"
+
+// Mock the OpenAICompatibleEmbedder
+vitest.mock("../openai-compatible")
+
+// Mock TelemetryService
+vitest.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ instance: {
+ captureEvent: vitest.fn(),
+ },
+ },
+}))
+
+const MockedOpenAICompatibleEmbedder = OpenAICompatibleEmbedder as MockedClass
+
+describe("MistralEmbedder", () => {
+ let embedder: MistralEmbedder
+
+ beforeEach(() => {
+ vitest.clearAllMocks()
+ })
+
+ describe("constructor", () => {
+ it("should create an instance with default model when no model specified", () => {
+ // Arrange
+ const apiKey = "test-mistral-api-key"
+
+ // Act
+ embedder = new MistralEmbedder(apiKey)
+
+ // Assert
+ expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith(
+ "https://api.mistral.ai/v1",
+ apiKey,
+ "codestral-embed-2505",
+ 8191,
+ )
+ })
+
+ it("should create an instance with specified model", () => {
+ // Arrange
+ const apiKey = "test-mistral-api-key"
+ const modelId = "custom-embed-model"
+
+ // Act
+ embedder = new MistralEmbedder(apiKey, modelId)
+
+ // Assert
+ expect(MockedOpenAICompatibleEmbedder).toHaveBeenCalledWith(
+ "https://api.mistral.ai/v1",
+ apiKey,
+ "custom-embed-model",
+ 8191,
+ )
+ })
+
+ it("should throw error when API key is not provided", () => {
+ // Act & Assert
+ expect(() => new MistralEmbedder("")).toThrow("validation.apiKeyRequired")
+ expect(() => new MistralEmbedder(null as any)).toThrow("validation.apiKeyRequired")
+ expect(() => new MistralEmbedder(undefined as any)).toThrow("validation.apiKeyRequired")
+ })
+ })
+
+ describe("embedderInfo", () => {
+ it("should return correct embedder info", () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+
+ // Act
+ const info = embedder.embedderInfo
+
+ // Assert
+ expect(info).toEqual({
+ name: "mistral",
+ })
+ })
+
+ describe("createEmbeddings", () => {
+ let mockCreateEmbeddings: any
+
+ beforeEach(() => {
+ mockCreateEmbeddings = vitest.fn()
+ MockedOpenAICompatibleEmbedder.prototype.createEmbeddings = mockCreateEmbeddings
+ })
+
+ it("should use instance model when no model parameter provided", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+ const texts = ["test text 1", "test text 2"]
+ const mockResponse = {
+ embeddings: [
+ [0.1, 0.2],
+ [0.3, 0.4],
+ ],
+ }
+ mockCreateEmbeddings.mockResolvedValue(mockResponse)
+
+ // Act
+ const result = await embedder.createEmbeddings(texts)
+
+ // Assert
+ expect(mockCreateEmbeddings).toHaveBeenCalledWith(texts, "codestral-embed-2505")
+ expect(result).toEqual(mockResponse)
+ })
+
+ it("should use provided model parameter when specified", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key", "custom-embed-model")
+ const texts = ["test text 1", "test text 2"]
+ const mockResponse = {
+ embeddings: [
+ [0.1, 0.2],
+ [0.3, 0.4],
+ ],
+ }
+ mockCreateEmbeddings.mockResolvedValue(mockResponse)
+
+ // Act
+ const result = await embedder.createEmbeddings(texts, "codestral-embed-2505")
+
+ // Assert
+ expect(mockCreateEmbeddings).toHaveBeenCalledWith(texts, "codestral-embed-2505")
+ expect(result).toEqual(mockResponse)
+ })
+
+ it("should handle errors from OpenAICompatibleEmbedder", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+ const texts = ["test text"]
+ const error = new Error("Embedding failed")
+ mockCreateEmbeddings.mockRejectedValue(error)
+
+ // Act & Assert
+ await expect(embedder.createEmbeddings(texts)).rejects.toThrow("Embedding failed")
+ })
+ })
+ })
+
+ describe("validateConfiguration", () => {
+ let mockValidateConfiguration: any
+
+ beforeEach(() => {
+ mockValidateConfiguration = vitest.fn()
+ MockedOpenAICompatibleEmbedder.prototype.validateConfiguration = mockValidateConfiguration
+ })
+
+ it("should delegate validation to OpenAICompatibleEmbedder", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+ mockValidateConfiguration.mockResolvedValue({ valid: true })
+
+ // Act
+ const result = await embedder.validateConfiguration()
+
+ // Assert
+ expect(mockValidateConfiguration).toHaveBeenCalled()
+ expect(result).toEqual({ valid: true })
+ })
+
+ it("should pass through validation errors from OpenAICompatibleEmbedder", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+ mockValidateConfiguration.mockResolvedValue({
+ valid: false,
+ error: "embeddings:validation.authenticationFailed",
+ })
+
+ // Act
+ const result = await embedder.validateConfiguration()
+
+ // Assert
+ expect(mockValidateConfiguration).toHaveBeenCalled()
+ expect(result).toEqual({
+ valid: false,
+ error: "embeddings:validation.authenticationFailed",
+ })
+ })
+
+ it("should handle validation exceptions", async () => {
+ // Arrange
+ embedder = new MistralEmbedder("test-api-key")
+ mockValidateConfiguration.mockRejectedValue(new Error("Validation failed"))
+
+ // Act & Assert
+ await expect(embedder.validateConfiguration()).rejects.toThrow("Validation failed")
+ })
+ })
+})
diff --git a/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts
new file mode 100644
index 0000000000..3e2acc398e
--- /dev/null
+++ b/src/services/code-index/embedders/__tests__/openai-compatible-rate-limit.spec.ts
@@ -0,0 +1,213 @@
+import { describe, it, expect, vi, beforeEach, afterEach, MockedClass, MockedFunction } from "vitest"
+import { OpenAI } from "openai"
+import { OpenAICompatibleEmbedder } from "../openai-compatible"
+
+// Mock the OpenAI SDK
+vi.mock("openai")
+
+// Mock TelemetryService
+vi.mock("@roo-code/telemetry", () => ({
+ TelemetryService: {
+ instance: {
+ captureEvent: vi.fn(),
+ },
+ },
+}))
+
+// Mock i18n
+vi.mock("../../../../i18n", () => ({
+ t: (key: string, params?: Record) => {
+ const translations: Record = {
+ "embeddings:rateLimitRetry": `Rate limit hit, retrying in ${params?.delayMs}ms (attempt ${params?.attempt}/${params?.maxRetries})`,
+ "embeddings:failedMaxAttempts": `Failed to create embeddings after ${params?.attempts} attempts`,
+ "embeddings:failedWithStatus": `Failed to create embeddings after ${params?.attempts} attempts: HTTP ${params?.statusCode} - ${params?.errorMessage}`,
+ "embeddings:failedWithError": `Failed to create embeddings after ${params?.attempts} attempts: ${params?.errorMessage}`,
+ }
+ return translations[key] || key
+ },
+}))
+
+const MockedOpenAI = OpenAI as MockedClass
+
+describe("OpenAICompatibleEmbedder - Global Rate Limiting", () => {
+ let mockOpenAIInstance: any
+ let mockEmbeddingsCreate: MockedFunction
+
+ const testBaseUrl = "https://api.openai.com/v1"
+ const testApiKey = "test-api-key"
+ const testModelId = "text-embedding-3-small"
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ vi.useFakeTimers()
+ vi.spyOn(console, "warn").mockImplementation(() => {})
+ vi.spyOn(console, "error").mockImplementation(() => {})
+
+ // Setup mock OpenAI instance
+ mockEmbeddingsCreate = vi.fn()
+ mockOpenAIInstance = {
+ embeddings: {
+ create: mockEmbeddingsCreate,
+ },
+ }
+
+ MockedOpenAI.mockImplementation(() => mockOpenAIInstance)
+
+ // Reset global rate limit state
+ const embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ ;(embedder as any).constructor.globalRateLimitState = {
+ isRateLimited: false,
+ rateLimitResetTime: 0,
+ consecutiveRateLimitErrors: 0,
+ lastRateLimitError: 0,
+ mutex: (embedder as any).constructor.globalRateLimitState.mutex,
+ }
+ })
+
+ afterEach(() => {
+ vi.useRealTimers()
+ vi.restoreAllMocks()
+ })
+
+ it("should apply global rate limiting across multiple batch requests", async () => {
+ const embedder1 = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ const embedder2 = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+
+ // First batch hits rate limit
+ const rateLimitError = new Error("Rate limit exceeded") as any
+ rateLimitError.status = 429
+
+ mockEmbeddingsCreate
+ .mockRejectedValueOnce(rateLimitError) // First attempt fails
+ .mockResolvedValue({
+ data: [{ embedding: "base64encodeddata" }],
+ usage: { prompt_tokens: 10, total_tokens: 15 },
+ })
+
+ // Start first batch request
+ const batch1Promise = embedder1.createEmbeddings(["test1"])
+
+ // Advance time slightly to let the first request fail and set global rate limit
+ await vi.advanceTimersByTimeAsync(100)
+
+ // Start second batch request while global rate limit is active
+ const batch2Promise = embedder2.createEmbeddings(["test2"])
+
+ // Check that global rate limit was set
+ const state = (embedder1 as any).constructor.globalRateLimitState
+ expect(state.isRateLimited).toBe(true)
+ expect(state.consecutiveRateLimitErrors).toBe(1)
+
+ // Advance time to complete rate limit delay (5 seconds base delay)
+ await vi.advanceTimersByTimeAsync(5000)
+
+ // Both requests should complete
+ const [result1, result2] = await Promise.all([batch1Promise, batch2Promise])
+
+ expect(result1.embeddings).toHaveLength(1)
+ expect(result2.embeddings).toHaveLength(1)
+
+ // The second embedder should have waited for the global rate limit
+ // No logging expected - we've removed it to prevent log flooding
+ })
+
+ it("should track consecutive rate limit errors", async () => {
+ const embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ const state = (embedder as any).constructor.globalRateLimitState
+
+ const rateLimitError = new Error("Rate limit exceeded") as any
+ rateLimitError.status = 429
+
+ // Test that consecutive errors increment when they happen quickly
+ // Mock multiple rate limit errors in a single request
+ mockEmbeddingsCreate
+ .mockRejectedValueOnce(rateLimitError) // First attempt
+ .mockRejectedValueOnce(rateLimitError) // Retry 1
+ .mockResolvedValueOnce({
+ data: [{ embedding: "base64encodeddata" }],
+ usage: { prompt_tokens: 10, total_tokens: 15 },
+ })
+
+ const promise1 = embedder.createEmbeddings(["test1"])
+
+ // Wait for first attempt to fail
+ await vi.advanceTimersByTimeAsync(100)
+ expect(state.consecutiveRateLimitErrors).toBe(1)
+
+ // Wait for first retry (500ms) to also fail
+ await vi.advanceTimersByTimeAsync(500)
+
+ // The state should show 2 consecutive errors now
+ // Note: The count might be 1 if the global rate limit kicked in before the second attempt
+ expect(state.consecutiveRateLimitErrors).toBeGreaterThanOrEqual(1)
+
+ // Wait for the global rate limit and successful retry
+ await vi.advanceTimersByTimeAsync(20000)
+ await promise1
+
+ // Verify the delay increases with consecutive errors
+ // Make another request immediately that also hits rate limit
+ mockEmbeddingsCreate.mockRejectedValueOnce(rateLimitError).mockResolvedValueOnce({
+ data: [{ embedding: "base64encodeddata" }],
+ usage: { prompt_tokens: 10, total_tokens: 15 },
+ })
+
+ // Store the current consecutive count before the next request
+ const previousCount = state.consecutiveRateLimitErrors
+
+ const promise2 = embedder.createEmbeddings(["test2"])
+ await vi.advanceTimersByTimeAsync(100)
+
+ // Should have incremented from the previous count
+ expect(state.consecutiveRateLimitErrors).toBeGreaterThan(previousCount)
+
+ // Complete the second request
+ await vi.advanceTimersByTimeAsync(20000)
+ await promise2
+ })
+
+ it("should reset consecutive error count after time passes", async () => {
+ const embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ const state = (embedder as any).constructor.globalRateLimitState
+
+ // Manually set state to simulate previous errors
+ state.consecutiveRateLimitErrors = 3
+ state.lastRateLimitError = Date.now() - 70000 // 70 seconds ago
+
+ const rateLimitError = new Error("Rate limit exceeded") as any
+ rateLimitError.status = 429
+
+ mockEmbeddingsCreate.mockRejectedValueOnce(rateLimitError).mockResolvedValueOnce({
+ data: [{ embedding: "base64encodeddata" }],
+ usage: { prompt_tokens: 10, total_tokens: 15 },
+ })
+
+ // Trigger the updateGlobalRateLimitState method
+ await (embedder as any).updateGlobalRateLimitState(rateLimitError)
+
+ // Should reset to 1 since more than 60 seconds passed
+ expect(state.consecutiveRateLimitErrors).toBe(1)
+ })
+
+ it("should not exceed maximum delay of 5 minutes", async () => {
+ const embedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ const state = (embedder as any).constructor.globalRateLimitState
+
+ // Set state to simulate many consecutive errors
+ state.consecutiveRateLimitErrors = 10 // This would normally result in a very long delay
+
+ const rateLimitError = new Error("Rate limit exceeded") as any
+ rateLimitError.status = 429
+
+ // Trigger the updateGlobalRateLimitState method
+ await (embedder as any).updateGlobalRateLimitState(rateLimitError)
+
+ // Calculate the expected delay
+ const now = Date.now()
+ const delay = state.rateLimitResetTime - now
+
+ // Should be capped at 5 minutes (300000ms)
+ expect(delay).toBeLessThanOrEqual(300000)
+ expect(delay).toBeGreaterThan(0)
+ })
+})
diff --git a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts
index ff757b86c7..0353771f60 100644
--- a/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts
+++ b/src/services/code-index/embedders/__tests__/openai-compatible.spec.ts
@@ -60,6 +60,16 @@ describe("OpenAICompatibleEmbedder", () => {
}
MockedOpenAI.mockImplementation(() => mockOpenAIInstance)
+
+ // Reset global rate limit state to prevent interference between tests
+ const tempEmbedder = new OpenAICompatibleEmbedder(testBaseUrl, testApiKey, testModelId)
+ ;(tempEmbedder as any).constructor.globalRateLimitState = {
+ isRateLimited: false,
+ rateLimitResetTime: 0,
+ consecutiveRateLimitErrors: 0,
+ lastRateLimitError: 0,
+ mutex: (tempEmbedder as any).constructor.globalRateLimitState.mutex,
+ }
})
afterEach(() => {
@@ -385,9 +395,17 @@ describe("OpenAICompatibleEmbedder", () => {
const resultPromise = embedder.createEmbeddings(testTexts)
- // Fast-forward through the delays
- await vitest.advanceTimersByTimeAsync(INITIAL_RETRY_DELAY_MS) // First retry delay
- await vitest.advanceTimersByTimeAsync(INITIAL_RETRY_DELAY_MS * 2) // Second retry delay
+ // First attempt fails immediately, triggering global rate limit (5s)
+ await vitest.advanceTimersByTimeAsync(100)
+
+ // Wait for global rate limit delay
+ await vitest.advanceTimersByTimeAsync(5000)
+
+ // Second attempt also fails, increasing delay
+ await vitest.advanceTimersByTimeAsync(100)
+
+ // Wait for increased global rate limit delay (10s)
+ await vitest.advanceTimersByTimeAsync(10000)
const result = await resultPromise
@@ -445,7 +463,7 @@ describe("OpenAICompatibleEmbedder", () => {
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("OpenAI Compatible embedder error"),
- expect.any(Error),
+ apiError,
)
})
@@ -461,7 +479,7 @@ describe("OpenAICompatibleEmbedder", () => {
expect(console.error).toHaveBeenCalledWith(
expect.stringContaining("OpenAI Compatible embedder error"),
- batchError,
+ expect.any(Error),
)
})
@@ -791,10 +809,23 @@ describe("OpenAICompatibleEmbedder", () => {
)
const resultPromise = embedder.createEmbeddings(["test"])
- await vitest.advanceTimersByTimeAsync(INITIAL_RETRY_DELAY_MS * 3)
+
+ // First attempt fails, triggering global rate limit
+ await vitest.advanceTimersByTimeAsync(100)
+
+ // Wait for global rate limit (5s)
+ await vitest.advanceTimersByTimeAsync(5000)
+
+ // Second attempt also fails
+ await vitest.advanceTimersByTimeAsync(100)
+
+ // Wait for increased global rate limit (10s)
+ await vitest.advanceTimersByTimeAsync(10000)
+
const result = await resultPromise
expect(global.fetch).toHaveBeenCalledTimes(3)
+ // Check that rate limit warnings were logged
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("Rate limit hit"))
expectEmbeddingValues(result.embeddings[0], [0.1, 0.2, 0.3])
vitest.useRealTimers()
diff --git a/src/services/code-index/embedders/mistral.ts b/src/services/code-index/embedders/mistral.ts
new file mode 100644
index 0000000000..c23bcbba1d
--- /dev/null
+++ b/src/services/code-index/embedders/mistral.ts
@@ -0,0 +1,91 @@
+import { OpenAICompatibleEmbedder } from "./openai-compatible"
+import { IEmbedder, EmbeddingResponse, EmbedderInfo } from "../interfaces/embedder"
+import { MAX_ITEM_TOKENS } from "../constants"
+import { t } from "../../../i18n"
+import { TelemetryEventName } from "@roo-code/types"
+import { TelemetryService } from "@roo-code/telemetry"
+
+/**
+ * Mistral embedder implementation that wraps the OpenAI Compatible embedder
+ * with configuration for Mistral's embedding API.
+ *
+ * Supported models:
+ * - codestral-embed-2505 (dimension: 1536)
+ */
+export class MistralEmbedder implements IEmbedder {
+ private readonly openAICompatibleEmbedder: OpenAICompatibleEmbedder
+ private static readonly MISTRAL_BASE_URL = "https://api.mistral.ai/v1"
+ private static readonly DEFAULT_MODEL = "codestral-embed-2505"
+ private readonly modelId: string
+
+ /**
+ * Creates a new Mistral embedder
+ * @param apiKey The Mistral API key for authentication
+ * @param modelId The model ID to use (defaults to codestral-embed-2505)
+ */
+ constructor(apiKey: string, modelId?: string) {
+ if (!apiKey) {
+ throw new Error(t("embeddings:validation.apiKeyRequired"))
+ }
+
+ // Use provided model or default
+ this.modelId = modelId || MistralEmbedder.DEFAULT_MODEL
+
+ // Create an OpenAI Compatible embedder with Mistral's configuration
+ this.openAICompatibleEmbedder = new OpenAICompatibleEmbedder(
+ MistralEmbedder.MISTRAL_BASE_URL,
+ apiKey,
+ this.modelId,
+ MAX_ITEM_TOKENS, // This is the max token limit (8191), not the embedding dimension
+ )
+ }
+
+ /**
+ * Creates embeddings for the given texts using Mistral's embedding API
+ * @param texts Array of text strings to embed
+ * @param model Optional model identifier (uses constructor model if not provided)
+ * @returns Promise resolving to embedding response
+ */
+ async createEmbeddings(texts: string[], model?: string): Promise {
+ try {
+ // Use the provided model or fall back to the instance's model
+ const modelToUse = model || this.modelId
+ return await this.openAICompatibleEmbedder.createEmbeddings(texts, modelToUse)
+ } catch (error) {
+ TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
+ error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
+ location: "MistralEmbedder:createEmbeddings",
+ })
+ throw error
+ }
+ }
+
+ /**
+ * Validates the Mistral embedder configuration by delegating to the underlying OpenAI-compatible embedder
+ * @returns Promise resolving to validation result with success status and optional error message
+ */
+ async validateConfiguration(): Promise<{ valid: boolean; error?: string }> {
+ try {
+ // Delegate validation to the OpenAI-compatible embedder
+ // The error messages will be specific to Mistral since we're using Mistral's base URL
+ return await this.openAICompatibleEmbedder.validateConfiguration()
+ } catch (error) {
+ TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
+ error: error instanceof Error ? error.message : String(error),
+ stack: error instanceof Error ? error.stack : undefined,
+ location: "MistralEmbedder:validateConfiguration",
+ })
+ throw error
+ }
+ }
+
+ /**
+ * Returns information about this embedder
+ */
+ get embedderInfo(): EmbedderInfo {
+ return {
+ name: "mistral",
+ }
+ }
+}
diff --git a/src/services/code-index/embedders/openai-compatible.ts b/src/services/code-index/embedders/openai-compatible.ts
index d882e78313..035f50f386 100644
--- a/src/services/code-index/embedders/openai-compatible.ts
+++ b/src/services/code-index/embedders/openai-compatible.ts
@@ -11,6 +11,7 @@ import { t } from "../../../i18n"
import { withValidationErrorHandling, HttpError, formatEmbeddingError } from "../shared/validation-helpers"
import { TelemetryEventName } from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
+import { Mutex } from "async-mutex"
interface EmbeddingItem {
embedding: string | number[]
@@ -38,6 +39,16 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
private readonly isFullUrl: boolean
private readonly maxItemTokens: number
+ // Global rate limiting state shared across all instances
+ private static globalRateLimitState = {
+ isRateLimited: false,
+ rateLimitResetTime: 0,
+ consecutiveRateLimitErrors: 0,
+ lastRateLimitError: 0,
+ // Mutex to ensure thread-safe access to rate limit state
+ mutex: new Mutex(),
+ }
+
/**
* Creates a new OpenAI Compatible embedder
* @param baseUrl The base URL for the OpenAI-compatible API endpoint
@@ -239,6 +250,9 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
const isFullUrl = this.isFullUrl
for (let attempts = 0; attempts < MAX_RETRIES; attempts++) {
+ // Check global rate limit before attempting request
+ await this.waitForGlobalRateLimit()
+
try {
let response: OpenAIEmbeddingResponse
@@ -298,17 +312,26 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
// Check if it's a rate limit error
const httpError = error as HttpError
- if (httpError?.status === 429 && hasMoreAttempts) {
- const delayMs = INITIAL_DELAY_MS * Math.pow(2, attempts)
- console.warn(
- t("embeddings:rateLimitRetry", {
- delayMs,
- attempt: attempts + 1,
- maxRetries: MAX_RETRIES,
- }),
- )
- await new Promise((resolve) => setTimeout(resolve, delayMs))
- continue
+ if (httpError?.status === 429) {
+ // Update global rate limit state
+ await this.updateGlobalRateLimitState(httpError)
+
+ if (hasMoreAttempts) {
+ // Calculate delay based on global rate limit state
+ const baseDelay = INITIAL_DELAY_MS * Math.pow(2, attempts)
+ const globalDelay = await this.getGlobalRateLimitDelay()
+ const delayMs = Math.max(baseDelay, globalDelay)
+
+ console.warn(
+ t("embeddings:rateLimitRetry", {
+ delayMs,
+ attempt: attempts + 1,
+ maxRetries: MAX_RETRIES,
+ }),
+ )
+ await new Promise((resolve) => setTimeout(resolve, delayMs))
+ continue
+ }
}
// Log the error for debugging
@@ -376,4 +399,87 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
name: "openai-compatible",
}
}
+
+ /**
+ * Waits if there's an active global rate limit
+ */
+ private async waitForGlobalRateLimit(): Promise {
+ const release = await OpenAICompatibleEmbedder.globalRateLimitState.mutex.acquire()
+ try {
+ const state = OpenAICompatibleEmbedder.globalRateLimitState
+
+ if (state.isRateLimited && state.rateLimitResetTime > Date.now()) {
+ const waitTime = state.rateLimitResetTime - Date.now()
+ // Silent wait - no logging to prevent flooding
+ release() // Release mutex before waiting
+ await new Promise((resolve) => setTimeout(resolve, waitTime))
+ return
+ }
+
+ // Reset rate limit if time has passed
+ if (state.isRateLimited && state.rateLimitResetTime <= Date.now()) {
+ state.isRateLimited = false
+ state.consecutiveRateLimitErrors = 0
+ }
+ } finally {
+ // Only release if we haven't already
+ try {
+ release()
+ } catch {
+ // Already released
+ }
+ }
+ }
+
+ /**
+ * Updates global rate limit state when a 429 error occurs
+ */
+ private async updateGlobalRateLimitState(error: HttpError): Promise {
+ const release = await OpenAICompatibleEmbedder.globalRateLimitState.mutex.acquire()
+ try {
+ const state = OpenAICompatibleEmbedder.globalRateLimitState
+ const now = Date.now()
+
+ // Increment consecutive rate limit errors
+ if (now - state.lastRateLimitError < 60000) {
+ // Within 1 minute
+ state.consecutiveRateLimitErrors++
+ } else {
+ state.consecutiveRateLimitErrors = 1
+ }
+
+ state.lastRateLimitError = now
+
+ // Calculate exponential backoff based on consecutive errors
+ const baseDelay = 5000 // 5 seconds base
+ const maxDelay = 300000 // 5 minutes max
+ const exponentialDelay = Math.min(baseDelay * Math.pow(2, state.consecutiveRateLimitErrors - 1), maxDelay)
+
+ // Set global rate limit
+ state.isRateLimited = true
+ state.rateLimitResetTime = now + exponentialDelay
+
+ // Silent rate limit activation - no logging to prevent flooding
+ } finally {
+ release()
+ }
+ }
+
+ /**
+ * Gets the current global rate limit delay
+ */
+ private async getGlobalRateLimitDelay(): Promise {
+ const release = await OpenAICompatibleEmbedder.globalRateLimitState.mutex.acquire()
+ try {
+ const state = OpenAICompatibleEmbedder.globalRateLimitState
+
+ if (state.isRateLimited && state.rateLimitResetTime > Date.now()) {
+ return state.rateLimitResetTime - Date.now()
+ }
+
+ return 0
+ } finally {
+ release()
+ }
+ }
}
diff --git a/src/services/code-index/interfaces/config.ts b/src/services/code-index/interfaces/config.ts
index 190a23e2a3..9098a60091 100644
--- a/src/services/code-index/interfaces/config.ts
+++ b/src/services/code-index/interfaces/config.ts
@@ -13,6 +13,7 @@ export interface CodeIndexConfig {
ollamaOptions?: ApiHandlerOptions
openAiCompatibleOptions?: { baseUrl: string; apiKey: string }
geminiOptions?: { apiKey: string }
+ mistralOptions?: { apiKey: string }
qdrantUrl?: string
qdrantApiKey?: string
searchMinScore?: number
@@ -33,6 +34,7 @@ export type PreviousConfigSnapshot = {
openAiCompatibleBaseUrl?: string
openAiCompatibleApiKey?: string
geminiApiKey?: string
+ mistralApiKey?: string
qdrantUrl?: string
qdrantApiKey?: string
}
diff --git a/src/services/code-index/interfaces/embedder.ts b/src/services/code-index/interfaces/embedder.ts
index 0a74446d5e..c5653ea2b7 100644
--- a/src/services/code-index/interfaces/embedder.ts
+++ b/src/services/code-index/interfaces/embedder.ts
@@ -28,7 +28,7 @@ export interface EmbeddingResponse {
}
}
-export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini"
+export type AvailableEmbedders = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral"
export interface EmbedderInfo {
name: AvailableEmbedders
diff --git a/src/services/code-index/interfaces/manager.ts b/src/services/code-index/interfaces/manager.ts
index 70e3fd9765..fd3b2bfdda 100644
--- a/src/services/code-index/interfaces/manager.ts
+++ b/src/services/code-index/interfaces/manager.ts
@@ -70,7 +70,7 @@ export interface ICodeIndexManager {
}
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
-export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini"
+export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral"
export interface IndexProgressUpdate {
systemStatus: IndexingState
diff --git a/src/services/code-index/processors/scanner.ts b/src/services/code-index/processors/scanner.ts
index e6ca297399..3203076d12 100644
--- a/src/services/code-index/processors/scanner.ts
+++ b/src/services/code-index/processors/scanner.ts
@@ -23,6 +23,7 @@ import {
INITIAL_RETRY_DELAY_MS,
PARSING_CONCURRENCY,
BATCH_PROCESSING_CONCURRENCY,
+ MAX_PENDING_BATCHES,
} from "../constants"
import { isPathInIgnoredDirectory } from "../../glob/ignore-utils"
import { TelemetryService } from "@roo-code/telemetry"
@@ -98,6 +99,7 @@ export class DirectoryScanner implements IDirectoryScanner {
let currentBatchTexts: string[] = []
let currentBatchFileInfos: { filePath: string; fileHash: string; isNew: boolean }[] = []
const activeBatchPromises = new Set>()
+ let pendingBatchCount = 0
// Initialize block counter
let totalBlockCount = 0
@@ -152,6 +154,12 @@ export class DirectoryScanner implements IDirectoryScanner {
// Check if batch threshold is met
if (currentBatchBlocks.length >= BATCH_SEGMENT_THRESHOLD) {
+ // Wait if we've reached the maximum pending batches
+ while (pendingBatchCount >= MAX_PENDING_BATCHES) {
+ // Wait for at least one batch to complete
+ await Promise.race(activeBatchPromises)
+ }
+
// Copy current batch data and clear accumulators
const batchBlocks = [...currentBatchBlocks]
const batchTexts = [...currentBatchTexts]
@@ -160,6 +168,9 @@ export class DirectoryScanner implements IDirectoryScanner {
currentBatchTexts = []
currentBatchFileInfos = []
+ // Increment pending batch count
+ pendingBatchCount++
+
// Queue batch processing
const batchPromise = batchLimiter(() =>
this.processBatch(
@@ -176,6 +187,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Clean up completed promises to prevent memory accumulation
batchPromise.finally(() => {
activeBatchPromises.delete(batchPromise)
+ pendingBatchCount--
})
}
} finally {
@@ -238,6 +250,9 @@ export class DirectoryScanner implements IDirectoryScanner {
currentBatchTexts = []
currentBatchFileInfos = []
+ // Increment pending batch count for final batch
+ pendingBatchCount++
+
// Queue final batch processing
const batchPromise = batchLimiter(() =>
this.processBatch(batchBlocks, batchTexts, batchFileInfos, scanWorkspace, onError, onBlocksIndexed),
@@ -247,6 +262,7 @@ export class DirectoryScanner implements IDirectoryScanner {
// Clean up completed promises to prevent memory accumulation
batchPromise.finally(() => {
activeBatchPromises.delete(batchPromise)
+ pendingBatchCount--
})
} finally {
release()
diff --git a/src/services/code-index/service-factory.ts b/src/services/code-index/service-factory.ts
index b7951db7ac..68b0f5c0bc 100644
--- a/src/services/code-index/service-factory.ts
+++ b/src/services/code-index/service-factory.ts
@@ -3,6 +3,7 @@ import { OpenAiEmbedder } from "./embedders/openai"
import { CodeIndexOllamaEmbedder } from "./embedders/ollama"
import { OpenAICompatibleEmbedder } from "./embedders/openai-compatible"
import { GeminiEmbedder } from "./embedders/gemini"
+import { MistralEmbedder } from "./embedders/mistral"
import { EmbedderProvider, getDefaultModelId, getModelDimension } from "../../shared/embeddingModels"
import { QdrantVectorStore } from "./vector-store/qdrant-client"
import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
@@ -64,6 +65,11 @@ export class CodeIndexServiceFactory {
throw new Error(t("embeddings:serviceFactory.geminiConfigMissing"))
}
return new GeminiEmbedder(config.geminiOptions.apiKey, config.modelId)
+ } else if (provider === "mistral") {
+ if (!config.mistralOptions?.apiKey) {
+ throw new Error(t("embeddings:serviceFactory.mistralConfigMissing"))
+ }
+ return new MistralEmbedder(config.mistralOptions.apiKey, config.modelId)
}
throw new Error(
diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts
index 98ef4514c2..7dc7f00c04 100644
--- a/src/services/mcp/__tests__/McpHub.spec.ts
+++ b/src/services/mcp/__tests__/McpHub.spec.ts
@@ -93,7 +93,6 @@ describe("McpHub", () => {
// Mock console.error to suppress error messages during tests
console.error = vi.fn()
-
const mockUri: Uri = {
scheme: "file",
authority: "",
diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts
index 53757bb2a3..8fe3323941 100644
--- a/src/shared/ExtensionMessage.ts
+++ b/src/shared/ExtensionMessage.ts
@@ -209,6 +209,7 @@ export type ExtensionState = Pick<
// | "maxReadFileLine" // Optional in GlobalSettings, required here.
| "maxConcurrentFileReads" // Optional in GlobalSettings, required here.
| "terminalOutputLineLimit"
+ | "terminalOutputCharacterLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
@@ -218,6 +219,7 @@ export type ExtensionState = Pick<
| "terminalZshP10k"
| "terminalZdotdir"
| "terminalCompressProgressBar"
+ | "diagnosticsEnabled"
| "diffEnabled"
| "fuzzyMatchThreshold"
// | "experiments" // Optional in GlobalSettings, required here.
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index b81e3f737e..8921a26003 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -107,6 +107,7 @@ export interface WebviewMessage {
| "updateMcpTimeout"
| "fuzzyMatchThreshold"
| "writeDelayMs"
+ | "diagnosticsEnabled"
| "enhancePrompt"
| "enhancedPrompt"
| "draggedImages"
@@ -115,6 +116,7 @@ export interface WebviewMessage {
| "submitEditedMessage"
| "editMessageConfirm"
| "terminalOutputLineLimit"
+ | "terminalOutputCharacterLimit"
| "terminalShellIntegrationTimeout"
| "terminalShellIntegrationDisabled"
| "terminalCommandDelay"
@@ -252,7 +254,7 @@ export interface WebviewMessage {
// Global state settings
codebaseIndexEnabled: boolean
codebaseIndexQdrantUrl: string
- codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini"
+ codebaseIndexEmbedderProvider: "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral"
codebaseIndexEmbedderBaseUrl?: string
codebaseIndexEmbedderModelId: string
codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers
@@ -265,6 +267,7 @@ export interface WebviewMessage {
codeIndexQdrantApiKey?: string
codebaseIndexOpenAiCompatibleApiKey?: string
codebaseIndexGeminiApiKey?: string
+ codebaseIndexMistralApiKey?: string
}
}
diff --git a/src/shared/embeddingModels.ts b/src/shared/embeddingModels.ts
index f387480c65..a3cd61e659 100644
--- a/src/shared/embeddingModels.ts
+++ b/src/shared/embeddingModels.ts
@@ -2,7 +2,7 @@
* Defines profiles for different embedding models, including their dimensions.
*/
-export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" // Add other providers as needed
+export type EmbedderProvider = "openai" | "ollama" | "openai-compatible" | "gemini" | "mistral" // Add other providers as needed
export interface EmbeddingModelProfile {
dimension: number
@@ -50,6 +50,9 @@ export const EMBEDDING_MODEL_PROFILES: EmbeddingModelProfiles = {
"text-embedding-004": { dimension: 768 },
"gemini-embedding-001": { dimension: 3072, scoreThreshold: 0.4 },
},
+ mistral: {
+ "codestral-embed-2505": { dimension: 1536, scoreThreshold: 0.4 },
+ },
}
/**
@@ -137,6 +140,9 @@ export function getDefaultModelId(provider: EmbedderProvider): string {
case "gemini":
return "gemini-embedding-001"
+ case "mistral":
+ return "codestral-embed-2505"
+
default:
// Fallback for unknown providers
console.warn(`Unknown provider for default model ID: ${provider}. Falling back to OpenAI default.`)
diff --git a/src/shared/support-prompt.ts b/src/shared/support-prompt.ts
index 1767a20753..51f4310fc2 100644
--- a/src/shared/support-prompt.ts
+++ b/src/shared/support-prompt.ts
@@ -35,6 +35,7 @@ interface SupportPromptConfig {
type SupportPromptType =
| "ENHANCE"
+ | "CONDENSE"
| "EXPLAIN"
| "FIX"
| "IMPROVE"
@@ -49,6 +50,45 @@ const supportPromptConfigs: Record = {
template: `Generate an enhanced version of this prompt (reply with only the enhanced prompt - no conversation, explanations, lead-in, bullet points, placeholders, or surrounding quotes):
\${userInput}`,
+ },
+ CONDENSE: {
+ template: `Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
+This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the conversation and supporting any continuing tasks.
+
+Your summary should be structured as follows:
+Context: The context to continue the conversation with. If applicable based on the current task, this should include:
+ 1. Previous Conversation: High level details about what was discussed throughout the entire conversation with the user. This should be written to allow someone to be able to follow the general overarching conversation flow.
+ 2. Current Work: Describe in detail what was being worked on prior to this request to summarize the conversation. Pay special attention to the more recent messages in the conversation.
+ 3. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for continuing with this work.
+ 4. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
+ 5. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
+ 6. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks.
+
+Example summary structure:
+1. Previous Conversation:
+ [Detailed description]
+2. Current Work:
+ [Detailed description]
+3. Key Technical Concepts:
+ - [Concept 1]
+ - [Concept 2]
+ - [...]
+4. Relevant Files and Code:
+ - [File Name 1]
+ - [Summary of why this file is important]
+ - [Summary of the changes made to this file, if any]
+ - [Important Code Snippet]
+ - [File Name 2]
+ - [Important Code Snippet]
+ - [...]
+5. Problem Solving:
+ [Detailed description]
+6. Pending Tasks and Next Steps:
+ - [Task 1 details & next steps]
+ - [Task 2 details & next steps]
+ - [...]
+
+Output only the summary of the conversation so far, without any additional commentary or explanation.`,
},
EXPLAIN: {
template: `Explain the following code from file path \${filePath}:\${startLine}-\${endLine}
diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx
index 4385e2e844..d7683e8c7e 100644
--- a/webview-ui/src/components/chat/CodeIndexPopover.tsx
+++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx
@@ -68,6 +68,7 @@ interface LocalCodeIndexSettings {
codebaseIndexOpenAiCompatibleBaseUrl?: string
codebaseIndexOpenAiCompatibleApiKey?: string
codebaseIndexGeminiApiKey?: string
+ codebaseIndexMistralApiKey?: string
}
// Validation schema for codebase index settings
@@ -126,6 +127,14 @@ const createValidationSchema = (provider: EmbedderProvider, t: any) => {
.min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
})
+ case "mistral":
+ return baseSchema.extend({
+ codebaseIndexMistralApiKey: z.string().min(1, t("settings:codeIndex.validation.mistralApiKeyRequired")),
+ codebaseIndexEmbedderModelId: z
+ .string()
+ .min(1, t("settings:codeIndex.validation.modelSelectionRequired")),
+ })
+
default:
return baseSchema
}
@@ -169,6 +178,7 @@ export const CodeIndexPopover: React.FC = ({
codebaseIndexOpenAiCompatibleBaseUrl: "",
codebaseIndexOpenAiCompatibleApiKey: "",
codebaseIndexGeminiApiKey: "",
+ codebaseIndexMistralApiKey: "",
})
// Initial settings state - stores the settings when popover opens
@@ -202,6 +212,7 @@ export const CodeIndexPopover: React.FC = ({
codebaseIndexOpenAiCompatibleBaseUrl: codebaseIndexConfig.codebaseIndexOpenAiCompatibleBaseUrl || "",
codebaseIndexOpenAiCompatibleApiKey: "",
codebaseIndexGeminiApiKey: "",
+ codebaseIndexMistralApiKey: "",
}
setInitialSettings(settings)
setCurrentSettings(settings)
@@ -293,6 +304,9 @@ export const CodeIndexPopover: React.FC = ({
if (!prev.codebaseIndexGeminiApiKey || prev.codebaseIndexGeminiApiKey === SECRET_PLACEHOLDER) {
updated.codebaseIndexGeminiApiKey = secretStatus.hasGeminiApiKey ? SECRET_PLACEHOLDER : ""
}
+ if (!prev.codebaseIndexMistralApiKey || prev.codebaseIndexMistralApiKey === SECRET_PLACEHOLDER) {
+ updated.codebaseIndexMistralApiKey = secretStatus.hasMistralApiKey ? SECRET_PLACEHOLDER : ""
+ }
return updated
}
@@ -364,7 +378,8 @@ export const CodeIndexPopover: React.FC = ({
if (
key === "codeIndexOpenAiKey" ||
key === "codebaseIndexOpenAiCompatibleApiKey" ||
- key === "codebaseIndexGeminiApiKey"
+ key === "codebaseIndexGeminiApiKey" ||
+ key === "codebaseIndexMistralApiKey"
) {
dataToValidate[key] = "placeholder-valid"
}
@@ -606,6 +621,9 @@ export const CodeIndexPopover: React.FC = ({
{t("settings:codeIndex.geminiProvider")}
+
+ {t("settings:codeIndex.mistralProvider")}
+
@@ -933,6 +951,71 @@ export const CodeIndexPopover: React.FC = ({
>
)}
+ {currentSettings.codebaseIndexEmbedderProvider === "mistral" && (
+ <>
+
({
}))
// Mock vscode utilities - this is necessary since we're not in a VSCode environment
-import { vscode } from "@/utils/vscode"
vitest.mock("@/utils/vscode", () => ({
vscode: {
@@ -169,8 +168,6 @@ describe("ContextManagementSettings", () => {
...defaultProps,
autoCondenseContext: true,
autoCondenseContextPercent: 75,
- condensingApiConfigId: "test-config",
- customCondensingPrompt: "Test prompt",
}
render()
@@ -182,13 +179,9 @@ describe("ContextManagementSettings", () => {
const slider = screen.getByTestId("condense-threshold-slider")
expect(slider).toBeInTheDocument()
- // Should render both select dropdowns (profile and API config)
+ // Should render the profile select dropdown
const selects = screen.getAllByRole("combobox")
- expect(selects).toHaveLength(2)
-
- // Should render the custom prompt textarea
- const textarea = screen.getByRole("textbox")
- expect(textarea).toBeInTheDocument()
+ expect(selects).toHaveLength(1)
})
describe("Auto Condense Context functionality", () => {
@@ -196,8 +189,6 @@ describe("ContextManagementSettings", () => {
...defaultProps,
autoCondenseContext: true,
autoCondenseContextPercent: 75,
- condensingApiConfigId: "test-config",
- customCondensingPrompt: "Custom test prompt",
listApiConfigMeta: [
{ id: "config-1", name: "Config 1" },
{ id: "config-2", name: "Config 2" },
@@ -217,14 +208,13 @@ describe("ContextManagementSettings", () => {
expect(mockSetCachedStateField).toHaveBeenCalledWith("autoCondenseContext", false)
})
- it("shows additional settings when auto condense is enabled", () => {
+ it("shows threshold settings when auto condense is enabled", () => {
render()
- // Additional settings should be visible
+ // Threshold settings should be visible
expect(screen.getByTestId("condense-threshold-slider")).toBeInTheDocument()
- // Two comboboxes: one for profile selection, one for API config
- expect(screen.getAllByRole("combobox")).toHaveLength(2)
- expect(screen.getByRole("textbox")).toBeInTheDocument()
+ // One combobox for profile selection
+ expect(screen.getAllByRole("combobox")).toHaveLength(1)
})
it("updates auto condense context percent", () => {
@@ -246,112 +236,6 @@ describe("ContextManagementSettings", () => {
render()
expect(screen.getByText("75%")).toBeInTheDocument()
})
-
- it("updates condensing API configuration", () => {
- const mockSetCachedStateField = vitest.fn()
- const mockPostMessage = vitest.fn()
- const postMessageSpy = vitest.spyOn(vscode, "postMessage")
- postMessageSpy.mockImplementation(mockPostMessage)
-
- const props = { ...autoCondenseProps, setCachedStateField: mockSetCachedStateField }
- render()
-
- // Get the second combobox (API config select)
- const selects = screen.getAllByRole("combobox")
- const apiSelect = selects[1]
- fireEvent.click(apiSelect)
-
- const configOption = screen.getByText("Config 1")
- fireEvent.click(configOption)
-
- expect(mockSetCachedStateField).toHaveBeenCalledWith("condensingApiConfigId", "config-1")
- expect(mockPostMessage).toHaveBeenCalledWith({
- type: "condensingApiConfigId",
- text: "config-1",
- })
- })
-
- it("handles selecting default config option", () => {
- const mockSetCachedStateField = vitest.fn()
- const mockPostMessage = vitest.fn()
- const postMessageSpy = vitest.spyOn(vscode, "postMessage")
- postMessageSpy.mockImplementation(mockPostMessage)
-
- const props = { ...autoCondenseProps, setCachedStateField: mockSetCachedStateField }
- render()
-
- // Test selecting default config - get the second combobox (API config)
- const selects = screen.getAllByRole("combobox")
- const apiSelect = selects[1]
- fireEvent.click(apiSelect)
- const defaultOption = screen.getByText(
- "settings:contextManagement.condensingApiConfiguration.useCurrentConfig",
- )
- fireEvent.click(defaultOption)
-
- expect(mockSetCachedStateField).toHaveBeenCalledWith("condensingApiConfigId", "")
- expect(mockPostMessage).toHaveBeenCalledWith({
- type: "condensingApiConfigId",
- text: "",
- })
- })
-
- it("updates custom condensing prompt", () => {
- const mockSetCachedStateField = vitest.fn()
- const mockPostMessage = vitest.fn()
- const postMessageSpy = vitest.spyOn(vscode, "postMessage")
- postMessageSpy.mockImplementation(mockPostMessage)
-
- const props = { ...autoCondenseProps, setCachedStateField: mockSetCachedStateField }
- render()
-
- const textarea = screen.getByRole("textbox")
- const newPrompt = "Updated custom prompt"
- fireEvent.change(textarea, { target: { value: newPrompt } })
-
- expect(mockSetCachedStateField).toHaveBeenCalledWith("customCondensingPrompt", newPrompt)
- expect(mockPostMessage).toHaveBeenCalledWith({
- type: "updateCondensingPrompt",
- text: newPrompt,
- })
- })
-
- it("resets custom condensing prompt to default", () => {
- const mockSetCachedStateField = vitest.fn()
- const mockPostMessage = vitest.fn()
- const postMessageSpy = vitest.spyOn(vscode, "postMessage")
- postMessageSpy.mockImplementation(mockPostMessage)
-
- const props = { ...autoCondenseProps, setCachedStateField: mockSetCachedStateField }
- render()
-
- const resetButton = screen.getByRole("button", {
- name: "settings:contextManagement.customCondensingPrompt.reset",
- })
- fireEvent.click(resetButton)
-
- // Should reset to the default SUMMARY_PROMPT
- expect(mockSetCachedStateField).toHaveBeenCalledWith(
- "customCondensingPrompt",
- expect.stringContaining("Your task is to create a detailed summary"),
- )
- expect(mockPostMessage).toHaveBeenCalledWith({
- type: "updateCondensingPrompt",
- text: expect.stringContaining("Your task is to create a detailed summary"),
- })
- })
-
- it("uses default prompt when customCondensingPrompt is undefined", () => {
- const propsWithoutCustomPrompt = {
- ...autoCondenseProps,
- customCondensingPrompt: undefined,
- }
- render()
-
- const textarea = screen.getByRole("textbox") as HTMLTextAreaElement
- // The textarea should contain the full default SUMMARY_PROMPT
- expect(textarea.value).toContain("Your task is to create a detailed summary")
- })
})
describe("Edge cases and validation", () => {
@@ -427,8 +311,6 @@ describe("ContextManagementSettings", () => {
...defaultProps,
showRooIgnoredFiles: undefined,
maxReadFileLine: undefined,
- condensingApiConfigId: undefined,
- customCondensingPrompt: undefined,
}
expect(() => {
@@ -442,21 +324,15 @@ describe("ContextManagementSettings", () => {
})
describe("Conditional rendering", () => {
- it("does not render auto condense section when autoCondenseContext is false", () => {
+ it("does not render threshold settings when autoCondenseContext is false", () => {
const propsWithoutAutoCondense = {
...defaultProps,
autoCondenseContext: false,
}
render()
- // When auto condense is false, all condensing-related UI should not be visible
+ // When auto condense is false, threshold slider should not be visible
expect(screen.queryByTestId("condense-threshold-slider")).not.toBeInTheDocument()
- expect(
- screen.queryByText("settings:contextManagement.condensingApiConfiguration.label"),
- ).not.toBeInTheDocument()
- expect(
- screen.queryByText("settings:contextManagement.customCondensingPrompt.label"),
- ).not.toBeInTheDocument()
})
it("renders max read file controls with default value when maxReadFileLine is undefined", () => {
diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx
index 72d4598ea6..694ff174a7 100644
--- a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx
+++ b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx
@@ -58,6 +58,16 @@ vi.mock("@vscode/webview-ui-toolkit/react", () => ({
),
VSCodeRadioGroup: ({ children, onChange }: any) =>
{children}
,
+ VSCodeTextArea: ({ value, onChange, rows, className, "data-testid": dataTestId }: any) => (
+
+ ),
}))
vi.mock("../../../components/common/Tab", () => ({
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index 6c70c8940d..c970733fba 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -88,6 +88,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setScreenshotQuality: (value: number) => void
terminalOutputLineLimit?: number
setTerminalOutputLineLimit: (value: number) => void
+ terminalOutputCharacterLimit?: number
+ setTerminalOutputCharacterLimit: (value: number) => void
mcpEnabled: boolean
setMcpEnabled: (value: boolean) => void
enableMcpServerCreation: boolean
@@ -176,6 +178,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
browserViewportSize: "900x600",
screenshotQuality: 75,
terminalOutputLineLimit: 500,
+ terminalOutputCharacterLimit: 50000,
terminalShellIntegrationTimeout: 4000,
mcpEnabled: true,
enableMcpServerCreation: false,
@@ -410,6 +413,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })),
setTerminalOutputLineLimit: (value) =>
setState((prevState) => ({ ...prevState, terminalOutputLineLimit: value })),
+ setTerminalOutputCharacterLimit: (value) =>
+ setState((prevState) => ({ ...prevState, terminalOutputCharacterLimit: value })),
setTerminalShellIntegrationTimeout: (value) =>
setState((prevState) => ({ ...prevState, terminalShellIntegrationTimeout: value })),
setTerminalShellIntegrationDisabled: (value) =>
diff --git a/webview-ui/src/i18n/locales/ca/prompts.json b/webview-ui/src/i18n/locales/ca/prompts.json
index 394e8e5c8c..1f67068df0 100644
--- a/webview-ui/src/i18n/locales/ca/prompts.json
+++ b/webview-ui/src/i18n/locales/ca/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Previsualització de la millora del prompt",
"testEnhancement": "Prova la millora"
},
+ "condense": {
+ "apiConfiguration": "Configuració de l'API per a la condensació de context",
+ "apiConfigDescription": "Seleccioneu quina configuració d'API voleu utilitzar per a les operacions de condensació de context. Deixeu-ho sense seleccionar per utilitzar la configuració activa actual.",
+ "useCurrentConfig": "Utilitza la configuració de l'API seleccionada actualment"
+ },
"types": {
"ENHANCE": {
"label": "Millorar prompt",
"description": "Utilitzeu la millora de prompts per obtenir suggeriments o millores personalitzades per a les vostres entrades. Això assegura que Roo entengui la vostra intenció i proporcioni les millors respostes possibles. Disponible a través de la icona ✨ al xat."
},
+ "CONDENSE": {
+ "label": "Condensació de context",
+ "description": "Configureu com es condensa el context de la conversa per gestionar els límits de testimonis. Aquest indicador s'utilitza tant per a les operacions de condensació de context manuals com automàtiques."
+ },
"EXPLAIN": {
"label": "Explicar codi",
"description": "Obtingueu explicacions detallades de fragments de codi, funcions o fitxers sencers. Útil per entendre codi complex o aprendre nous patrons. Disponible a les accions de codi (icona de bombeta a l'editor) i al menú contextual de l'editor (clic dret al codi seleccionat)."
diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json
index dd2eff7b2e..9c7267aa80 100644
--- a/webview-ui/src/i18n/locales/ca/settings.json
+++ b/webview-ui/src/i18n/locales/ca/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Clau API:",
"geminiApiKeyPlaceholder": "Introduïu la vostra clau d'API de Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Clau de l'API:",
+ "mistralApiKeyPlaceholder": "Introduïu la vostra clau de l'API de Mistral",
"openaiCompatibleProvider": "Compatible amb OpenAI",
"openAiKeyLabel": "Clau API OpenAI",
"openAiKeyPlaceholder": "Introduïu la vostra clau API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "Cal un ID de model",
"modelDimensionRequired": "Cal una dimensió de model",
"geminiApiKeyRequired": "Cal una clau d'API de Gemini",
+ "mistralApiKeyRequired": "La clau de l'API de Mistral és requerida",
"ollamaBaseUrlRequired": "Cal una URL base d'Ollama",
"baseUrlRequired": "Cal una URL base",
"modelDimensionMinValue": "La dimensió del model ha de ser superior a 0"
@@ -504,6 +508,10 @@
"label": "Límit de sortida de terminal",
"description": "Nombre màxim de línies a incloure a la sortida del terminal en executar comandes. Quan s'excedeix, s'eliminaran línies del mig, estalviant token. <0>Més informació0>"
},
+ "outputCharacterLimit": {
+ "label": "Límit de caràcters del terminal",
+ "description": "Nombre màxim de caràcters a incloure en la sortida del terminal en executar ordres. Aquest límit té precedència sobre el límit de línies per evitar problemes de memòria amb línies extremadament llargues. Quan se superi, la sortida es truncarà. <0>Més informació0>"
+ },
"shellIntegrationTimeout": {
"label": "Temps d'espera d'integració de shell del terminal",
"description": "Temps màxim d'espera per a la inicialització de la integració de shell abans d'executar comandes. Per a usuaris amb temps d'inici de shell llargs, aquest valor pot necessitar ser augmentat si veieu errors \"Shell Integration Unavailable\" al terminal. <0>Més informació0>"
diff --git a/webview-ui/src/i18n/locales/de/prompts.json b/webview-ui/src/i18n/locales/de/prompts.json
index ea94f9228d..229178abb3 100644
--- a/webview-ui/src/i18n/locales/de/prompts.json
+++ b/webview-ui/src/i18n/locales/de/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Vorschau der Prompt-Verbesserung",
"testEnhancement": "Verbesserung testen"
},
+ "condense": {
+ "apiConfiguration": "API-Konfiguration für die Kontextverdichtung",
+ "apiConfigDescription": "Wählen Sie aus, welche API-Konfiguration für Kontextverdichtungsvorgänge verwendet werden soll. Lassen Sie die Auswahl leer, um die aktuell aktive Konfiguration zu verwenden.",
+ "useCurrentConfig": "Derzeit ausgewählte API-Konfiguration verwenden"
+ },
"types": {
"ENHANCE": {
"label": "Prompt verbessern",
"description": "Verwenden Sie die Prompt-Verbesserung, um maßgeschneiderte Vorschläge oder Verbesserungen für Ihre Eingaben zu erhalten. Dies stellt sicher, dass Roo Ihre Absicht versteht und die bestmöglichen Antworten liefert. Verfügbar über das ✨-Symbol im Chat."
},
+ "CONDENSE": {
+ "label": "Kontextverdichtung",
+ "description": "Konfigurieren Sie, wie der Konversationskontext verdichtet wird, um Token-Limits zu verwalten. Dieser Prompt wird sowohl für manuelle als auch für automatische Kontextverdichtungsvorgänge verwendet."
+ },
"EXPLAIN": {
"label": "Code erklären",
"description": "Erhalten Sie detaillierte Erklärungen zu Code-Schnipseln, Funktionen oder ganzen Dateien. Nützlich zum Verständnis komplexen Codes oder zum Erlernen neuer Muster. Verfügbar in Code-Aktionen (Glühbirnen-Symbol im Editor) und im Kontextmenü des Editors (Rechtsklick auf ausgewählten Code)."
diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json
index 4e1d0e186c..60f69f21db 100644
--- a/webview-ui/src/i18n/locales/de/settings.json
+++ b/webview-ui/src/i18n/locales/de/settings.json
@@ -52,6 +52,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API-Schlüssel:",
"geminiApiKeyPlaceholder": "Geben Sie Ihren Gemini-API-Schlüssel ein",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API-Schlüssel:",
+ "mistralApiKeyPlaceholder": "Gib deinen Mistral-API-Schlüssel ein",
"openaiCompatibleProvider": "OpenAI-kompatibel",
"openAiKeyLabel": "OpenAI API-Schlüssel",
"openAiKeyPlaceholder": "Gib deinen OpenAI API-Schlüssel ein",
@@ -109,6 +112,7 @@
"modelIdRequired": "Modell-ID ist erforderlich",
"modelDimensionRequired": "Modellabmessung ist erforderlich",
"geminiApiKeyRequired": "Gemini-API-Schlüssel ist erforderlich",
+ "mistralApiKeyRequired": "Mistral-API-Schlüssel ist erforderlich",
"ollamaBaseUrlRequired": "Ollama-Basis-URL ist erforderlich",
"baseUrlRequired": "Basis-URL ist erforderlich",
"modelDimensionMinValue": "Modellabmessung muss größer als 0 sein"
@@ -504,6 +508,10 @@
"label": "Terminal-Ausgabelimit",
"description": "Maximale Anzahl von Zeilen, die in der Terminal-Ausgabe bei der Ausführung von Befehlen enthalten sein sollen. Bei Überschreitung werden Zeilen aus der Mitte entfernt, wodurch Token gespart werden. <0>Mehr erfahren0>"
},
+ "outputCharacterLimit": {
+ "label": "Terminal-Zeichenlimit",
+ "description": "Maximale Anzahl von Zeichen, die in die Terminalausgabe bei der Ausführung von Befehlen aufgenommen werden sollen. Dieses Limit hat Vorrang vor dem Zeilenlimit, um Speicherprobleme durch extrem lange Zeilen zu vermeiden. Bei Überschreitung wird die Ausgabe abgeschnitten. <0>Mehr erfahren0>"
+ },
"shellIntegrationTimeout": {
"label": "Terminal-Shell-Integrationszeit-Limit",
"description": "Maximale Wartezeit für die Shell-Integration, bevor Befehle ausgeführt werden. Für Benutzer mit langen Shell-Startzeiten musst du diesen Wert möglicherweise erhöhen, wenn du Fehler vom Typ \"Shell Integration Unavailable\" im Terminal siehst. <0>Mehr erfahren0>"
diff --git a/webview-ui/src/i18n/locales/en/prompts.json b/webview-ui/src/i18n/locales/en/prompts.json
index 6b83b61268..5d0e6ff8db 100644
--- a/webview-ui/src/i18n/locales/en/prompts.json
+++ b/webview-ui/src/i18n/locales/en/prompts.json
@@ -95,11 +95,20 @@
"previewButton": "Preview Prompt Enhancement",
"testEnhancement": "Test Enhancement"
},
+ "condense": {
+ "apiConfiguration": "API Configuration for Context Condensing",
+ "apiConfigDescription": "Select which API configuration to use for context condensing operations. Leave unselected to use the current active configuration.",
+ "useCurrentConfig": "Use currently selected API configuration"
+ },
"types": {
"ENHANCE": {
"label": "Enhance Prompt",
"description": "Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo understands your intent and provides the best possible responses. Available via the ✨ icon in chat."
},
+ "CONDENSE": {
+ "label": "Context Condensing",
+ "description": "Configure how conversation context is condensed to manage token limits. This prompt is used for both manual and automatic context condensing operations."
+ },
"EXPLAIN": {
"label": "Explain Code",
"description": "Get detailed explanations of code snippets, functions, or entire files. Useful for understanding complex code or learning new patterns. Available in code actions (lightbulb icon in the editor) and the editor context menu (right-click on selected code)."
diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json
index 7b115605e7..af3ca3e15d 100644
--- a/webview-ui/src/i18n/locales/en/settings.json
+++ b/webview-ui/src/i18n/locales/en/settings.json
@@ -52,6 +52,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API Key:",
"geminiApiKeyPlaceholder": "Enter your Gemini API key",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API Key:",
+ "mistralApiKeyPlaceholder": "Enter your Mistral API key",
"openaiCompatibleProvider": "OpenAI Compatible",
"openAiKeyLabel": "OpenAI API Key",
"openAiKeyPlaceholder": "Enter your OpenAI API key",
@@ -116,6 +119,7 @@
"modelIdRequired": "Model ID is required",
"modelDimensionRequired": "Model dimension is required",
"geminiApiKeyRequired": "Gemini API key is required",
+ "mistralApiKeyRequired": "Mistral API key is required",
"ollamaBaseUrlRequired": "Ollama base URL is required",
"baseUrlRequired": "Base URL is required",
"modelDimensionMinValue": "Model dimension must be greater than 0"
@@ -504,6 +508,10 @@
"label": "Terminal output limit",
"description": "Maximum number of lines to include in terminal output when executing commands. When exceeded lines will be removed from the middle, saving tokens. <0>Learn more0>"
},
+ "outputCharacterLimit": {
+ "label": "Terminal character limit",
+ "description": "Maximum number of characters to include in terminal output when executing commands. This limit takes precedence over the line limit to prevent memory issues from extremely long lines. When exceeded, output will be truncated. <0>Learn more0>"
+ },
"shellIntegrationTimeout": {
"label": "Terminal shell integration timeout",
"description": "Maximum time to wait for shell integration to initialize before executing commands. For users with long shell startup times, this value may need to be increased if you see \"Shell Integration Unavailable\" errors in the terminal. <0>Learn more0>"
diff --git a/webview-ui/src/i18n/locales/es/prompts.json b/webview-ui/src/i18n/locales/es/prompts.json
index daa03f4ffc..50ab1cdb76 100644
--- a/webview-ui/src/i18n/locales/es/prompts.json
+++ b/webview-ui/src/i18n/locales/es/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Vista previa de la mejora de solicitud",
"testEnhancement": "Probar mejora"
},
+ "condense": {
+ "apiConfiguration": "Configuración de API para la condensación de contexto",
+ "apiConfigDescription": "Selecciona qué configuración de API usar para las operaciones de condensación de contexto. Deja sin seleccionar para usar la configuración activa actual.",
+ "useCurrentConfig": "Usar la configuración de API actualmente seleccionada"
+ },
"types": {
"ENHANCE": {
"label": "Mejorar solicitud",
"description": "Utiliza la mejora de solicitudes para obtener sugerencias o mejoras personalizadas para tus entradas. Esto asegura que Roo entienda tu intención y proporcione las mejores respuestas posibles. Disponible a través del icono ✨ en el chat."
},
+ "CONDENSE": {
+ "label": "Condensación de contexto",
+ "description": "Configura cómo se condensa el contexto de la conversación para gestionar los límites de tokens. Este prompt se utiliza tanto para operaciones de condensación de contexto manuales como automáticas."
+ },
"EXPLAIN": {
"label": "Explicar código",
"description": "Obtén explicaciones detalladas de fragmentos de código, funciones o archivos completos. Útil para entender código complejo o aprender nuevos patrones. Disponible en acciones de código (icono de bombilla en el editor) y en el menú contextual del editor (clic derecho en el código seleccionado)."
diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json
index 6351646297..3a123fe97e 100644
--- a/webview-ui/src/i18n/locales/es/settings.json
+++ b/webview-ui/src/i18n/locales/es/settings.json
@@ -52,6 +52,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Clave API:",
"geminiApiKeyPlaceholder": "Introduce tu clave de API de Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Clave API:",
+ "mistralApiKeyPlaceholder": "Introduce tu clave de API de Mistral",
"openaiCompatibleProvider": "Compatible con OpenAI",
"openAiKeyLabel": "Clave API de OpenAI",
"openAiKeyPlaceholder": "Introduce tu clave API de OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "Se requiere el ID del modelo",
"modelDimensionRequired": "Se requiere la dimensión del modelo",
"geminiApiKeyRequired": "Se requiere la clave API de Gemini",
+ "mistralApiKeyRequired": "Se requiere la clave de API de Mistral",
"ollamaBaseUrlRequired": "Se requiere la URL base de Ollama",
"baseUrlRequired": "Se requiere la URL base",
"modelDimensionMinValue": "La dimensión del modelo debe ser mayor que 0"
@@ -504,6 +508,10 @@
"label": "Límite de salida de terminal",
"description": "Número máximo de líneas a incluir en la salida del terminal al ejecutar comandos. Cuando se excede, se eliminarán líneas del medio, ahorrando token. <0>Más información0>"
},
+ "outputCharacterLimit": {
+ "label": "Límite de caracteres del terminal",
+ "description": "Número máximo de caracteres a incluir en la salida del terminal al ejecutar comandos. Este límite tiene prioridad sobre el límite de líneas para evitar problemas de memoria con líneas extremadamente largas. Cuando se excede, la salida se truncará. <0>Aprende más0>"
+ },
"shellIntegrationTimeout": {
"label": "Tiempo de espera de integración del shell del terminal",
"description": "Tiempo máximo de espera para la inicialización de la integración del shell antes de ejecutar comandos. Para usuarios con tiempos de inicio de shell largos, este valor puede necesitar ser aumentado si ve errores \"Shell Integration Unavailable\" en el terminal. <0>Más información0>"
diff --git a/webview-ui/src/i18n/locales/fr/prompts.json b/webview-ui/src/i18n/locales/fr/prompts.json
index d8c3317081..d48ef28fa4 100644
--- a/webview-ui/src/i18n/locales/fr/prompts.json
+++ b/webview-ui/src/i18n/locales/fr/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Aperçu de l'amélioration du prompt",
"testEnhancement": "Tester l'amélioration"
},
+ "condense": {
+ "apiConfiguration": "Configuration de l'API pour la condensation du contexte",
+ "apiConfigDescription": "Sélectionnez la configuration d'API à utiliser pour les opérations de condensation de contexte. Laissez non sélectionné pour utiliser la configuration active actuelle.",
+ "useCurrentConfig": "Utiliser la configuration d'API actuellement sélectionnée"
+ },
"types": {
"ENHANCE": {
"label": "Améliorer le prompt",
"description": "Utilisez l'amélioration de prompt pour obtenir des suggestions ou des améliorations personnalisées pour vos entrées. Cela garantit que Roo comprend votre intention et fournit les meilleures réponses possibles. Disponible via l'icône ✨ dans le chat."
},
+ "CONDENSE": {
+ "label": "Condensation du contexte",
+ "description": "Configurez la manière dont le contexte de la conversation est condensé pour gérer les limites de jetons. Ce prompt est utilisé pour les opérations de condensation de contexte manuelles et automatiques."
+ },
"EXPLAIN": {
"label": "Expliquer le code",
"description": "Obtenez des explications détaillées sur des extraits de code, des fonctions ou des fichiers entiers. Utile pour comprendre un code complexe ou apprendre de nouveaux modèles. Disponible dans les actions de code (icône d'ampoule dans l'éditeur) et dans le menu contextuel de l'éditeur (clic droit sur le code sélectionné)."
diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json
index 2282577d2c..1d9f8d0bda 100644
--- a/webview-ui/src/i18n/locales/fr/settings.json
+++ b/webview-ui/src/i18n/locales/fr/settings.json
@@ -52,6 +52,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Clé API :",
"geminiApiKeyPlaceholder": "Entrez votre clé API Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Clé d'API:",
+ "mistralApiKeyPlaceholder": "Entrez votre clé d'API Mistral",
"openaiCompatibleProvider": "Compatible OpenAI",
"openAiKeyLabel": "Clé API OpenAI",
"openAiKeyPlaceholder": "Entrez votre clé API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "L'ID du modèle est requis",
"modelDimensionRequired": "La dimension du modèle est requise",
"geminiApiKeyRequired": "La clé API Gemini est requise",
+ "mistralApiKeyRequired": "La clé API Mistral est requise",
"ollamaBaseUrlRequired": "L'URL de base Ollama est requise",
"baseUrlRequired": "L'URL de base est requise",
"modelDimensionMinValue": "La dimension du modèle doit être supérieure à 0"
@@ -504,6 +508,10 @@
"label": "Limite de sortie du terminal",
"description": "Nombre maximum de lignes à inclure dans la sortie du terminal lors de l'exécution de commandes. Lorsque ce nombre est dépassé, les lignes seront supprimées du milieu, économisant des token. <0>En savoir plus0>"
},
+ "outputCharacterLimit": {
+ "label": "Limite de caractères du terminal",
+ "description": "Nombre maximum de caractères à inclure dans la sortie du terminal lors de l'exécution de commandes. Cette limite prévaut sur la limite de lignes pour éviter les problèmes de mémoire avec des lignes extrêmement longues. Lorsque cette limite est dépassée, la sortie sera tronquée. <0>En savoir plus0>"
+ },
"shellIntegrationTimeout": {
"label": "Délai d'intégration du shell du terminal",
"description": "Temps maximum d'attente pour l'initialisation de l'intégration du shell avant d'exécuter des commandes. Pour les utilisateurs avec des temps de démarrage de shell longs, cette valeur peut nécessiter d'être augmentée si vous voyez des erreurs \"Shell Integration Unavailable\" dans le terminal. <0>En savoir plus0>"
diff --git a/webview-ui/src/i18n/locales/hi/prompts.json b/webview-ui/src/i18n/locales/hi/prompts.json
index 1cb21673b0..ff409f41f5 100644
--- a/webview-ui/src/i18n/locales/hi/prompts.json
+++ b/webview-ui/src/i18n/locales/hi/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "प्रॉम्प्ट वृद्धि का पूर्वावलोकन",
"testEnhancement": "वृद्धि का परीक्षण करें"
},
+ "condense": {
+ "apiConfiguration": "संदर्भ संघनन के लिए API कॉन्फ़िगरेशन",
+ "apiConfigDescription": "संदर्भ संघनन कार्यों के लिए कौन सा API कॉन्फ़िgerेशन उपयोग करना है, इसका चयन करें। वर्तमान सक्रिय कॉन्फ़िगरेशन का उपयोग करने के लिए अचयनित छोड़ दें।",
+ "useCurrentConfig": "वर्तमान में चयनित API कॉन्फ़िगरेशन का उपयोग करें"
+ },
"types": {
"ENHANCE": {
"label": "प्रॉम्प्ट बढ़ाएँ",
"description": "अपने इनपुट के लिए अनुकूलित सुझाव या सुधार प्राप्त करने के लिए प्रॉम्प्ट वृद्धि का उपयोग करें। यह सुनिश्चित करता है कि Roo आपके इरादे को समझता है और सर्वोत्तम संभव प्रतिक्रियाएँ प्रदान करता है। चैट में ✨ आइकन के माध्यम से उपलब्ध है।"
},
+ "CONDENSE": {
+ "label": "संदर्भ संघनन",
+ "description": "टोकन सीमाओं का प्रबंधन करने के लिए बातचीत के संदर्भ को कैसे संघनित किया जाता है, इसे कॉन्फ़iger करें। इस प्रॉम्प्ट का उपयोग मैनुअल और स्वचालित दोनों संदर्भ संघनन संचालन के लिए किया जाता है।"
+ },
"EXPLAIN": {
"label": "कोड समझाएँ",
"description": "कोड स्निपेट, फंक्शन या पूरी फाइलों के विस्तृत स्पष्टीकरण प्राप्त करें। जटिल कोड को समझने या नए पैटर्न सीखने के लिए उपयोगी। कोड कार्रवाइयों (एडिटर में बल्ब आइकन) और एडिटर के कंटेक्स्ट मेनू (चयनित कोड पर राइट-क्लिक) में उपलब्ध है।"
diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json
index 0915a04cd1..74b6ccae04 100644
--- a/webview-ui/src/i18n/locales/hi/settings.json
+++ b/webview-ui/src/i18n/locales/hi/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API कुंजी:",
"geminiApiKeyPlaceholder": "अपना जेमिनी एपीआई कुंजी दर्ज करें",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API कुंजी:",
+ "mistralApiKeyPlaceholder": "अपनी मिस्ट्रल एपीआई कुंजी दर्ज करें",
"openaiCompatibleProvider": "OpenAI संगत",
"openAiKeyLabel": "OpenAI API कुंजी",
"openAiKeyPlaceholder": "अपना OpenAI API कुंजी दर्ज करें",
@@ -109,6 +112,7 @@
"modelIdRequired": "मॉडल आईडी आवश्यक है",
"modelDimensionRequired": "मॉडल आयाम आवश्यक है",
"geminiApiKeyRequired": "Gemini API कुंजी आवश्यक है",
+ "mistralApiKeyRequired": "मिस्ट्रल एपीआई कुंजी आवश्यक है",
"ollamaBaseUrlRequired": "Ollama आधार URL आवश्यक है",
"baseUrlRequired": "आधार URL आवश्यक है",
"modelDimensionMinValue": "मॉडल आयाम 0 से बड़ा होना चाहिए"
@@ -504,6 +508,10 @@
"label": "टर्मिनल आउटपुट सीमा",
"description": "कमांड निष्पादित करते समय टर्मिनल आउटपुट में शामिल करने के लिए पंक्तियों की अधिकतम संख्या। पार होने पर पंक्तियाँ मध्य से हटा दी जाएंगी, token बचाते हुए। <0>अधिक जानें0>"
},
+ "outputCharacterLimit": {
+ "label": "टर्मिनल वर्ण सीमा",
+ "description": "कमांड निष्पादित करते समय टर्मिनल आउटपुट में शामिल किए जाने वाले वर्णों की अधिकतम संख्या। यह सीमा अत्यधिक लंबी लाइनों से मेमोरी समस्याओं को रोकने के लिए लाइन सीमा पर पूर्वता लेती है। जब यह सीमा पार हो जाती है, तो आउटपुट छोटा कर दिया जाएगा। <0>और जानें0>"
+ },
"shellIntegrationTimeout": {
"label": "टर्मिनल शेल एकीकरण टाइमआउट",
"description": "कमांड निष्पादित करने से पहले शेल एकीकरण के आरंभ होने के लिए प्रतीक्षा का अधिकतम समय। लंबे शेल स्टार्टअप समय वाले उपयोगकर्ताओं के लिए, यदि आप टर्मिनल में \"Shell Integration Unavailable\" त्रुटियाँ देखते हैं तो इस मान को बढ़ाने की आवश्यकता हो सकती है। <0>अधिक जानें0>"
diff --git a/webview-ui/src/i18n/locales/id/prompts.json b/webview-ui/src/i18n/locales/id/prompts.json
index 4990911f3e..52736ad69b 100644
--- a/webview-ui/src/i18n/locales/id/prompts.json
+++ b/webview-ui/src/i18n/locales/id/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Pratinjau Peningkatan Prompt",
"testEnhancement": "Uji Peningkatan"
},
+ "condense": {
+ "apiConfiguration": "Konfigurasi API untuk Peringkasan Konteks",
+ "apiConfigDescription": "Pilih konfigurasi API mana yang akan digunakan untuk operasi peringkasan konteks. Biarkan tidak dipilih untuk menggunakan konfigurasi aktif saat ini.",
+ "useCurrentConfig": "Gunakan konfigurasi API yang saat ini dipilih"
+ },
"types": {
"ENHANCE": {
"label": "Tingkatkan Prompt",
"description": "Gunakan peningkatan prompt untuk mendapatkan saran atau perbaikan yang disesuaikan untuk input Anda. Ini memastikan Roo memahami maksud Anda dan memberikan respons terbaik. Tersedia melalui ikon ✨ di chat."
},
+ "CONDENSE": {
+ "label": "Peringkasan Konteks",
+ "description": "Konfigurasikan bagaimana konteks percakapan diringkas untuk mengelola batas token. Prompt ini digunakan untuk operasi peringkasan konteks manual dan otomatis."
+ },
"EXPLAIN": {
"label": "Jelaskan Kode",
"description": "Dapatkan penjelasan detail tentang snippet kode, fungsi, atau seluruh file. Berguna untuk memahami kode kompleks atau mempelajari pola baru. Tersedia di code actions (ikon lightbulb di editor) dan menu konteks editor (klik kanan pada kode yang dipilih)."
diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json
index 5a9c383aa5..75cb66a71f 100644
--- a/webview-ui/src/i18n/locales/id/settings.json
+++ b/webview-ui/src/i18n/locales/id/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API Key:",
"geminiApiKeyPlaceholder": "Masukkan kunci API Gemini Anda",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Kunci API:",
+ "mistralApiKeyPlaceholder": "Masukkan kunci API Mistral Anda",
"openaiCompatibleProvider": "OpenAI Compatible",
"openAiKeyLabel": "OpenAI API Key",
"openAiKeyPlaceholder": "Masukkan kunci API OpenAI kamu",
@@ -109,6 +112,7 @@
"modelIdRequired": "ID Model diperlukan",
"modelDimensionRequired": "Dimensi model diperlukan",
"geminiApiKeyRequired": "Kunci API Gemini diperlukan",
+ "mistralApiKeyRequired": "Kunci API Mistral diperlukan",
"ollamaBaseUrlRequired": "URL dasar Ollama diperlukan",
"baseUrlRequired": "URL dasar diperlukan",
"modelDimensionMinValue": "Dimensi model harus lebih besar dari 0"
@@ -508,6 +512,10 @@
"label": "Batas output terminal",
"description": "Jumlah maksimum baris yang disertakan dalam output terminal saat mengeksekusi perintah. Ketika terlampaui, baris akan dihapus dari tengah, menghemat token. <0>Pelajari lebih lanjut0>"
},
+ "outputCharacterLimit": {
+ "label": "Batas karakter terminal",
+ "description": "Jumlah maksimum karakter yang akan disertakan dalam output terminal saat menjalankan perintah. Batas ini lebih diutamakan daripada batas baris untuk mencegah masalah memori dari baris yang sangat panjang. Ketika terlampaui, output akan dipotong. <0>Pelajari lebih lanjut0>"
+ },
"shellIntegrationTimeout": {
"label": "Timeout integrasi shell terminal",
"description": "Waktu maksimum untuk menunggu integrasi shell menginisialisasi sebelum mengeksekusi perintah. Untuk pengguna dengan waktu startup shell yang lama, nilai ini mungkin perlu ditingkatkan jika kamu melihat error \"Shell Integration Unavailable\" di terminal. <0>Pelajari lebih lanjut0>"
diff --git a/webview-ui/src/i18n/locales/it/prompts.json b/webview-ui/src/i18n/locales/it/prompts.json
index 0cb6236ff5..e6356f828b 100644
--- a/webview-ui/src/i18n/locales/it/prompts.json
+++ b/webview-ui/src/i18n/locales/it/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Anteprima miglioramento prompt",
"testEnhancement": "Testa miglioramento"
},
+ "condense": {
+ "apiConfiguration": "Configurazione API per la condensazione del contesto",
+ "apiConfigDescription": "Seleziona quale configurazione API utilizzare per le operazioni di condensazione del contesto. Lascia deselezionato per utilizzare la configurazione attiva corrente.",
+ "useCurrentConfig": "Usa la configurazione API currently selezionata"
+ },
"types": {
"ENHANCE": {
"label": "Migliora prompt",
"description": "Utilizza il miglioramento dei prompt per ottenere suggerimenti o miglioramenti personalizzati per i tuoi input. Questo assicura che Roo comprenda la tua intenzione e fornisca le migliori risposte possibili. Disponibile tramite l'icona ✨ nella chat."
},
+ "CONDENSE": {
+ "label": "Condensazione del contesto",
+ "description": "Configura come viene condensato il contesto della conversazione per gestire i limiti dei token. Questo prompt viene utilizzato sia per le operazioni di condensazione del contesto manuali che automatiche."
+ },
"EXPLAIN": {
"label": "Spiega codice",
"description": "Ottieni spiegazioni dettagliate di frammenti di codice, funzioni o file interi. Utile per comprendere codice complesso o imparare nuovi pattern. Disponibile nelle azioni di codice (icona della lampadina nell'editor) e nel menu contestuale dell'editor (clic destro sul codice selezionato)."
diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json
index db63d16be8..64a8a3bc9b 100644
--- a/webview-ui/src/i18n/locales/it/settings.json
+++ b/webview-ui/src/i18n/locales/it/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Chiave API:",
"geminiApiKeyPlaceholder": "Inserisci la tua chiave API Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Chiave API:",
+ "mistralApiKeyPlaceholder": "Inserisci la tua chiave API Mistral",
"openaiCompatibleProvider": "Compatibile con OpenAI",
"openAiKeyLabel": "Chiave API OpenAI",
"openAiKeyPlaceholder": "Inserisci la tua chiave API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "È richiesto l'ID del modello",
"modelDimensionRequired": "È richiesta la dimensione del modello",
"geminiApiKeyRequired": "È richiesta la chiave API Gemini",
+ "mistralApiKeyRequired": "La chiave API di Mistral è richiesta",
"ollamaBaseUrlRequired": "È richiesto l'URL di base di Ollama",
"baseUrlRequired": "È richiesto l'URL di base",
"modelDimensionMinValue": "La dimensione del modello deve essere maggiore di 0"
@@ -504,6 +508,10 @@
"label": "Limite output terminale",
"description": "Numero massimo di righe da includere nell'output del terminale durante l'esecuzione dei comandi. Quando superato, le righe verranno rimosse dal centro, risparmiando token. <0>Scopri di più0>"
},
+ "outputCharacterLimit": {
+ "label": "Limite di caratteri del terminale",
+ "description": "Numero massimo di caratteri da includere nell'output del terminale durante l'esecuzione dei comandi. Questo limite ha la precedenza sul limite di righe per prevenire problemi di memoria causati da righe estremamente lunghe. Se superato, l'output verrà troncato. <0>Scopri di più0>"
+ },
"shellIntegrationTimeout": {
"label": "Timeout integrazione shell del terminale",
"description": "Tempo massimo di attesa per l'inizializzazione dell'integrazione della shell prima di eseguire i comandi. Per gli utenti con tempi di avvio della shell lunghi, questo valore potrebbe dover essere aumentato se si vedono errori \"Shell Integration Unavailable\" nel terminale. <0>Scopri di più0>"
diff --git a/webview-ui/src/i18n/locales/ja/prompts.json b/webview-ui/src/i18n/locales/ja/prompts.json
index f9f9042924..e9f108f615 100644
--- a/webview-ui/src/i18n/locales/ja/prompts.json
+++ b/webview-ui/src/i18n/locales/ja/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "プロンプト強化のプレビュー",
"testEnhancement": "強化をテスト"
},
+ "condense": {
+ "apiConfiguration": "コンテキスト圧縮のためのAPI構成",
+ "apiConfigDescription": "コンテキスト圧縮操作に使用するAPI構成を選択します。現在アクティブな構成を使用するには、選択しないままにします。",
+ "useCurrentConfig": "現在選択されているAPI構成を使用する"
+ },
"types": {
"ENHANCE": {
"label": "プロンプトを強化",
"description": "プロンプト強化を使用して、入力に合わせたカスタマイズされた提案や改善を得ることができます。これにより、Rooがあなたの意図を理解し、最適な回答を提供できます。チャットの✨アイコンから利用できます。"
},
+ "CONDENSE": {
+ "label": "コンテキスト圧縮",
+ "description": "トークン制限を管理するために会話のコンテキストを圧縮する方法を設定します。このプロンプトは、手動および自動のコンテキスト圧縮操作の両方に使用されます。"
+ },
"EXPLAIN": {
"label": "コードを説明",
"description": "コードスニペット、関数、またはファイル全体の詳細な説明を得ることができます。複雑なコードを理解したり、新しいパターンを学んだりするのに役立ちます。コードアクション(エディタの電球アイコン)やエディタのコンテキストメニュー(選択したコードで右クリック)から利用できます。"
diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json
index 1d23908f67..59bd4f15fd 100644
--- a/webview-ui/src/i18n/locales/ja/settings.json
+++ b/webview-ui/src/i18n/locales/ja/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "APIキー:",
"geminiApiKeyPlaceholder": "Gemini APIキーを入力してください",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "APIキー:",
+ "mistralApiKeyPlaceholder": "Mistral APIキーを入力してください",
"openaiCompatibleProvider": "OpenAI互換",
"openAiKeyLabel": "OpenAI APIキー",
"openAiKeyPlaceholder": "OpenAI APIキーを入力してください",
@@ -109,6 +112,7 @@
"modelIdRequired": "モデルIDが必要です",
"modelDimensionRequired": "モデルの次元が必要です",
"geminiApiKeyRequired": "Gemini APIキーが必要です",
+ "mistralApiKeyRequired": "Mistral APIキーが必要です",
"ollamaBaseUrlRequired": "OllamaのベースURLが必要です",
"baseUrlRequired": "ベースURLが必要です",
"modelDimensionMinValue": "モデルの次元は0より大きくなければなりません"
@@ -504,6 +508,10 @@
"label": "ターミナル出力制限",
"description": "コマンド実行時にターミナル出力に含める最大行数。超過すると中央から行が削除され、tokenを節約します。 <0>詳細情報0>"
},
+ "outputCharacterLimit": {
+ "label": "ターミナルの文字数制限",
+ "description": "コマンド実行時にターミナル出力に含める最大文字数。この制限は、非常に長い行によるメモリ問題を回避するため、行数制限よりも優先されます。超過した場合、出力は切り捨てられます。<0>詳細0>"
+ },
"shellIntegrationTimeout": {
"label": "ターミナルシェル統合タイムアウト",
"description": "コマンドを実行する前にシェル統合の初期化を待つ最大時間。シェルの起動時間が長いユーザーの場合、ターミナルで「Shell Integration Unavailable」エラーが表示される場合は、この値を増やす必要があるかもしれません。 <0>詳細情報0>"
diff --git a/webview-ui/src/i18n/locales/ko/prompts.json b/webview-ui/src/i18n/locales/ko/prompts.json
index 2b7d25d620..688ddd18a9 100644
--- a/webview-ui/src/i18n/locales/ko/prompts.json
+++ b/webview-ui/src/i18n/locales/ko/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "프롬프트 향상 미리보기",
"testEnhancement": "향상 테스트"
},
+ "condense": {
+ "apiConfiguration": "컨텍스트 압축을 위한 API 구성",
+ "apiConfigDescription": "컨텍스트 압축 작업에 사용할 API 구성을 선택합니다. 현재 활성 구성을 사용하려면 선택하지 않은 상태로 둡니다.",
+ "useCurrentConfig": "현재 선택된 API 구성 사용"
+ },
"types": {
"ENHANCE": {
"label": "프롬프트 향상",
"description": "입력에 맞춤화된 제안이나 개선을 얻기 위해 프롬프트 향상을 사용하세요. 이를 통해 Roo가 의도를 이해하고 최상의 응답을 제공할 수 있습니다. 채팅에서 ✨ 아이콘을 통해 이용 가능합니다."
},
+ "CONDENSE": {
+ "label": "컨텍스트 압축",
+ "description": "토큰 제한을 관리하기 위해 대화 컨텍스트를 압축하는 방법을 구성합니다. 이 프롬프트는 수동 및 자동 컨텍스트 압축 작업 모두에 사용됩니다."
+ },
"EXPLAIN": {
"label": "코드 설명",
"description": "코드 스니펫, 함수 또는 전체 파일에 대한 상세한 설명을 얻을 수 있습니다. 복잡한 코드를 이해하거나 새로운 패턴을 배우는 데 유용합니다. 코드 액션(에디터의 전구 아이콘)과 에디터 컨텍스트 메뉴(선택한 코드에서 우클릭)에서 이용 가능합니다."
diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json
index f3c88d2f27..8ba19a0b5d 100644
--- a/webview-ui/src/i18n/locales/ko/settings.json
+++ b/webview-ui/src/i18n/locales/ko/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API 키:",
"geminiApiKeyPlaceholder": "Gemini API 키를 입력하세요",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API 키:",
+ "mistralApiKeyPlaceholder": "Mistral API 키를 입력하세요",
"openaiCompatibleProvider": "OpenAI 호환",
"openAiKeyLabel": "OpenAI API 키",
"openAiKeyPlaceholder": "OpenAI API 키를 입력하세요",
@@ -109,6 +112,7 @@
"modelIdRequired": "모델 ID가 필요합니다",
"modelDimensionRequired": "모델 차원이 필요합니다",
"geminiApiKeyRequired": "Gemini API 키가 필요합니다",
+ "mistralApiKeyRequired": "Mistral API 키가 필요합니다",
"ollamaBaseUrlRequired": "Ollama 기본 URL이 필요합니다",
"baseUrlRequired": "기본 URL이 필요합니다",
"modelDimensionMinValue": "모델 차원은 0보다 커야 합니다"
@@ -504,6 +508,10 @@
"label": "터미널 출력 제한",
"description": "명령 실행 시 터미널 출력에 포함할 최대 라인 수. 초과 시 중간에서 라인이 제거되어 token이 절약됩니다. <0>더 알아보기0>"
},
+ "outputCharacterLimit": {
+ "label": "터미널 문자 제한",
+ "description": "명령을 실행할 때 터미널 출력에 포함할 최대 문자 수입니다. 이 제한은 매우 긴 줄로 인한 메모리 문제를 방지하기 위해 줄 제한보다 우선합니다. 초과하면 출력이 잘립니다. <0>더 알아보기0>"
+ },
"shellIntegrationTimeout": {
"label": "터미널 쉘 통합 타임아웃",
"description": "명령을 실행하기 전에 쉘 통합이 초기화될 때까지 기다리는 최대 시간. 쉘 시작 시간이 긴 사용자의 경우, 터미널에서 \"Shell Integration Unavailable\" 오류가 표시되면 이 값을 늘려야 할 수 있습니다. <0>더 알아보기0>"
diff --git a/webview-ui/src/i18n/locales/nl/prompts.json b/webview-ui/src/i18n/locales/nl/prompts.json
index 04c8eb0703..7c8ba28605 100644
--- a/webview-ui/src/i18n/locales/nl/prompts.json
+++ b/webview-ui/src/i18n/locales/nl/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Voorbeeld promptverbetering",
"testEnhancement": "Test verbetering"
},
+ "condense": {
+ "apiConfiguration": "API-configuratie voor contextcondensatie",
+ "apiConfigDescription": "Selecteer welke API-configuratie moet worden gebruikt voor contextcondensatiebewerkingen. Laat leeg om de huidige actieve configuratie te gebruiken.",
+ "useCurrentConfig": "Gebruik de momenteel geselecteerde API-configuratie"
+ },
"types": {
"ENHANCE": {
"label": "Prompt verbeteren",
"description": "Gebruik promptverbetering om op maat gemaakte suggesties of verbeteringen voor je invoer te krijgen. Zo begrijpt Roo je intentie en krijg je de best mogelijke antwoorden. Beschikbaar via het ✨-icoon in de chat."
},
+ "CONDENSE": {
+ "label": "Contextcondensatie",
+ "description": "Configureer hoe de gesprekscontext wordt gecondenseerd om tokenlimieten te beheren.Deze prompt wordt gebruikt voor zowel handmatige als automatische contextcondensatiebewerkingen."
+ },
"EXPLAIN": {
"label": "Code uitleggen",
"description": "Krijg gedetailleerde uitleg over codefragmenten, functies of hele bestanden. Handig om complexe code te begrijpen of nieuwe patronen te leren. Beschikbaar via codeacties (lampje in de editor) en het contextmenu (rechtsklik op geselecteerde code)."
diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json
index 8cae755fc5..e0b4150371 100644
--- a/webview-ui/src/i18n/locales/nl/settings.json
+++ b/webview-ui/src/i18n/locales/nl/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API-sleutel:",
"geminiApiKeyPlaceholder": "Voer uw Gemini API-sleutel in",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API-sleutel:",
+ "mistralApiKeyPlaceholder": "Voer uw Mistral API-sleutel in",
"openaiCompatibleProvider": "OpenAI-compatibel",
"openAiKeyLabel": "OpenAI API-sleutel",
"openAiKeyPlaceholder": "Voer uw OpenAI API-sleutel in",
@@ -109,6 +112,7 @@
"modelIdRequired": "Model-ID is vereist",
"modelDimensionRequired": "Modelafmeting is vereist",
"geminiApiKeyRequired": "Gemini API-sleutel is vereist",
+ "mistralApiKeyRequired": "Mistral API-sleutel is vereist",
"ollamaBaseUrlRequired": "Ollama basis-URL is vereist",
"baseUrlRequired": "Basis-URL is vereist",
"modelDimensionMinValue": "Modelafmeting moet groter zijn dan 0"
@@ -504,6 +508,10 @@
"label": "Terminaluitvoerlimiet",
"description": "Maximaal aantal regels dat wordt opgenomen in de terminaluitvoer bij het uitvoeren van commando's. Overtollige regels worden uit het midden verwijderd om tokens te besparen. <0>Meer informatie0>"
},
+ "outputCharacterLimit": {
+ "label": "Tekenlimiet terminal",
+ "description": "Maximaal aantal tekens dat moet worden opgenomen in de terminaluitvoer bij het uitvoeren van commando's. Deze limiet heeft voorrang op de regellimiet om geheugenproblemen door extreem lange regels te voorkomen. Bij overschrijding wordt de uitvoer afgekapt. <0>Meer informatie0>"
+ },
"shellIntegrationTimeout": {
"label": "Terminal shell-integratie timeout",
"description": "Maximale wachttijd voor het initialiseren van shell-integratie voordat commando's worden uitgevoerd. Voor gebruikers met lange shell-opstarttijden moet deze waarde mogelijk worden verhoogd als je 'Shell Integration Unavailable'-fouten ziet in de terminal. <0>Meer informatie0>"
diff --git a/webview-ui/src/i18n/locales/pl/prompts.json b/webview-ui/src/i18n/locales/pl/prompts.json
index e769750bc3..c627f3a84d 100644
--- a/webview-ui/src/i18n/locales/pl/prompts.json
+++ b/webview-ui/src/i18n/locales/pl/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Podgląd ulepszenia podpowiedzi",
"testEnhancement": "Testuj ulepszenie"
},
+ "condense": {
+ "apiConfiguration": "Konfiguracja API do kondensacji kontekstu",
+ "apiConfigDescription": "Wybierz, której konfiguracji API użyć do operacji kondensacji kontekstu. Pozostaw niewybrane, aby użyć bieżącej aktywnej konfiguracji.",
+ "useCurrentConfig": "Użyj aktualnie wybranej konfiguracji API"
+ },
"types": {
"ENHANCE": {
"label": "Ulepsz podpowiedź",
"description": "Użyj ulepszenia podpowiedzi, aby uzyskać dostosowane sugestie lub ulepszenia dla swoich danych wejściowych. Zapewnia to, że Roo rozumie Twoje intencje i dostarcza najlepsze możliwe odpowiedzi. Dostępne za pośrednictwem ikony ✨ w czacie."
},
+ "CONDENSE": {
+ "label": "Kondensacja kontekstu",
+ "description": "Skonfiguruj, w jaki sposób kontekst rozmowy jest kondensowany w celu zarządzania limitami tokenów. Ten monit jest używany zarówno do ręcznych, jak i automatycznych operacji kondensacji kontekstu."
+ },
"EXPLAIN": {
"label": "Wyjaśnij kod",
"description": "Uzyskaj szczegółowe wyjaśnienia fragmentów kodu, funkcji lub całych plików. Przydatne do zrozumienia złożonego kodu lub nauki nowych wzorców. Dostępne w akcjach kodu (ikona żarówki w edytorze) i w menu kontekstowym edytor (prawy przycisk myszy na wybranym kodzie)."
diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json
index 21a3bba7b5..d7396e6579 100644
--- a/webview-ui/src/i18n/locales/pl/settings.json
+++ b/webview-ui/src/i18n/locales/pl/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Klucz API:",
"geminiApiKeyPlaceholder": "Wprowadź swój klucz API Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Klucz API:",
+ "mistralApiKeyPlaceholder": "Wprowadź swój klucz API Mistral",
"openaiCompatibleProvider": "Kompatybilny z OpenAI",
"openAiKeyLabel": "Klucz API OpenAI",
"openAiKeyPlaceholder": "Wprowadź swój klucz API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "Wymagane jest ID modelu",
"modelDimensionRequired": "Wymagany jest wymiar modelu",
"geminiApiKeyRequired": "Wymagany jest klucz API Gemini",
+ "mistralApiKeyRequired": "Klucz API Mistral jest wymagany",
"ollamaBaseUrlRequired": "Wymagany jest bazowy adres URL Ollama",
"baseUrlRequired": "Wymagany jest bazowy adres URL",
"modelDimensionMinValue": "Wymiar modelu musi być większy niż 0"
@@ -504,6 +508,10 @@
"label": "Limit wyjścia terminala",
"description": "Maksymalna liczba linii do uwzględnienia w wyjściu terminala podczas wykonywania poleceń. Po przekroczeniu linie będą usuwane ze środka, oszczędzając token. <0>Dowiedz się więcej0>"
},
+ "outputCharacterLimit": {
+ "label": "Limit znaków w terminalu",
+ "description": "Maksymalna liczba znaków do uwzględnienia w danych wyjściowych terminala podczas wykonywania poleceń. Limit ten ma pierwszeństwo przed limitem linii, aby zapobiec problemom z pamięcią spowodowanym przez bardzo długie linie. Po przekroczeniu limitu dane wyjściowe zostaną obcięte. <0>Dowiedz się więcej0>"
+ },
"shellIntegrationTimeout": {
"label": "Limit czasu integracji powłoki terminala",
"description": "Maksymalny czas oczekiwania na inicjalizację integracji powłoki przed wykonaniem poleceń. Dla użytkowników z długim czasem uruchamiania powłoki, ta wartość może wymagać zwiększenia, jeśli widzisz błędy \"Shell Integration Unavailable\" w terminalu. <0>Dowiedz się więcej0>"
diff --git a/webview-ui/src/i18n/locales/pt-BR/prompts.json b/webview-ui/src/i18n/locales/pt-BR/prompts.json
index cce3e7a23a..e5989d2894 100644
--- a/webview-ui/src/i18n/locales/pt-BR/prompts.json
+++ b/webview-ui/src/i18n/locales/pt-BR/prompts.json
@@ -96,11 +96,20 @@
"previewButton": "Visualizar aprimoramento do prompt",
"testEnhancement": "Testar aprimoramento"
},
+ "condense": {
+ "apiConfiguration": "Configuração da API para condensação de contexto",
+ "apiConfigDescription": "Selecione qual configuração de API usar para operações de condensação de contexto. Deixe desmarcado para usar a configuração ativa atual.",
+ "useCurrentConfig": "Usar a configuração de API selecionada no momento"
+ },
"types": {
"ENHANCE": {
"label": "Aprimorar Prompt",
"description": "Use o aprimoramento de prompt para obter sugestões ou melhorias personalizadas para suas entradas. Isso garante que o Roo entenda sua intenção e forneça as melhores respostas possíveis. Disponível através do ícone ✨ no chat."
},
+ "CONDENSE": {
+ "label": "Condensação de Contexto",
+ "description": "Configure como o contexto da conversa é condensado para gerenciar os limites de token. Este prompt é usado para operações de condensação de contexto manuais e automáticas."
+ },
"EXPLAIN": {
"label": "Explicar Código",
"description": "Obtenha explicações detalhadas de trechos de código, funções ou arquivos inteiros. Útil para entender código complexo ou aprender novos padrões. Disponível nas ações de código (ícone de lâmpada no editor) e no menu de contexto do editor (clique direito no código selecionado)."
diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json
index 8c29798f36..d1cb3cddfa 100644
--- a/webview-ui/src/i18n/locales/pt-BR/settings.json
+++ b/webview-ui/src/i18n/locales/pt-BR/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Chave de API:",
"geminiApiKeyPlaceholder": "Digite sua chave de API do Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Chave de API:",
+ "mistralApiKeyPlaceholder": "Digite sua chave de API da Mistral",
"openaiCompatibleProvider": "Compatível com OpenAI",
"openAiKeyLabel": "Chave de API OpenAI",
"openAiKeyPlaceholder": "Digite sua chave de API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "O ID do modelo é obrigatório",
"modelDimensionRequired": "A dimensão do modelo é obrigatória",
"geminiApiKeyRequired": "A chave de API do Gemini é obrigatória",
+ "mistralApiKeyRequired": "A chave de API Mistral é obrigatória",
"ollamaBaseUrlRequired": "A URL base do Ollama é obrigatória",
"baseUrlRequired": "A URL base é obrigatória",
"modelDimensionMinValue": "A dimensão do modelo deve ser maior que 0"
@@ -504,6 +508,10 @@
"label": "Limite de saída do terminal",
"description": "Número máximo de linhas a incluir na saída do terminal ao executar comandos. Quando excedido, as linhas serão removidas do meio, economizando token. <0>Saiba mais0>"
},
+ "outputCharacterLimit": {
+ "label": "Limite de caracteres do terminal",
+ "description": "Número máximo de caracteres a serem incluídos na saída do terminal ao executar comandos. Este limite tem precedência sobre o limite de linhas para evitar problemas de memória com linhas extremamente longas. Quando excedido, a saída será truncada. <0>Saiba mais0>"
+ },
"shellIntegrationTimeout": {
"label": "Tempo limite de integração do shell do terminal",
"description": "Tempo máximo de espera para a inicialização da integração do shell antes de executar comandos. Para usuários com tempos de inicialização de shell longos, este valor pode precisar ser aumentado se você vir erros \"Shell Integration Unavailable\" no terminal. <0>Saiba mais0>"
diff --git a/webview-ui/src/i18n/locales/ru/prompts.json b/webview-ui/src/i18n/locales/ru/prompts.json
index 29be2a2fe0..c96d4b54a1 100644
--- a/webview-ui/src/i18n/locales/ru/prompts.json
+++ b/webview-ui/src/i18n/locales/ru/prompts.json
@@ -93,11 +93,20 @@
"previewButton": "Просмотреть улучшенный промпт",
"testEnhancement": "Тестировать улучшение"
},
+ "condense": {
+ "apiConfiguration": "Конфигурация API для сжатия контекста",
+ "apiConfigDescription": "Выберите, какую конфигурацию API использовать для операций сжатия контекста. Оставьте невыбранным, чтобы использовать текущую активную конфигурацию.",
+ "useCurrentConfig": "Использовать текущую выбранную конфигурацию API"
+ },
"types": {
"ENHANCE": {
"label": "Улучшить промпт",
"description": "Используйте улучшение промпта для получения индивидуальных предложений или улучшений ваших запросов. Это гарантирует, что Roo правильно поймет ваш запрос и даст лучший ответ. Доступно через ✨ в чате."
},
+ "CONDENSE": {
+ "label": "Сжатие контекста",
+ "description": "Настройте, как сжимается контекст беседы для управления лимитами токенов. Этот запрос используется как для ручных, так и для автоматических операций сжатия контекста."
+ },
"EXPLAIN": {
"label": "Объяснить код",
"description": "Получите подробные объяснения фрагментов кода, функций или целых файлов. Полезно для понимания сложного кода или изучения новых паттернов. Доступно в действиях с кодом (иконка лампочки в редакторе) и в контекстном меню редактора (ПКМ по выделенному коду)."
diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json
index f6494281bd..57aa87e123 100644
--- a/webview-ui/src/i18n/locales/ru/settings.json
+++ b/webview-ui/src/i18n/locales/ru/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Ключ API:",
"geminiApiKeyPlaceholder": "Введите свой API-ключ Gemini",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Ключ API:",
+ "mistralApiKeyPlaceholder": "Введите свой API-ключ Mistral",
"openaiCompatibleProvider": "OpenAI-совместимый",
"openAiKeyLabel": "Ключ API OpenAI",
"openAiKeyPlaceholder": "Введите ваш ключ API OpenAI",
@@ -109,6 +112,7 @@
"modelIdRequired": "Требуется идентификатор модели",
"modelDimensionRequired": "Требуется размерность модели",
"geminiApiKeyRequired": "Требуется ключ API Gemini",
+ "mistralApiKeyRequired": "Требуется API-ключ Mistral",
"ollamaBaseUrlRequired": "Требуется базовый URL Ollama",
"baseUrlRequired": "Требуется базовый URL",
"modelDimensionMinValue": "Размерность модели должна быть больше 0"
@@ -504,6 +508,10 @@
"label": "Лимит вывода терминала",
"description": "Максимальное количество строк, включаемых в вывод терминала при выполнении команд. При превышении строки из середины будут удаляться для экономии токенов. <0>Подробнее0>"
},
+ "outputCharacterLimit": {
+ "label": "Лимит символов терминала",
+ "description": "Максимальное количество символов для включения в вывод терминала при выполнении команд. Этот лимит имеет приоритет над лимитом строк, чтобы предотвратить проблемы с памятью из-за чрезвычайно длинных строк. При превышении лимита вывод будет усечен. <0>Узнать больше0>"
+ },
"shellIntegrationTimeout": {
"label": "Таймаут интеграции оболочки терминала",
"description": "Максимальное время ожидания инициализации интеграции оболочки перед выполнением команд. Для пользователей с долгим стартом shell это значение можно увеличить, если появляются ошибки \"Shell Integration Unavailable\". <0>Подробнее0>"
diff --git a/webview-ui/src/i18n/locales/tr/prompts.json b/webview-ui/src/i18n/locales/tr/prompts.json
index a8d23cfcce..9b7e2c569f 100644
--- a/webview-ui/src/i18n/locales/tr/prompts.json
+++ b/webview-ui/src/i18n/locales/tr/prompts.json
@@ -93,11 +93,20 @@
"previewButton": "Prompt geliştirmesini önizle",
"testEnhancement": "Geliştirmeyi test et"
},
+ "condense": {
+ "apiConfiguration": "Bağlam Yoğunlaştırma için API Yapılandırması",
+ "apiConfigDescription": "Bağlam yoğunlaştırma işlemleri için hangi API yapılandırmasının kullanılacağını seçin. Geçerli etkin yapılandırmayı kullanmak için seçilmemiş bırakın.",
+ "useCurrentConfig": "Şu anda seçili olan API yapılandırmasını kullan"
+ },
"types": {
"ENHANCE": {
"label": "Promptu Geliştir",
"description": "Girdileriniz için özel öneriler veya iyileştirmeler almak için prompt geliştirmeyi kullanın. Bu, Roo'nun niyetinizi anlamasını ve mümkün olan en iyi yanıtları sağlamasını garanti eder. Sohbetteki ✨ simgesi aracılığıyla kullanılabilir."
},
+ "CONDENSE": {
+ "label": "Bağlam Yoğunlaştırma",
+ "description": "Jeton sınırlarını yönetmek için konuşma bağlamının nasıl yoğunlaştırılacağını yapılandırın. Bu istem, hem manuel hem de otomatik bağlam yoğunlaştırma işlemleri için kullanılır."
+ },
"EXPLAIN": {
"label": "Kodu Açıkla",
"description": "Kod parçaları, fonksiyonlar veya tüm dosyalar hakkında ayrıntılı açıklamalar alın. Karmaşık kodu anlamak veya yeni kalıpları öğrenmek için faydalıdır. Kod eylemlerinde (editördeki ampul simgesi) ve editör bağlam menüsünde (seçili koda sağ tıklayın) kullanılabilir."
diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json
index 530d096a2b..56e2445ace 100644
--- a/webview-ui/src/i18n/locales/tr/settings.json
+++ b/webview-ui/src/i18n/locales/tr/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API Anahtarı:",
"geminiApiKeyPlaceholder": "Gemini API anahtarınızı girin",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API Anahtarı:",
+ "mistralApiKeyPlaceholder": "Mistral API anahtarınızı girin",
"openaiCompatibleProvider": "OpenAI Uyumlu",
"openAiKeyLabel": "OpenAI API Anahtarı",
"openAiKeyPlaceholder": "OpenAI API anahtarınızı girin",
@@ -109,6 +112,7 @@
"modelIdRequired": "Model kimliği gereklidir",
"modelDimensionRequired": "Model boyutu gereklidir",
"geminiApiKeyRequired": "Gemini API anahtarı gereklidir",
+ "mistralApiKeyRequired": "Mistral API anahtarı gereklidir",
"ollamaBaseUrlRequired": "Ollama temel URL'si gereklidir",
"baseUrlRequired": "Temel URL'si gereklidir",
"modelDimensionMinValue": "Model boyutu 0'dan büyük olmalıdır"
@@ -504,6 +508,10 @@
"label": "Terminal çıktısı sınırı",
"description": "Komutları yürütürken terminal çıktısına dahil edilecek maksimum satır sayısı. Aşıldığında, token tasarrufu sağlayarak satırlar ortadan kaldırılacaktır. <0>Daha fazla bilgi0>"
},
+ "outputCharacterLimit": {
+ "label": "Terminal karakter sınırı",
+ "description": "Komutları yürütürken terminal çıktısına dahil edilecek maksimum karakter sayısı. Bu sınır, aşırı uzun satırlardan kaynaklanan bellek sorunlarını önlemek için satır sınırına göre önceliklidir. Aşıldığında, çıktı kesilir. <0>Daha fazla bilgi edinin0>"
+ },
"shellIntegrationTimeout": {
"label": "Terminal kabuk entegrasyonu zaman aşımı",
"description": "Komutları yürütmeden önce kabuk entegrasyonunun başlatılması için beklenecek maksimum süre. Kabuk başlatma süresi uzun olan kullanıcılar için, terminalde \"Shell Integration Unavailable\" hatalarını görürseniz bu değerin artırılması gerekebilir. <0>Daha fazla bilgi0>"
diff --git a/webview-ui/src/i18n/locales/vi/prompts.json b/webview-ui/src/i18n/locales/vi/prompts.json
index 89eb107a5f..d3b7e75f3c 100644
--- a/webview-ui/src/i18n/locales/vi/prompts.json
+++ b/webview-ui/src/i18n/locales/vi/prompts.json
@@ -93,11 +93,20 @@
"previewButton": "Xem trước nâng cao lời nhắc",
"testEnhancement": "Kiểm tra cải tiến"
},
+ "condense": {
+ "apiConfiguration": "Cấu hình API để cô đọng ngữ cảnh",
+ "apiConfigDescription": "Chọn cấu hình API nào sẽ sử dụng cho các hoạt động cô đọng ngữ cảnh. Để trống để sử dụng cấu hình hiện đang hoạt động.",
+ "useCurrentConfig": "Sử dụng cấu hình API được chọn hiện tại"
+ },
"types": {
"ENHANCE": {
"label": "Nâng cao lời nhắc",
"description": "Sử dụng nâng cao lời nhắc để nhận đề xuất hoặc cải tiến phù hợp cho đầu vào của bạn. Điều này đảm bảo Roo hiểu ý định của bạn và cung cấp phản hồi tốt nhất có thể. Có sẵn thông qua biểu tượng ✨ trong chat."
},
+ "CONDENSE": {
+ "label": "Cô đọng ngữ cảnh",
+ "description": "Định cấu hình cách cô đọng ngữ cảnh cuộc trò chuyện để quản lý giới hạn token. Lời nhắc này được sử dụng cho cả hoạt động cô đọng ngữ cảnh thủ công và tự động."
+ },
"EXPLAIN": {
"label": "Giải thích mã",
"description": "Nhận giải thích chi tiết về đoạn mã, hàm hoặc toàn bộ tệp. Hữu ích để hiểu mã phức tạp hoặc học các mẫu mới. Có sẵn trong hành động mã (biểu tượng bóng đèn trong trình soạn thảo) và menu ngữ cảnh trình soạn thảo (nhấp chuột phải vào mã đã chọn)."
diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json
index 347ba0df01..c0dcdb1095 100644
--- a/webview-ui/src/i18n/locales/vi/settings.json
+++ b/webview-ui/src/i18n/locales/vi/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "Khóa API:",
"geminiApiKeyPlaceholder": "Nhập khóa API Gemini của bạn",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "Khóa API:",
+ "mistralApiKeyPlaceholder": "Nhập khóa API Mistral của bạn",
"openaiCompatibleProvider": "Tương thích OpenAI",
"openAiKeyLabel": "Khóa API OpenAI",
"openAiKeyPlaceholder": "Nhập khóa API OpenAI của bạn",
@@ -109,6 +112,7 @@
"modelIdRequired": "Yêu cầu ID mô hình",
"modelDimensionRequired": "Yêu cầu kích thước mô hình",
"geminiApiKeyRequired": "Yêu cầu khóa API Gemini",
+ "mistralApiKeyRequired": "Cần có khóa API của Mistral",
"ollamaBaseUrlRequired": "Yêu cầu URL cơ sở Ollama",
"baseUrlRequired": "Yêu cầu URL cơ sở",
"modelDimensionMinValue": "Kích thước mô hình phải lớn hơn 0"
@@ -504,6 +508,10 @@
"label": "Giới hạn đầu ra terminal",
"description": "Số dòng tối đa để đưa vào đầu ra terminal khi thực hiện lệnh. Khi vượt quá, các dòng sẽ bị xóa khỏi phần giữa, tiết kiệm token. <0>Tìm hiểu thêm0>"
},
+ "outputCharacterLimit": {
+ "label": "Giới hạn ký tự của terminal",
+ "description": "Số ký tự tối đa để bao gồm trong đầu ra của terminal khi thực thi lệnh. Giới hạn này được ưu tiên hơn giới hạn dòng để ngăn chặn các vấn đề về bộ nhớ do các dòng quá dài. Khi vượt quá, đầu ra sẽ bị cắt bớt. <0>Tìm hiểu thêm0>"
+ },
"shellIntegrationTimeout": {
"label": "Thời gian chờ tích hợp shell terminal",
"description": "Thời gian tối đa để chờ tích hợp shell khởi tạo trước khi thực hiện lệnh. Đối với người dùng có thời gian khởi động shell dài, giá trị này có thể cần được tăng lên nếu bạn thấy lỗi \"Shell Integration Unavailable\" trong terminal. <0>Tìm hiểu thêm0>"
diff --git a/webview-ui/src/i18n/locales/zh-CN/prompts.json b/webview-ui/src/i18n/locales/zh-CN/prompts.json
index 6406fdf639..157ba5d7ea 100644
--- a/webview-ui/src/i18n/locales/zh-CN/prompts.json
+++ b/webview-ui/src/i18n/locales/zh-CN/prompts.json
@@ -93,11 +93,20 @@
"previewButton": "测试提示词增强",
"testEnhancement": "测试增强"
},
+ "condense": {
+ "apiConfiguration": "用于上下文压缩的 API 配置",
+ "apiConfigDescription": "选择用于上下文压缩操作的 API 配置。保留未选择状态以使用当前活动的配置。",
+ "useCurrentConfig": "使用当前选中的 API 配置"
+ },
"types": {
"ENHANCE": {
"label": "增强提示词",
"description": "优化提示获取更好回答(点击✨使用)"
},
+ "CONDENSE": {
+ "label": "上下文压缩",
+ "description": "配置如何压缩对话上下文以管理令牌限制。此提示用于手动和自动上下文压缩操作。"
+ },
"EXPLAIN": {
"label": "解释代码",
"description": "解读代码逻辑(支持文件/片段),可在代码操作(编辑器中的灯泡图标)和编辑器上下文菜单(右键点击选中的代码)中使用。"
diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json
index 5e7293321f..32ffc83cfc 100644
--- a/webview-ui/src/i18n/locales/zh-CN/settings.json
+++ b/webview-ui/src/i18n/locales/zh-CN/settings.json
@@ -52,6 +52,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API 密钥:",
"geminiApiKeyPlaceholder": "输入您的Gemini API密钥",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API 密钥:",
+ "mistralApiKeyPlaceholder": "输入您的 Mistral API 密钥",
"openaiCompatibleProvider": "OpenAI 兼容",
"openAiKeyLabel": "OpenAI API 密钥",
"openAiKeyPlaceholder": "输入你的 OpenAI API 密钥",
@@ -109,6 +112,7 @@
"modelIdRequired": "需要模型 ID",
"modelDimensionRequired": "需要模型维度",
"geminiApiKeyRequired": "需要 Gemini API 密钥",
+ "mistralApiKeyRequired": "需要 Mistral API 密钥",
"ollamaBaseUrlRequired": "需要 Ollama 基础 URL",
"baseUrlRequired": "需要基础 URL",
"modelDimensionMinValue": "模型维度必须大于 0"
@@ -504,6 +508,10 @@
"label": "终端输出限制",
"description": "执行命令时在终端输出中包含的最大行数。超过时将从中间删除行,节省 token。 <0>了解更多0>"
},
+ "outputCharacterLimit": {
+ "label": "终端字符限制",
+ "description": "执行命令时在终端输出中包含的最大字符数。此限制优先于行数限制,以防止因行过长而导致的内存问题。超出后,输出将被截断。 <0>了解更多0>"
+ },
"shellIntegrationTimeout": {
"label": "终端初始化等待时间",
"description": "执行命令前等待 Shell 集成初始化的最长时间。对于 Shell 启动时间较长的用户,如果在终端中看到\"Shell Integration Unavailable\"错误,可能需要增加此值。 <0>了解更多0>"
diff --git a/webview-ui/src/i18n/locales/zh-TW/prompts.json b/webview-ui/src/i18n/locales/zh-TW/prompts.json
index 143efd0ded..3a2bae4af5 100644
--- a/webview-ui/src/i18n/locales/zh-TW/prompts.json
+++ b/webview-ui/src/i18n/locales/zh-TW/prompts.json
@@ -93,11 +93,20 @@
"previewButton": "預覽提示詞增強",
"testEnhancement": "測試增強"
},
+ "condense": {
+ "apiConfiguration": "用於上下文壓縮的 API 設定",
+ "apiConfigDescription": "選取用於上下文壓縮作業的 API 設定。保留未選取狀態以使用目前作用中的設定。",
+ "useCurrentConfig": "使用當前選取的 API 設定"
+ },
"types": {
"ENHANCE": {
"label": "增強提示詞",
"description": "使用提示詞增強功能取得針對您輸入的客製化建議或改進。這確保 Roo 能理解您的意圖並提供最佳的回應。可透過聊天中的 ✨ 圖示使用。"
},
+ "CONDENSE": {
+ "label": "上下文壓縮",
+ "description": "設定如何壓縮對話上下文以管理權杖限制。此提示用於手動和自動上下文壓縮作業。"
+ },
"EXPLAIN": {
"label": "解釋程式碼",
"description": "取得程式碼片段、函式或整個檔案的詳細解釋。有助於理解複雜程式碼或學習新模式。可在程式碼操作(編輯器中的燈泡圖示)和編輯器右鍵選單中使用。"
diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json
index d565108a82..ac946d785d 100644
--- a/webview-ui/src/i18n/locales/zh-TW/settings.json
+++ b/webview-ui/src/i18n/locales/zh-TW/settings.json
@@ -47,6 +47,9 @@
"geminiProvider": "Gemini",
"geminiApiKeyLabel": "API 金鑰:",
"geminiApiKeyPlaceholder": "輸入您的Gemini API金鑰",
+ "mistralProvider": "Mistral",
+ "mistralApiKeyLabel": "API 金鑰:",
+ "mistralApiKeyPlaceholder": "輸入您的 Mistral API 金鑰",
"openaiCompatibleProvider": "OpenAI 相容",
"openAiKeyLabel": "OpenAI API 金鑰",
"openAiKeyPlaceholder": "輸入您的 OpenAI API 金鑰",
@@ -109,6 +112,7 @@
"modelIdRequired": "需要模型 ID",
"modelDimensionRequired": "需要模型維度",
"geminiApiKeyRequired": "需要 Gemini API 金鑰",
+ "mistralApiKeyRequired": "需要 Mistral API 金鑰",
"ollamaBaseUrlRequired": "需要 Ollama 基礎 URL",
"baseUrlRequired": "需要基礎 URL",
"modelDimensionMinValue": "模型維度必須大於 0"
@@ -504,6 +508,10 @@
"label": "終端機輸出行數限制",
"description": "執行命令時終端機輸出的最大行數。超過此限制時,會從中間移除多餘的行數,以節省 token 用量。 <0>瞭解更多0>"
},
+ "outputCharacterLimit": {
+ "label": "終端機字元限制",
+ "description": "執行指令時在終端輸出中包含的最大字元數。此限制優先於行數限制,以防止因行過長而導致的記憶體問題。超過後,輸出將被截斷。 <0>了解更多0>"
+ },
"shellIntegrationTimeout": {
"label": "終端機 Shell 整合逾時",
"description": "執行命令前等待 Shell 整合初始化的最長時間。如果您的 Shell 啟動較慢,且終端機出現「Shell 整合無法使用」的錯誤訊息,可能需要提高此數值。 <0>瞭解更多0>"