Merge branch 'RooCodeInc:main' into main

This commit is contained in:
Murilo Pires 2025-07-21 12:14:04 -03:00 committed by GitHub
commit a6d1e60e23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
177 changed files with 4052 additions and 644 deletions

14
.gitattributes vendored
View file

@ -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

1
.gitignore vendored
View file

@ -3,6 +3,7 @@ dist
out
out-*
node_modules
package-lock.json
coverage/
mock/

View file

@ -0,0 +1,98 @@
<workflow_instructions>
<mode_overview>
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.
</mode_overview>
<initialization_steps>
<step number="1">
<action>Understand the user's request</action>
<details>
The user will provide a GitHub issue URL or number. Your first step is to fetch the issue details using the `gh` CLI.
</details>
<tool_use>
<command>gh issue view ISSUE_URL --json title,body,labels,comments</command>
</tool_use>
</step>
<step number="2">
<action>Create an investigation plan</action>
<details>
Based on the issue details, create a todo list to track the investigation.
</details>
<tool_use><![CDATA[
<update_todo_list>
<todos>
[ ] 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.
</todos>
</update_todo_list>
]]></tool_use>
</step>
</initialization_steps>
<main_workflow>
<phase name="investigation">
<description>
Systematically search the codebase to identify the root cause. This is an iterative process.
</description>
<steps>
<step>
<title>Extract Keywords</title>
<description>Identify key terms, function names, error messages, and concepts from the issue title, body, and comments.</description>
</step>
<step>
<title>Iterative Codebase Search</title>
<description>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.</description>
<tool_use>
<command>codebase_search</command>
</tool_use>
</step>
<step>
<title>Form a Hypothesis</title>
<description>Based on the search results, form a hypothesis about the probable cause of the issue. Document this hypothesis.</description>
</step>
<step>
<title>Attempt to Disprove Hypothesis</title>
<description>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.</description>
</step>
</steps>
</phase>
<phase name="solution">
<description>Formulate a solution and prepare to communicate it.</description>
<steps>
<step>
<title>Formulate Theoretical Solution</title>
<description>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...".</description>
</step>
<step>
<title>Draft Comment</title>
<description>Draft a comment for the GitHub issue that explains your findings and suggested solution in a conversational, human-like tone.</description>
</step>
</steps>
</phase>
<phase name="user_confirmation">
<description>Ask the user for confirmation before posting any comments.</description>
<tool_use><![CDATA[
<ask_followup_question>
<question>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?</question>
<follow_up>
<suggest>Yes, please post the comment to the issue.</suggest>
<suggest>Show me the draft comment first.</suggest>
<suggest>No, do not post the comment.</suggest>
</follow_up>
</ask_followup_question>
]]></tool_use>
</phase>
</main_workflow>
<completion_criteria>
<criterion>A probable cause has been identified and validated.</criterion>
<criterion>A theoretical solution has been proposed.</criterion>
<criterion>The user has decided whether to post a comment on the issue.</criterion>
</completion_criteria>
</workflow_instructions>

View file

@ -0,0 +1,59 @@
<best_practices>
<general_principles>
<principle priority="high">
<name>Be Methodical</name>
<description>Follow the workflow steps precisely. Do not skip the hypothesis validation step. A rigorous process leads to more accurate conclusions.</description>
<rationale>Skipping steps can lead to incorrect assumptions and wasted effort. The goal is to be confident in the proposed solution.</rationale>
</principle>
<principle priority="high">
<name>Embrace Iteration</name>
<description>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.</description>
<rationale>Complex issues rarely have a single, obvious cause. Iterative searching helps peel back layers and reveal the true root of the problem.</rationale>
</principle>
<principle priority="medium">
<name>Think like a Skeptic</name>
<description>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.</description>
<rationale>Confirmation bias is a common pitfall. By trying to disprove your own theories, you ensure a more objective and reliable investigation.</rationale>
</principle>
</general_principles>
<code_conventions>
<convention category="searching">
<rule>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.</rule>
<examples>
<good>Initial search: "user authentication fails". Follow-up search: "getUserById invalid token".</good>
<bad>Searching for a generic term like "error" without context.</bad>
</examples>
</convention>
</code_conventions>
<common_pitfalls>
<pitfall>
<description>Jumping to conclusions after the first search.</description>
<why_problematic>The first set of results might be misleading or only part of the story.</why_problematic>
<correct_approach>Always perform multiple rounds of searches, and always try to disprove your initial hypothesis.</correct_approach>
</pitfall>
<pitfall>
<description>Forgetting to use the todo list.</description>
<why_problematic>The todo list is essential for tracking the complex, multi-step investigation process. Without it, you can lose track of your progress and findings.</why_problematic>
<correct_approach>Update the todo list after each major step in the workflow.</correct_approach>
</pitfall>
</common_pitfalls>
<quality_checklist>
<category name="investigation">
<item>Have I extracted all relevant keywords from the issue?</item>
<item>Have I performed at least two rounds of codebase searches?</item>
<item>Have I genuinely tried to disprove my hypothesis?</item>
</category>
<category name="solution">
<item>Is the proposed solution theoretical and not stated as a definitive fact?</item>
<item>Is the explanation clear and easy to understand?</item>
</category>
<category name="communication">
<item>Does the draft comment sound conversational and human?</item>
<item>Have I avoided technical jargon where possible?</item>
<item>Is the tone helpful and not condescending?</item>
</category>
</quality_checklist>
</best_practices>

View file

@ -0,0 +1,45 @@
<common_patterns>
<pattern name="bug_investigation">
<usage>For investigating bug reports where something is broken.</usage>
<template>
<workflow>
<step>1. Identify the exact error message from the issue.</step>
<step>2. Search for the error message in the codebase using `codebase_search`.</step>
<step>3. Analyze the code that throws the error to understand the context.</step>
<step>4. Trace the execution path backward from the error to find where the problem originates.</step>
<step>5. Form a hypothesis about the incorrect logic or state.</step>
<step>6. Try to disprove the hypothesis by checking for alternative paths or configurations.</step>
<step>7. Propose a code change to correct the logic.</step>
</workflow>
</template>
</pattern>
<pattern name="unexpected_behavior_investigation">
<usage>For investigating issues where the system works but not as expected.</usage>
<template>
<workflow>
<step>1. Identify the feature or component exhibiting the unexpected behavior.</step>
<step>2. Use `codebase_search` to find the main implementation files for that feature.</step>
<step>3. Read the relevant code to understand the intended logic.</step>
<step>4. Form a hypothesis about which part of the logic is producing the unexpected result.</step>
<step>5. Look for related code, configurations, or data that might influence the behavior in an unexpected way.</step>
<step>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.</step>
<step>7. Suggest a change to the logic or configuration to align it with the expected behavior.</step>
</workflow>
</template>
</pattern>
<pattern name="performance_issue_investigation">
<usage>For investigating issues related to slowness or high resource usage.</usage>
<template>
<workflow>
<step>1. Identify the specific action or process that is slow.</step>
<step>2. Use `codebase_search` to find the code responsible for that action.</step>
<step>3. Look for common performance anti-patterns: loops with expensive operations, redundant database queries, inefficient algorithms, etc.</step>
<step>4. Form a hypothesis about the performance bottleneck.</step>
<step>5. Try to disprove the hypothesis. Could another part of the system be contributing to the slowness?</step>
<step>6. Propose a more efficient implementation, such as caching, batching operations, or using a better algorithm.</step>
</workflow>
</template>
</pattern>
</common_patterns>

View file

@ -0,0 +1,78 @@
<tool_usage_guide>
<tool_priorities>
<priority level="1">
<tool>gh issue view</tool>
<when>Always use first to get the issue context.</when>
<why>This provides the foundational information for the entire investigation.</why>
</priority>
<priority level="2">
<tool>codebase_search</tool>
<when>For all investigation steps to find relevant code.</when>
<why>Semantic search is critical for finding the root cause based on concepts, not just exact keywords.</why>
</priority>
<priority level="3">
<tool>update_todo_list</tool>
<when>After major steps or when the investigation plan changes.</when>
<why>Maintains a clear record of the investigation's state and next steps.</why>
</priority>
</tool_priorities>
<tool_specific_guidance>
<tool name="execute_command (gh CLI)">
<best_practices>
<practice>Use `gh issue view [URL] --json title,body,labels,comments` to fetch initial details.</practice>
<practice>Use `gh issue comment [URL] --body "..."` to add comments, but only after explicit user approval.</practice>
<practice>Always wrap the comment body in quotes to handle special characters.</practice>
</best_practices>
<example><![CDATA[
<execute_command>
<command>gh issue view https://github.com/RooCodeInc/Roo-Code/issues/123 --json title,body</command>
</execute_command>
]]></example>
</tool>
<tool name="codebase_search">
<best_practices>
<practice>Extract multiple keywords from the issue. Combine them in your search query.</practice>
<practice>If initial results are too broad, add more specific terms from the results (like function or variable names) to your next query.</practice>
<practice>Use this tool iteratively. Don't rely on a single search.</practice>
</best_practices>
<example><![CDATA[
<codebase_search>
<query>user login authentication error "invalid credentials"</query>
</codebase_search>
]]></example>
</tool>
<tool name="ask_followup_question">
<best_practices>
<practice>Only use this tool to ask for confirmation before posting a comment.</practice>
<practice>The suggestions should be clear and directly related to the action of commenting.</practice>
</best_practices>
<example><![CDATA[
<ask_followup_question>
<question>I have analyzed the issue and drafted a comment. Would you like me to post it?</question>
<follow_up>
<suggest>Yes, go ahead and post the comment.</suggest>
<suggest>Let me see the comment first before you post it.</suggest>
<suggest>No, do not post anything. I will handle it.</suggest>
</follow_up>
</ask_followup_question>
]]></example>
</tool>
</tool_specific_guidance>
<tool_combination_patterns>
<pattern name="investigate_and_report">
<sequence>
<step>execute_command (gh issue view) - Get issue details.</step>
<step>update_todo_list - Create an investigation plan.</step>
<step>codebase_search - Perform initial search.</step>
<step>read_file - Analyze relevant file contents.</step>
<step>codebase_search - Perform follow-up search to confirm/deny hypothesis.</step>
<step>ask_followup_question - Ask user to confirm before commenting.</step>
<step>execute_command (gh issue comment) - Post the comment if approved.</step>
</sequence>
</pattern>
</tool_combination_patterns>
</tool_usage_guide>

View file

@ -0,0 +1,99 @@
<complete_examples>
<example name="investigating_a_login_bug">
<scenario>
A user has reported a bug where they are unable to log in with valid credentials, but no error message is shown.
</scenario>
<user_request>
"Please investigate issue #456. Users are reporting that they can't log in."
</user_request>
<workflow>
<step number="1">
<description>Fetch issue details from GitHub.</description>
<tool_use><![CDATA[
<execute_command>
<command>gh issue view 456 --json title,body,comments</command>
</execute_command>
]]></tool_use>
<expected_outcome>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."</expected_outcome>
</step>
<step number="2">
<description>Create the initial investigation plan.</description>
<tool_use><![CDATA[
<update_todo_list>
<todos>
[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.
</todos>
</update_todo_list>
]]></tool_use>
</step>
<step number="3">
<description>Perform initial codebase search using keywords "login", "authentication", "silent fail".</description>
<tool_use><![CDATA[
<codebase_search>
<query>user login authentication silent fail</query>
</codebase_search>
]]></tool_use>
<analysis>The search results point to `src/auth/login.ts` as a highly relevant file.</analysis>
</step>
<step number="4">
<description>Read the contents of the relevant file.</description>
<tool_use><![CDATA[
<read_file>
<path>src/auth/login.ts</path>
</read_file>
]]></tool_use>
<analysis>
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.
</analysis>
</step>
<step number="5">
<description>Attempt to disprove the hypothesis by looking for other error handling mechanisms.</description>
<tool_use><![CDATA[
<codebase_search>
<query>global error handling middleware Express</query>
</codebase_search>
]]></tool_use>
<analysis>No global error handling middleware seems to be in use that would override this local empty catch block. The hypothesis stands.</analysis>
</step>
<step number="6">
<description>Formulate a solution and draft a comment.</description>
<draft>
"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."
</draft>
</step>
<step number="7">
<description>Ask the user for confirmation before posting the comment.</description>
<tool_use><![CDATA[
<ask_followup_question>
<question>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?</question>
<follow_up>
<suggest>Yes, please post the comment to the issue.</suggest>
<suggest>Show me the draft comment first.</suggest>
<suggest>No, do not post the comment.</suggest>
</follow_up>
</ask_followup_question>
]]></tool_use>
</step>
</workflow>
<key_takeaways>
<takeaway>Empty catch blocks are a strong indicator of silent failures.</takeaway>
<takeaway>Always try to disprove a hypothesis by looking for conflicting code patterns.</takeaway>
</key_takeaways>
</example>
</complete_examples>

View file

@ -0,0 +1,35 @@
<communication_guidelines>
<tone_and_style>
<principle>Be conversational and helpful, not robotic.</principle>
<principle>Write comments as if you are a human developer collaborating on the project.</principle>
<avoid>
<phrase>Analysis complete.</phrase>
<phrase>The investigation has yielded the following result.</phrase>
</avoid>
<prefer>
<phrase>Hey, I took a look at this and found something interesting...</phrase>
<phrase>I've been digging into this issue, and I think I've found a possible cause.</phrase>
</prefer>
</tone_and_style>
<comment_structure>
<element>Start with a friendly opening.</element>
<element>State your main finding or hypothesis clearly but not definitively.</element>
<element>Provide context, like file paths and function names.</element>
<element>Propose a next step or a theoretical solution.</element>
<element>Keep it concise and easy to read. Avoid large blocks of text.</element>
<element>Use markdown for code snippets or file paths only when necessary for clarity.</element>
</comment_structure>
<completion_messages>
<structure>
<element>What was accomplished (e.g., "Investigation complete.").</element>
<element>A summary of the findings and the proposed solution.</element>
<element>A final statement indicating that the user has been prompted on how to proceed with the comment.</element>
</structure>
<avoid>
<element>Ending with a question.</element>
<element>Offers for further assistance.</element>
</avoid>
</completion_messages>
</communication_guidelines>

View file

@ -1,16 +1,74 @@
<workflow>
<initialization>
<step number="1">
<name>Initialize Issue Creation Process</name>
<instructions>
When the user requests to create an issue, immediately set up a todo list to track the workflow.
<update_todo_list>
<todos>
[ ] 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
</todos>
</update_todo_list>
</instructions>
</step>
</initialization>
<step number="1">
<name>Determine Issue Type</name>
<instructions>
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:
<ask_followup_question>
<question>What type of issue would you like to create?</question>
<question>Based on your request, what type of issue would you like to create?</question>
<follow_up>
[If bug indicators found:]
<suggest>Bug Report - Report a problem with existing functionality</suggest>
<suggest>Detailed Feature Proposal - Propose a new feature or enhancement</suggest>
[If feature indicators found:]
<suggest>Detailed Feature Proposal - Propose a new feature or enhancement</suggest>
<suggest>Bug Report - Report a problem with existing functionality</suggest>
[If unclear:]
<suggest>Bug Report - Report a problem with existing functionality</suggest>
<suggest>Detailed Feature Proposal - Propose a new feature or enhancement</suggest>
</follow_up>
</ask_followup_question>
After determining the type, update the todo list:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
@ -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:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
<step number="3">
<name>Search GitHub Discussions</name>
<instructions>
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
</instructions>
</step>
<step number="4">
<name>Determine if User Wants to Contribute</name>
<instructions>
Before exploring the codebase, determine if the user wants to contribute the implementation:
<ask_followup_question>
<question>Are you interested in implementing this feature yourself, or are you just reporting the problem for the Roo team to solve?</question>
<question>Are you interested in implementing this yourself, or are you just reporting the problem for the Roo team to solve?</question>
<follow_up>
<suggest>Just reporting the problem - the Roo team can design the solution</suggest>
<suggest>I want to contribute and implement this feature myself</suggest>
<suggest>I want to contribute and implement this myself</suggest>
<suggest>I'm not sure yet, but I'd like to provide technical analysis</suggest>
</follow_up>
</ask_followup_question>
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:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
<step number="4">
<name>Technical Analysis for Contributors</name>
<instructions>
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:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
<step number="5">
<name>Explore Codebase for Contributors</name>
<instructions>
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
</instructions>
</step>
<step number="6">
<name>Draft Issue Content</name>
<instructions>
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:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
<step number="7">
<step number="6">
<name>Review and Confirm with User</name>
<instructions>
Present the complete drafted issue to the user for review:
@ -302,10 +322,23 @@
</ask_followup_question>
If user requests changes, make them and show the updated version for confirmation.
After confirmation:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
<step number="8">
<step number="7">
<name>Create GitHub Issue</name>
<instructions>
Once user confirms, create the issue using the GitHub CLI:
@ -313,7 +346,7 @@
First, save the issue body to a temporary file:
<execute_command>
<command>cat > /tmp/issue_body.md << 'EOF'
[The complete formatted issue body from step 6]
[The complete formatted issue body from step 5]
EOF</command>
</execute_command>
@ -333,6 +366,19 @@ EOF</command>
<execute_command>
<command>rm /tmp/issue_body.md</command>
</execute_command>
Complete the workflow:
<update_todo_list>
<todos>
[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
</todos>
</update_todo_list>
</instructions>
</step>
</workflow>

View file

@ -0,0 +1,349 @@
<technical_analysis_workflow>
<overview>
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.
</overview>
<initialization>
<step number="1">
<name>Create Investigation Plan</name>
<instructions>
When technical analysis is requested, immediately create a comprehensive todo list
to track the investigation progress.
</instructions>
<tool_use><![CDATA[
<update_todo_list>
<todos>
[ ] 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
</todos>
</update_todo_list>
]]></tool_use>
</step>
</initialization>
<investigation_phases>
<phase name="keyword_extraction">
<description>
Extract all relevant keywords, concepts, and technical terms from the issue description.
Be exhaustive - include function names, error messages, feature names, and related concepts.
</description>
<actions>
<action>Identify primary technical concepts</action>
<action>Extract error messages or specific symptoms</action>
<action>Note any mentioned file paths or components</action>
<action>List related features or functionality</action>
<action>Include synonyms and related terms</action>
</actions>
<update_todo>Mark "Extract keywords from the issue description" as complete</update_todo>
</phase>
<phase name="iterative_search">
<description>
Perform multiple rounds of codebase searches, starting broad and progressively
narrowing based on findings. This is an aggressive, exhaustive search process.
</description>
<iteration number="1">
<title>Initial Broad Search</title>
<instructions>
Use codebase_search with all extracted keywords to get an overview of relevant code.
<tool_use><![CDATA[
<codebase_search>
<query>[Combined keywords from extraction phase]</query>
</codebase_search>
]]></tool_use>
</instructions>
</iteration>
<iteration number="2">
<title>Component Discovery</title>
<instructions>
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
</instructions>
</iteration>
<iteration number="3">
<title>Deep Implementation Search</title>
<instructions>
Search for specific implementation details:
- Error handling patterns
- State management
- API endpoints or routes
- Database queries or models
- UI components and their interactions
</instructions>
</iteration>
<iteration number="4">
<title>Edge Case and Integration Search</title>
<instructions>
Look for:
- Edge cases in the code
- Integration points with other systems
- Configuration options that affect behavior
- Feature flags or conditional logic
</instructions>
</iteration>
<update_todo>Update search-related todos as each iteration completes</update_todo>
</phase>
<phase name="file_analysis">
<description>
Thoroughly analyze all relevant files discovered during the search phase.
</description>
<actions>
<action>Use list_code_definition_names to understand file structure</action>
<action>Read complete files to understand full context</action>
<action>Trace execution paths through the code</action>
<action>Identify all dependencies and imports</action>
<action>Map relationships between components</action>
</actions>
<documentation>
Document findings including:
- File paths and their purposes
- Key functions and their responsibilities
- Data flow through the system
- External dependencies
- Potential impact areas
</documentation>
<update_todo>Mark file analysis todos as complete</update_todo>
</phase>
<phase name="hypothesis_formation">
<description>
Form a comprehensive hypothesis about the issue or feature implementation.
</description>
<for_bugs>
<steps>
<step>Identify the most likely root cause</step>
<step>Trace the bug through the execution path</step>
<step>Determine why the current implementation fails</step>
<step>Consider environmental factors</step>
</steps>
</for_bugs>
<for_features>
<steps>
<step>Identify the optimal integration points</step>
<step>Determine required architectural changes</step>
<step>Plan the implementation approach</step>
<step>Consider scalability and maintainability</step>
</steps>
</for_features>
<update_todo>Mark hypothesis formation as complete</update_todo>
</phase>
<phase name="hypothesis_validation">
<description>
Aggressively attempt to disprove the hypothesis by searching for contradictory evidence.
</description>
<validation_steps>
<step>
<title>Search for Alternative Implementations</title>
<action>Look for similar features implemented differently</action>
<action>Check for deprecated code that might interfere</action>
</step>
<step>
<title>Configuration and Environment Check</title>
<action>Search for configuration that could change behavior</action>
<action>Look for environment-specific code paths</action>
</step>
<step>
<title>Test Case Analysis</title>
<action>Find existing tests that might contradict hypothesis</action>
<action>Look for test cases that reveal edge cases</action>
</step>
<step>
<title>Historical Context</title>
<action>Search for comments explaining design decisions</action>
<action>Look for TODO or FIXME comments related to the area</action>
</step>
</validation_steps>
<outcome>
If hypothesis is disproven, return to search phase with new insights.
If hypothesis stands, proceed to solution formulation.
</outcome>
<update_todo>Update hypothesis validation status</update_todo>
</phase>
<phase name="solution_formulation">
<description>
Create a comprehensive technical solution with extreme detail.
</description>
<components>
<component name="implementation_plan">
<details>
- 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
</details>
</component>
<component name="dependency_analysis">
<details>
- All files that import affected code
- API contracts that must be maintained
- Database schema changes if any
- Configuration changes required
- Documentation updates needed
</details>
</component>
<component name="test_strategy">
<details>
- Unit tests to add or modify
- Integration tests required
- Edge cases to test
- Performance testing needs
- Manual testing scenarios
</details>
</component>
<component name="risk_assessment">
<details>
- Breaking changes identified
- Performance implications
- Security considerations
- Backward compatibility issues
- Rollback strategy
</details>
</component>
</components>
<update_todo>Mark solution formulation as complete</update_todo>
</phase>
<phase name="acceptance_criteria">
<description>
Create extremely detailed acceptance criteria that can guide automated implementation.
</description>
<format><![CDATA[
Given [detailed context including system state]
When [specific user or system action]
Then [exact expected outcome]
And [additional verifiable outcomes]
But [what should NOT happen]
Include:
- Specific UI changes with exact text/behavior
- API response formats
- Database state changes
- Performance requirements
- Error handling scenarios
]]></format>
<guidelines>
<guideline>Each criterion must be independently testable</guideline>
<guideline>Include both positive and negative test cases</guideline>
<guideline>Specify exact error messages and codes</guideline>
<guideline>Define performance thresholds where applicable</guideline>
</guidelines>
<update_todo>Mark acceptance criteria creation as complete</update_todo>
</phase>
</investigation_phases>
<output_format>
<technical_analysis_section><![CDATA[
## 🔍 Comprehensive Technical Analysis
### Root Cause / Implementation Target
[Detailed explanation of the core issue or feature target]
### Affected Components
- **Primary Files:**
- `path/to/file1.ts` (lines X-Y): [Purpose and changes needed]
- `path/to/file2.ts` (lines A-B): [Purpose and changes needed]
- **Secondary Impact:**
- Files that import affected components
- Related test files
- Documentation files
### Current Implementation Analysis
[Detailed explanation of how the current code works and why it's insufficient]
### Proposed Implementation
#### Step 1: [First implementation step]
- File: `path/to/file.ts`
- Changes: [Specific code changes]
- Rationale: [Why this change is needed]
#### Step 2: [Second implementation step]
[Continue for all steps...]
### Code Architecture Considerations
- Design patterns to follow
- Existing patterns in codebase to match
- Architectural constraints
### Testing Requirements
- Unit Tests:
- [ ] Test case 1: [Description]
- [ ] Test case 2: [Description]
- Integration Tests:
- [ ] Test scenario 1: [Description]
- Edge Cases:
- [ ] Edge case 1: [Description]
### Performance Impact
- Expected performance change: [Increase/Decrease/Neutral]
- Benchmarking needed: [Yes/No, specifics]
- Optimization opportunities: [List any]
### Security Considerations
- Input validation requirements
- Authentication/Authorization changes
- Data exposure risks
### Migration Strategy
[If applicable, how to migrate existing data/functionality]
### Rollback Plan
[How to safely rollback if issues arise]
### Dependencies and Breaking Changes
- External dependencies affected: [List]
- API contract changes: [List]
- Breaking changes for users: [List with mitigation]
### Implementation Complexity
- Estimated effort: [Small/Medium/Large]
- Risk level: [Low/Medium/High]
- Prerequisites: [Any required changes that must happen first]
]]></technical_analysis_section>
</output_format>
<completion>
<checklist>
<item>All keywords extracted and searched</item>
<item>Multiple search iterations completed</item>
<item>All relevant files analyzed</item>
<item>Hypothesis formed and validated</item>
<item>Comprehensive solution documented</item>
<item>Acceptance criteria defined</item>
<item>All risks and edge cases identified</item>
<item>Technical analysis formatted for issue</item>
</checklist>
<final_todo_update>
Mark all investigation todos as complete and update the main workflow todo list
</final_todo_update>
</completion>
</technical_analysis_workflow>

View file

@ -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 <relative-path-from-workspace-root>`
- Run tests with: `npx vitest run <relative-path-from-workspace-root>`
- 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:

View file

@ -18,6 +18,7 @@ customModes:
- Ensuring instructions are well-organized with proper XML tags
- Following established patterns from existing modes
whenToUse: Use this mode when you need to create a new custom mode.
description: Create and implement custom modes.
groups:
- read
- - edit
@ -33,6 +34,7 @@ customModes:
Your focus is on maintaining high test quality and coverage across the codebase, working primarily with: - Test files in __tests__ directories - Mock implementations in __mocks__ - Test utilities and helpers - Vitest configuration and setup
You ensure tests are: - Well-structured and maintainable - Following Vitest best practices - Properly typed with TypeScript - Providing meaningful coverage - Using appropriate mocking strategies
whenToUse: Use this mode when you need to write, modify, or maintain tests for the codebase.
description: Write, modify, and maintain tests.
groups:
- read
- browser
@ -55,6 +57,8 @@ customModes:
- slug: design-engineer
name: 🎨 Design Engineer
roleDefinition: "You are Roo, an expert Design Engineer focused on VSCode Extension development. Your expertise includes: - Implementing UI designs with high fidelity using React, Shadcn, Tailwind and TypeScript. - Ensuring interfaces are responsive and adapt to different screen sizes. - Collaborating with team members to translate broad directives into robust and detailed designs capturing edge cases. - Maintaining uniformity and consistency across the user interface."
whenToUse: Implement UI designs and ensure consistency.
description: Implement UI designs; ensure consistency.
groups:
- read
- - edit
@ -68,12 +72,30 @@ customModes:
- slug: release-engineer
name: 🚀 Release Engineer
roleDefinition: You are Roo, a release engineer specialized in automating the release process for software projects. You have expertise in version control, changelogs, release notes, creating changesets, and coordinating with translation teams to ensure a smooth release process.
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
@ -86,6 +108,8 @@ customModes:
- slug: translate
name: 🌐 Translate
roleDefinition: You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.
whenToUse: Translate and manage localization files.
description: Translate and manage localization files.
groups:
- read
- command
@ -107,6 +131,7 @@ customModes:
You work with issues from any GitHub repository, transforming them into working code that addresses all requirements while maintaining code quality and consistency. You use the GitHub CLI (gh) for all GitHub operations instead of MCP tools.
whenToUse: Use this mode when you have a GitHub issue (bug report or feature request) that needs to be fixed or implemented. Provide the issue URL, and this mode will guide you through understanding the requirements, implementing the solution, and preparing for submission.
description: Fix GitHub issues and implement features.
groups:
- read
- edit
@ -118,6 +143,7 @@ customModes:
You are Roo, a GitHub issue creation specialist focused on crafting well-structured, detailed issues based on the project's issue templates. Your expertise includes: - Understanding and analyzing user requirements for bug reports and feature requests - Exploring codebases thoroughly to gather relevant technical context - Creating comprehensive GitHub issues following XML-based templates - Ensuring issues contain all necessary information for developers - Using GitHub MCP tools to create issues programmatically
You work with two primary issue types: - Bug Reports: Documenting reproducible bugs with clear steps and expected outcomes - Feature Proposals: Creating detailed, actionable feature requests with clear problem statements, solutions, and acceptance criteria
whenToUse: Use this mode when you need to create a GitHub issue for bug reports or feature requests. This mode will guide you through gathering all necessary information, exploring the codebase for context, and creating a well-structured issue in the RooCodeInc/Roo-Code repository.
description: Create well-structured GitHub issues.
groups:
- read
- command
@ -129,6 +155,8 @@ customModes:
You are Roo, an integration testing specialist focused on VSCode E2E tests with expertise in: - Writing and maintaining integration tests using Mocha and VSCode Test framework - Testing Roo Code API interactions and event-driven workflows - Creating complex multi-step task scenarios and mode switching sequences - Validating message formats, API responses, and event emission patterns - Test data generation and fixture management - Coverage analysis and test scenario identification
Your focus is on ensuring comprehensive integration test coverage for the Roo Code extension, working primarily with: - E2E test files in apps/vscode-e2e/src/suite/ - Test utilities and helpers - API type definitions in packages/types/ - Extension API testing patterns
You ensure integration tests are: - Comprehensive and cover critical user workflows - Following established Mocha TDD patterns - Using async/await with proper timeout handling - Validating both success and failure scenarios - Properly typed with TypeScript
whenToUse: Write, modify, or maintain integration tests.
description: Write and maintain integration tests.
groups:
- read
- command
@ -156,6 +184,7 @@ customModes:
You work primarily with the RooCodeInc/Roo-Code repository, creating context reports to track findings and delegating complex pattern analysis to specialized modes while maintaining overall review coordination. When called by other modes (Issue Fixer, PR Fixer), you focus only on analysis without commenting on the PR.
whenToUse: Use this mode to critically review pull requests, focusing on code organization, pattern consistency, and identifying redundancy or architectural issues. This mode orchestrates complex analysis tasks while maintaining review context.
description: Critically review pull requests.
groups:
- read
- - edit
@ -168,6 +197,7 @@ customModes:
name: 📚 Docs Extractor
roleDefinition: You are Roo, a comprehensive documentation extraction specialist focused on analyzing and documenting all technical and non-technical information about features and components within codebases.
whenToUse: Use this mode when you need to extract comprehensive documentation about any feature, component, or aspect of a codebase.
description: Extract comprehensive documentation.
groups:
- read
- - edit
@ -179,8 +209,19 @@ customModes:
name: 🛠️ PR Fixer
roleDefinition: "You are Roo, a pull request resolution specialist. Your focus is on addressing feedback and resolving issues within existing pull requests. Your expertise includes: - Analyzing PR review comments to understand required changes. - Checking CI/CD workflow statuses to identify failing tests. - Fetching and analyzing test logs to diagnose failures. - Identifying and resolving merge conflicts. - Guiding the user through the resolution process."
whenToUse: Use this mode to fix pull requests. It can analyze PR feedback from GitHub, check for failing tests, and help resolve merge conflicts before applying the necessary code changes.
description: Fix pull requests.
groups:
- read
- 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

View file

@ -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

View file

@ -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.
})

View file

@ -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() {
)}
/>
<FormField
control={form.control}
name="timeout"
render={({ field }) => (
<FormItem>
<FormLabel>Timeout (minutes)</FormLabel>
<FormControl>
<div className="flex flex-row items-center gap-2">
<Slider
defaultValue={[field.value]}
min={TIMEOUT_MIN}
max={TIMEOUT_MAX}
step={1}
onValueChange={(value) => field.onChange(value[0])}
/>
<div>{field.value} min</div>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"

View file

@ -12,6 +12,10 @@ export const CONCURRENCY_MIN = 1
export const CONCURRENCY_MAX = 25
export const CONCURRENCY_DEFAULT = 1
export const TIMEOUT_MIN = 5
export const TIMEOUT_MAX = 10
export const TIMEOUT_DEFAULT = 5
export const createRunSchema = z
.object({
model: z.string().min(1, { message: "Model is required." }),
@ -20,6 +24,7 @@ export const createRunSchema = z
exercises: z.array(z.string()).optional(),
settings: rooCodeSettingsSchema.optional(),
concurrency: z.number().int().min(CONCURRENCY_MIN).max(CONCURRENCY_MAX),
timeout: z.number().int().min(TIMEOUT_MIN).max(TIMEOUT_MAX),
systemPrompt: z.string().optional(),
})
.refine((data) => data.suite === "full" || (data.exercises || []).length > 0, {

View file

@ -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() {
<FaLinkedin className="h-6 w-6" />
<span className="sr-only">LinkedIn</span>
</a>
<a
href={EXTERNAL_LINKS.BLUESKY}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground transition-colors hover:text-foreground">
<FaBluesky className="h-6 w-6" />
<span className="sr-only">Bluesky</span>
</a>
<a
href={EXTERNAL_LINKS.TIKTOK}
target="_blank"
@ -89,12 +97,12 @@ export function Footer() {
<span className="sr-only">TikTok</span>
</a>
<a
href={EXTERNAL_LINKS.BLUESKY}
href={EXTERNAL_LINKS.YOUTUBE}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground transition-colors hover:text-foreground">
<FaBluesky className="h-6 w-6" />
<span className="sr-only">Bluesky</span>
<FaYoutube className="h-6 w-6" />
<span className="sr-only">YouTube</span>
</a>
</div>
</div>

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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 }) => {

View file

@ -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

View file

@ -0,0 +1 @@
ALTER TABLE "runs" ADD COLUMN "timeout" integer DEFAULT 5 NOT NULL;

View file

@ -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 })

View file

@ -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(),

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
"version": "1.34.0",
"version": "1.36.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",

View file

@ -0,0 +1,79 @@
import { describe, it, expect } from "vitest"
import { getApiProtocol } from "../provider-settings.js"
describe("getApiProtocol", () => {
describe("Anthropic-style providers", () => {
it("should return 'anthropic' for anthropic provider", () => {
expect(getApiProtocol("anthropic")).toBe("anthropic")
expect(getApiProtocol("anthropic", "gpt-4")).toBe("anthropic")
})
it("should return 'anthropic' for claude-code provider", () => {
expect(getApiProtocol("claude-code")).toBe("anthropic")
expect(getApiProtocol("claude-code", "some-model")).toBe("anthropic")
})
})
describe("Vertex provider with Claude models", () => {
it("should return 'anthropic' for vertex provider with claude models", () => {
expect(getApiProtocol("vertex", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "Claude-3-Sonnet")).toBe("anthropic")
expect(getApiProtocol("vertex", "CLAUDE-instant")).toBe("anthropic")
expect(getApiProtocol("vertex", "anthropic/claude-3-haiku")).toBe("anthropic")
})
it("should return 'openai' for vertex provider with non-claude models", () => {
expect(getApiProtocol("vertex", "gpt-4")).toBe("openai")
expect(getApiProtocol("vertex", "gemini-pro")).toBe("openai")
expect(getApiProtocol("vertex", "llama-2")).toBe("openai")
})
})
describe("Bedrock provider with Claude models", () => {
it("should return 'anthropic' for bedrock provider with claude models", () => {
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("bedrock", "Claude-3-Sonnet")).toBe("anthropic")
expect(getApiProtocol("bedrock", "CLAUDE-instant")).toBe("anthropic")
expect(getApiProtocol("bedrock", "anthropic.claude-v2")).toBe("anthropic")
})
it("should return 'openai' for bedrock provider with non-claude models", () => {
expect(getApiProtocol("bedrock", "gpt-4")).toBe("openai")
expect(getApiProtocol("bedrock", "titan-text")).toBe("openai")
expect(getApiProtocol("bedrock", "llama-2")).toBe("openai")
})
})
describe("Other providers with Claude models", () => {
it("should return 'openai' for non-vertex/bedrock providers with claude models", () => {
expect(getApiProtocol("openrouter", "claude-3-opus")).toBe("openai")
expect(getApiProtocol("openai", "claude-3-sonnet")).toBe("openai")
expect(getApiProtocol("litellm", "claude-instant")).toBe("openai")
expect(getApiProtocol("ollama", "claude-model")).toBe("openai")
})
})
describe("Edge cases", () => {
it("should return 'openai' when provider is undefined", () => {
expect(getApiProtocol(undefined)).toBe("openai")
expect(getApiProtocol(undefined, "claude-3-opus")).toBe("openai")
})
it("should return 'openai' when model is undefined", () => {
expect(getApiProtocol("openai")).toBe("openai")
expect(getApiProtocol("vertex")).toBe("openai")
expect(getApiProtocol("bedrock")).toBe("openai")
})
it("should handle empty strings", () => {
expect(getApiProtocol("vertex", "")).toBe("openai")
expect(getApiProtocol("bedrock", "")).toBe("openai")
})
it("should be case-insensitive for claude detection", () => {
expect(getApiProtocol("vertex", "CLAUDE-3-OPUS")).toBe("anthropic")
expect(getApiProtocol("bedrock", "claude-3-opus")).toBe("anthropic")
expect(getApiProtocol("vertex", "ClAuDe-InStAnT")).toBe("anthropic")
})
})
})

View file

@ -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<typeof codebaseIndexModelsSchema>
@ -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<typeof codebaseIndexProviderSchema>

View file

@ -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,8 @@ 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(),
autoCondenseContextPercent: z.number().optional(),
@ -76,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(),
@ -86,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(),
@ -151,6 +170,7 @@ export const SECRET_STATE_KEYS = [
"codeIndexQdrantApiKey",
"codebaseIndexOpenAiCompatibleApiKey",
"codebaseIndexGeminiApiKey",
"codebaseIndexMistralApiKey",
] as const satisfies readonly (keyof ProviderSettings)[]
export type SecretState = Pick<ProviderSettings, (typeof SECRET_STATE_KEYS)[number]>
@ -202,6 +222,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
followupAutoApproveTimeoutMs: 0,
allowedCommands: ["*"],
commandExecutionTimeout: 30_000,
commandTimeoutAllowlist: [],
preventCompletionWithOpenTodos: false,
browserToolEnabled: false,
browserViewportSize: "900x600",
@ -214,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,
@ -224,6 +247,8 @@ export const EVALS_SETTINGS: RooCodeSettings = {
terminalCompressProgressBar: true,
terminalShellIntegrationDisabled: true,
diagnosticsEnabled: true,
diffEnabled: true,
fuzzyMatchThreshold: 1,

View file

@ -303,7 +303,23 @@ export const getModelId = (settings: ProviderSettings): string | undefined => {
// Providers that use Anthropic-style API protocol
export const ANTHROPIC_STYLE_PROVIDERS: ProviderName[] = ["anthropic", "claude-code"]
// Helper function to determine API protocol for a provider
export const getApiProtocol = (provider: ProviderName | undefined): "anthropic" | "openai" => {
return provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider) ? "anthropic" : "openai"
// Helper function to determine API protocol for a provider and model
export const getApiProtocol = (provider: ProviderName | undefined, modelId?: string): "anthropic" | "openai" => {
// First check if the provider is an Anthropic-style provider
if (provider && ANTHROPIC_STYLE_PROVIDERS.includes(provider)) {
return "anthropic"
}
// For vertex and bedrock providers, check if the model ID contains "claude" (case-insensitive)
if (
provider &&
(provider === "vertex" || provider === "bedrock") &&
modelId &&
modelId.toLowerCase().includes("claude")
) {
return "anthropic"
}
// Default to OpenAI protocol
return "openai"
}

View file

@ -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}`)
}
}

View file

@ -269,55 +269,6 @@ Examples:
<ignore_case>true</ignore_case>
</search_and_replace>
## 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:
<use_mcp_tool>
<server_name>server name here</server_name>
<tool_name>tool name here</tool_name>
<arguments>
{
"param1": "value1",
"param2": "value2"
}
</arguments>
</use_mcp_tool>
Example: Requesting to use an MCP tool
<use_mcp_tool>
<server_name>weather-server</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{
"city": "San Francisco",
"days": 5
}
</arguments>
</use_mcp_tool>
## 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:
<access_mcp_resource>
<server_name>server name here</server_name>
<uri>resource URI here</uri>
</access_mcp_resource>
Example: Requesting to access an MCP resource
<access_mcp_resource>
<server_name>weather-server</server_name>
<uri>weather://san-francisco/current</uri>
</access_mcp_resource>
## 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.
====

View file

@ -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:

View file

@ -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:

View file

@ -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,

View file

@ -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,

View file

@ -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}

View file

@ -23,6 +23,7 @@ import {
TelemetryEventName,
TodoItem,
getApiProtocol,
getModelId,
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService } from "@roo-code/cloud"
@ -1211,8 +1212,9 @@ export class Task extends EventEmitter<ClineEvents> {
// take a few seconds. For the best UX we show a placeholder api_req_started
// message with a loading spinner as this happens.
// Determine API protocol based on provider
const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider)
// Determine API protocol based on provider and model
const modelId = getModelId(this.apiConfiguration)
const apiProtocol = getApiProtocol(this.apiConfiguration.apiProvider, modelId)
await this.say(
"api_req_started",

View file

@ -1398,5 +1398,100 @@ describe("Cline", () => {
expect(task.diffStrategy).toBeUndefined()
})
})
describe("getApiProtocol", () => {
it("should determine API protocol based on provider and model", async () => {
// Test with Anthropic provider
const anthropicConfig = {
...mockApiConfig,
apiProvider: "anthropic" as const,
apiModelId: "gpt-4",
}
const anthropicTask = new Task({
provider: mockProvider,
apiConfiguration: anthropicConfig,
task: "test task",
startTask: false,
})
// Should use anthropic protocol even with non-claude model
expect(anthropicTask.apiConfiguration.apiProvider).toBe("anthropic")
// Test with OpenRouter provider and Claude model
const openrouterClaudeConfig = {
apiProvider: "openrouter" as const,
openRouterModelId: "anthropic/claude-3-opus",
}
const openrouterClaudeTask = new Task({
provider: mockProvider,
apiConfiguration: openrouterClaudeConfig,
task: "test task",
startTask: false,
})
expect(openrouterClaudeTask.apiConfiguration.apiProvider).toBe("openrouter")
// Test with OpenRouter provider and non-Claude model
const openrouterGptConfig = {
apiProvider: "openrouter" as const,
openRouterModelId: "openai/gpt-4",
}
const openrouterGptTask = new Task({
provider: mockProvider,
apiConfiguration: openrouterGptConfig,
task: "test task",
startTask: false,
})
expect(openrouterGptTask.apiConfiguration.apiProvider).toBe("openrouter")
// Test with various Claude model formats
const claudeModelFormats = [
"claude-3-opus",
"Claude-3-Sonnet",
"CLAUDE-instant",
"anthropic/claude-3-haiku",
"some-provider/claude-model",
]
for (const modelId of claudeModelFormats) {
const config = {
apiProvider: "openai" as const,
openAiModelId: modelId,
}
const task = new Task({
provider: mockProvider,
apiConfiguration: config,
task: "test task",
startTask: false,
})
// Verify the model ID contains claude (case-insensitive)
expect(modelId.toLowerCase()).toContain("claude")
}
})
it("should handle edge cases for API protocol detection", async () => {
// Test with undefined provider
const undefinedProviderConfig = {
apiModelId: "claude-3-opus",
}
const undefinedProviderTask = new Task({
provider: mockProvider,
apiConfiguration: undefinedProviderConfig,
task: "test task",
startTask: false,
})
expect(undefinedProviderTask.apiConfiguration.apiProvider).toBeUndefined()
// Test with no model ID
const noModelConfig = {
apiProvider: "openai" as const,
}
const noModelTask = new Task({
provider: mockProvider,
apiConfiguration: noModelConfig,
task: "test task",
startTask: false,
})
expect(noModelTask.apiConfiguration.apiProvider).toBe("openai")
})
})
})
})

View file

@ -0,0 +1,412 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { TodoItem } from "@roo-code/types"
import { AttemptCompletionToolUse } from "../../../shared/tools"
// Mock the formatResponse module before importing the tool
vi.mock("../../prompts/responses", () => ({
formatResponse: {
toolError: vi.fn((msg: string) => `Error: ${msg}`),
},
}))
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
getConfiguration: vi.fn(() => ({
get: vi.fn(),
})),
},
}))
// Mock Package module
vi.mock("../../../shared/package", () => ({
Package: {
name: "roo-cline",
},
}))
import { attemptCompletionTool } from "../attemptCompletionTool"
import { Task } from "../../task/Task"
import * as vscode from "vscode"
describe("attemptCompletionTool", () => {
let mockTask: Partial<Task>
let mockPushToolResult: ReturnType<typeof vi.fn>
let mockAskApproval: ReturnType<typeof vi.fn>
let mockHandleError: ReturnType<typeof vi.fn>
let mockRemoveClosingTag: ReturnType<typeof vi.fn>
let mockToolDescription: ReturnType<typeof vi.fn>
let mockAskFinishSubTaskApproval: ReturnType<typeof vi.fn>
let mockGetConfiguration: ReturnType<typeof vi.fn>
beforeEach(() => {
mockPushToolResult = vi.fn()
mockAskApproval = vi.fn()
mockHandleError = vi.fn()
mockRemoveClosingTag = vi.fn()
mockToolDescription = vi.fn()
mockAskFinishSubTaskApproval = vi.fn()
mockGetConfiguration = vi.fn(() => ({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return defaultValue // Default to false unless overridden in test
}
return defaultValue
}),
}))
// Setup vscode mock
vi.mocked(vscode.workspace.getConfiguration).mockImplementation(mockGetConfiguration)
mockTask = {
consecutiveMistakeCount: 0,
recordToolError: vi.fn(),
todoList: undefined,
}
})
describe("todo list validation", () => {
it("should allow completion when there is no todo list", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
mockTask.todoList = undefined
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should not call pushToolResult with an error for empty todo list
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should allow completion when todo list is empty", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
mockTask.todoList = []
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should allow completion when all todos are completed", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const completedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "completed" },
]
mockTask.todoList = completedTodos
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
})
it("should prevent completion when there are pending todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when there are in-progress todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithInProgress: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "in_progress" },
]
mockTask.todoList = todosWithInProgress
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when there are mixed incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const mixedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
{ id: "3", content: "Third task", status: "in_progress" },
]
mockTask.todoList = mixedTodos
// Enable the setting to prevent completion with open todos
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should allow completion when setting is disabled even with incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Ensure the setting is disabled (default behavior)
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return false // Setting is disabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should not prevent completion when setting is disabled
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
expect(mockPushToolResult).not.toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should prevent completion when setting is enabled with incomplete todos", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const todosWithPending: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "pending" },
]
mockTask.todoList = todosWithPending
// Enable the setting
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should prevent completion when setting is enabled and there are incomplete todos
expect(mockTask.consecutiveMistakeCount).toBe(1)
expect(mockTask.recordToolError).toHaveBeenCalledWith("attempt_completion")
expect(mockPushToolResult).toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
it("should allow completion when setting is enabled but all todos are completed", async () => {
const block: AttemptCompletionToolUse = {
type: "tool_use",
name: "attempt_completion",
params: { result: "Task completed successfully" },
partial: false,
}
const completedTodos: TodoItem[] = [
{ id: "1", content: "First task", status: "completed" },
{ id: "2", content: "Second task", status: "completed" },
]
mockTask.todoList = completedTodos
// Enable the setting
mockGetConfiguration.mockReturnValue({
get: vi.fn((key: string, defaultValue: any) => {
if (key === "preventCompletionWithOpenTodos") {
return true // Setting is enabled
}
return defaultValue
}),
})
await attemptCompletionTool(
mockTask as Task,
block,
mockAskApproval,
mockHandleError,
mockPushToolResult,
mockRemoveClosingTag,
mockToolDescription,
mockAskFinishSubTaskApproval,
)
// Should allow completion when setting is enabled but all todos are completed
expect(mockTask.consecutiveMistakeCount).toBe(0)
expect(mockTask.recordToolError).not.toHaveBeenCalled()
expect(mockPushToolResult).not.toHaveBeenCalledWith(
expect.stringContaining("Cannot complete task while there are incomplete todos"),
)
})
})
})

View file

@ -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)
})
})

View file

@ -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),
},

View file

@ -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 })

View file

@ -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) {

View file

@ -1,4 +1,5 @@
import Anthropic from "@anthropic-ai/sdk"
import * as vscode from "vscode"
import { TelemetryService } from "@roo-code/telemetry"
@ -14,6 +15,7 @@ import {
AskFinishSubTaskApproval,
} from "../../shared/tools"
import { formatResponse } from "../prompts/responses"
import { Package } from "../../shared/package"
export async function attemptCompletionTool(
cline: Task,
@ -28,6 +30,25 @@ export async function attemptCompletionTool(
const result: string | undefined = block.params.result
const command: string | undefined = block.params.command
// Get the setting for preventing completion with open todos from VSCode configuration
const preventCompletionWithOpenTodos = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("preventCompletionWithOpenTodos", false)
// Check if there are incomplete todos (only if the setting is enabled)
const hasIncompleteTodos = cline.todoList && cline.todoList.some((todo) => todo.status !== "completed")
if (preventCompletionWithOpenTodos && hasIncompleteTodos) {
cline.consecutiveMistakeCount++
cline.recordToolError("attempt_completion")
pushToolResult(
formatResponse.toolError(
"Cannot complete task while there are incomplete todos. Please finish all todos before attempting completion.",
),
)
return
}
try {
const lastMessage = cline.clineMessages.at(-1)

View file

@ -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<number>("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<string[]>("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
},

View file

@ -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) {

View file

@ -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)

View file

@ -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) {

View file

@ -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) {

View file

@ -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,

View file

@ -540,6 +540,7 @@ describe("ClineProvider", () => {
sharingEnabled: false,
profileThresholds: {},
hasOpenedModeSelector: false,
diagnosticsEnabled: true,
}
const message: ExtensionMessage = {

View file

@ -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
@ -1966,6 +1996,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({
@ -2058,6 +2094,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",
@ -2066,6 +2103,7 @@ export const webviewMessageHandler = async (
hasQdrantApiKey,
hasOpenAiCompatibleApiKey,
hasGeminiApiKey,
hasMistralApiKey,
},
})
break

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Esteu segur que voleu suprimir aquest mode personalitzat?",
"confirm": "Suprimeix"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Evitar la finalització de tasques quan hi ha todos incomplets a la llista de todos"
}
}
}

View file

@ -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ó.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Bist du sicher, dass du diesen benutzerdefinierten Modus löschen möchtest?",
"confirm": "Löschen"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Aufgabenabschluss verhindern, wenn unvollständige Todos in der Todo-Liste vorhanden sind"
}
}
}

View file

@ -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.",

View file

@ -155,5 +155,10 @@
"descriptionNoRules": "Are you sure you want to delete this custom mode?",
"confirm": "Delete"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Prevent task completion when there are incomplete todos in the todo list"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "¿Estás seguro de que quieres eliminar este modo personalizado?",
"confirm": "Eliminar"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Prevenir la finalización de tareas cuando hay todos incompletos en la lista de todos"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Êtes-vous sûr de vouloir supprimer ce mode personnalisé ?",
"confirm": "Supprimer"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Empêcher la finalisation des tâches lorsqu'il y a des todos incomplets dans la liste de todos"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "क्या आप वाकई इस कस्टम मोड को हटाना चाहते हैं?",
"confirm": "हटाएं"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "जब टूडू सूची में अधूरे टूडू हों तो कार्य पूर्ण होने से रोकें"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "एम्बेडर बनाने के लिए Ollama कॉन्फ़िगरेशन गायब है",
"openAiCompatibleConfigMissing": "एम्बेडर बनाने के लिए OpenAI संगत कॉन्फ़िगरेशन गायब है",
"geminiConfigMissing": "एम्बेडर बनाने के लिए Gemini कॉन्फ़िगरेशन गायब है",
"mistralConfigMissing": "एम्बेडर निर्माण के लिए मिस्ट्रल कॉन्फ़िगरेशन गायब है",
"invalidEmbedderType": "अमान्य एम्बेडर प्रकार कॉन्फ़िगर किया गया: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। कृपया सुनिश्चित करें कि OpenAI-संगत प्रदाता सेटिंग्स में 'एम्बेडिंग आयाम' सही तरीके से सेट है।",
"vectorDimensionNotDetermined": "प्रदाता '{{provider}}' के साथ मॉडल '{{modelId}}' के लिए वेक्टर आयाम निर्धारित नहीं कर सका। मॉडल प्रोफ़ाइल या कॉन्फ़िगरेशन की जांच करें।",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Anda yakin ingin menghapus mode kustom ini?",
"confirm": "Hapus"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Mencegah penyelesaian tugas ketika ada todo yang belum selesai dalam daftar todo"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Sei sicuro di voler eliminare questa modalità personalizzata?",
"confirm": "Elimina"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Impedire il completamento delle attività quando ci sono todo incompleti nella lista dei todo"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "このカスタムモードを削除してもよろしいですか?",
"confirm": "削除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Todoリストに未完了のTodoがある場合、タスクの完了を防ぐ"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "エンベッダー作成のためのOllama設定がありません",
"openAiCompatibleConfigMissing": "エンベッダー作成のためのOpenAI互換設定がありません",
"geminiConfigMissing": "エンベッダー作成のためのGemini設定がありません",
"mistralConfigMissing": "エンベッダー作成のためのMistral設定がありません",
"invalidEmbedderType": "無効なエンベッダータイプが設定されています: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。OpenAI互換プロバイダー設定で「埋め込み次元」が正しく設定されていることを確認してください。",
"vectorDimensionNotDetermined": "プロバイダー '{{provider}}' のモデル '{{modelId}}' の埋め込み次元を決定できませんでした。モデルプロファイルまたは設定を確認してください。",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "이 사용자 정의 모드를 삭제하시겠습니까?",
"confirm": "삭제"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "할 일 목록에 미완료된 할 일이 있을 때 작업 완료를 방지"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "임베더 생성을 위한 Ollama 구성이 누락되었습니다",
"openAiCompatibleConfigMissing": "임베더 생성을 위한 OpenAI 호환 구성이 누락되었습니다",
"geminiConfigMissing": "임베더 생성을 위한 Gemini 구성이 누락되었습니다",
"mistralConfigMissing": "임베더 생성을 위한 Mistral 구성이 없습니다",
"invalidEmbedderType": "잘못된 임베더 유형이 구성되었습니다: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. OpenAI 호환 프로바이더 설정에서 '임베딩 차원'이 올바르게 설정되어 있는지 확인하세요.",
"vectorDimensionNotDetermined": "프로바이더 '{{provider}}'의 모델 '{{modelId}}'에 대한 벡터 차원을 결정할 수 없습니다. 모델 프로필 또는 구성을 확인하세요.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Weet je zeker dat je deze aangepaste modus wilt verwijderen?",
"confirm": "Verwijderen"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Voorkom taakafronding wanneer er onvolledige todos in de todolijst staan"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Czy na pewno chcesz usunąć ten tryb niestandardowy?",
"confirm": "Usuń"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Zapobiegaj ukończeniu zadania gdy na liście zadań są nieukończone zadania"
}
}
}

View file

@ -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ę.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Tem certeza de que deseja excluir este modo personalizado?",
"confirm": "Excluir"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Impedir a conclusão de tarefas quando há todos incompletos na lista de todos"
}
}
}

View file

@ -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.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Вы уверены, что хотите удалить этот пользовательский режим?",
"confirm": "Удалить"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Предотвратить завершение задач при наличии незавершенных дел в списке дел"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "Отсутствует конфигурация Ollama для создания эмбеддера",
"openAiCompatibleConfigMissing": "Отсутствует конфигурация, совместимая с OpenAI, для создания эмбеддера",
"geminiConfigMissing": "Отсутствует конфигурация Gemini для создания эмбеддера",
"mistralConfigMissing": "Конфигурация Mistral отсутствует для создания эмбеддера",
"invalidEmbedderType": "Настроен недопустимый тип эмбеддера: {{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Убедитесь, что 'Размерность эмбеддинга' правильно установлена в настройках провайдера, совместимого с OpenAI.",
"vectorDimensionNotDetermined": "Не удалось определить размерность вектора для модели '{{modelId}}' с провайдером '{{provider}}'. Проверьте профили модели или конфигурацию.",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "Bu özel modu silmek istediğinizden emin misiniz?",
"confirm": "Sil"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Todo listesinde tamamlanmamış todolar olduğunda görev tamamlanmasını engelle"
}
}
}

View file

@ -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.",

View file

@ -173,5 +173,10 @@
"descriptionNoRules": "Bạn có chắc chắn muốn xóa chế độ tùy chỉnh này không?",
"confirm": "Xóa"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "Ngăn chặn hoàn thành nhiệm vụ khi có các todo chưa hoàn thành trong danh sách todo"
}
}
}

View file

@ -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.",

View file

@ -171,5 +171,10 @@
"descriptionNoRules": "您确定要删除此自定义模式吗?",
"confirm": "删除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "当待办事项列表中有未完成的待办事项时阻止任务完成"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "创建嵌入器缺少 Ollama 配置",
"openAiCompatibleConfigMissing": "创建嵌入器缺少 OpenAI 兼容配置",
"geminiConfigMissing": "创建嵌入器缺少 Gemini 配置",
"mistralConfigMissing": "创建嵌入器时缺少 Mistral 配置",
"invalidEmbedderType": "配置的嵌入器类型无效:{{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请确保在 OpenAI 兼容提供商设置中正确设置了「嵌入维度」。",
"vectorDimensionNotDetermined": "无法确定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量维度。请检查模型配置文件或配置。",

View file

@ -166,5 +166,10 @@
"descriptionNoRules": "您確定要刪除此自訂模式嗎?",
"confirm": "刪除"
}
},
"commands": {
"preventCompletionWithOpenTodos": {
"description": "當待辦事項清單中有未完成的待辦事項時阻止工作完成"
}
}
}

View file

@ -46,6 +46,7 @@
"ollamaConfigMissing": "建立嵌入器缺少 Ollama 設定",
"openAiCompatibleConfigMissing": "建立嵌入器缺少 OpenAI 相容設定",
"geminiConfigMissing": "建立嵌入器缺少 Gemini 設定",
"mistralConfigMissing": "建立嵌入器時缺少 Mistral 設定",
"invalidEmbedderType": "設定的嵌入器類型無效:{{embedderProvider}}",
"vectorDimensionNotDeterminedOpenAiCompatible": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請確保在 OpenAI 相容提供商設定中正確設定了「嵌入維度」。",
"vectorDimensionNotDetermined": "無法確定提供商 '{{provider}}' 的模型 '{{modelId}}' 的向量維度。請檢查模型設定檔或設定。",

View file

@ -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.

View file

@ -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()
})
})
})

View file

@ -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", () => {

View file

@ -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
}

View file

@ -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)
}
/**

View file

@ -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",
@ -224,38 +224,38 @@
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@3",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@4",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "overflow@1",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "roo-cline.promptsButtonClicked",
"group": "overflow@2",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@3",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@4",
"when": "view == roo-cline.SidebarProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"group": "overflow@5",
"group": "overflow@4",
"when": "view == roo-cline.SidebarProvider"
}
],
@ -266,38 +266,38 @@
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.settingsButtonClicked",
"command": "roo-cline.marketplaceButtonClicked",
"group": "navigation@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"command": "roo-cline.settingsButtonClicked",
"group": "navigation@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.accountButtonClicked",
"group": "navigation@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.historyButtonClicked",
"group": "overflow@1",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.marketplaceButtonClicked",
"command": "roo-cline.promptsButtonClicked",
"group": "overflow@2",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.promptsButtonClicked",
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@3",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.mcpButtonClicked",
"group": "overflow@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
},
{
"command": "roo-cline.popoutButtonClicked",
"group": "overflow@5",
"group": "overflow@4",
"when": "activeWebviewPanelId == roo-cline.TabPanelProvider"
}
]
@ -345,6 +345,19 @@
"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,
"description": "%commands.preventCompletionWithOpenTodos.description%"
},
"roo-cline.vsCodeLmModelSelector": {
"type": "object",
"properties": {

View file

@ -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)",

View file

@ -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)",

View file

@ -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)",

View file

@ -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)",

Some files were not shown because too many files have changed in this diff Show more