Merge branch 'main' into feat/webview-image-uri
|
|
@ -1,493 +0,0 @@
|
|||
<workflow>
|
||||
<step number="1">
|
||||
<name>Initialize Review Process</name>
|
||||
<instructions>
|
||||
Create a todo list to track the PR review workflow:
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[ ] Fetch pull request information
|
||||
[ ] Fetch associated issue (if any)
|
||||
[ ] Fetch pull request diff
|
||||
[ ] Fetch existing PR comments and reviews
|
||||
[ ] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
|
||||
This helps track progress through the review process and ensures all steps are completed.
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="2">
|
||||
<name>Fetch Pull Request Information</name>
|
||||
<instructions>
|
||||
If the user provides a PR number or URL, extract the necessary information:
|
||||
- Repository owner and name
|
||||
- Pull request number
|
||||
|
||||
Use the GitHub CLI to fetch the PR details:
|
||||
|
||||
<execute_command>
|
||||
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --json number,title,body,author,state,url,headRefName,baseRefName,headRefOid,mergeable,isDraft,createdAt,updatedAt</command>
|
||||
</execute_command>
|
||||
|
||||
Parse the JSON output to understand the PR's current state and metadata.
|
||||
IMPORTANT: Save the headRefOid value as it will be needed for submitting the review via the API.
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[ ] Fetch associated issue (if any)
|
||||
[ ] Fetch pull request diff
|
||||
[ ] Fetch existing PR comments and reviews
|
||||
[ ] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="3">
|
||||
<name>Fetch Associated Issue (If Any)</name>
|
||||
<instructions>
|
||||
Check the pull request body for a reference to a GitHub issue (e.g., "Fixes #123", "Closes #456").
|
||||
If an issue is referenced, use the GitHub CLI to fetch its details:
|
||||
|
||||
<execute_command>
|
||||
<command>gh issue view [issue_number] --repo [owner]/[repo] --json number,title,body,author,state,url,createdAt,updatedAt,comments</command>
|
||||
</execute_command>
|
||||
|
||||
The issue description and comments can provide valuable context for the review.
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[ ] Fetch pull request diff
|
||||
[ ] Fetch existing PR comments and reviews
|
||||
[ ] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="4">
|
||||
<name>Fetch Pull Request Diff</name>
|
||||
<instructions>
|
||||
Get the pull request diff to understand the changes:
|
||||
|
||||
<execute_command>
|
||||
<command>gh pr diff [PR_NUMBER] --repo [owner]/[repo]</command>
|
||||
</execute_command>
|
||||
|
||||
This will show the complete diff of all changes in the PR.
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[ ] Fetch existing PR comments and reviews
|
||||
[ ] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="5">
|
||||
<name>Fetch Existing PR Comments and Reviews</name>
|
||||
<instructions>
|
||||
IMPORTANT: Before reviewing any code, first get all existing comments and reviews to understand what feedback has already been provided:
|
||||
|
||||
Fetch all review comments:
|
||||
<execute_command>
|
||||
<command>gh pr view [PR_NUMBER] --repo [owner]/[repo] --comments</command>
|
||||
</execute_command>
|
||||
|
||||
Also fetch review details:
|
||||
<execute_command>
|
||||
<command>gh api repos/[owner]/[repo]/pulls/[PR_NUMBER]/reviews</command>
|
||||
</execute_command>
|
||||
|
||||
Create a mental or written list of:
|
||||
- All issues/suggestions that have been raised
|
||||
- The specific files and line numbers mentioned
|
||||
- Whether comments appear to be resolved or still pending
|
||||
|
||||
This information will guide your review to avoid duplicate feedback.
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[ ] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="6">
|
||||
<name>Check Out Pull Request Locally</name>
|
||||
<instructions>
|
||||
Use the GitHub CLI to check out the pull request locally:
|
||||
|
||||
<execute_command>
|
||||
<command>gh pr checkout [PR_NUMBER] --repo [owner]/[repo]</command>
|
||||
</execute_command>
|
||||
|
||||
This allows you to:
|
||||
- Navigate the actual code structure
|
||||
- Understand how changes interact with existing code
|
||||
- Get better context for your review
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[ ] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="7">
|
||||
<name>Verify Existing Comments Against Current Code</name>
|
||||
<instructions>
|
||||
Now that you have the code checked out locally and know what comments exist:
|
||||
|
||||
1. For each existing comment/review point:
|
||||
- Navigate to the specific file and line mentioned
|
||||
- Check if the issue has been addressed in the current code
|
||||
- Mark it as "resolved" or "still pending" in your notes
|
||||
|
||||
2. Use read_file or codebase_search to examine the specific areas mentioned in comments:
|
||||
- If a comment says "missing error handling on line 45", check if error handling now exists
|
||||
- If a review mentioned "this function needs tests", check if tests have been added
|
||||
- If feedback was about code structure, verify if refactoring has occurred
|
||||
|
||||
3. Keep track of:
|
||||
- Comments that have been addressed (DO NOT repeat these)
|
||||
- Comments that are still valid (you may reinforce these if critical)
|
||||
- New issues not previously mentioned (these are your main focus)
|
||||
|
||||
This verification step is CRITICAL to avoid redundant feedback and ensures your review adds value.
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[x] Verify existing comments against current code
|
||||
[ ] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="8">
|
||||
<name>Perform Comprehensive Review</name>
|
||||
<instructions>
|
||||
Review the pull request thoroughly:
|
||||
- Verify that the changes are directly related to the linked issue and do not include unrelated modifications.
|
||||
- Focus primarily on the changes made in the PR.
|
||||
- Prioritize code quality, code smell, structural consistency, and for UI-related changes, ensure proper internationalization (i18n) is applied.
|
||||
- Watch for signs of technical debt (e.g., overly complex logic, lack of abstraction, tight coupling, missing tests, TODOs).
|
||||
- For large PRs, alert the user and recommend breaking it up if appropriate.
|
||||
- NEVER run tests or execute code in PR Reviewer mode. The repository likely has automated testing. Your role is limited to:
|
||||
- Code review and analysis
|
||||
- Leaving review comments
|
||||
- Checking code quality and structure
|
||||
- Reviewing test coverage and quality (without execution)
|
||||
|
||||
Document your findings in a numbered list format:
|
||||
1. Code quality issues
|
||||
2. Structural improvements
|
||||
3. Missing tests or documentation
|
||||
4. Potential bugs or edge cases
|
||||
5. Performance concerns
|
||||
6. Security considerations
|
||||
7. Internationalization (i18n) issues
|
||||
8. Technical debt indicators
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[x] Verify existing comments against current code
|
||||
[x] Perform comprehensive review
|
||||
[ ] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="9">
|
||||
<name>Prepare Review Comments</name>
|
||||
<instructions>
|
||||
Format your review comments following these guidelines:
|
||||
|
||||
CRITICAL: Before adding any comment, verify it's not already addressed:
|
||||
- Cross-reference with your notes from Step 7
|
||||
- Only comment on NEW issues or UNRESOLVED existing issues
|
||||
- Never repeat feedback that has been addressed in the current code
|
||||
|
||||
Your suggestions should:
|
||||
- Use a **friendly, curious tone** — prefer asking: "Is this intentional?" or "Could we approach this differently to improve X?"
|
||||
- Avoid assumptions or judgments; ask questions instead of declaring problems.
|
||||
- Skip ALL praise and positive comments. Focus exclusively on issues that need attention.
|
||||
- Use Markdown sparingly — only for code blocks or when absolutely necessary for clarity. Avoid markdown headings (###, ##, etc.) entirely.
|
||||
- Avoid including internal evaluation terminology (e.g., scores or internal tags) in public comments.
|
||||
|
||||
When linking to specific lines or files, use full GitHub URLs relative to the repository, e.g.
|
||||
`https://github.com/[owner]/[repo]/blob/[branch]/[path/to/file]#L[line-number]`.
|
||||
|
||||
Present your findings as a numbered list organized by priority:
|
||||
|
||||
**Critical Issues (Must Fix):**
|
||||
1. [Issue description with file/line reference]
|
||||
2. [Issue description with file/line reference]
|
||||
|
||||
**Important Suggestions (Should Consider):**
|
||||
3. [Suggestion with rationale]
|
||||
4. [Suggestion with rationale]
|
||||
|
||||
**Minor Improvements (Nice to Have):**
|
||||
5. [Improvement suggestion]
|
||||
6. [Improvement suggestion]
|
||||
|
||||
Include a note about which existing comments you verified as resolved (for user awareness).
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[x] Verify existing comments against current code
|
||||
[x] Perform comprehensive review
|
||||
[x] Prepare review comments
|
||||
[ ] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="10">
|
||||
<name>Preview Review with User</name>
|
||||
<instructions>
|
||||
Always show the user a preview of your review suggestions and comments before taking any action.
|
||||
Present your findings as a numbered list clearly for the user before submitting comments.
|
||||
|
||||
<ask_followup_question>
|
||||
<question>I've completed my review of PR #[number]. Here's what I found:
|
||||
|
||||
[If applicable: **Existing comments that have been resolved:**
|
||||
- Comment about X on file Y - now addressed
|
||||
- Suggestion about Z - implemented]
|
||||
|
||||
**Review Findings:**
|
||||
|
||||
**Critical Issues (Must Fix):**
|
||||
1. [Specific issue with file/line reference]
|
||||
2. [Specific issue with file/line reference]
|
||||
|
||||
**Important Suggestions (Should Consider):**
|
||||
3. [Suggestion with rationale]
|
||||
4. [Suggestion with rationale]
|
||||
|
||||
**Minor Improvements (Nice to Have):**
|
||||
5. [Improvement suggestion]
|
||||
6. [Improvement suggestion]
|
||||
|
||||
Would you like me to:</question>
|
||||
<follow_up>
|
||||
<suggest>Create a comprehensive review with all comments</suggest>
|
||||
<suggest>Create individual tasks for each suggestion using new_task</suggest>
|
||||
<suggest>Let me modify the suggestions first</suggest>
|
||||
<suggest>Skip submission - just wanted the analysis</suggest>
|
||||
</follow_up>
|
||||
</ask_followup_question>
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[x] Verify existing comments against current code
|
||||
[x] Perform comprehensive review
|
||||
[x] Prepare review comments
|
||||
[x] Preview review with user
|
||||
[ ] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="11">
|
||||
<name>Submit Review</name>
|
||||
<instructions>
|
||||
Based on user preference, submit the review using the GitHub API to support inline comments:
|
||||
|
||||
1. Construct the review payload with inline comments. For each comment, you need:
|
||||
- The file path (relative to repository root)
|
||||
- The line number where the comment should appear
|
||||
- The comment body text
|
||||
- The side ("RIGHT" for new code, "LEFT" for old code)
|
||||
|
||||
2. Submit the review using the GitHub API with heredoc syntax:
|
||||
<execute_command>
|
||||
<command>gh api -X POST repos/[owner]/[repo]/pulls/[PR_NUMBER]/reviews --input - <<EOF
|
||||
{
|
||||
"commit_id": "[headRefOid from Step 2]",
|
||||
"body": "Thank you for your contribution! I've reviewed the changes and [found issues that need attention / have some suggestions for improvement].",
|
||||
"event": "COMMENT",
|
||||
"comments": [
|
||||
{
|
||||
"path": "[file/path/to/code.ts]",
|
||||
"body": "[Your comment text here - use friendly, curious tone]",
|
||||
"line": [line_number],
|
||||
"side": "RIGHT"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF</command>
|
||||
</execute_command>
|
||||
|
||||
The review will be created with all inline comments attached to specific lines of code.
|
||||
|
||||
Example for a review:
|
||||
<execute_command>
|
||||
<command>gh api -X POST repos/RooCodeInc/Roo-Code/pulls/6378/reviews --input - <<EOF
|
||||
{
|
||||
"commit_id": "abc123def4567890...",
|
||||
"body": "Thank you for your contribution! I've reviewed the changes and found that the critical issues from the previous review are still pending. I've left some suggestions inline to help improve the implementation.",
|
||||
"event": "COMMENT",
|
||||
"comments": [
|
||||
{
|
||||
"path": "packages/cloud/src/CloudService.ts",
|
||||
"body": "Missing error handling here...",
|
||||
"line": 19,
|
||||
"side": "RIGHT"
|
||||
},
|
||||
{
|
||||
"path": "packages/cloud/src/CloudService.ts",
|
||||
"body": "Is this intentional? The timeout seems quite high (30s). Could we consider reducing it or making it configurable?",
|
||||
"line": 45,
|
||||
"side": "RIGHT"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF</command>
|
||||
</execute_command>
|
||||
|
||||
<update_todo_list>
|
||||
<todos>
|
||||
[x] Fetch pull request information
|
||||
[x] Fetch associated issue (if any)
|
||||
[x] Fetch pull request diff
|
||||
[x] Fetch existing PR comments and reviews
|
||||
[x] Check out pull request locally
|
||||
[x] Verify existing comments against current code
|
||||
[x] Perform comprehensive review
|
||||
[x] Prepare review comments
|
||||
[x] Preview review with user
|
||||
[x] Submit review or create tasks
|
||||
</todos>
|
||||
</update_todo_list>
|
||||
</instructions>
|
||||
</step>
|
||||
|
||||
<step number="12">
|
||||
<name>Create Tasks for Suggestions (Optional)</name>
|
||||
<instructions>
|
||||
If the user chooses to create individual tasks for each suggestion, use the new_task tool to create separate tasks:
|
||||
|
||||
For each numbered finding from your review:
|
||||
1. Determine the appropriate mode based on the type of work needed:
|
||||
- Use "code" mode for bug fixes, implementation changes, or refactoring
|
||||
- Use "translate" mode for internationalization (i18n) issues
|
||||
- Use "test" mode for missing or inadequate test coverage
|
||||
- Use "docs-extractor" mode for documentation issues
|
||||
- Use "architect" mode for structural or design improvements
|
||||
- Use "debug" mode for investigating potential bugs
|
||||
|
||||
2. Create a clear, actionable task message that includes:
|
||||
- The specific issue or suggestion
|
||||
- The file(s) and line numbers affected
|
||||
- Any relevant context from the PR
|
||||
- The expected outcome
|
||||
|
||||
3. Use the new_task tool for each suggestion:
|
||||
<new_task>
|
||||
<mode>[appropriate mode based on task type]</mode>
|
||||
<message>Fix [issue type] in [file]: [specific description of what needs to be done]</message>
|
||||
</new_task>
|
||||
|
||||
Example task creation:
|
||||
<new_task>
|
||||
<mode>code</mode>
|
||||
<message>Fix missing error handling in src/api/users.ts:45-52. The getUserById function should handle cases where the user is not found and return an appropriate error response.</message>
|
||||
</new_task>
|
||||
|
||||
<new_task>
|
||||
<mode>translate</mode>
|
||||
<message>Add missing i18n translations for new user profile fields in src/components/UserProfile.tsx. The fields 'bio', 'location', and 'website' need to be wrapped with translation functions.</message>
|
||||
</new_task>
|
||||
|
||||
After creating all tasks, provide a summary:
|
||||
"I've created [X] individual tasks for the review findings:
|
||||
- [Y] code fixes/improvements
|
||||
- [Z] translation/i18n tasks
|
||||
- [etc.]
|
||||
|
||||
Each task contains the specific context and requirements for addressing the issue."
|
||||
</instructions>
|
||||
</step>
|
||||
</workflow>
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<best_practices>
|
||||
- ALWAYS create a todo list at the start to track the review workflow (Step 1)
|
||||
- Use GitHub CLI (`gh`) commands instead of MCP tools for all GitHub operations
|
||||
- ALWAYS fetch the PR's headRefOid in Step 2 - this is required for API review submission
|
||||
- ALWAYS fetch existing comments and reviews BEFORE reviewing any code (Step 5)
|
||||
- Create a list of all existing feedback before starting your review
|
||||
- Check out the PR locally using `gh pr checkout` for better context understanding
|
||||
- Systematically verify each existing comment against the current code (Step 7)
|
||||
- Track which comments are resolved vs still pending
|
||||
- Only provide feedback on NEW issues or UNRESOLVED existing issues
|
||||
- Never duplicate feedback that has already been addressed
|
||||
- Always fetch and review the entire PR diff before commenting
|
||||
- Check for and review any associated issue for context
|
||||
- Focus on the changes made, not unrelated code
|
||||
- Ensure all changes are directly related to the linked issue
|
||||
- Use a friendly, curious tone in all comments
|
||||
- Ask questions rather than making assumptions - there may be intentions behind the code choices
|
||||
- Provide actionable feedback with specific suggestions
|
||||
- Focus exclusively on issues and improvements - skip all praise or positive comments
|
||||
- Use minimal markdown - avoid headings (###, ##) and excessive formatting
|
||||
- Only use markdown for code blocks or when absolutely necessary for clarity
|
||||
- Consider the PR's scope - suggest breaking up large PRs
|
||||
- Verify proper i18n implementation for UI changes
|
||||
- Check for test coverage without executing tests
|
||||
- Look for signs of technical debt and code smells
|
||||
- Ensure consistency with existing code patterns
|
||||
- Link to specific lines using full GitHub URLs
|
||||
- Present findings in a numbered list format for clarity
|
||||
- Group feedback by priority (critical, important, minor)
|
||||
- Always preview comments with the user before submitting
|
||||
- Use the GitHub API for submitting reviews to support inline comments
|
||||
- Construct proper JSON payloads with commit_id, body, event, and comments array
|
||||
- Each inline comment needs: path, body, line number, and side (RIGHT for new code)
|
||||
- Use COMMENT when submitting the review
|
||||
- Use heredoc syntax (--input - <<EOF) to pass JSON directly
|
||||
- Offer the option to create individual tasks for each suggestion
|
||||
- When creating tasks, choose the appropriate mode for each type of work
|
||||
- Include specific context and file references in each task
|
||||
- Update the todo list after each major step to track progress
|
||||
</best_practices>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<common_mistakes_to_avoid>
|
||||
- Not creating a todo list at the start to track the review workflow
|
||||
- Using MCP tools instead of GitHub CLI commands for GitHub operations
|
||||
- Forgetting to fetch headRefOid in Step 2 - this is REQUIRED for API review submission
|
||||
- Starting to review code WITHOUT first fetching existing comments and reviews
|
||||
- Failing to create a list of existing feedback before reviewing
|
||||
- Not systematically checking each existing comment against the current code
|
||||
- Repeating feedback that has already been addressed in the current code
|
||||
- Ignoring existing PR comments or failing to verify if they have already been resolved
|
||||
- Running tests or executing code during review
|
||||
- Making judgmental or harsh comments
|
||||
- Providing feedback on code outside the PR's scope
|
||||
- Overlooking unrelated changes not tied to the main issue
|
||||
- Including ANY praise or positive comments - focus only on issues
|
||||
- Using markdown headings (###, ##, #) in review comments
|
||||
- Using excessive markdown formatting when plain text would suffice
|
||||
- Submitting comments without user preview/approval
|
||||
- Forgetting to check for an associated issue for additional context
|
||||
- Missing critical security or performance issues
|
||||
- Not checking for proper i18n in UI changes
|
||||
- Failing to suggest breaking up large PRs
|
||||
- Using internal evaluation terminology in public comments
|
||||
- Not providing actionable suggestions for improvements
|
||||
- Reviewing only the diff without local context
|
||||
- Making assumptions instead of asking clarifying questions about potential intentions
|
||||
- Forgetting to link to specific lines with full GitHub URLs
|
||||
- Not presenting findings in a clear numbered list format
|
||||
- Failing to offer the task creation option for addressing suggestions
|
||||
- Creating tasks without specific context or file references
|
||||
- Choosing inappropriate modes when creating tasks for suggestions
|
||||
- Not updating the todo list after completing each step
|
||||
- Not including --repo flag when using gh commands for non-default repositories
|
||||
- Using wrong commit_id in review payload (must use headRefOid from PR info)
|
||||
- Forgetting to specify "side": "RIGHT" for comments on new code
|
||||
- Using incorrect line numbers that don't match the actual diff
|
||||
- Not escaping special characters in JSON payload properly
|
||||
- Not using COMMENT as the event type in the review payload
|
||||
- Not constructing proper file paths relative to repository root
|
||||
- Submitting empty comments array when inline comments are needed
|
||||
- Forgetting to use <<EOF syntax properly in the command
|
||||
- Not properly escaping special characters in heredoc JSON content
|
||||
- Missing the EOF delimiter at the end of the heredoc
|
||||
</common_mistakes_to_avoid>
|
||||
15
.roomodes
|
|
@ -205,21 +205,6 @@ customModes:
|
|||
- command
|
||||
- mcp
|
||||
source: project
|
||||
- slug: pr-reviewer
|
||||
name: 🔍 PR Reviewer
|
||||
roleDefinition: |-
|
||||
You are Roo, a pull request reviewer specializing in code quality, structure, and translation consistency. Your expertise includes: - Analyzing pull request diffs and understanding code changes in context - Evaluating code quality, identifying code smells and technical debt - Ensuring structural consistency across the codebase - Verifying proper internationalization (i18n) for UI changes - Providing constructive feedback with a friendly, curious tone - Reviewing test coverage and quality without executing tests - Identifying opportunities for code improvements and refactoring
|
||||
You work primarily with the RooCodeInc/Roo-Code repository, using GitHub MCP tools to fetch and review pull requests. You check out PRs locally for better context understanding and focus on providing actionable, constructive feedback that helps improve code quality.
|
||||
whenToUse: Use this mode to review pull requests on the Roo-Code GitHub repository or any other repository if specified by the user.
|
||||
description: Review PRs for code quality, structure, and i18n compliance.
|
||||
groups:
|
||||
- read
|
||||
- - edit
|
||||
- fileRegex: \.md$
|
||||
description: Markdown files only
|
||||
- mcp
|
||||
- command
|
||||
source: project
|
||||
- slug: mode-writer
|
||||
name: ✍️ Mode Writer
|
||||
roleDefinition: |-
|
||||
|
|
|
|||
55
CHANGELOG.md
|
|
@ -1,5 +1,60 @@
|
|||
# Roo Code Changelog
|
||||
|
||||
## [3.28.8] - 2025-09-25
|
||||
|
||||

|
||||
|
||||
- Fix: Resolve frequent "No tool used" errors by clarifying tool-use rules (thanks @hannesrudolph!)
|
||||
- Fix: Include initial ask in condense summarization (thanks @hannesrudolph!)
|
||||
- Add support for more free models in the Roo provider (thanks @mrubens!)
|
||||
- Show cloud switcher and option to add a team when logged in (thanks @mrubens!)
|
||||
- Add Opengraph image for web (thanks @brunobergher!)
|
||||
|
||||
## [3.28.7] - 2025-09-23
|
||||
|
||||

|
||||
|
||||
- UX: Collapse thinking blocks by default with UI settings to always show them (thanks @brunobergher!)
|
||||
- Fix: Resolve checkpoint restore popover positioning issue (#8219 by @NaccOll, PR by @app/roomote)
|
||||
- Add cloud account switcher functionality (thanks @mrubens!)
|
||||
- Add support for zai-org/GLM-4.5-turbo model in Chutes provider (#8155 by @mugnimaestra, PR by @app/roomote)
|
||||
|
||||
## [3.28.6] - 2025-09-23
|
||||
|
||||

|
||||
|
||||
- Feat: Add GPT-5-Codex model (thanks @daniel-lxs!)
|
||||
- Feat: Add keyboard shortcut for toggling auto-approve (Cmd/Ctrl+Alt+A) (thanks @brunobergher!)
|
||||
- Fix: Improve reasoning block formatting for better readability (thanks @daniel-lxs!)
|
||||
- Fix: Respect Ollama Modelfile num_ctx configuration (#7797 by @hannesrudolph, PR by @app/roomote)
|
||||
- Fix: Prevent checkpoint text from wrapping in non-English languages (#8206 by @NaccOll, PR by @app/roomote)
|
||||
- Remove language selection and word wrap toggle from CodeBlock (thanks @mrubens!)
|
||||
- Feat: Add package.nls.json checking to find-missing-translations script (thanks @app/roomote!)
|
||||
- Fix: Bare metal evals fixes (thanks @cte!)
|
||||
- Fix: Follow-up questions should trigger the "interactive" state (thanks @cte!)
|
||||
|
||||
## [3.28.5] - 2025-09-20
|
||||
|
||||

|
||||
|
||||
- Fix: Resolve duplicate rehydrate during reasoning; centralize rehydrate and preserve cancel metadata (#8153 by @hannesrudolph, PR by @hannesrudolph)
|
||||
- Add an announcement for Supernova (thanks @mrubens!)
|
||||
- Wrap code blocks by default for improved readability (thanks @mrubens!)
|
||||
- Fix: Support dash prefix in parseMarkdownChecklist for todo lists (#8054 by @NaccOll, PR by app/roomote)
|
||||
- Fix: Apply tiered pricing for Gemini models via Vertex AI (#8017 by @ikumi3, PR by app/roomote)
|
||||
- Update SambaNova models to latest versions (thanks @snova-jorgep!)
|
||||
- Update privacy policy to allow occasional emails (thanks @jdilla1277!)
|
||||
|
||||
## [3.28.4] - 2025-09-19
|
||||
|
||||

|
||||
|
||||
- UX: Redesigned Message Feed (thanks @brunobergher!)
|
||||
- UX: Responsive Auto-Approve (thanks @brunobergher!)
|
||||
- Add telemetry retry queue for network resilience (thanks @daniel-lxs!)
|
||||
- Fix: Transform keybindings in nightly build to fix command+y shortcut (thanks @app/roomote!)
|
||||
- New code-supernova stealth model in the Roo Code Cloud provider (thanks @mrubens!)
|
||||
|
||||
## [3.28.3] - 2025-09-16
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
"scripts": {
|
||||
"lint": "next lint --max-warnings 0",
|
||||
"check-types": "tsc -b",
|
||||
"dev": "scripts/check-services.sh && next dev",
|
||||
"dev": "scripts/check-services.sh && next dev -p 3446",
|
||||
"format": "prettier --write src",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start": "next start -p 3446",
|
||||
"clean": "rimraf tsconfig.tsbuildinfo .next .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 10 KiB |
BIN
apps/web-roo-code/public/opengraph.png
Normal file
|
After Width: | Height: | Size: 428 KiB |
|
|
@ -3,7 +3,6 @@
|
|||
import { getVSCodeDownloads } from "@/lib/stats"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedText } from "@/components/animated-text"
|
||||
import {
|
||||
AnimatedBackground,
|
||||
InstallSection,
|
||||
|
|
@ -12,6 +11,8 @@ import {
|
|||
FAQSection,
|
||||
CodeExample,
|
||||
} from "@/components/homepage"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import { ArrowRight } from "lucide-react"
|
||||
|
||||
// Invalidate cache when a request comes in, at most once every hour.
|
||||
export const revalidate = 3600
|
||||
|
|
@ -21,28 +22,18 @@ export default async function Home() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<section className="relative flex h-[calc(125vh-theme(spacing.12))] items-center overflow-hidden md:h-[calc(100svh-theme(spacing.12))] lg:h-[calc(100vh-theme(spacing.12))]">
|
||||
<section className="relative flex h-[calc(125vh-theme(spacing.12))] items-center overflow-hidden md:h-[calc(80svh-theme(spacing.12))]">
|
||||
<AnimatedBackground />
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="flex flex-col justify-center space-y-6 sm:space-y-8">
|
||||
<div className="container relative flex items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid h-full relative gap-8 md:gap-12 lg:grid-cols-2 lg:gap-16">
|
||||
<div className="flex flex-col px-4 justify-center space-y-6 sm:space-y-8">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl lg:text-6xl">
|
||||
<span className="block">Your</span>
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
AI-Powered
|
||||
</AnimatedText>
|
||||
<span className="block">Dev Team, in Your Editor</span>
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
and Beyond
|
||||
</AnimatedText>
|
||||
<h1 className="text-3xl font-bold tracking-tight mt-8 sm:text-4xl md:text-5xl lg:text-6xl lg:mt-0">
|
||||
An entire AI-powered dev team. In your editor and beyond.
|
||||
</h1>
|
||||
<p className="mt-4 max-w-md text-base text-muted-foreground sm:mt-6 sm:text-lg">
|
||||
Supercharge your software development with AI that{" "}
|
||||
<AnimatedText className="bg-gradient-to-r from-blue-400 to-cyan-400 bg-clip-text text-transparent">
|
||||
understands your codebase
|
||||
</AnimatedText>{" "}
|
||||
and helps you write, refactor, and debug with ease in your editor and in the cloud.
|
||||
Roo's model-agnostic, specialized modes and fine-grained auto-approval controls
|
||||
give you the tools (and the confidence) to get AI working for you.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
|
|
@ -53,18 +44,8 @@ export default async function Home() {
|
|||
href="https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline"
|
||||
target="_blank"
|
||||
className="flex w-full items-center justify-center">
|
||||
Install Roo Code
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="ml-2 h-4 w-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L12.586 11H5a1 1 0 110-2h7.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Install Extension
|
||||
<ArrowRight className="ml-2" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
|
|
@ -72,24 +53,24 @@ export default async function Home() {
|
|||
size="lg"
|
||||
className="w-full sm:w-auto bg-white/20 dark:bg-white/10 backdrop-blur-sm border border-black/40 dark:border-white/30 hover:border-blue-400 hover:bg-white/30 dark:hover:bg-white/20 hover:shadow-[0_0_20px_rgba(59,130,246,0.5)] transition-all duration-300">
|
||||
<a
|
||||
href="https://roocode.com/enterprise"
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
|
||||
target="_blank"
|
||||
className="flex w-full items-center justify-center">
|
||||
For Enterprise
|
||||
Get started with Cloud
|
||||
<ArrowRight className="ml-2" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-8 flex items-center justify-center lg:mt-0">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="h-[250px] w-[250px] rounded-full bg-blue-500/20 blur-[100px] sm:h-[300px] sm:w-[300px] md:h-[350px] md:w-[350px]" />
|
||||
<div className="relative flex items-center mx-auto h-full mt-8 lg:mt-0">
|
||||
<div className="flex items-center justify-center">
|
||||
<CodeExample />
|
||||
</div>
|
||||
<CodeExample />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="features">
|
||||
<div id="product">
|
||||
<Features />
|
||||
</div>
|
||||
<div id="testimonials">
|
||||
|
|
|
|||
328
apps/web-roo-code/src/app/pricing/page.tsx
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
import { Users, Building2, ArrowRight, Star, LucideIcon, Check, Cloud } from "lucide-react"
|
||||
import type { Metadata } from "next"
|
||||
import Link from "next/link"
|
||||
|
||||
import { Button } from "@/components/ui"
|
||||
import { AnimatedBackground } from "@/components/homepage"
|
||||
import { ContactForm } from "@/components/enterprise/contact-form"
|
||||
import { SEO } from "@/lib/seo"
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
|
||||
const TITLE = "Pricing - Roo Code Cloud"
|
||||
const DESCRIPTION =
|
||||
"Simple, transparent pricing for Roo Code Cloud. The VS Code extension is free forever. Choose the cloud plan that fits your needs."
|
||||
const PATH = "/pricing"
|
||||
const OG_IMAGE = SEO.ogImage
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
alternates: {
|
||||
canonical: `${SEO.url}${PATH}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
url: `${SEO.url}${PATH}`,
|
||||
siteName: SEO.name,
|
||||
images: [
|
||||
{
|
||||
url: OG_IMAGE.url,
|
||||
width: OG_IMAGE.width,
|
||||
height: OG_IMAGE.height,
|
||||
alt: OG_IMAGE.alt,
|
||||
},
|
||||
],
|
||||
locale: SEO.locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: SEO.twitterCard,
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
images: [OG_IMAGE.url],
|
||||
},
|
||||
keywords: [
|
||||
...SEO.keywords,
|
||||
"pricing",
|
||||
"plans",
|
||||
"subscription",
|
||||
"cloud pricing",
|
||||
"AI development pricing",
|
||||
"team pricing",
|
||||
"enterprise pricing",
|
||||
],
|
||||
}
|
||||
|
||||
interface PricingTier {
|
||||
name: string
|
||||
icon: LucideIcon
|
||||
price: string
|
||||
period?: string
|
||||
trial?: string
|
||||
cancellation?: string
|
||||
description: string
|
||||
featuresIntro?: string
|
||||
features: string[]
|
||||
cta: {
|
||||
text: string
|
||||
href?: string
|
||||
isContactForm?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const pricingTiers: PricingTier[] = [
|
||||
{
|
||||
name: "Cloud Free",
|
||||
icon: Cloud,
|
||||
price: "$0",
|
||||
cancellation: "Cancel anytime",
|
||||
description: "For folks just getting started",
|
||||
features: [
|
||||
"Token usage analytics",
|
||||
"Access your task history across devices",
|
||||
"Follow your tasks from anywhere",
|
||||
"Community support",
|
||||
],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Pro",
|
||||
icon: Star,
|
||||
price: "$20",
|
||||
period: "/mo",
|
||||
trial: "Free 14-day trial · ",
|
||||
cancellation: "Cancel anytime",
|
||||
description: "For pro Roo coders",
|
||||
featuresIntro: "Everything in Free, plus:",
|
||||
features: [
|
||||
"Roomote Control",
|
||||
"Start, stop and control tasks from anywhere",
|
||||
"Course-correct Roo from afar",
|
||||
"Paid support",
|
||||
],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Team",
|
||||
icon: Users,
|
||||
price: "$99",
|
||||
period: "/mo",
|
||||
trial: "Free 14-day trial · ",
|
||||
cancellation: "Cancel anytime",
|
||||
description: "For AI-forward teams",
|
||||
featuresIntro: "Everything in Pro, plus:",
|
||||
features: ["Unlimited users (no per-seat cost)", "Shared configuration & policies", "Centralized billing"],
|
||||
cta: {
|
||||
text: "Get started",
|
||||
href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Enterprise",
|
||||
icon: Building2,
|
||||
price: "Custom",
|
||||
description: "For complex orgs",
|
||||
featuresIntro: "Everything in Team, plus:",
|
||||
features: [
|
||||
"SAML SSO provisioning",
|
||||
"Custom integrations and terms",
|
||||
"Security questionnaires and all that fun stuff",
|
||||
"Dedicated support",
|
||||
],
|
||||
cta: {
|
||||
text: "Talk to Sales",
|
||||
isContactForm: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<>
|
||||
<AnimatedBackground />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative overflow-hidden pt-16 pb-12">
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-5xl font-bold tracking-tight">Roo Code Cloud Pricing</h1>
|
||||
<p className="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">
|
||||
Simple, transparent pricing that scales with your needs.
|
||||
<br />
|
||||
Free 14-day trials to kick the tires.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Free Extension Notice */}
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="rounded-xl p-4 mb-8 text-center bg-gradient-to-r from-blue-500/10 via-cyan-500/10 to-purple-500/10 border border-blue-500/20 dark:border-white/20">
|
||||
<p className="text-center">
|
||||
<strong className="font-semibold">The Roo Code extension is free! </strong>
|
||||
Roo Code Cloud is an optional service which takes it to the next level.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pricing Tiers */}
|
||||
<section className="">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto grid max-w-6xl gap-4 lg:grid-cols-4">
|
||||
{pricingTiers.map((tier) => {
|
||||
const Icon = tier.icon
|
||||
return (
|
||||
<div
|
||||
key={tier.name}
|
||||
className="relative p-6 flex flex-col justify-start bg-background border rounded-2xl transition-all hover:shadow-lg">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-2xl font-bold tracking-tight">{tier.name}</h3>
|
||||
<Icon className="size-6" />
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{tier.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="grow mb-8">
|
||||
<p className="text-sm text-muted-foreground font-light mb-2">
|
||||
{tier.featuresIntro}
|
||||
</p>
|
||||
<ul className="space-y-3 my-0">
|
||||
{tier.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-2">
|
||||
<Check className="mt-0.5 h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<span className="text-sm">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p className="text-2xl mt-0 mb-1 tracking-tight">
|
||||
<strong>{tier.price}</strong>
|
||||
{tier.period}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
{tier.trial}
|
||||
{tier.cancellation}
|
||||
</p>
|
||||
|
||||
{tier.cta.isContactForm ? (
|
||||
<ContactForm
|
||||
formType="demo"
|
||||
buttonText={tier.cta.text}
|
||||
buttonClassName="w-full transition-all duration-300"
|
||||
/>
|
||||
) : (
|
||||
<Button size="lg" className="w-full transition-all duration-300" asChild>
|
||||
<Link href={tier.cta.href!} className="flex items-center justify-center">
|
||||
{tier.cta.text}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Additional Information */}
|
||||
<section className="bg-background py-16 my-16 border-t border-b relative z-50">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">Frequently Asked Questions</h2>
|
||||
<p className="mt-4 text-lg text-muted-foreground">Got questions about our pricing?</p>
|
||||
</div>
|
||||
<div className="mx-auto mt-12 grid max-w-5xl gap-8 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">Wait, is Roo Code free or not?</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Yes! The Roo Code VS Code extension is open source and free forever. The extension acts
|
||||
as a powerful AI coding assistant right in your editor. These are the prices for Roo
|
||||
Code Cloud.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">Is there a free trial?</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Yes, all paid plans come with a 14-day free trial.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">Do I need a credit card for the free trial?</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Yes, but you won't be charged until your trial ends. You can cancel anytime with
|
||||
one click .
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">What payment methods do you accept?</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
We accept all major credit cards, debit cards, and can arrange invoice billing for
|
||||
Enterprise customers.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-card p-6">
|
||||
<h3 className="font-semibold">Can I change plans anytime?</h3>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
Yes, you can upgrade or downgrade your plan at any time. Changes will be reflected in
|
||||
your next billing cycle.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 text-center">
|
||||
<p className="text-muted-foreground">
|
||||
Still have questions?{" "}
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
Join our Discord
|
||||
</a>{" "}
|
||||
or{" "}
|
||||
<Link
|
||||
href="/enterprise#contact"
|
||||
className="font-medium text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300">
|
||||
contact our sales team
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20">
|
||||
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-blue-500/5 via-cyan-500/5 to-purple-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/20 dark:bg-gradient-to-br dark:from-gray-800 dark:via-gray-900 dark:to-black sm:p-12">
|
||||
<h2 className="mb-4 text-3xl font-bold tracking-tight sm:text-4xl">Try Roo Code Cloud now</h2>
|
||||
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted-foreground">Code from anywhere.</p>
|
||||
<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<Button
|
||||
size="lg"
|
||||
className="bg-black text-white hover:bg-gray-800 hover:shadow-lg hover:shadow-black/20 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:hover:shadow-white/20 transition-all duration-300"
|
||||
asChild>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center">
|
||||
Create a free Cloud account
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ export default function Privacy() {
|
|||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
|
||||
Roo Code Cloud Privacy Policy
|
||||
</h1>
|
||||
<p className="text-muted-foreground">Last Updated: August 20, 2025</p>
|
||||
<p className="text-muted-foreground">Last Updated: September 19, 2025</p>
|
||||
|
||||
<p className="lead">
|
||||
This Privacy Policy explains how Roo Code, Inc. ("Roo Code," "we,"
|
||||
|
|
@ -86,8 +86,8 @@ export default function Privacy() {
|
|||
Your source code does not transit Roo Code servers unless you explicitly choose Roo Code
|
||||
as a model provider (proxy mode).
|
||||
</strong>{" "}
|
||||
When Roo Code Cloud is your model provider, your code briefly transits Roo Code servers only to
|
||||
forward it to the upstream model, is not stored, and is deleted immediately after
|
||||
When Roo Code Cloud is your model provider, your code briefly transits Roo Code servers only
|
||||
to forward it to the upstream model, is not stored, and is deleted immediately after
|
||||
forwarding. Otherwise, your code is sent <strong>directly</strong>—via client‑to‑provider
|
||||
TLS—to the model you select. Roo Code never stores, inspects, or trains on your code.
|
||||
</li>
|
||||
|
|
@ -184,6 +184,13 @@ export default function Privacy() {
|
|||
<li>
|
||||
<strong>Send product updates and roadmap communications</strong> (opt‑out available)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Send onboarding, educational, and promotional communications</strong>. We may use
|
||||
your account information (such as your name and email address) to send you onboarding
|
||||
messages, product tutorials, feature announcements, newsletters, and other marketing
|
||||
communications. You can opt out of non‑transactional emails at any time (see “Your Choices”
|
||||
below).
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-12 text-2xl font-bold">3. Where Your Data Goes (And Doesn't)</h2>
|
||||
|
|
@ -277,6 +284,12 @@ export default function Privacy() {
|
|||
<strong>Delete your Cloud account</strong> at any time from{" "}
|
||||
<strong>Security Settings</strong> inside Roo Code Cloud.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Marketing communications:</strong> You can unsubscribe from marketing and
|
||||
promotional emails by clicking the unsubscribe link in those emails. Transactional or
|
||||
service‑related emails (such as password resets, billing notices, or security alerts) will
|
||||
continue even if you opt out.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 className="mt-12 text-2xl font-bold">6. Security Practices</h2>
|
||||
|
|
|
|||
|
|
@ -64,43 +64,18 @@ export function Footer() {
|
|||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
targetId="product"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Features
|
||||
</ScrollButton>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Enterprise
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.EVALS}
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Evals
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Security
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.INTEGRATIONS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Integrations
|
||||
Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -112,11 +87,45 @@ export function Footer() {
|
|||
Changelog
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.TESTIMONIALS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Testimonials
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Enterprise
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Security Center
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-10 md:mt-0">
|
||||
<h3 className="text-sm font-semibold uppercase leading-6 text-foreground">Resources</h3>
|
||||
<ul className="mt-6 space-y-4">
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.EVALS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Evals
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.FAQ}
|
||||
|
|
@ -126,15 +135,6 @@ export function Footer() {
|
|||
FAQ
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.TUTORIALS}
|
||||
|
|
@ -194,24 +194,6 @@ export function Footer() {
|
|||
Careers
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.BLOG}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Blog
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.TESTIMONIALS}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm leading-6 text-muted-foreground transition-colors hover:text-foreground">
|
||||
Testimonials
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="/terms"
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import { useState } from "react"
|
|||
import { RxGithubLogo } from "react-icons/rx"
|
||||
import { VscVscode } from "react-icons/vsc"
|
||||
import { HiMenu } from "react-icons/hi"
|
||||
import { IoClose } from "react-icons/io5"
|
||||
|
||||
import { EXTERNAL_LINKS } from "@/lib/constants"
|
||||
import { useLogoSrc } from "@/lib/hooks/use-logo-src"
|
||||
import { ScrollButton } from "@/components/ui"
|
||||
import ThemeToggle from "@/components/chromes/theme-toggle"
|
||||
import { ChevronDown, Cloud, X } from "lucide-react"
|
||||
|
||||
interface NavBarProps {
|
||||
stars: string | null
|
||||
|
|
@ -26,56 +26,66 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-md">
|
||||
<div className="container mx-auto flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="container flex h-16 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="flex items-center">
|
||||
<Image src={logoSrc} alt="Roo Code Logo" width={120} height={40} className="h-8 w-auto" />
|
||||
<Image src={logoSrc} alt="Roo Code Logo" width={130} height={24} className="h-[24px] w-auto" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden text-sm font-medium md:flex md:items-center md:space-x-3 xl:space-x-8">
|
||||
{/* note: features and testimonials links are hidden for screen sizes smaller than lg */}
|
||||
<nav className="grow ml-6 hidden text-sm font-medium md:flex md:items-center">
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Features
|
||||
targetId="product"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Product
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="testimonials"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground max-lg:hidden">
|
||||
Testimonials
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Evals
|
||||
</Link>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Enterprise
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Docs
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Community
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Cloud
|
||||
</a>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="text-muted-foreground px-4 py-6 transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Pricing
|
||||
</Link>
|
||||
{/* Resources Dropdown */}
|
||||
<div className="relative group">
|
||||
<button className="flex items-center px-4 py-6 gap-1 text-muted-foreground transition-transform duration-200 hover:scale-105 hover:text-foreground">
|
||||
Resources
|
||||
<ChevronDown className="size-3" />
|
||||
</button>
|
||||
{/* Dropdown Menu */}
|
||||
<div className="absolute left-0 top-12 mt-2 w-40 rounded-md border border-border bg-background py-1 shadow-lg opacity-0 -translate-y-2 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-200">
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
Evals
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground">
|
||||
Discord
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Trust Center
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="hidden md:flex md:items-center md:space-x-4">
|
||||
|
|
@ -92,108 +102,128 @@ export function NavBar({ stars, downloads }: NavBarProps) {
|
|||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:bg-primary/80 hover:shadow-lg hover:scale-105 md:flex">
|
||||
className="hidden items-center gap-1.5 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
|
||||
<VscVscode className="-mr-[2px] mt-[1px] h-4 w-4" />
|
||||
<span>
|
||||
Install <span className="font-black max-lg:text-xs">·</span>
|
||||
</span>
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_LOGIN}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden items-center gap-1.5 rounded-md py-2 text-sm border border-primary-background px-4 font-medium text-primary-background transition-all duration-200 hover:shadow-lg hover:scale-105 md:flex">
|
||||
<Cloud className="inline h-4 w-4" />
|
||||
Log in
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
aria-expanded={isMenuOpen}
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="flex items-center justify-center rounded-full p-2 transition-colors hover:bg-accent md:hidden"
|
||||
className="relative z-10 flex items-center justify-center rounded-full p-2 transition-colors hover:bg-accent md:hidden"
|
||||
aria-label="Toggle mobile menu">
|
||||
{isMenuOpen ? <IoClose className="h-6 w-6" /> : <HiMenu className="h-6 w-6" />}
|
||||
<HiMenu className={`h-6 w-6 ${isMenuOpen ? "hidden" : "block"}`} />
|
||||
<X className={`h-6 w-6 ${isMenuOpen ? "block" : "hidden"}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Panel */}
|
||||
{/* Mobile Menu Panel - Full Screen */}
|
||||
<div
|
||||
className={`absolute left-0 right-0 top-16 z-50 transform border-b border-border bg-background shadow-lg backdrop-blur-none transition-all duration-200 md:hidden ${isMenuOpen ? "translate-y-0 opacity-100" : "pointer-events-none -translate-y-2 opacity-0"}`}>
|
||||
<nav className="flex flex-col py-2">
|
||||
<ScrollButton
|
||||
targetId="features"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Features
|
||||
</ScrollButton>
|
||||
<ScrollButton
|
||||
targetId="testimonials"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Testimonials
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Evals
|
||||
</Link>
|
||||
<Link
|
||||
href="/enterprise"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Enterprise
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Security
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Docs
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Community
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-full px-8 py-3 text-left text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Cloud
|
||||
</a>
|
||||
|
||||
<hr className="mx-8 my-2 border-t border-border/50" />
|
||||
|
||||
{/* Icons & Stats */}
|
||||
<div className="flex items-center justify-center gap-8 px-8 py-3">
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
className={`fixed top-16 left-0 bg-background right-0 z-[100] transition-all duration-200 pointer-events-none md:hidden ${isMenuOpen ? "block h-dvh" : "hidden"}`}>
|
||||
<nav className="flex flex-col justify-between h-full pb-16 overflow-y-auto bg-background pointer-events-auto">
|
||||
{/* Main navigation items */}
|
||||
<div className="grow-1 py-4 font-semibold text-lg">
|
||||
<ScrollButton
|
||||
targetId="product"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<RxGithubLogo className="h-5 w-5" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
Product
|
||||
</ScrollButton>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DOCUMENTATION}
|
||||
target="_blank"
|
||||
className="block w-full p-5 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Docs
|
||||
</a>
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="block w-full p-5 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Pricing
|
||||
</Link>
|
||||
<div className="flex items-center rounded-md p-2 transition-colors hover:bg-accent">
|
||||
<ThemeToggle />
|
||||
|
||||
{/* Resources Section */}
|
||||
<div className="mt-4 w-full">
|
||||
<div className="px-5 pb-2 pt-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
Resources
|
||||
</div>
|
||||
<ScrollButton
|
||||
targetId="faq"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
FAQ
|
||||
</ScrollButton>
|
||||
<Link
|
||||
href="/evals"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Evals
|
||||
</Link>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Discord
|
||||
</a>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.SECURITY}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block w-full p-5 py-3 text-left text-foreground active:opacity-50"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
Security Center
|
||||
</a>
|
||||
</div>
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
</div>
|
||||
|
||||
{/* Bottom section with Cloud Login and stats */}
|
||||
<div className="border-t border-border">
|
||||
<div className="flex items-center justify-around px-6 pt-2">
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.GITHUB}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<RxGithubLogo className="h-6 w-6" />
|
||||
{stars !== null && <span>{stars}</span>}
|
||||
</Link>
|
||||
<div className="flex items-center rounded-md p-3 transition-colors hover:bg-accent">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<Link
|
||||
href={EXTERNAL_LINKS.MARKETPLACE}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<VscVscode className="h-6 w-6" />
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
<a
|
||||
href={EXTERNAL_LINKS.CLOUD_APP_LOGIN}
|
||||
target="_blank"
|
||||
className="inline-flex items-center gap-2 rounded-md p-2 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center gap-2 rounded-lg border border-primary bg-background p-4 mx-4 mb-4 text-base font-semibold text-primary"
|
||||
onClick={() => setIsMenuOpen(false)}>
|
||||
<VscVscode className="h-5 w-5" />
|
||||
{downloads !== null && <span>{downloads}</span>}
|
||||
</Link>
|
||||
<Cloud className="h-5 w-5" />
|
||||
Log in
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import Autoplay from "embla-carousel-autoplay"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { features } from "@/components/homepage/features"
|
||||
|
||||
export function FeaturesMobile() {
|
||||
// configure autoplay with Embla
|
||||
const autoplayPlugin = Autoplay({
|
||||
delay: 5000,
|
||||
stopOnInteraction: true,
|
||||
stopOnMouseEnter: true,
|
||||
rootNode: (emblaRoot) => emblaRoot,
|
||||
})
|
||||
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel(
|
||||
{
|
||||
loop: true,
|
||||
containScroll: "trimSnaps",
|
||||
},
|
||||
[autoplayPlugin],
|
||||
)
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [scrollSnaps, setScrollSnaps] = useState<number[]>([])
|
||||
|
||||
const scrollTo = useCallback((index: number) => emblaApi && emblaApi.scrollTo(index), [emblaApi])
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const onInit = useCallback((emblaApi: any) => {
|
||||
setScrollSnaps(emblaApi.scrollSnapList())
|
||||
}, [])
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const onSelect = useCallback((emblaApi: any) => {
|
||||
setSelectedIndex(emblaApi.selectedScrollSnap())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return
|
||||
|
||||
onInit(emblaApi)
|
||||
onSelect(emblaApi)
|
||||
emblaApi.on("reInit", onInit)
|
||||
emblaApi.on("select", onSelect)
|
||||
|
||||
return () => {
|
||||
emblaApi.off("reInit", onInit)
|
||||
emblaApi.off("select", onSelect)
|
||||
}
|
||||
}, [emblaApi, onInit, onSelect])
|
||||
|
||||
return (
|
||||
<div className="md:hidden">
|
||||
<div className="relative px-4">
|
||||
<div className="overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{features.map((feature, index) => (
|
||||
<div className="flex min-w-0 flex-[0_0_100%] px-4" key={index}>
|
||||
<div className="relative h-full min-h-[280px] rounded-2xl border border-border/50 bg-background/30 p-6 backdrop-blur-xl transition-all duration-300 hover:border-border hover:bg-background/50 dark:hover:border-border/80 dark:hover:bg-background/40">
|
||||
<div className="mb-2 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/10 to-cyan-500/10 p-2.5 dark:from-blue-500/20 dark:to-cyan-500/20">
|
||||
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
|
||||
<div className="text-white">{feature.icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
|
||||
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Controls */}
|
||||
<div className="mt-6 flex items-center justify-between px-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
|
||||
onClick={() => emblaApi?.scrollPrev()}>
|
||||
<ChevronLeft className="h-4 w-4 text-foreground/80" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full border-border/50 bg-background/80 hover:bg-background"
|
||||
onClick={() => emblaApi?.scrollNext()}>
|
||||
<ChevronRight className="h-4 w-4 text-foreground/80" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{scrollSnaps.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
type="button"
|
||||
className={`h-3 w-3 rounded-full border border-border p-0 ${index === selectedIndex ? "bg-foreground" : "bg-background"}`}
|
||||
onClick={() => scrollTo(index)}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,71 +1,48 @@
|
|||
"use client"
|
||||
|
||||
import { motion } from "framer-motion"
|
||||
import { Bot, Code, Brain, Wrench, Terminal, Puzzle, Globe, Shield, Zap } from "lucide-react"
|
||||
import { FeaturesMobile } from "./features-mobile"
|
||||
|
||||
import { ReactNode } from "react"
|
||||
import { Brain, Shield, Users2, ReplaceAll, Keyboard, LucideIcon, CheckCheck } from "lucide-react"
|
||||
|
||||
export interface Feature {
|
||||
icon: ReactNode
|
||||
icon: LucideIcon
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export const features: Feature[] = [
|
||||
{
|
||||
icon: <Bot className="h-6 w-6 text-white" />,
|
||||
title: "Your AI Dev Team in VS Code",
|
||||
icon: Users2,
|
||||
title: "Specialized Modes",
|
||||
description:
|
||||
"Roo Code puts a team of agentic AI assistants directly in your editor, with the power to plan, write, and fix code across multiple files.",
|
||||
"Planning, Architecture, Debugging and beyond: Roo's modes stay on-task and deliver. Create your own modes or download from the marketplace.",
|
||||
},
|
||||
{
|
||||
icon: <Code className="h-6 w-6 text-white" />,
|
||||
title: "Multiple Specialized Modes",
|
||||
description:
|
||||
"From coding to debugging to architecture, Roo Code has a mode for every dev scenario—just switch on the fly.",
|
||||
icon: ReplaceAll,
|
||||
title: "Model-Agnostic",
|
||||
description: "Bring your own model key or use local inference — no markup, lock-in, no restrictions.",
|
||||
},
|
||||
{
|
||||
icon: <Brain className="h-6 w-6 text-white" />,
|
||||
icon: CheckCheck,
|
||||
title: "Granular auto-approval",
|
||||
description: "Control each action and make Roo as autonomous as you want as you build confidence. Or go YOLO.",
|
||||
},
|
||||
{
|
||||
icon: Keyboard,
|
||||
title: "Highly Customizable",
|
||||
description:
|
||||
"Fine-tune settings for Roo to work for you, like inference context, model properties, slash commands and more.",
|
||||
},
|
||||
{
|
||||
icon: Brain,
|
||||
title: "Deep Project-wide Context",
|
||||
description:
|
||||
"Roo Code reads your entire codebase, preserving valid code through diff-based edits for seamless multi-file refactors.",
|
||||
},
|
||||
{
|
||||
icon: <Wrench className="h-6 w-6 text-white" />,
|
||||
title: "Open-Source and Model-Agnostic",
|
||||
icon: Shield,
|
||||
title: "Secure and Private by Design",
|
||||
description:
|
||||
"Bring your own model or use local AI—no vendor lock-in. Roo Code is free, open, and adaptable to your needs.",
|
||||
},
|
||||
{
|
||||
icon: <Terminal className="h-6 w-6 text-white" />,
|
||||
title: "Guarded Command Execution",
|
||||
description:
|
||||
"Approve or deny commands as needed. Roo Code automates your dev workflow while keeping oversight firmly in your hands.",
|
||||
},
|
||||
{
|
||||
icon: <Puzzle className="h-6 w-6 text-white" />,
|
||||
title: "Fully Customizable",
|
||||
description:
|
||||
"Create or tweak modes, define usage rules, and shape Roo Code's behavior precisely—your code, your way.",
|
||||
},
|
||||
{
|
||||
icon: <Globe className="h-6 w-6 text-white" />,
|
||||
title: "Automated Browser Actions",
|
||||
description:
|
||||
"Seamlessly test and verify your web app directly from VS Code—Roo Code can open a browser, run checks, and more.",
|
||||
},
|
||||
{
|
||||
icon: <Shield className="h-6 w-6 text-white" />,
|
||||
title: "Secure by Design",
|
||||
description:
|
||||
"Security-first from the ground up, Roo Code meets rigorous standards without slowing you down. Monitoring and strict policies keep your code safe at scale.",
|
||||
},
|
||||
{
|
||||
icon: <Zap className="h-6 w-6 text-white" />,
|
||||
title: "Seamless Setup and Workflows",
|
||||
description:
|
||||
"Get started in minutes—no heavy configs. Roo Code fits alongside your existing tools and dev flow, while supercharging your productivity.",
|
||||
"Open source and local-first. No code leaves your machine unless you say so. SOC 2 Type II compliant.",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -81,21 +58,6 @@ export function Features() {
|
|||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const backgroundVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
|
|
@ -118,11 +80,11 @@ export function Features() {
|
|||
viewport={{ once: true }}
|
||||
variants={backgroundVariants}>
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 dark:bg-blue-700/30 blur-[120px]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-24 max-w-3xl text-center">
|
||||
<div className="mx-auto mb-12 md:mb-24 max-w-4xl text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
|
|
@ -132,40 +94,36 @@ export function Features() {
|
|||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
|
||||
Powerful features for modern developers.
|
||||
Power and flexibility to get stuff done.
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Everything you need to build faster and write better code.
|
||||
The features you need to build, debug and ship faster – without compromising quality.
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Carousel */}
|
||||
<FeaturesMobile />
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<motion.div
|
||||
className="relative mx-auto hidden max-w-[1200px] md:block"
|
||||
className="relative mx-auto md:max-w-[1200px]"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div key={index} variants={itemVariants} className="group relative">
|
||||
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 group-hover:opacity-100 dark:from-blue-500/50 dark:via-cyan-500/50 dark:to-purple-500/50" />
|
||||
<div className="relative h-full rounded-2xl border border-border/50 bg-background/30 p-8 backdrop-blur-xl transition-all duration-300 hover:border-border hover:bg-background/50 dark:hover:border-border/80 dark:hover:bg-background/40">
|
||||
<div className="mb-5 inline-flex items-center justify-center rounded-xl bg-gradient-to-r from-blue-500/10 to-cyan-500/10 p-2.5 dark:from-blue-500/20 dark:to-cyan-500/20">
|
||||
<div className="rounded-lg bg-gradient-to-r from-blue-500/80 to-cyan-500/80 p-2.5">
|
||||
{feature.icon}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-medium text-foreground/90">{feature.title}</h3>
|
||||
<p className="leading-relaxed text-muted-foreground">{feature.description}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
<ul className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-8">
|
||||
{features.map((feature, index) => {
|
||||
const Icon = feature.icon
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300">
|
||||
<Icon className="size-6 text-foreground/80" />
|
||||
<h3 className="mb-3 mt-3 text-xl font-semibold text-foreground">{feature.title}</h3>
|
||||
<p className="leading-relaxed font-light text-muted-foreground">
|
||||
{feature.description}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ export * from "./animated-background"
|
|||
export * from "./code-example"
|
||||
export * from "./company-logos"
|
||||
export * from "./faq-section"
|
||||
export * from "./features-mobile"
|
||||
export * from "./features"
|
||||
export * from "./install-section"
|
||||
export * from "./testimonials-mobile"
|
||||
export * from "./testimonials"
|
||||
export * from "./whats-new-button"
|
||||
|
|
|
|||
|
|
@ -46,12 +46,13 @@ export function InstallSection({ downloads }: InstallSectionProps) {
|
|||
|
||||
<div className="relative text-center">
|
||||
{/* Updated h2 to match other sections */}
|
||||
<h2 className="bg-gradient-to-b from-foreground to-foreground/70 bg-clip-text text-4xl font-bold tracking-tight text-transparent sm:text-5xl">
|
||||
Install Roo Code — Open & Flexible
|
||||
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-5xl">
|
||||
Install Roo Code now
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Roo Code is open-source, model-agnostic, and developer-focused. Install from the VS Code
|
||||
Marketplace or the CLI in minutes, then bring your own AI model.
|
||||
Install from the VSCode Marketplace or the CLI in minutes, then bring your own AI model.
|
||||
<br />
|
||||
Roo Code is also compatible with all VSCode forks.
|
||||
</p>
|
||||
|
||||
<div className="mt-12 flex flex-col items-center justify-center gap-6">
|
||||
|
|
@ -64,7 +65,7 @@ export function InstallSection({ downloads }: InstallSectionProps) {
|
|||
<div className="relative flex items-center gap-3">
|
||||
<VscVscode className="h-6 w-6 sm:h-7 sm:w-7" />
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
<span>VSCode Marketplace</span>
|
||||
<span>From VSCode Marketplace</span>
|
||||
{downloads !== null && (
|
||||
<>
|
||||
<span className="font-black opacity-60">·</span>
|
||||
|
|
@ -80,7 +81,7 @@ export function InstallSection({ downloads }: InstallSectionProps) {
|
|||
<div className="absolute -inset-px rounded-xl bg-gradient-to-r from-blue-500/50 via-cyan-500/50 to-purple-500/50 opacity-30 blur-sm transition-all duration-500 group-hover:opacity-60 dark:opacity-40 dark:group-hover:opacity-70" />
|
||||
<div className="relative overflow-hidden rounded-xl border border-border bg-background/80 shadow-lg backdrop-blur-xl transition-all duration-500 ease-out group-hover:border-blue-500/50 group-hover:shadow-xl group-hover:shadow-blue-500/10 dark:border-border/50 dark:bg-background/60 dark:group-hover:border-blue-400/50">
|
||||
<div className="border-b border-border/50 bg-muted/30 px-4 py-3 dark:bg-muted/20">
|
||||
<div className="text-sm font-medium text-foreground">Install via CLI</div>
|
||||
<div className="text-sm font-medium text-foreground">or via CLI</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto bg-background/50 dark:bg-background/30">
|
||||
<pre className="p-4">
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import AutoScroll from "embla-carousel-auto-scroll"
|
||||
import { testimonials } from "@/components/homepage/testimonials"
|
||||
|
||||
export function TestimonialsMobile() {
|
||||
const [emblaRef] = useEmblaCarousel({ loop: true }, [
|
||||
AutoScroll({
|
||||
playOnInit: true,
|
||||
speed: 1, // pixels per second - slower for smoother scrolling
|
||||
stopOnInteraction: true,
|
||||
stopOnMouseEnter: true,
|
||||
}),
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="md:hidden">
|
||||
<div className="overflow-hidden px-4" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{testimonials.map((testimonial) => (
|
||||
<div className="min-w-0 flex-[0_0_100%] px-4" key={testimonial.id}>
|
||||
<div className="relative rounded-2xl border border-border/50 bg-background/30 p-8 backdrop-blur-xl dark:border-border/70 dark:bg-background/40">
|
||||
<svg
|
||||
className="absolute left-8 top-8 h-8 w-8 text-blue-500/30 dark:text-blue-400/50"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<filter id="glow-mobile">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<path
|
||||
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
|
||||
className="dark:filter dark:drop-shadow-[0_0_8px_rgba(96,165,250,0.4)]"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<blockquote className="mt-12">
|
||||
<p className="text-lg font-light italic leading-relaxed text-muted-foreground dark:text-foreground/70">
|
||||
"{testimonial.quote}"
|
||||
</p>
|
||||
|
||||
<footer className="mt-6">
|
||||
<div className="h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent dark:from-blue-400/70" />
|
||||
<p className="mt-4 font-medium text-foreground/90 dark:text-foreground">
|
||||
{testimonial.name}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground dark:text-muted-foreground/80">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
"use client"
|
||||
|
||||
import { useRef } from "react"
|
||||
import { useRef, useCallback, useEffect } from "react"
|
||||
import { motion } from "framer-motion"
|
||||
import Image from "next/image"
|
||||
import { TestimonialsMobile } from "./testimonials-mobile"
|
||||
import useEmblaCarousel from "embla-carousel-react"
|
||||
import AutoPlay from "embla-carousel-autoplay"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
|
||||
export interface Testimonial {
|
||||
id: number
|
||||
|
|
@ -47,26 +48,66 @@ export const testimonials: Testimonial[] = [
|
|||
|
||||
export function Testimonials() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [emblaRef, emblaApi] = useEmblaCarousel(
|
||||
{
|
||||
loop: true,
|
||||
align: "center",
|
||||
skipSnaps: false,
|
||||
containScroll: false,
|
||||
},
|
||||
[
|
||||
AutoPlay({
|
||||
playOnInit: true,
|
||||
delay: 4000,
|
||||
stopOnInteraction: true,
|
||||
stopOnMouseEnter: true,
|
||||
stopOnFocusIn: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
|
||||
const scrollPrev = useCallback(() => {
|
||||
if (emblaApi) emblaApi.scrollPrev()
|
||||
}, [emblaApi])
|
||||
|
||||
const scrollNext = useCallback(() => {
|
||||
if (emblaApi) emblaApi.scrollNext()
|
||||
}, [emblaApi])
|
||||
|
||||
// Re-init auto-play on user interaction
|
||||
useEffect(() => {
|
||||
if (!emblaApi) return
|
||||
|
||||
const autoPlay = emblaApi?.plugins()?.autoPlay as
|
||||
| {
|
||||
isPlaying?: () => boolean
|
||||
play?: () => void
|
||||
}
|
||||
| undefined
|
||||
if (!autoPlay) return
|
||||
|
||||
const handleInteraction = () => {
|
||||
const isPlaying = autoPlay.isPlaying && autoPlay.isPlaying()
|
||||
if (!isPlaying) {
|
||||
setTimeout(() => {
|
||||
if (autoPlay.play) {
|
||||
autoPlay.play()
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
|
||||
emblaApi.on("pointerUp", handleInteraction)
|
||||
|
||||
return () => {
|
||||
emblaApi.off("pointerUp", handleInteraction)
|
||||
}
|
||||
}, [emblaApi])
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
|
|
@ -74,123 +115,78 @@ export function Testimonials() {
|
|||
},
|
||||
}
|
||||
|
||||
const backgroundVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: "easeOut",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<section ref={containerRef} className="relative overflow-hidden border-t border-border py-32">
|
||||
<motion.div
|
||||
className="absolute inset-0"
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
variants={backgroundVariants}>
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
|
||||
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
|
||||
</div>
|
||||
</motion.div>
|
||||
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
|
||||
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-violet-500/10 dark:bg-violet-700/30 blur-[120px]" />
|
||||
</div>
|
||||
|
||||
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto mb-24 max-w-3xl text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.6,
|
||||
ease: [0.21, 0.45, 0.27, 0.9],
|
||||
}}>
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
|
||||
Empowering developers worldwide.
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Join thousands of developers who are revolutionizing their workflow with AI-powered
|
||||
assistance.
|
||||
</p>
|
||||
</motion.div>
|
||||
<div className="mx-auto mb-8 max-w-5xl text-center">
|
||||
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
|
||||
AI-forward developers are using Roo Code
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-muted-foreground">
|
||||
Join more than 800k people revolutionizing their workflow worldwide
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Mobile Carousel */}
|
||||
<TestimonialsMobile />
|
||||
|
||||
{/* Desktop Grid */}
|
||||
<motion.div
|
||||
className="relative mx-auto hidden max-w-[1200px] md:block"
|
||||
className="relative mx-auto max-w-[1400px]"
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}>
|
||||
<div className="relative grid grid-cols-1 gap-12 md:grid-cols-2">
|
||||
{testimonials.map((testimonial, index) => (
|
||||
<motion.div
|
||||
key={testimonial.id}
|
||||
variants={itemVariants}
|
||||
className={`group relative ${index % 2 === 0 ? "md:translate-y-4" : "md:translate-y-12"}`}>
|
||||
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 ease-out group-hover:opacity-100 dark:from-blue-400/40 dark:via-cyan-400/40 dark:to-purple-400/40" />
|
||||
<div className="relative flex h-full flex-col rounded-2xl border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-2xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
|
||||
{testimonial.image && (
|
||||
<div className="absolute -right-3 -top-3 h-16 w-16 overflow-hidden rounded-xl border border-border/50 bg-background/50 p-1.5 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-110 dark:border-border/70 dark:bg-background/60">
|
||||
<div className="relative h-full w-full overflow-hidden rounded-lg">
|
||||
<Image
|
||||
src={testimonial.image || "/placeholder_pfp.png"}
|
||||
alt={testimonial.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
{/* Previous Button */}
|
||||
<button
|
||||
onClick={scrollPrev}
|
||||
className="absolute left-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:left-4 md:p-3 lg:left-8"
|
||||
aria-label="Previous testimonial">
|
||||
<ChevronLeft className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
|
||||
</button>
|
||||
|
||||
{/* Next Button */}
|
||||
<button
|
||||
onClick={scrollNext}
|
||||
className="absolute right-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:right-4 md:p-3 lg:right-8"
|
||||
aria-label="Next testimonial">
|
||||
<ChevronRight className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
|
||||
</button>
|
||||
|
||||
{/* Gradient Overlays */}
|
||||
<div className="absolute inset-y-0 left-0 z-10 w-[10%] bg-gradient-to-r from-background to-transparent pointer-events-none md:w-[15%]" />
|
||||
<div className="absolute inset-y-0 right-0 z-10 w-[10%] bg-gradient-to-l from-background to-transparent pointer-events-none md:w-[15%]" />
|
||||
|
||||
{/* Embla Carousel Container */}
|
||||
<div className="overflow-hidden" ref={emblaRef}>
|
||||
<div className="flex">
|
||||
{testimonials.map((testimonial) => (
|
||||
<div
|
||||
key={testimonial.id}
|
||||
className="relative min-w-0 flex-[0_0_85%] px-2 md:flex-[0_0_70%] md:px-4 lg:flex-[0_0_60%]">
|
||||
<div className="group relative py-10 h-full">
|
||||
<div className="relative flex h-full flex-col rounded-2xl border border-border bg-background transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
|
||||
<div className="flex flex-1 flex-col p-6 md:p-8">
|
||||
<div className="flex-1">
|
||||
<p className="relative text-sm leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90 md:text-lg">
|
||||
{testimonial.quote}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-4 md:mt-6">
|
||||
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 flex-col p-8">
|
||||
<div className="flex-1">
|
||||
<div className="mb-6">
|
||||
<svg
|
||||
className="h-8 w-8 text-blue-500/20 transition-all duration-500 group-hover:text-blue-500/30 dark:text-blue-400/40 dark:group-hover:text-blue-400/60"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<path
|
||||
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
|
||||
className="dark:filter dark:drop-shadow-[0_0_8px_rgba(96,165,250,0.4)]"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<p className="relative text-lg leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90">
|
||||
{testimonial.quote}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-6">
|
||||
<div className="mb-4 h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent transition-all duration-500 group-hover:w-16 group-hover:from-blue-500/70 dark:from-blue-400/70 dark:group-hover:from-blue-400/90" />
|
||||
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
|
||||
{testimonial.name}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
|
||||
{testimonial.role} at {testimonial.company}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ export const EXTERNAL_LINKS = {
|
|||
OFFICE_HOURS_PODCAST: "https://www.youtube.com/@RooCodeYT/podcasts",
|
||||
FAQ: "https://roocode.com/#faq",
|
||||
TESTIMONIALS: "https://roocode.com/#testimonials",
|
||||
CLOUD_APP: "https://app.roocode.com",
|
||||
CLOUD_APP_LOGIN: "https://app.roocode.com/sign-in",
|
||||
CLOUD_APP_SIGNUP: "https://app.roocode.com/sign-up",
|
||||
}
|
||||
|
||||
export const INTERNAL_LINKS = {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "https://roocode.com"
|
|||
export const SEO = {
|
||||
url: SITE_URL,
|
||||
name: "Roo Code",
|
||||
title: "Roo Code – Your AI-Powered Dev Team in VS Code",
|
||||
title: "Roo Code – Your AI-Powered Dev Team in VS Code and Beyond",
|
||||
description:
|
||||
"Roo Code puts an entire AI dev team right in your editor, outpacing closed tools with deep project-wide context, multi-step agentic coding, and unmatched developer-centric flexibility.",
|
||||
locale: "en_US",
|
||||
ogImage: {
|
||||
url: "/android-chrome-512x512.png",
|
||||
width: 512,
|
||||
height: 512,
|
||||
alt: "Roo Code Logo",
|
||||
url: "/opengraph.png",
|
||||
width: 1200,
|
||||
height: 600,
|
||||
alt: "Roo Code",
|
||||
},
|
||||
keywords: [
|
||||
"Roo Code",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type {
|
|||
AuthService,
|
||||
SettingsService,
|
||||
CloudUserInfo,
|
||||
CloudOrganizationMembership,
|
||||
OrganizationAllowList,
|
||||
OrganizationSettings,
|
||||
ShareVisibility,
|
||||
|
|
@ -170,9 +171,9 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
|
|||
|
||||
// AuthService
|
||||
|
||||
public async login(): Promise<void> {
|
||||
public async login(landingPageSlug?: string): Promise<void> {
|
||||
this.ensureInitialized()
|
||||
return this.authService!.login()
|
||||
return this.authService!.login(landingPageSlug)
|
||||
}
|
||||
|
||||
public async logout(): Promise<void> {
|
||||
|
|
@ -242,6 +243,21 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
|
|||
return this.authService!.handleCallback(code, state, organizationId)
|
||||
}
|
||||
|
||||
public async switchOrganization(organizationId: string | null): Promise<void> {
|
||||
this.ensureInitialized()
|
||||
|
||||
// Perform the organization switch
|
||||
// StaticTokenAuthService will throw an error if organization switching is not supported
|
||||
await this.authService!.switchOrganization(organizationId)
|
||||
}
|
||||
|
||||
public async getOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
|
||||
this.ensureInitialized()
|
||||
|
||||
// StaticTokenAuthService will throw an error if organization memberships are not supported
|
||||
return await this.authService!.getOrganizationMemberships()
|
||||
}
|
||||
|
||||
// SettingsService
|
||||
|
||||
public getAllowList(): OrganizationAllowList {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> impl
|
|||
throw new Error("Authentication methods are disabled in StaticTokenAuthService")
|
||||
}
|
||||
|
||||
public async switchOrganization(_organizationId: string | null): Promise<void> {
|
||||
throw new Error("Authentication methods are disabled in StaticTokenAuthService")
|
||||
}
|
||||
|
||||
public async getOrganizationMemberships(): Promise<import("@roo-code/types").CloudOrganizationMembership[]> {
|
||||
throw new Error("Authentication methods are disabled in StaticTokenAuthService")
|
||||
}
|
||||
|
||||
public getState(): AuthState {
|
||||
return this.state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,8 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
if (
|
||||
this.credentials === null ||
|
||||
this.credentials.clientToken !== credentials.clientToken ||
|
||||
this.credentials.sessionId !== credentials.sessionId
|
||||
this.credentials.sessionId !== credentials.sessionId ||
|
||||
this.credentials.organizationId !== credentials.organizationId
|
||||
) {
|
||||
this.transitionToAttemptingSession(credentials)
|
||||
}
|
||||
|
|
@ -174,6 +175,7 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
|
||||
this.changeState("attempting-session")
|
||||
|
||||
this.timer.stop()
|
||||
this.timer.start()
|
||||
}
|
||||
|
||||
|
|
@ -248,8 +250,10 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
*
|
||||
* This method initiates the authentication flow by generating a state parameter
|
||||
* and opening the browser to the authorization URL.
|
||||
*
|
||||
* @param landingPageSlug Optional slug of a specific landing page (e.g., "supernova", "special-offer", etc.)
|
||||
*/
|
||||
public async login(): Promise<void> {
|
||||
public async login(landingPageSlug?: string): Promise<void> {
|
||||
try {
|
||||
const vscode = await importVscode()
|
||||
|
||||
|
|
@ -267,11 +271,17 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
state,
|
||||
auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`,
|
||||
})
|
||||
const url = `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}`
|
||||
|
||||
// Use landing page URL if slug is provided, otherwise use default sign-in URL
|
||||
const url = landingPageSlug
|
||||
? `${getRooCodeApiUrl()}/l/${landingPageSlug}?${params.toString()}`
|
||||
: `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}`
|
||||
|
||||
await vscode.env.openExternal(vscode.Uri.parse(url))
|
||||
} catch (error) {
|
||||
this.log(`[auth] Error initiating Roo Code Cloud auth: ${error}`)
|
||||
throw new Error(`Failed to initiate Roo Code Cloud authentication: ${error}`)
|
||||
const context = landingPageSlug ? ` (landing page: ${landingPageSlug})` : ""
|
||||
this.log(`[auth] Error initiating Roo Code Cloud auth${context}: ${error}`)
|
||||
throw new Error(`Failed to initiate Roo Code Cloud authentication${context}: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -461,6 +471,42 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
return this.credentials?.organizationId || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different organization context
|
||||
* @param organizationId The organization ID to switch to, or null for personal account
|
||||
*/
|
||||
public async switchOrganization(organizationId: string | null): Promise<void> {
|
||||
if (!this.credentials) {
|
||||
throw new Error("Cannot switch organization: not authenticated")
|
||||
}
|
||||
|
||||
// Update the stored credentials with the new organization ID
|
||||
const updatedCredentials: AuthCredentials = {
|
||||
...this.credentials,
|
||||
organizationId: organizationId,
|
||||
}
|
||||
|
||||
// Store the updated credentials, handleCredentialsChange will handle the update
|
||||
await this.storeCredentials(updatedCredentials)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all organization memberships for the current user
|
||||
* @returns Array of organization memberships
|
||||
*/
|
||||
public async getOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
|
||||
if (!this.credentials) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.clerkGetOrganizationMemberships()
|
||||
} catch (error) {
|
||||
this.log(`[auth] Failed to get organization memberships: ${error}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private async clerkSignIn(ticket: string): Promise<AuthCredentials> {
|
||||
const formData = new URLSearchParams()
|
||||
formData.append("strategy", "ticket")
|
||||
|
|
@ -645,9 +691,14 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
}
|
||||
|
||||
private async clerkGetOrganizationMemberships(): Promise<CloudOrganizationMembership[]> {
|
||||
if (!this.credentials) {
|
||||
this.log("[auth] Cannot get organization memberships: missing credentials")
|
||||
return []
|
||||
}
|
||||
|
||||
const response = await fetch(`${getClerkBaseUrl()}/v1/me/organization_memberships`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.credentials!.clientToken}`,
|
||||
Authorization: `Bearer ${this.credentials.clientToken}`,
|
||||
"User-Agent": this.userAgent(),
|
||||
},
|
||||
signal: AbortSignal.timeout(10000),
|
||||
|
|
|
|||
|
|
@ -60,5 +60,5 @@ RUN chmod +x /usr/local/bin/entrypoint.sh
|
|||
|
||||
ENV DATABASE_URL=postgresql://postgres:password@db:5432/evals_development
|
||||
ENV REDIS_URL=redis://redis:6379
|
||||
EXPOSE 3000
|
||||
EXPOSE 3446
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Start the evals service:
|
|||
pnpm evals
|
||||
```
|
||||
|
||||
The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on localhost:3000:
|
||||
The initial build process can take a minute or two. Upon success you should see output indicating that a web service is running on localhost:3446:
|
||||
<img width="1182" src="https://github.com/user-attachments/assets/34f25a59-1362-458c-aafa-25e13cdb2a7a" />
|
||||
|
||||
Additionally, you'll find in Docker Desktop that database and redis services are running:
|
||||
|
|
@ -95,7 +95,7 @@ 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)
|
||||
- **Web Service**: 3446 (external) → 3446 (internal)
|
||||
|
||||
These ports are configured to avoid conflicts with other services that might be running on the standard PostgreSQL (5432) and Redis (6379) ports.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ services:
|
|||
context: ../../
|
||||
dockerfile: packages/evals/Dockerfile.web
|
||||
ports:
|
||||
- "${EVALS_WEB_PORT:-3446}:3000"
|
||||
- "${EVALS_WEB_PORT:-3446}:3446"
|
||||
environment:
|
||||
- HOST_EXECUTION_METHOD=docker
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ build_extension() {
|
|||
echo "🔨 Building the Roo Code extension..."
|
||||
pnpm -w vsix -- --out ../bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1
|
||||
code --install-extension ../../bin/roo-code-$(git rev-parse --short HEAD).vsix || exit 1
|
||||
cd evals
|
||||
}
|
||||
|
||||
check_docker_services() {
|
||||
|
|
@ -377,7 +376,7 @@ fi
|
|||
|
||||
echo -e "\n🚀 You're ready to rock and roll! \n"
|
||||
|
||||
if ! nc -z localhost 3000; then
|
||||
if ! nc -z localhost 3446; then
|
||||
read -p "🌐 Would you like to start the evals web app? (Y/n): " start_evals
|
||||
|
||||
if [[ "$start_evals" =~ ^[Yy]|^$ ]]; then
|
||||
|
|
@ -386,5 +385,5 @@ if ! nc -z localhost 3000; then
|
|||
echo "💡 You can start it anytime with 'pnpm --filter @roo-code/web-evals dev'."
|
||||
fi
|
||||
else
|
||||
echo "👟 The evals web app is running at http://localhost:3000 (or http://localhost:3446 if using Docker)"
|
||||
echo "👟 The evals web app is running at http://localhost:3446"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -239,9 +239,10 @@ export interface AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
broadcast(): void
|
||||
|
||||
// Authentication methods
|
||||
login(): Promise<void>
|
||||
login(landingPageSlug?: string): Promise<void>
|
||||
logout(): Promise<void>
|
||||
handleCallback(code: string | null, state: string | null, organizationId?: string | null): Promise<void>
|
||||
switchOrganization(organizationId: string | null): Promise<void>
|
||||
|
||||
// State methods
|
||||
getState(): AuthState
|
||||
|
|
@ -253,6 +254,9 @@ export interface AuthService extends EventEmitter<AuthServiceEvents> {
|
|||
getSessionToken(): string | undefined
|
||||
getUserInfo(): CloudUserInfo | null
|
||||
getStoredOrganizationId(): string | null
|
||||
|
||||
// Organization management
|
||||
getOrganizationMemberships(): Promise<CloudOrganizationMembership[]>
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ export const globalSettingsSchema = z.object({
|
|||
enhancementApiConfigId: z.string().optional(),
|
||||
includeTaskHistoryInEnhance: z.boolean().optional(),
|
||||
historyPreviewCollapsed: z.boolean().optional(),
|
||||
reasoningBlockCollapsed: z.boolean().optional(),
|
||||
profileThresholds: z.record(z.string(), z.number()).optional(),
|
||||
hasOpenedModeSelector: z.boolean().optional(),
|
||||
lastModeExportPath: z.string().optional(),
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export function isResumableAsk(ask: ClineAsk): ask is ResumableAsk {
|
|||
*/
|
||||
|
||||
export const interactiveAsks = [
|
||||
"followup",
|
||||
"command",
|
||||
"tool",
|
||||
"browser_action_launch",
|
||||
|
|
|
|||
|
|
@ -258,6 +258,7 @@ const ollamaSchema = baseProviderSettingsSchema.extend({
|
|||
ollamaModelId: z.string().optional(),
|
||||
ollamaBaseUrl: z.string().optional(),
|
||||
ollamaApiKey: z.string().optional(),
|
||||
ollamaNumCtx: z.number().int().min(128).optional(),
|
||||
})
|
||||
|
||||
const vsCodeLmSchema = baseProviderSettingsSchema.extend({
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export type ChutesModelId =
|
|||
| "tngtech/DeepSeek-R1T-Chimera"
|
||||
| "zai-org/GLM-4.5-Air"
|
||||
| "zai-org/GLM-4.5-FP8"
|
||||
| "zai-org/GLM-4.5-turbo"
|
||||
| "moonshotai/Kimi-K2-Instruct-75k"
|
||||
| "moonshotai/Kimi-K2-Instruct-0905"
|
||||
| "Qwen/Qwen3-235B-A22B-Thinking-2507"
|
||||
|
|
@ -274,6 +275,15 @@ export const chutesModels = {
|
|||
description:
|
||||
"GLM-4.5-FP8 model with 128k token context window, optimized for agent-based applications with MoE architecture.",
|
||||
},
|
||||
"zai-org/GLM-4.5-turbo": {
|
||||
maxTokens: 32768,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 1,
|
||||
outputPrice: 3,
|
||||
description: "GLM-4.5-turbo model with 128K token context window, optimized for fast inference.",
|
||||
},
|
||||
"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8": {
|
||||
maxTokens: 32768,
|
||||
contextWindow: 262144,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,20 @@ export const openAiNativeModels = {
|
|||
supportsTemperature: false,
|
||||
tiers: [{ name: "flex", contextWindow: 400000, inputPrice: 0.025, outputPrice: 0.2, cacheReadsPrice: 0.0025 }],
|
||||
},
|
||||
"gpt-5-codex": {
|
||||
maxTokens: 128000,
|
||||
contextWindow: 400000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsReasoningEffort: true,
|
||||
reasoningEffort: "medium",
|
||||
inputPrice: 1.25,
|
||||
outputPrice: 10.0,
|
||||
cacheReadsPrice: 0.13,
|
||||
description: "GPT-5-Codex: A version of GPT-5 optimized for agentic coding in Codex",
|
||||
supportsVerbosity: true,
|
||||
supportsTemperature: false,
|
||||
},
|
||||
"gpt-4.1": {
|
||||
maxTokens: 32_768,
|
||||
contextWindow: 1_047_576,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import type { ModelInfo } from "../model.js"
|
||||
|
||||
// Roo provider with single model
|
||||
export type RooModelId = "xai/grok-code-fast-1"
|
||||
export type RooModelId =
|
||||
| "xai/grok-code-fast-1"
|
||||
| "roo/code-supernova"
|
||||
| "xai/grok-4-fast"
|
||||
| "deepseek/deepseek-chat-v3.1"
|
||||
|
||||
export const rooDefaultModelId: RooModelId = "xai/grok-code-fast-1"
|
||||
|
||||
|
|
@ -16,4 +19,34 @@ export const rooModels = {
|
|||
description:
|
||||
"A reasoning model that is blazing fast and excels at agentic coding, accessible for free through Roo Code Cloud for a limited time. (Note: the free prompts and completions are logged by xAI and used to improve the model.)",
|
||||
},
|
||||
"roo/code-supernova": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description:
|
||||
"A versatile agentic coding stealth model that supports image inputs, accessible for free through Roo Code Cloud for a limited time. (Note: the free prompts and completions are logged by the model provider and used to improve the model.)",
|
||||
},
|
||||
"xai/grok-4-fast": {
|
||||
maxTokens: 30_000,
|
||||
contextWindow: 2_000_000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description:
|
||||
"Grok 4 Fast is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. (Note: prompts and completions are logged by xAI and used to improve the model.)",
|
||||
},
|
||||
"deepseek/deepseek-chat-v3.1": {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 163_840,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description:
|
||||
"DeepSeek-V3.1 is a large hybrid reasoning model (671B parameters, 37B active). It extends the DeepSeek-V3 base with a two-phase long-context training process, reaching up to 128K tokens, and uses FP8 microscaling for efficient inference.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ export type SambaNovaModelId =
|
|||
| "Meta-Llama-3.3-70B-Instruct"
|
||||
| "DeepSeek-R1"
|
||||
| "DeepSeek-V3-0324"
|
||||
| "DeepSeek-V3.1"
|
||||
| "DeepSeek-R1-Distill-Llama-70B"
|
||||
| "Llama-4-Maverick-17B-128E-Instruct"
|
||||
| "Llama-3.3-Swallow-70B-Instruct-v0.4"
|
||||
| "Qwen3-32B"
|
||||
| "gpt-oss-120b"
|
||||
|
||||
export const sambaNovaDefaultModelId: SambaNovaModelId = "Meta-Llama-3.3-70B-Instruct"
|
||||
|
||||
|
|
@ -51,6 +53,15 @@ export const sambaNovaModels = {
|
|||
outputPrice: 4.5,
|
||||
description: "DeepSeek V3 model with 32K context window.",
|
||||
},
|
||||
"DeepSeek-V3.1": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 32768,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 4.5,
|
||||
description: "DeepSeek V3.1 model with 32K context window.",
|
||||
},
|
||||
"DeepSeek-R1-Distill-Llama-70B": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
|
|
@ -87,4 +98,13 @@ export const sambaNovaModels = {
|
|||
outputPrice: 0.8,
|
||||
description: "Alibaba Qwen 3 32B model with 8K context window.",
|
||||
},
|
||||
"gpt-oss-120b": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.22,
|
||||
outputPrice: 0.59,
|
||||
description: "OpenAI gpt oss 120b model with 128k context window.",
|
||||
},
|
||||
} as const satisfies Record<string, ModelInfo>
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@
|
|||
* @returns true if the model should use single file reads
|
||||
*/
|
||||
export function shouldUseSingleFileRead(modelId: string): boolean {
|
||||
return modelId.includes("grok-code-fast-1")
|
||||
return modelId.includes("grok-code-fast-1") || modelId.includes("code-supernova")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ export enum TelemetryEventName {
|
|||
ACCOUNT_LOGOUT_CLICKED = "Account Logout Clicked",
|
||||
ACCOUNT_LOGOUT_SUCCESS = "Account Logout Success",
|
||||
|
||||
FEATURED_PROVIDER_CLICKED = "Featured Provider Clicked",
|
||||
|
||||
UPSELL_DISMISSED = "Upsell Dismissed",
|
||||
UPSELL_CLICKED = "Upsell Clicked",
|
||||
|
||||
SCHEMA_VALIDATION_ERROR = "Schema Validation Error",
|
||||
DIFF_APPLICATION_ERROR = "Diff Application Error",
|
||||
SHELL_INTEGRATION_ERROR = "Shell Integration Error",
|
||||
|
|
@ -181,6 +186,9 @@ export const rooCodeTelemetryEventSchema = z.discriminatedUnion("type", [
|
|||
TelemetryEventName.ACCOUNT_CONNECT_SUCCESS,
|
||||
TelemetryEventName.ACCOUNT_LOGOUT_CLICKED,
|
||||
TelemetryEventName.ACCOUNT_LOGOUT_SUCCESS,
|
||||
TelemetryEventName.FEATURED_PROVIDER_CLICKED,
|
||||
TelemetryEventName.UPSELL_DISMISSED,
|
||||
TelemetryEventName.UPSELL_CLICKED,
|
||||
TelemetryEventName.SCHEMA_VALIDATION_ERROR,
|
||||
TelemetryEventName.DIFF_APPLICATION_ERROR,
|
||||
TelemetryEventName.SHELL_INTEGRATION_ERROR,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export const commandIds = [
|
|||
"focusInput",
|
||||
"acceptInput",
|
||||
"focusPanel",
|
||||
"toggleAutoApprove",
|
||||
] as const
|
||||
|
||||
export type CommandId = (typeof commandIds)[number]
|
||||
|
|
|
|||
BIN
releases/3.28.4-release.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
releases/3.28.5-release.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
releases/3.28.6-release.png
Normal file
|
After Width: | Height: | Size: 2 MiB |
BIN
releases/3.28.7-release.png
Normal file
|
After Width: | Height: | Size: 970 KiB |
BIN
releases/3.28.8-release.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
|
@ -7,12 +7,16 @@
|
|||
* Options:
|
||||
* --locale=<locale> Only check a specific locale (e.g. --locale=fr)
|
||||
* --file=<file> Only check a specific file (e.g. --file=chat.json)
|
||||
* --area=<area> Only check a specific area (core, webview, or both)
|
||||
* --area=<area> Only check a specific area (core, webview, package-nls, or all)
|
||||
* --help Show this help message
|
||||
*/
|
||||
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const { promises: fs } = require("fs")
|
||||
|
||||
const readFile = fs.readFile
|
||||
const readdir = fs.readdir
|
||||
const stat = fs.stat
|
||||
|
||||
// Process command line arguments
|
||||
const args = process.argv.slice(2).reduce(
|
||||
|
|
@ -26,15 +30,15 @@ const args = process.argv.slice(2).reduce(
|
|||
} else if (arg.startsWith("--area=")) {
|
||||
acc.area = arg.split("=")[1]
|
||||
// Validate area value
|
||||
if (!["core", "webview", "both"].includes(acc.area)) {
|
||||
console.error(`Error: Invalid area '${acc.area}'. Must be 'core', 'webview', or 'both'.`)
|
||||
if (!["core", "webview", "package-nls", "all"].includes(acc.area)) {
|
||||
console.error(`Error: Invalid area '${acc.area}'. Must be 'core', 'webview', 'package-nls', or 'all'.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ area: "both" },
|
||||
) // Default to checking both areas
|
||||
{ area: "all" },
|
||||
) // Default to checking all areas
|
||||
|
||||
// Show help if requested
|
||||
if (args.help) {
|
||||
|
|
@ -50,10 +54,11 @@ Usage:
|
|||
Options:
|
||||
--locale=<locale> Only check a specific locale (e.g. --locale=fr)
|
||||
--file=<file> Only check a specific file (e.g. --file=chat.json)
|
||||
--area=<area> Only check a specific area (core, webview, or both)
|
||||
--area=<area> Only check a specific area (core, webview, package-nls, or all)
|
||||
'core' = Backend (src/i18n/locales)
|
||||
'webview' = Frontend UI (webview-ui/src/i18n/locales)
|
||||
'both' = Check both areas (default)
|
||||
'package-nls' = VSCode package.nls.json files
|
||||
'all' = Check all areas (default)
|
||||
--help Show this help message
|
||||
|
||||
Output:
|
||||
|
|
@ -69,7 +74,7 @@ const LOCALES_DIRS = {
|
|||
}
|
||||
|
||||
// Determine which areas to check based on args
|
||||
const areasToCheck = args.area === "both" ? ["core", "webview"] : [args.area]
|
||||
const areasToCheck = args.area === "all" ? ["core", "webview", "package-nls"] : [args.area]
|
||||
|
||||
// Recursively find all keys in an object
|
||||
function findKeys(obj, parentKey = "") {
|
||||
|
|
@ -105,18 +110,45 @@ function getValueAtPath(obj, path) {
|
|||
return current
|
||||
}
|
||||
|
||||
// Shared utility to safely parse JSON files with error handling
|
||||
async function parseJsonFile(filePath) {
|
||||
try {
|
||||
const content = await readFile(filePath, "utf8")
|
||||
return JSON.parse(content)
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT") {
|
||||
return null // File doesn't exist
|
||||
}
|
||||
throw new Error(`Error parsing JSON file '${filePath}': ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that a JSON object has a flat structure (no nested objects)
|
||||
function validateFlatStructure(obj, filePath) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
console.error(`Error: ${filePath} should be a flat JSON structure. Found nested object at key '${key}'`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check translations for a specific area
|
||||
function checkAreaTranslations(area) {
|
||||
async function checkAreaTranslations(area) {
|
||||
const LOCALES_DIR = LOCALES_DIRS[area]
|
||||
|
||||
// Get all locale directories (or filter to the specified locale)
|
||||
const allLocales = fs.readdirSync(LOCALES_DIR).filter((item) => {
|
||||
const stats = fs.statSync(path.join(LOCALES_DIR, item))
|
||||
return stats.isDirectory() && item !== "en" // Exclude English as it's our source
|
||||
})
|
||||
const dirContents = await readdir(LOCALES_DIR)
|
||||
const allLocales = await Promise.all(
|
||||
dirContents.map(async (item) => {
|
||||
const stats = await stat(path.join(LOCALES_DIR, item))
|
||||
return stats.isDirectory() && item !== "en" ? item : null
|
||||
}),
|
||||
)
|
||||
const filteredLocales = allLocales.filter(Boolean)
|
||||
|
||||
// Filter to the specified locale if provided
|
||||
const locales = args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales
|
||||
const locales = args.locale ? filteredLocales.filter((locale) => locale === args.locale) : filteredLocales
|
||||
|
||||
if (args.locale && locales.length === 0) {
|
||||
console.error(`Error: Locale '${args.locale}' not found in ${LOCALES_DIR}`)
|
||||
|
|
@ -129,7 +161,8 @@ function checkAreaTranslations(area) {
|
|||
|
||||
// Get all English JSON files
|
||||
const englishDir = path.join(LOCALES_DIR, "en")
|
||||
let englishFiles = fs.readdirSync(englishDir).filter((file) => file.endsWith(".json") && !file.startsWith("."))
|
||||
const englishDirContents = await readdir(englishDir)
|
||||
let englishFiles = englishDirContents.filter((file) => file.endsWith(".json") && !file.startsWith("."))
|
||||
|
||||
// Filter to the specified file if provided
|
||||
if (args.file) {
|
||||
|
|
@ -140,72 +173,71 @@ function checkAreaTranslations(area) {
|
|||
englishFiles = englishFiles.filter((file) => file === args.file)
|
||||
}
|
||||
|
||||
// Load file contents
|
||||
let englishFileContents
|
||||
|
||||
try {
|
||||
englishFileContents = englishFiles.map((file) => ({
|
||||
name: file,
|
||||
content: JSON.parse(fs.readFileSync(path.join(englishDir, file), "utf8")),
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error(`Error: File '${englishDir}' is not a valid JSON file`)
|
||||
process.exit(1)
|
||||
}
|
||||
// Load file contents in parallel
|
||||
const englishFileContents = await Promise.all(
|
||||
englishFiles.map(async (file) => {
|
||||
const filePath = path.join(englishDir, file)
|
||||
const content = await parseJsonFile(filePath)
|
||||
if (!content) {
|
||||
console.error(`Error: Could not read file '${filePath}'`)
|
||||
process.exit(1)
|
||||
}
|
||||
return { name: file, content }
|
||||
}),
|
||||
)
|
||||
|
||||
console.log(
|
||||
`Checking ${englishFileContents.length} translation file(s): ${englishFileContents.map((f) => f.name).join(", ")}`,
|
||||
)
|
||||
|
||||
// Precompute English keys per file
|
||||
const englishFileKeys = new Map(englishFileContents.map((f) => [f.name, findKeys(f.content)]))
|
||||
|
||||
// Results object to store missing translations
|
||||
const missingTranslations = {}
|
||||
|
||||
// For each locale, check for missing translations
|
||||
for (const locale of locales) {
|
||||
missingTranslations[locale] = {}
|
||||
// Process all locales in parallel
|
||||
await Promise.all(
|
||||
locales.map(async (locale) => {
|
||||
missingTranslations[locale] = {}
|
||||
|
||||
for (const { name, content: englishContent } of englishFileContents) {
|
||||
const localeFilePath = path.join(LOCALES_DIR, locale, name)
|
||||
// Process all files for this locale in parallel
|
||||
await Promise.all(
|
||||
englishFileContents.map(async ({ name, content: englishContent }) => {
|
||||
const localeFilePath = path.join(LOCALES_DIR, locale, name)
|
||||
|
||||
// Check if the file exists in the locale
|
||||
if (!fs.existsSync(localeFilePath)) {
|
||||
missingTranslations[locale][name] = { file: "File is missing entirely" }
|
||||
continue
|
||||
}
|
||||
// Check if the file exists in the locale
|
||||
const localeContent = await parseJsonFile(localeFilePath)
|
||||
if (!localeContent) {
|
||||
missingTranslations[locale][name] = { file: "File is missing entirely" }
|
||||
return
|
||||
}
|
||||
|
||||
// Load the locale file
|
||||
let localeContent
|
||||
// Find all keys in the English file
|
||||
const englishKeys = englishFileKeys.get(name) || []
|
||||
|
||||
try {
|
||||
localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8"))
|
||||
} catch (e) {
|
||||
console.error(`Error: File '${localeFilePath}' is not a valid JSON file`)
|
||||
process.exit(1)
|
||||
}
|
||||
// Check for missing keys in the locale file
|
||||
const missingKeys = []
|
||||
|
||||
// Find all keys in the English file
|
||||
const englishKeys = findKeys(englishContent)
|
||||
for (const key of englishKeys) {
|
||||
const englishValue = getValueAtPath(englishContent, key)
|
||||
const localeValue = getValueAtPath(localeContent, key)
|
||||
|
||||
// Check for missing keys in the locale file
|
||||
const missingKeys = []
|
||||
if (localeValue === undefined) {
|
||||
missingKeys.push({
|
||||
key,
|
||||
englishValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of englishKeys) {
|
||||
const englishValue = getValueAtPath(englishContent, key)
|
||||
const localeValue = getValueAtPath(localeContent, key)
|
||||
|
||||
if (localeValue === undefined) {
|
||||
missingKeys.push({
|
||||
key,
|
||||
englishValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
missingTranslations[locale][name] = missingKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
if (missingKeys.length > 0) {
|
||||
missingTranslations[locale][name] = missingKeys
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
return { missingTranslations, hasMissingTranslations: outputResults(missingTranslations, area) }
|
||||
}
|
||||
|
|
@ -244,8 +276,124 @@ function outputResults(missingTranslations, area) {
|
|||
return hasMissingTranslations
|
||||
}
|
||||
|
||||
// Function to check package.nls.json translations
|
||||
async function checkPackageNlsTranslations() {
|
||||
const SRC_DIR = path.join(__dirname, "../src")
|
||||
|
||||
// Read the base package.nls.json file
|
||||
const baseFilePath = path.join(SRC_DIR, "package.nls.json")
|
||||
const baseContent = await parseJsonFile(baseFilePath)
|
||||
|
||||
if (!baseContent) {
|
||||
console.warn(`Warning: Base package.nls.json not found at ${baseFilePath} - skipping package.nls checks`)
|
||||
return { missingTranslations: {}, hasMissingTranslations: false }
|
||||
}
|
||||
|
||||
// Validate that the base file has a flat structure
|
||||
validateFlatStructure(baseContent, baseFilePath)
|
||||
|
||||
// Get all package.nls.*.json files
|
||||
const srcDirContents = await readdir(SRC_DIR)
|
||||
const nlsFiles = srcDirContents
|
||||
.filter((file) => file.startsWith("package.nls.") && file.endsWith(".json"))
|
||||
.filter((file) => file !== "package.nls.json") // Exclude the base file
|
||||
|
||||
// Filter to the specified locale if provided
|
||||
const filesToCheck = args.locale
|
||||
? nlsFiles.filter((file) => {
|
||||
const locale = file.replace("package.nls.", "").replace(".json", "")
|
||||
return locale === args.locale
|
||||
})
|
||||
: nlsFiles
|
||||
|
||||
if (args.locale && filesToCheck.length === 0) {
|
||||
console.error(`Error: Locale '${args.locale}' not found in package.nls files`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\nPACKAGE.NLS - Checking ${filesToCheck.length} locale file(s): ${filesToCheck.map((f) => f.replace("package.nls.", "").replace(".json", "")).join(", ")}`,
|
||||
)
|
||||
console.log(`Checking against base package.nls.json with ${Object.keys(baseContent).length} keys`)
|
||||
|
||||
// Results object to store missing translations
|
||||
const missingTranslations = {}
|
||||
|
||||
// Get all keys from the base file (package.nls files are flat, not nested)
|
||||
const baseKeys = Object.keys(baseContent)
|
||||
|
||||
// Process all locale files in parallel
|
||||
await Promise.all(
|
||||
filesToCheck.map(async (file) => {
|
||||
const locale = file.replace("package.nls.", "").replace(".json", "")
|
||||
const localeFilePath = path.join(SRC_DIR, file)
|
||||
|
||||
const localeContent = await parseJsonFile(localeFilePath)
|
||||
if (!localeContent) {
|
||||
console.error(`Error: Could not read file '${localeFilePath}'`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Validate that the locale file has a flat structure
|
||||
validateFlatStructure(localeContent, localeFilePath)
|
||||
|
||||
// Check for missing keys
|
||||
const missingKeys = []
|
||||
|
||||
for (const key of baseKeys) {
|
||||
const baseValue = baseContent[key]
|
||||
const localeValue = localeContent[key]
|
||||
|
||||
if (localeValue === undefined) {
|
||||
missingKeys.push({
|
||||
key,
|
||||
englishValue: baseValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
missingTranslations[locale] = {
|
||||
"package.nls.json": missingKeys,
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
return { missingTranslations, hasMissingTranslations: outputPackageNlsResults(missingTranslations) }
|
||||
}
|
||||
|
||||
// Function to output package.nls results
|
||||
function outputPackageNlsResults(missingTranslations) {
|
||||
let hasMissingTranslations = false
|
||||
|
||||
console.log(`\nPACKAGE.NLS Missing Translations Report:\n`)
|
||||
|
||||
for (const [locale, files] of Object.entries(missingTranslations)) {
|
||||
if (Object.keys(files).length === 0) {
|
||||
console.log(`✅ ${locale}: No missing translations`)
|
||||
continue
|
||||
}
|
||||
|
||||
hasMissingTranslations = true
|
||||
console.log(`📝 ${locale}:`)
|
||||
|
||||
for (const [fileName, missingItems] of Object.entries(files)) {
|
||||
console.log(` - ${fileName}: ${missingItems.length} missing translations`)
|
||||
|
||||
for (const { key, englishValue } of missingItems) {
|
||||
console.log(` ${key}: "${englishValue}"`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("")
|
||||
}
|
||||
|
||||
return hasMissingTranslations
|
||||
}
|
||||
|
||||
// Main function to find missing translations
|
||||
function findMissingTranslations() {
|
||||
async function findMissingTranslations() {
|
||||
try {
|
||||
console.log("Starting translation check...")
|
||||
|
||||
|
|
@ -253,8 +401,13 @@ function findMissingTranslations() {
|
|||
|
||||
// Check each requested area
|
||||
for (const area of areasToCheck) {
|
||||
const { hasMissingTranslations } = checkAreaTranslations(area)
|
||||
anyAreaMissingTranslations = anyAreaMissingTranslations || hasMissingTranslations
|
||||
if (area === "package-nls") {
|
||||
const { hasMissingTranslations } = await checkPackageNlsTranslations()
|
||||
anyAreaMissingTranslations = anyAreaMissingTranslations || hasMissingTranslations
|
||||
} else {
|
||||
const { hasMissingTranslations } = await checkAreaTranslations(area)
|
||||
anyAreaMissingTranslations = anyAreaMissingTranslations || hasMissingTranslations
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
|
|
|
|||
|
|
@ -221,6 +221,18 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
|
|||
|
||||
visibleProvider.postMessageToWebview({ type: "acceptInput" })
|
||||
},
|
||||
toggleAutoApprove: async () => {
|
||||
const visibleProvider = getVisibleProviderOrLog(outputChannel)
|
||||
|
||||
if (!visibleProvider) {
|
||||
return
|
||||
}
|
||||
|
||||
visibleProvider.postMessageToWebview({
|
||||
type: "action",
|
||||
action: "toggleAutoApprove",
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
|
||||
|
|
|
|||
|
|
@ -253,6 +253,28 @@ describe("ChutesHandler", () => {
|
|||
)
|
||||
})
|
||||
|
||||
it("should return zai-org/GLM-4.5-turbo model with correct configuration", () => {
|
||||
const testModelId: ChutesModelId = "zai-org/GLM-4.5-turbo"
|
||||
const handlerWithModel = new ChutesHandler({
|
||||
apiModelId: testModelId,
|
||||
chutesApiKey: "test-chutes-api-key",
|
||||
})
|
||||
const model = handlerWithModel.getModel()
|
||||
expect(model.id).toBe(testModelId)
|
||||
expect(model.info).toEqual(
|
||||
expect.objectContaining({
|
||||
maxTokens: 32768,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 1,
|
||||
outputPrice: 3,
|
||||
description: "GLM-4.5-turbo model with 128K token context window, optimized for fast inference.",
|
||||
temperature: 0.5, // Default temperature for non-DeepSeek models
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 model with correct configuration", () => {
|
||||
const testModelId: ChutesModelId = "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8"
|
||||
const handlerWithModel = new ChutesHandler({
|
||||
|
|
|
|||
|
|
@ -73,6 +73,61 @@ describe("NativeOllamaHandler", () => {
|
|||
expect(results[2]).toEqual({ type: "usage", inputTokens: 10, outputTokens: 2 })
|
||||
})
|
||||
|
||||
it("should not include num_ctx by default", async () => {
|
||||
// Mock the chat response
|
||||
mockChat.mockImplementation(async function* () {
|
||||
yield { message: { content: "Response" } }
|
||||
})
|
||||
|
||||
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
|
||||
|
||||
// Consume the stream
|
||||
for await (const _ of stream) {
|
||||
// consume stream
|
||||
}
|
||||
|
||||
// Verify that num_ctx was NOT included in the options
|
||||
expect(mockChat).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.not.objectContaining({
|
||||
num_ctx: expect.anything(),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should include num_ctx when explicitly set via ollamaNumCtx", async () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "llama2",
|
||||
ollamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaNumCtx: 8192, // Explicitly set num_ctx
|
||||
}
|
||||
|
||||
handler = new NativeOllamaHandler(options)
|
||||
|
||||
// Mock the chat response
|
||||
mockChat.mockImplementation(async function* () {
|
||||
yield { message: { content: "Response" } }
|
||||
})
|
||||
|
||||
const stream = handler.createMessage("System", [{ role: "user" as const, content: "Test" }])
|
||||
|
||||
// Consume the stream
|
||||
for await (const _ of stream) {
|
||||
// consume stream
|
||||
}
|
||||
|
||||
// Verify that num_ctx was included with the specified value
|
||||
expect(mockChat).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.objectContaining({
|
||||
num_ctx: 8192,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle DeepSeek R1 models with reasoning detection", async () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "deepseek-r1",
|
||||
|
|
@ -120,6 +175,49 @@ describe("NativeOllamaHandler", () => {
|
|||
})
|
||||
expect(result).toBe("This is the response")
|
||||
})
|
||||
|
||||
it("should not include num_ctx in completePrompt by default", async () => {
|
||||
mockChat.mockResolvedValue({
|
||||
message: { content: "Response" },
|
||||
})
|
||||
|
||||
await handler.completePrompt("Test prompt")
|
||||
|
||||
// Verify that num_ctx was NOT included in the options
|
||||
expect(mockChat).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.not.objectContaining({
|
||||
num_ctx: expect.anything(),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should include num_ctx in completePrompt when explicitly set", async () => {
|
||||
const options: ApiHandlerOptions = {
|
||||
apiModelId: "llama2",
|
||||
ollamaModelId: "llama2",
|
||||
ollamaBaseUrl: "http://localhost:11434",
|
||||
ollamaNumCtx: 4096, // Explicitly set num_ctx
|
||||
}
|
||||
|
||||
handler = new NativeOllamaHandler(options)
|
||||
|
||||
mockChat.mockResolvedValue({
|
||||
message: { content: "Response" },
|
||||
})
|
||||
|
||||
await handler.completePrompt("Test prompt")
|
||||
|
||||
// Verify that num_ctx was included with the specified value
|
||||
expect(mockChat).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.objectContaining({
|
||||
num_ctx: 4096,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("error handling", () => {
|
||||
|
|
|
|||
|
|
@ -36,26 +36,12 @@ vitest.mock("openai", () => {
|
|||
return {
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: { content: "Test response" },
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
choices: [{ delta: { content: "Test response" }, index: 0 }],
|
||||
usage: null,
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {},
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 5,
|
||||
total_tokens: 15,
|
||||
},
|
||||
choices: [{ delta: {}, index: 0 }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -73,6 +59,7 @@ const mockHasInstance = vitest.fn()
|
|||
// Create mock functions that we can control
|
||||
const mockGetSessionTokenFn = vitest.fn()
|
||||
const mockHasInstanceFn = vitest.fn()
|
||||
const mockOnFn = vitest.fn()
|
||||
|
||||
vitest.mock("@roo-code/cloud", () => ({
|
||||
CloudService: {
|
||||
|
|
@ -82,6 +69,8 @@ vitest.mock("@roo-code/cloud", () => ({
|
|||
authService: {
|
||||
getSessionToken: () => mockGetSessionTokenFn(),
|
||||
},
|
||||
on: vitest.fn(),
|
||||
off: vitest.fn(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -409,11 +398,18 @@ describe("RooHandler", () => {
|
|||
it("should handle undefined auth service gracefully", () => {
|
||||
mockHasInstanceFn.mockReturnValue(true)
|
||||
// Mock CloudService with undefined authService
|
||||
const originalGetter = Object.getOwnPropertyDescriptor(CloudService, "instance")?.get
|
||||
const originalGetSessionToken = mockGetSessionTokenFn.getMockImplementation()
|
||||
|
||||
// Temporarily make authService return undefined
|
||||
mockGetSessionTokenFn.mockImplementation(() => undefined)
|
||||
|
||||
try {
|
||||
Object.defineProperty(CloudService, "instance", {
|
||||
get: () => ({ authService: undefined }),
|
||||
get: () => ({
|
||||
authService: undefined,
|
||||
on: vitest.fn(),
|
||||
off: vitest.fn(),
|
||||
}),
|
||||
configurable: true,
|
||||
})
|
||||
|
||||
|
|
@ -424,12 +420,11 @@ describe("RooHandler", () => {
|
|||
const handler = new RooHandler(mockOptions)
|
||||
expect(handler).toBeInstanceOf(RooHandler)
|
||||
} finally {
|
||||
// Always restore original getter, even if test fails
|
||||
if (originalGetter) {
|
||||
Object.defineProperty(CloudService, "instance", {
|
||||
get: originalGetter,
|
||||
configurable: true,
|
||||
})
|
||||
// Restore original mock implementation
|
||||
if (originalGetSessionToken) {
|
||||
mockGetSessionTokenFn.mockImplementation(originalGetSessionToken)
|
||||
} else {
|
||||
mockGetSessionTokenFn.mockReturnValue("test-session-token")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -286,10 +286,7 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
outputTokens: number
|
||||
cacheReadTokens?: number
|
||||
}) {
|
||||
if (!info.inputPrice || !info.outputPrice || !info.cacheReadsPrice) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// For models with tiered pricing, prices might only be defined in tiers
|
||||
let inputPrice = info.inputPrice
|
||||
let outputPrice = info.outputPrice
|
||||
let cacheReadsPrice = info.cacheReadsPrice
|
||||
|
|
@ -306,6 +303,16 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
}
|
||||
}
|
||||
|
||||
// Check if we have the required prices after considering tiers
|
||||
if (!inputPrice || !outputPrice) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// cacheReadsPrice is optional - if not defined, treat as 0
|
||||
if (!cacheReadsPrice) {
|
||||
cacheReadsPrice = 0
|
||||
}
|
||||
|
||||
// Subtract the cached input tokens from the total input tokens.
|
||||
const uncachedInputTokens = inputTokens - cacheReadTokens
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import { getOllamaModels } from "./fetchers/ollama"
|
|||
import { XmlMatcher } from "../../utils/xml-matcher"
|
||||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
|
||||
interface OllamaChatOptions {
|
||||
temperature: number
|
||||
num_ctx?: number
|
||||
}
|
||||
|
||||
function convertToOllamaMessages(anthropicMessages: Anthropic.Messages.MessageParam[]): Message[] {
|
||||
const ollamaMessages: Message[] = []
|
||||
|
||||
|
|
@ -184,15 +189,22 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
|
|||
)
|
||||
|
||||
try {
|
||||
// Build options object conditionally
|
||||
const chatOptions: OllamaChatOptions = {
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
}
|
||||
|
||||
// Only include num_ctx if explicitly set via ollamaNumCtx
|
||||
if (this.options.ollamaNumCtx !== undefined) {
|
||||
chatOptions.num_ctx = this.options.ollamaNumCtx
|
||||
}
|
||||
|
||||
// Create the actual API request promise
|
||||
const stream = await client.chat({
|
||||
model: modelId,
|
||||
messages: ollamaMessages,
|
||||
stream: true,
|
||||
options: {
|
||||
num_ctx: modelInfo.contextWindow,
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
},
|
||||
options: chatOptions,
|
||||
})
|
||||
|
||||
let totalInputTokens = 0
|
||||
|
|
@ -274,13 +286,21 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
|
|||
const { id: modelId } = await this.fetchModel()
|
||||
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
|
||||
|
||||
// Build options object conditionally
|
||||
const chatOptions: OllamaChatOptions = {
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
}
|
||||
|
||||
// Only include num_ctx if explicitly set via ollamaNumCtx
|
||||
if (this.options.ollamaNumCtx !== undefined) {
|
||||
chatOptions.num_ctx = this.options.ollamaNumCtx
|
||||
}
|
||||
|
||||
const response = await client.chat({
|
||||
model: modelId,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: this.options.modelTemperature ?? (useR1Format ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
|
||||
},
|
||||
options: chatOptions,
|
||||
})
|
||||
|
||||
return response.message?.content || ""
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types"
|
||||
import { AuthState, rooDefaultModelId, rooModels, type RooModelId } from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
import type { ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { DEFAULT_HEADERS } from "./constants"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
||||
export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
||||
private authStateListener?: (state: { state: AuthState }) => void
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
// Get the session token if available, but don't throw if not.
|
||||
// The server will handle authentication errors and return appropriate status codes.
|
||||
let sessionToken = ""
|
||||
let sessionToken: string | undefined = undefined
|
||||
|
||||
if (CloudService.hasInstance()) {
|
||||
sessionToken = CloudService.instance.authService?.getSessionToken() || ""
|
||||
sessionToken = CloudService.instance.authService?.getSessionToken()
|
||||
}
|
||||
|
||||
// Always construct the handler, even without a valid token.
|
||||
|
|
@ -25,11 +27,39 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<RooModelId> {
|
|||
...options,
|
||||
providerName: "Roo Code Cloud",
|
||||
baseURL: process.env.ROO_CODE_PROVIDER_URL ?? "https://api.roocode.com/proxy/v1",
|
||||
apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token
|
||||
apiKey: sessionToken || "unauthenticated", // Use a placeholder if no token.
|
||||
defaultProviderModelId: rooDefaultModelId,
|
||||
providerModels: rooModels,
|
||||
defaultTemperature: 0.7,
|
||||
})
|
||||
|
||||
if (CloudService.hasInstance()) {
|
||||
const cloudService = CloudService.instance
|
||||
|
||||
this.authStateListener = (state: { state: AuthState }) => {
|
||||
if (state.state === "active-session") {
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.baseURL,
|
||||
apiKey: cloudService.authService?.getSessionToken() ?? "unauthenticated",
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
})
|
||||
} else if (state.state === "logged-out") {
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.baseURL,
|
||||
apiKey: "unauthenticated",
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cloudService.on("auth-state-changed", this.authStateListener)
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.authStateListener && CloudService.hasInstance()) {
|
||||
CloudService.instance.off("auth-state-changed", this.authStateListener)
|
||||
}
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
|
|
|
|||
BIN
src/assets/images/roo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -283,6 +283,32 @@ describe("summarizeConversation", () => {
|
|||
const mockCallArgs = (maybeRemoveImageBlocks as Mock).mock.calls[0][0] as any[]
|
||||
expect(mockCallArgs[mockCallArgs.length - 1]).toEqual(expectedFinalMessage)
|
||||
})
|
||||
it("should include the original first user message in summarization input", async () => {
|
||||
const messages: ApiMessage[] = [
|
||||
{ role: "user", content: "Initial ask", ts: 1 },
|
||||
{ role: "assistant", content: "Ack", ts: 2 },
|
||||
{ role: "user", content: "Follow-up", ts: 3 },
|
||||
{ role: "assistant", content: "Response", ts: 4 },
|
||||
{ role: "user", content: "More", ts: 5 },
|
||||
{ role: "assistant", content: "Later", ts: 6 },
|
||||
{ role: "user", content: "Newest", ts: 7 },
|
||||
]
|
||||
|
||||
await summarizeConversation(messages, mockApiHandler, defaultSystemPrompt, taskId, DEFAULT_PREV_CONTEXT_TOKENS)
|
||||
|
||||
const mockCallArgs = (maybeRemoveImageBlocks as Mock).mock.calls[0][0] as any[]
|
||||
|
||||
// Expect the original first user message to be present in the messages sent to the summarizer
|
||||
const hasInitialAsk = mockCallArgs.some(
|
||||
(m) =>
|
||||
m.role === "user" &&
|
||||
(typeof m.content === "string"
|
||||
? m.content === "Initial ask"
|
||||
: Array.isArray(m.content) &&
|
||||
m.content.some((b: any) => b.type === "text" && b.text === "Initial ask")),
|
||||
)
|
||||
expect(hasInitialAsk).toBe(true)
|
||||
})
|
||||
|
||||
it("should calculate newContextTokens correctly with systemPrompt", async () => {
|
||||
const messages: ApiMessage[] = [
|
||||
|
|
|
|||
|
|
@ -103,8 +103,8 @@ export async function summarizeConversation(
|
|||
|
||||
// Always preserve the first message (which may contain slash command content)
|
||||
const firstMessage = messages[0]
|
||||
// Get messages to summarize, excluding the first message and last N messages
|
||||
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(1, -N_MESSAGES_TO_KEEP))
|
||||
// Get messages to summarize, including the first message and excluding the last N messages
|
||||
const messagesToSummarize = getMessagesSinceLastSummary(messages.slice(0, -N_MESSAGES_TO_KEEP))
|
||||
|
||||
if (messagesToSummarize.length <= 1) {
|
||||
const error =
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ALL responses MUST show ANY `language construct` OR filename reference as clicka
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export function getSharedToolUseSection(): string {
|
|||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
You have access to a set of tools that are executed upon the user's approval. You must use exactly one tool per message, and every assistant message must include a tool call. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
didFinishAbortingStream = false
|
||||
abandoned = false
|
||||
abortReason?: ClineApiReqCancelReason
|
||||
isInitialized = false
|
||||
isPaused: boolean = false
|
||||
pausedModeSlug: string = defaultModeSlug
|
||||
|
|
@ -1264,6 +1265,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
modifiedClineMessages.splice(lastRelevantMessageIndex + 1)
|
||||
}
|
||||
|
||||
// Remove any trailing reasoning-only UI messages that were not part of the persisted API conversation
|
||||
while (modifiedClineMessages.length > 0) {
|
||||
const last = modifiedClineMessages[modifiedClineMessages.length - 1]
|
||||
if (last.type === "say" && last.say === "reasoning") {
|
||||
modifiedClineMessages.pop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Since we don't use `api_req_finished` anymore, we need to check if the
|
||||
// last `api_req_started` has a cost value, if it doesn't and no
|
||||
// cancellation reason to present, then we remove it since it indicates
|
||||
|
|
@ -1884,28 +1895,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
lastMessage.partial = false
|
||||
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
|
||||
console.log("updating partial message", lastMessage)
|
||||
// await this.saveClineMessages()
|
||||
}
|
||||
|
||||
// Let assistant know their response was interrupted for when task is resumed
|
||||
await this.addToApiConversationHistory({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
assistantMessage +
|
||||
`\n\n[${
|
||||
cancelReason === "streaming_failed"
|
||||
? "Response interrupted by API Error"
|
||||
: "Response interrupted by user"
|
||||
}]`,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Update `api_req_started` to have cancelled and cost, so that
|
||||
// we can display the cost of the partial stream.
|
||||
// we can display the cost of the partial stream and the cancellation reason
|
||||
updateApiReqMsg(cancelReason, streamingFailedMessage)
|
||||
await this.saveClineMessages()
|
||||
|
||||
|
|
@ -1951,10 +1944,22 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
}
|
||||
|
||||
switch (chunk.type) {
|
||||
case "reasoning":
|
||||
case "reasoning": {
|
||||
reasoningMessage += chunk.text
|
||||
await this.say("reasoning", reasoningMessage, undefined, true)
|
||||
// Only apply formatting if the message contains sentence-ending punctuation followed by **
|
||||
let formattedReasoning = reasoningMessage
|
||||
if (reasoningMessage.includes("**")) {
|
||||
// Add line breaks before **Title** patterns that appear after sentence endings
|
||||
// This targets section headers like "...end of sentence.**Title Here**"
|
||||
// Handles periods, exclamation marks, and question marks
|
||||
formattedReasoning = reasoningMessage.replace(
|
||||
/([.!?])\*\*([^*\n]+)\*\*/g,
|
||||
"$1\n\n**$2**",
|
||||
)
|
||||
}
|
||||
await this.say("reasoning", formattedReasoning, undefined, true)
|
||||
break
|
||||
}
|
||||
case "usage":
|
||||
inputTokens += chunk.inputTokens
|
||||
outputTokens += chunk.outputTokens
|
||||
|
|
@ -2187,24 +2192,23 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
// may have executed), so we just resort to replicating a
|
||||
// cancel task.
|
||||
|
||||
// Check if this was a user-initiated cancellation BEFORE calling abortTask
|
||||
// If this.abort is already true, it means the user clicked cancel, so we should
|
||||
// treat this as "user_cancelled" rather than "streaming_failed"
|
||||
const cancelReason = this.abort ? "user_cancelled" : "streaming_failed"
|
||||
// Determine cancellation reason BEFORE aborting to ensure correct persistence
|
||||
const cancelReason: ClineApiReqCancelReason = this.abort ? "user_cancelled" : "streaming_failed"
|
||||
|
||||
const streamingFailedMessage = this.abort
|
||||
? undefined
|
||||
: (error.message ?? JSON.stringify(serializeError(error), null, 2))
|
||||
|
||||
// Now call abortTask after determining the cancel reason.
|
||||
await this.abortTask()
|
||||
// Persist interruption details first to both UI and API histories
|
||||
await abortStream(cancelReason, streamingFailedMessage)
|
||||
|
||||
const history = await provider?.getTaskWithId(this.taskId)
|
||||
// Record reason for provider to decide rehydration path
|
||||
this.abortReason = cancelReason
|
||||
|
||||
if (history) {
|
||||
await provider?.createTaskWithHistoryItem(history.historyItem)
|
||||
}
|
||||
// Now abort (emits TaskAborted which provider listens to)
|
||||
await this.abortTask()
|
||||
|
||||
// Do not rehydrate here; provider owns rehydration to avoid duplication races
|
||||
}
|
||||
} finally {
|
||||
this.isStreaming = false
|
||||
|
|
|
|||
243
src/core/tools/__tests__/updateTodoListTool.spec.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
import { describe, it, expect, beforeEach, vi } from "vitest"
|
||||
import { parseMarkdownChecklist } from "../updateTodoListTool"
|
||||
import { TodoItem } from "@roo-code/types"
|
||||
|
||||
describe("parseMarkdownChecklist", () => {
|
||||
describe("standard checkbox format (without dash prefix)", () => {
|
||||
it("should parse pending tasks", () => {
|
||||
const md = `[ ] Task 1
|
||||
[ ] Task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Task 1")
|
||||
expect(result[0].status).toBe("pending")
|
||||
expect(result[1].content).toBe("Task 2")
|
||||
expect(result[1].status).toBe("pending")
|
||||
})
|
||||
|
||||
it("should parse completed tasks with lowercase x", () => {
|
||||
const md = `[x] Completed task 1
|
||||
[x] Completed task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Completed task 1")
|
||||
expect(result[0].status).toBe("completed")
|
||||
expect(result[1].content).toBe("Completed task 2")
|
||||
expect(result[1].status).toBe("completed")
|
||||
})
|
||||
|
||||
it("should parse completed tasks with uppercase X", () => {
|
||||
const md = `[X] Completed task 1
|
||||
[X] Completed task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Completed task 1")
|
||||
expect(result[0].status).toBe("completed")
|
||||
expect(result[1].content).toBe("Completed task 2")
|
||||
expect(result[1].status).toBe("completed")
|
||||
})
|
||||
|
||||
it("should parse in-progress tasks with dash", () => {
|
||||
const md = `[-] In progress task 1
|
||||
[-] In progress task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("In progress task 1")
|
||||
expect(result[0].status).toBe("in_progress")
|
||||
expect(result[1].content).toBe("In progress task 2")
|
||||
expect(result[1].status).toBe("in_progress")
|
||||
})
|
||||
|
||||
it("should parse in-progress tasks with tilde", () => {
|
||||
const md = `[~] In progress task 1
|
||||
[~] In progress task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("In progress task 1")
|
||||
expect(result[0].status).toBe("in_progress")
|
||||
expect(result[1].content).toBe("In progress task 2")
|
||||
expect(result[1].status).toBe("in_progress")
|
||||
})
|
||||
})
|
||||
|
||||
describe("dash-prefixed checkbox format", () => {
|
||||
it("should parse pending tasks with dash prefix", () => {
|
||||
const md = `- [ ] Task 1
|
||||
- [ ] Task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Task 1")
|
||||
expect(result[0].status).toBe("pending")
|
||||
expect(result[1].content).toBe("Task 2")
|
||||
expect(result[1].status).toBe("pending")
|
||||
})
|
||||
|
||||
it("should parse completed tasks with dash prefix and lowercase x", () => {
|
||||
const md = `- [x] Completed task 1
|
||||
- [x] Completed task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Completed task 1")
|
||||
expect(result[0].status).toBe("completed")
|
||||
expect(result[1].content).toBe("Completed task 2")
|
||||
expect(result[1].status).toBe("completed")
|
||||
})
|
||||
|
||||
it("should parse completed tasks with dash prefix and uppercase X", () => {
|
||||
const md = `- [X] Completed task 1
|
||||
- [X] Completed task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Completed task 1")
|
||||
expect(result[0].status).toBe("completed")
|
||||
expect(result[1].content).toBe("Completed task 2")
|
||||
expect(result[1].status).toBe("completed")
|
||||
})
|
||||
|
||||
it("should parse in-progress tasks with dash prefix and dash marker", () => {
|
||||
const md = `- [-] In progress task 1
|
||||
- [-] In progress task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("In progress task 1")
|
||||
expect(result[0].status).toBe("in_progress")
|
||||
expect(result[1].content).toBe("In progress task 2")
|
||||
expect(result[1].status).toBe("in_progress")
|
||||
})
|
||||
|
||||
it("should parse in-progress tasks with dash prefix and tilde marker", () => {
|
||||
const md = `- [~] In progress task 1
|
||||
- [~] In progress task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("In progress task 1")
|
||||
expect(result[0].status).toBe("in_progress")
|
||||
expect(result[1].content).toBe("In progress task 2")
|
||||
expect(result[1].status).toBe("in_progress")
|
||||
})
|
||||
})
|
||||
|
||||
describe("mixed formats", () => {
|
||||
it("should parse mixed formats correctly", () => {
|
||||
const md = `[ ] Task without dash
|
||||
- [ ] Task with dash
|
||||
[x] Completed without dash
|
||||
- [X] Completed with dash
|
||||
[-] In progress without dash
|
||||
- [~] In progress with dash`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(6)
|
||||
|
||||
expect(result[0].content).toBe("Task without dash")
|
||||
expect(result[0].status).toBe("pending")
|
||||
|
||||
expect(result[1].content).toBe("Task with dash")
|
||||
expect(result[1].status).toBe("pending")
|
||||
|
||||
expect(result[2].content).toBe("Completed without dash")
|
||||
expect(result[2].status).toBe("completed")
|
||||
|
||||
expect(result[3].content).toBe("Completed with dash")
|
||||
expect(result[3].status).toBe("completed")
|
||||
|
||||
expect(result[4].content).toBe("In progress without dash")
|
||||
expect(result[4].status).toBe("in_progress")
|
||||
|
||||
expect(result[5].content).toBe("In progress with dash")
|
||||
expect(result[5].status).toBe("in_progress")
|
||||
})
|
||||
})
|
||||
|
||||
describe("edge cases", () => {
|
||||
it("should handle empty strings", () => {
|
||||
const result = parseMarkdownChecklist("")
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle non-string input", () => {
|
||||
const result = parseMarkdownChecklist(null as any)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should handle undefined input", () => {
|
||||
const result = parseMarkdownChecklist(undefined as any)
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
|
||||
it("should ignore non-checklist lines", () => {
|
||||
const md = `This is not a checklist
|
||||
[ ] Valid task
|
||||
Just some text
|
||||
- Not a checklist item
|
||||
- [x] Valid completed task
|
||||
[not valid] Invalid format`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].content).toBe("Valid task")
|
||||
expect(result[0].status).toBe("pending")
|
||||
expect(result[1].content).toBe("Valid completed task")
|
||||
expect(result[1].status).toBe("completed")
|
||||
})
|
||||
|
||||
it("should handle extra spaces", () => {
|
||||
const md = ` [ ] Task with spaces
|
||||
- [ ] Task with dash and spaces
|
||||
[x] Completed with spaces
|
||||
- [X] Completed with dash and spaces`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(4)
|
||||
expect(result[0].content).toBe("Task with spaces")
|
||||
expect(result[1].content).toBe("Task with dash and spaces")
|
||||
expect(result[2].content).toBe("Completed with spaces")
|
||||
expect(result[3].content).toBe("Completed with dash and spaces")
|
||||
})
|
||||
|
||||
it("should handle Windows line endings", () => {
|
||||
const md = "[ ] Task 1\r\n- [x] Task 2\r\n[-] Task 3"
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result).toHaveLength(3)
|
||||
expect(result[0].content).toBe("Task 1")
|
||||
expect(result[0].status).toBe("pending")
|
||||
expect(result[1].content).toBe("Task 2")
|
||||
expect(result[1].status).toBe("completed")
|
||||
expect(result[2].content).toBe("Task 3")
|
||||
expect(result[2].status).toBe("in_progress")
|
||||
})
|
||||
})
|
||||
|
||||
describe("ID generation", () => {
|
||||
it("should generate consistent IDs for the same content and status", () => {
|
||||
const md1 = `[ ] Task 1
|
||||
[x] Task 2`
|
||||
const md2 = `[ ] Task 1
|
||||
[x] Task 2`
|
||||
const result1 = parseMarkdownChecklist(md1)
|
||||
const result2 = parseMarkdownChecklist(md2)
|
||||
|
||||
expect(result1[0].id).toBe(result2[0].id)
|
||||
expect(result1[1].id).toBe(result2[1].id)
|
||||
})
|
||||
|
||||
it("should generate different IDs for different content", () => {
|
||||
const md = `[ ] Task 1
|
||||
[ ] Task 2`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result[0].id).not.toBe(result[1].id)
|
||||
})
|
||||
|
||||
it("should generate different IDs for same content but different status", () => {
|
||||
const md = `[ ] Task 1
|
||||
[x] Task 1`
|
||||
const result = parseMarkdownChecklist(md)
|
||||
expect(result[0].id).not.toBe(result[1].id)
|
||||
})
|
||||
|
||||
it("should generate same IDs regardless of dash prefix", () => {
|
||||
const md1 = `[ ] Task 1`
|
||||
const md2 = `- [ ] Task 1`
|
||||
const result1 = parseMarkdownChecklist(md1)
|
||||
const result2 = parseMarkdownChecklist(md2)
|
||||
expect(result1[0].id).toBe(result2[0].id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -463,7 +463,7 @@ Error: ${failPart.error}
|
|||
Suggested fixes:
|
||||
1. Verify the search content exactly matches the file content (including whitespace and case)
|
||||
2. Check for correct indentation and line endings
|
||||
3. Use <read_file> to see the current file content
|
||||
3. Use the read_file tool to verify the file's current contents
|
||||
4. Consider breaking complex changes into smaller diffs
|
||||
5. Ensure start_line parameter matches the actual content location
|
||||
${errorDetails ? `\nDetailed error information:\n${errorDetails}\n` : ""}
|
||||
|
|
@ -476,7 +476,7 @@ Unable to apply diffs to file: ${absolutePath}
|
|||
Error: ${diffResult.error}
|
||||
|
||||
Recovery suggestions:
|
||||
1. Use <read_file> to examine the current file content
|
||||
1. Use the read_file tool to verify the file's current contents
|
||||
2. Verify the diff format matches the expected search/replace pattern
|
||||
3. Check that the search content exactly matches what's in the file
|
||||
4. Consider using line numbers with start_line parameter
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ export function parseMarkdownChecklist(md: string): TodoItem[] {
|
|||
.filter(Boolean)
|
||||
const todos: TodoItem[] = []
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^\[\s*([ xX\-~])\s*\]\s+(.+)$/)
|
||||
// Support both "[ ] Task" and "- [ ] Task" formats
|
||||
const match = line.match(/^(?:-\s*)?\[\s*([ xX\-~])\s*\]\s+(.+)$/)
|
||||
if (!match) continue
|
||||
let status: TodoStatus = "pending"
|
||||
if (match[1] === "x" || match[1] === "X") status = "completed"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
type TerminalActionPromptType,
|
||||
type HistoryItem,
|
||||
type CloudUserInfo,
|
||||
type CloudOrganizationMembership,
|
||||
type CreateTaskOptions,
|
||||
type TokenUsage,
|
||||
RooCodeEventName,
|
||||
|
|
@ -89,6 +90,8 @@ import { Task } from "../task/Task"
|
|||
import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
|
||||
|
||||
import { webviewMessageHandler } from "./webviewMessageHandler"
|
||||
import type { ClineMessage } from "@roo-code/types"
|
||||
import { readApiMessages, saveApiMessages, saveTaskMessages } from "../task-persistence"
|
||||
import { getNonce } from "./getNonce"
|
||||
import { getUri } from "./getUri"
|
||||
|
||||
|
|
@ -141,7 +144,7 @@ export class ClineProvider
|
|||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
public readonly latestAnnouncementId = "sep-2025-roo-code-cloud" // Roo Code Cloud announcement
|
||||
public readonly latestAnnouncementId = "sep-2025-code-supernova" // Code Supernova stealth model announcement
|
||||
public readonly providerSettingsManager: ProviderSettingsManager
|
||||
public readonly customModesManager: CustomModesManager
|
||||
|
||||
|
|
@ -196,7 +199,35 @@ export class ClineProvider
|
|||
const onTaskStarted = () => this.emit(RooCodeEventName.TaskStarted, instance.taskId)
|
||||
const onTaskCompleted = (taskId: string, tokenUsage: any, toolUsage: any) =>
|
||||
this.emit(RooCodeEventName.TaskCompleted, taskId, tokenUsage, toolUsage)
|
||||
const onTaskAborted = () => this.emit(RooCodeEventName.TaskAborted, instance.taskId)
|
||||
const onTaskAborted = async () => {
|
||||
this.emit(RooCodeEventName.TaskAborted, instance.taskId)
|
||||
|
||||
try {
|
||||
// Only rehydrate on genuine streaming failures.
|
||||
// User-initiated cancels are handled by cancelTask().
|
||||
if (instance.abortReason === "streaming_failed") {
|
||||
// Defensive safeguard: if another path already replaced this instance, skip
|
||||
const current = this.getCurrentTask()
|
||||
if (current && current.instanceId !== instance.instanceId) {
|
||||
this.log(
|
||||
`[onTaskAborted] Skipping rehydrate: current instance ${current.instanceId} != aborted ${instance.instanceId}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const { historyItem } = await this.getTaskWithId(instance.taskId)
|
||||
const rootTask = instance.rootTask
|
||||
const parentTask = instance.parentTask
|
||||
await this.createTaskWithHistoryItem({ ...historyItem, rootTask, parentTask })
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(
|
||||
`[onTaskAborted] Failed to rehydrate after streaming failure: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
const onTaskFocused = () => this.emit(RooCodeEventName.TaskFocused, instance.taskId)
|
||||
const onTaskUnfocused = () => this.emit(RooCodeEventName.TaskUnfocused, instance.taskId)
|
||||
const onTaskActive = (taskId: string) => this.emit(RooCodeEventName.TaskActive, taskId)
|
||||
|
|
@ -1761,6 +1792,7 @@ export class ClineProvider
|
|||
maxTotalImageSize,
|
||||
terminalCompressProgressBar,
|
||||
historyPreviewCollapsed,
|
||||
reasoningBlockCollapsed,
|
||||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
|
|
@ -1785,6 +1817,16 @@ export class ClineProvider
|
|||
featureRoomoteControlEnabled,
|
||||
} = await this.getState()
|
||||
|
||||
let cloudOrganizations: CloudOrganizationMembership[] = []
|
||||
|
||||
try {
|
||||
cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[getStateToPostToWebview] failed to get cloud organizations: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
}
|
||||
|
||||
const telemetryKey = process.env.POSTHOG_API_KEY
|
||||
const machineId = vscode.env.machineId
|
||||
const mergedAllowedCommands = this.mergeAllowedCommands(allowedCommands)
|
||||
|
|
@ -1884,8 +1926,10 @@ export class ClineProvider
|
|||
terminalCompressProgressBar: terminalCompressProgressBar ?? true,
|
||||
hasSystemPromptOverride,
|
||||
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
|
||||
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
|
||||
cloudUserInfo,
|
||||
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
|
||||
cloudOrganizations,
|
||||
sharingEnabled: sharingEnabled ?? false,
|
||||
organizationAllowList,
|
||||
organizationSettingsVersion,
|
||||
|
|
@ -2097,6 +2141,7 @@ export class ClineProvider
|
|||
maxTotalImageSize: stateValues.maxTotalImageSize ?? 20,
|
||||
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
|
||||
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
|
||||
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
|
||||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
sharingEnabled,
|
||||
|
|
@ -2210,6 +2255,18 @@ export class ClineProvider
|
|||
return
|
||||
}
|
||||
|
||||
// Log out from cloud if authenticated
|
||||
if (CloudService.hasInstance()) {
|
||||
try {
|
||||
await CloudService.instance.logout()
|
||||
} catch (error) {
|
||||
this.log(
|
||||
`Failed to logout from cloud during reset: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
// Continue with reset even if logout fails
|
||||
}
|
||||
}
|
||||
|
||||
await this.contextProxy.resetAllState()
|
||||
await this.providerSettingsManager.resetAllConfigs()
|
||||
await this.customModesManager.resetCustomModes()
|
||||
|
|
@ -2525,14 +2582,24 @@ export class ClineProvider
|
|||
|
||||
console.log(`[cancelTask] cancelling task ${task.taskId}.${task.instanceId}`)
|
||||
|
||||
const { historyItem } = await this.getTaskWithId(task.taskId)
|
||||
const { historyItem, uiMessagesFilePath } = await this.getTaskWithId(task.taskId)
|
||||
|
||||
// Preserve parent and root task information for history item.
|
||||
const rootTask = task.rootTask
|
||||
const parentTask = task.parentTask
|
||||
|
||||
// Mark this as a user-initiated cancellation so provider-only rehydration can occur
|
||||
task.abortReason = "user_cancelled"
|
||||
|
||||
// Capture the current instance to detect if rehydrate already occurred elsewhere
|
||||
const originalInstanceId = task.instanceId
|
||||
|
||||
// Begin abort (non-blocking)
|
||||
task.abortTask()
|
||||
|
||||
// Immediately mark the original instance as abandoned to prevent any residual activity
|
||||
task.abandoned = true
|
||||
|
||||
await pWaitFor(
|
||||
() =>
|
||||
this.getCurrentTask()! === undefined ||
|
||||
|
|
@ -2549,11 +2616,24 @@ export class ClineProvider
|
|||
console.error("Failed to abort task")
|
||||
})
|
||||
|
||||
if (this.getCurrentTask()) {
|
||||
// 'abandoned' will prevent this Cline instance from affecting
|
||||
// future Cline instances. This may happen if its hanging on a
|
||||
// streaming request.
|
||||
this.getCurrentTask()!.abandoned = true
|
||||
// Defensive safeguard: if current instance already changed, skip rehydrate
|
||||
const current = this.getCurrentTask()
|
||||
if (current && current.instanceId !== originalInstanceId) {
|
||||
this.log(
|
||||
`[cancelTask] Skipping rehydrate: current instance ${current.instanceId} != original ${originalInstanceId}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Final race check before rehydrate to avoid duplicate rehydration
|
||||
{
|
||||
const currentAfterCheck = this.getCurrentTask()
|
||||
if (currentAfterCheck && currentAfterCheck.instanceId !== originalInstanceId) {
|
||||
this.log(
|
||||
`[cancelTask] Skipping rehydrate after final check: current instance ${currentAfterCheck.instanceId} != original ${originalInstanceId}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Clears task again, so we need to abortTask manually above.
|
||||
|
|
|
|||
|
|
@ -1060,6 +1060,18 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
case "openKeyboardShortcuts": {
|
||||
// Open VSCode keyboard shortcuts settings and optionally filter to show the Roo Code commands
|
||||
const searchQuery = message.text || ""
|
||||
if (searchQuery) {
|
||||
// Open with a search query pre-filled
|
||||
await vscode.commands.executeCommand("workbench.action.openGlobalKeybindings", searchQuery)
|
||||
} else {
|
||||
// Just open the keyboard shortcuts settings
|
||||
await vscode.commands.executeCommand("workbench.action.openGlobalKeybindings")
|
||||
}
|
||||
break
|
||||
}
|
||||
case "openMcpSettings": {
|
||||
const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath()
|
||||
|
||||
|
|
@ -1621,6 +1633,10 @@ export const webviewMessageHandler = async (
|
|||
await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
break
|
||||
case "setReasoningBlockCollapsed":
|
||||
await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
break
|
||||
case "toggleApiConfigPin":
|
||||
if (message.text) {
|
||||
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
|
||||
|
|
@ -2318,6 +2334,17 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
case "cloudLandingPageSignIn": {
|
||||
try {
|
||||
const landingPageSlug = message.text || "supernova"
|
||||
TelemetryService.instance.captureEvent(TelemetryEventName.AUTHENTICATION_INITIATED)
|
||||
await CloudService.instance.login(landingPageSlug)
|
||||
} catch (error) {
|
||||
provider.log(`CloudService#login failed: ${error}`)
|
||||
vscode.window.showErrorMessage("Sign in failed.")
|
||||
}
|
||||
break
|
||||
}
|
||||
case "rooCloudSignOut": {
|
||||
try {
|
||||
await CloudService.instance.logout()
|
||||
|
|
@ -2372,6 +2399,38 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
case "switchOrganization": {
|
||||
try {
|
||||
const organizationId = message.organizationId ?? null
|
||||
|
||||
// Switch to the new organization context
|
||||
await CloudService.instance.switchOrganization(organizationId)
|
||||
|
||||
// Refresh the state to update UI
|
||||
await provider.postStateToWebview()
|
||||
|
||||
// Send success response back to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "organizationSwitchResult",
|
||||
success: true,
|
||||
organizationId: organizationId,
|
||||
})
|
||||
} catch (error) {
|
||||
provider.log(`Organization switch failed: ${error}`)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
|
||||
// Send error response back to webview
|
||||
await provider.postMessageToWebview({
|
||||
type: "organizationSwitchResult",
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
organizationId: message.organizationId ?? null,
|
||||
})
|
||||
|
||||
vscode.window.showErrorMessage(`Failed to switch organization: ${errorMessage}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case "saveCodeIndexSettingsAtomic": {
|
||||
if (!message.codeIndexSettings) {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
// Add to subscriptions for proper cleanup on deactivate.
|
||||
context.subscriptions.push(cloudService)
|
||||
|
||||
// Trigger initial cloud profile sync now that CloudService is ready
|
||||
// Trigger initial cloud profile sync now that CloudService is ready.
|
||||
try {
|
||||
await provider.initializeCloudProfileSyncWhenReady()
|
||||
} catch (error) {
|
||||
|
|
|
|||
4
src/i18n/locales/ca/common.json
generated
|
|
@ -165,6 +165,10 @@
|
|||
"incomplete": "Tasca #{{taskNumber}} (Incompleta)",
|
||||
"no_messages": "Tasca #{{taskNumber}} (Sense missatges)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Resposta interrompuda per l'usuari",
|
||||
"responseInterruptedByApiError": "Resposta interrompuda per error d'API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Introdueix una ruta d'emmagatzematge personalitzada per a l'historial de converses o deixa-ho buit per utilitzar la ubicació predeterminada",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/de/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Aufgabe #{{taskNumber}} (Unvollständig)",
|
||||
"no_messages": "Aufgabe #{{taskNumber}} (Keine Nachrichten)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Antwort vom Benutzer unterbrochen",
|
||||
"responseInterruptedByApiError": "Antwort durch API-Fehler unterbrochen"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Gib den benutzerdefinierten Speicherpfad für den Gesprächsverlauf ein, leer lassen für Standardspeicherort",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Task #{{taskNumber}} (Incomplete)",
|
||||
"no_messages": "Task #{{taskNumber}} (No messages)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Response interrupted by user",
|
||||
"responseInterruptedByApiError": "Response interrupted by API error"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Enter custom conversation history storage path, leave empty to use default location",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/es/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Tarea #{{taskNumber}} (Incompleta)",
|
||||
"no_messages": "Tarea #{{taskNumber}} (Sin mensajes)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Respuesta interrumpida por el usuario",
|
||||
"responseInterruptedByApiError": "Respuesta interrumpida por error de API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Ingresa la ruta de almacenamiento personalizada para el historial de conversaciones, déjala vacía para usar la ubicación predeterminada",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/fr/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Tâche #{{taskNumber}} (Incomplète)",
|
||||
"no_messages": "Tâche #{{taskNumber}} (Aucun message)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Réponse interrompue par l'utilisateur",
|
||||
"responseInterruptedByApiError": "Réponse interrompue par une erreur d'API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Entrez le chemin de stockage personnalisé pour l'historique des conversations, laissez vide pour utiliser l'emplacement par défaut",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/hi/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "टास्क #{{taskNumber}} (अधूरा)",
|
||||
"no_messages": "टास्क #{{taskNumber}} (कोई संदेश नहीं)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "उपयोगकर्ता द्वारा प्रतिक्रिया बाधित",
|
||||
"responseInterruptedByApiError": "API त्रुटि द्वारा प्रतिक्रिया बाधित"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "वार्तालाप इतिहास के लिए कस्टम स्टोरेज पाथ दर्ज करें, डिफ़ॉल्ट स्थान का उपयोग करने के लिए खाली छोड़ दें",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/id/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Tugas #{{taskNumber}} (Tidak lengkap)",
|
||||
"no_messages": "Tugas #{{taskNumber}} (Tidak ada pesan)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Respons diinterupsi oleh pengguna",
|
||||
"responseInterruptedByApiError": "Respons diinterupsi oleh error API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Masukkan path penyimpanan riwayat percakapan kustom, biarkan kosong untuk menggunakan lokasi default",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/it/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Attività #{{taskNumber}} (Incompleta)",
|
||||
"no_messages": "Attività #{{taskNumber}} (Nessun messaggio)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Risposta interrotta dall'utente",
|
||||
"responseInterruptedByApiError": "Risposta interrotta da errore API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Inserisci il percorso di archiviazione personalizzato per la cronologia delle conversazioni, lascia vuoto per utilizzare la posizione predefinita",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/ja/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "タスク #{{taskNumber}} (未完了)",
|
||||
"no_messages": "タスク #{{taskNumber}} (メッセージなし)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "ユーザーによって応答が中断されました",
|
||||
"responseInterruptedByApiError": "APIエラーによって応答が中断されました"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "会話履歴のカスタムストレージパスを入力してください。デフォルトの場所を使用する場合は空のままにしてください",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/ko/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "작업 #{{taskNumber}} (미완료)",
|
||||
"no_messages": "작업 #{{taskNumber}} (메시지 없음)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "사용자에 의해 응답이 중단됨",
|
||||
"responseInterruptedByApiError": "API 오류로 인해 응답이 중단됨"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "대화 내역을 위한 사용자 지정 저장 경로를 입력하세요. 기본 위치를 사용하려면 비워두세요",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/nl/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Taak #{{taskNumber}} (Onvolledig)",
|
||||
"no_messages": "Taak #{{taskNumber}} (Geen berichten)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Reactie onderbroken door gebruiker",
|
||||
"responseInterruptedByApiError": "Reactie onderbroken door API-fout"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Voer een aangepast opslagpad voor gespreksgeschiedenis in, laat leeg voor standaardlocatie",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/pl/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Zadanie #{{taskNumber}} (Niekompletne)",
|
||||
"no_messages": "Zadanie #{{taskNumber}} (Brak wiadomości)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Odpowiedź przerwana przez użytkownika",
|
||||
"responseInterruptedByApiError": "Odpowiedź przerwana przez błąd API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Wprowadź niestandardową ścieżkę przechowywania dla historii konwersacji lub pozostaw puste, aby użyć lokalizacji domyślnej",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/pt-BR/common.json
generated
|
|
@ -165,6 +165,10 @@
|
|||
"incomplete": "Tarefa #{{taskNumber}} (Incompleta)",
|
||||
"no_messages": "Tarefa #{{taskNumber}} (Sem mensagens)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Resposta interrompida pelo usuário",
|
||||
"responseInterruptedByApiError": "Resposta interrompida por erro da API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Digite o caminho de armazenamento personalizado para o histórico de conversas, deixe em branco para usar o local padrão",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/ru/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Задача #{{taskNumber}} (Незавершенная)",
|
||||
"no_messages": "Задача #{{taskNumber}} (Нет сообщений)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Ответ прерван пользователем",
|
||||
"responseInterruptedByApiError": "Ответ прерван ошибкой API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Введите пользовательский путь хранения истории разговоров, оставьте пустым для использования расположения по умолчанию",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/tr/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Görev #{{taskNumber}} (Tamamlanmamış)",
|
||||
"no_messages": "Görev #{{taskNumber}} (Mesaj yok)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Yanıt kullanıcı tarafından kesildi",
|
||||
"responseInterruptedByApiError": "Yanıt API hatası nedeniyle kesildi"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Konuşma geçmişi için özel depolama yolunu girin, varsayılan konumu kullanmak için boş bırakın",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/vi/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "Nhiệm vụ #{{taskNumber}} (Chưa hoàn thành)",
|
||||
"no_messages": "Nhiệm vụ #{{taskNumber}} (Không có tin nhắn)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "Phản hồi bị gián đoạn bởi người dùng",
|
||||
"responseInterruptedByApiError": "Phản hồi bị gián đoạn bởi lỗi API"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "Nhập đường dẫn lưu trữ tùy chỉnh cho lịch sử hội thoại, để trống để sử dụng vị trí mặc định",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/zh-CN/common.json
generated
|
|
@ -166,6 +166,10 @@
|
|||
"incomplete": "任务 #{{taskNumber}} (未完成)",
|
||||
"no_messages": "任务 #{{taskNumber}} (无消息)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "响应被用户中断",
|
||||
"responseInterruptedByApiError": "响应被 API 错误中断"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "输入自定义会话历史存储路径,留空以使用默认位置",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
4
src/i18n/locales/zh-TW/common.json
generated
|
|
@ -161,6 +161,10 @@
|
|||
"incomplete": "工作 #{{taskNumber}} (未完成)",
|
||||
"no_messages": "工作 #{{taskNumber}} (無訊息)"
|
||||
},
|
||||
"interruption": {
|
||||
"responseInterruptedByUser": "回應被使用者中斷",
|
||||
"responseInterruptedByApiError": "回應被 API 錯誤中斷"
|
||||
},
|
||||
"storage": {
|
||||
"prompt_custom_path": "輸入自訂會話歷史儲存路徑,留空以使用預設位置",
|
||||
"path_placeholder": "D:\\RooCodeStorage",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"displayName": "%extension.displayName%",
|
||||
"description": "%extension.description%",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "3.28.3",
|
||||
"version": "3.28.8",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
|
|
@ -174,6 +174,11 @@
|
|||
"command": "roo-cline.acceptInput",
|
||||
"title": "%command.acceptInput.title%",
|
||||
"category": "%configuration.title%"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.toggleAutoApprove",
|
||||
"title": "%command.toggleAutoApprove.title%",
|
||||
"category": "%configuration.title%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
|
@ -310,6 +315,13 @@
|
|||
"win": "ctrl+y",
|
||||
"linux": "ctrl+y",
|
||||
"when": "editorTextFocus && editorHasSelection"
|
||||
},
|
||||
{
|
||||
"command": "roo-cline.toggleAutoApprove",
|
||||
"key": "cmd+alt+a",
|
||||
"mac": "cmd+alt+a",
|
||||
"win": "ctrl+alt+a",
|
||||
"linux": "ctrl+alt+a"
|
||||
}
|
||||
],
|
||||
"submenus": [
|
||||
|
|
|
|||
3
src/package.nls.ca.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"command.terminal.fixCommand.title": "Corregir Aquesta Ordre",
|
||||
"command.terminal.explainCommand.title": "Explicar Aquesta Ordre",
|
||||
"command.acceptInput.title": "Acceptar Entrada/Suggeriment",
|
||||
"command.toggleAutoApprove.title": "Alternar Auto-Aprovació",
|
||||
"views.activitybar.title": "Roo Code",
|
||||
"views.contextMenu.label": "Roo Code",
|
||||
"views.terminalMenu.label": "Roo Code",
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
"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.",
|
||||
"commands.preventCompletionWithOpenTodos.description": "Evitar la finalització de tasques quan hi ha tasques pendents incompletes a la llista de tasques",
|
||||
"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)",
|
||||
|
|
@ -39,5 +41,6 @@
|
|||
"settings.autoImportSettingsPath.description": "Ruta a un fitxer de configuració de RooCode per importar automàticament en iniciar l'extensió. Admet rutes absolutes i rutes relatives al directori d'inici (per exemple, '~/Documents/roo-code-settings.json'). Deixeu-ho en blanc per desactivar la importació automàtica.",
|
||||
"settings.useAgentRules.description": "Activa la càrrega de fitxers AGENTS.md per a regles específiques de l'agent (vegeu https://agent-rules.org/)",
|
||||
"settings.apiRequestTimeout.description": "Temps màxim en segons per esperar les respostes de l'API (0 = sense temps d'espera, 1-3600s, per defecte: 600s). Es recomanen valors més alts per a proveïdors locals com LM Studio i Ollama que poden necessitar més temps de processament.",
|
||||
"settings.newTaskRequireTodos.description": "Requerir el paràmetre de tasques pendents quan es creïn noves tasques amb l'eina new_task",
|
||||
"settings.codeIndex.embeddingBatchSize.description": "La mida del lot per a operacions d'incrustació durant la indexació de codi. Ajusta això segons els límits del teu proveïdor d'API. Per defecte és 60."
|
||||
}
|
||||
|
|
|
|||
3
src/package.nls.de.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"command.terminal.fixCommand.title": "Diesen Befehl Reparieren",
|
||||
"command.terminal.explainCommand.title": "Diesen Befehl Erklären",
|
||||
"command.acceptInput.title": "Eingabe/Vorschlag Akzeptieren",
|
||||
"command.toggleAutoApprove.title": "Auto-Genehmigung Umschalten",
|
||||
"views.activitybar.title": "Roo Code",
|
||||
"views.contextMenu.label": "Roo Code",
|
||||
"views.terminalMenu.label": "Roo Code",
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
"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.",
|
||||
"commands.preventCompletionWithOpenTodos.description": "Aufgabenabschluss verhindern, wenn unvollständige Todos in der Todo-Liste vorhanden sind",
|
||||
"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)",
|
||||
|
|
@ -39,5 +41,6 @@
|
|||
"settings.autoImportSettingsPath.description": "Pfad zu einer RooCode-Konfigurationsdatei, die beim Start der Erweiterung automatisch importiert wird. Unterstützt absolute Pfade und Pfade relativ zum Home-Verzeichnis (z.B. '~/Documents/roo-code-settings.json'). Leer lassen, um den automatischen Import zu deaktivieren.",
|
||||
"settings.useAgentRules.description": "Aktiviert das Laden von AGENTS.md-Dateien für agentenspezifische Regeln (siehe https://agent-rules.org/)",
|
||||
"settings.apiRequestTimeout.description": "Maximale Wartezeit in Sekunden auf API-Antworten (0 = kein Timeout, 1-3600s, Standard: 600s). Höhere Werte werden für lokale Anbieter wie LM Studio und Ollama empfohlen, die möglicherweise mehr Verarbeitungszeit benötigen.",
|
||||
"settings.newTaskRequireTodos.description": "Todos-Parameter beim Erstellen neuer Aufgaben mit dem new_task-Tool erfordern",
|
||||
"settings.codeIndex.embeddingBatchSize.description": "Die Batch-Größe für Embedding-Operationen während der Code-Indexierung. Passe dies an die Limits deines API-Anbieters an. Standard ist 60."
|
||||
}
|
||||
|
|
|
|||
3
src/package.nls.es.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"command.terminal.fixCommand.title": "Corregir Este Comando",
|
||||
"command.terminal.explainCommand.title": "Explicar Este Comando",
|
||||
"command.acceptInput.title": "Aceptar Entrada/Sugerencia",
|
||||
"command.toggleAutoApprove.title": "Alternar Auto-Aprobación",
|
||||
"views.activitybar.title": "Roo Code",
|
||||
"views.contextMenu.label": "Roo Code",
|
||||
"views.terminalMenu.label": "Roo Code",
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
"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.",
|
||||
"commands.preventCompletionWithOpenTodos.description": "Prevenir la finalización de tareas cuando hay todos incompletos en la lista de todos",
|
||||
"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)",
|
||||
|
|
@ -39,5 +41,6 @@
|
|||
"settings.autoImportSettingsPath.description": "Ruta a un archivo de configuración de RooCode para importar automáticamente al iniciar la extensión. Admite rutas absolutas y rutas relativas al directorio de inicio (por ejemplo, '~/Documents/roo-code-settings.json'). Dejar vacío para desactivar la importación automática.",
|
||||
"settings.useAgentRules.description": "Habilita la carga de archivos AGENTS.md para reglas específicas del agente (ver https://agent-rules.org/)",
|
||||
"settings.apiRequestTimeout.description": "Tiempo máximo en segundos de espera para las respuestas de la API (0 = sin tiempo de espera, 1-3600s, por defecto: 600s). Se recomiendan valores más altos para proveedores locales como LM Studio y Ollama que puedan necesitar más tiempo de procesamiento.",
|
||||
"settings.newTaskRequireTodos.description": "Requerir el parámetro todos al crear nuevas tareas con la herramienta new_task",
|
||||
"settings.codeIndex.embeddingBatchSize.description": "El tamaño del lote para operaciones de embedding durante la indexación de código. Ajusta esto según los límites de tu proveedor de API. Por defecto es 60."
|
||||
}
|
||||
|
|
|
|||
3
src/package.nls.fr.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"command.terminal.fixCommand.title": "Corriger cette Commande",
|
||||
"command.terminal.explainCommand.title": "Expliquer cette Commande",
|
||||
"command.acceptInput.title": "Accepter l'Entrée/Suggestion",
|
||||
"command.toggleAutoApprove.title": "Basculer Auto-Approbation",
|
||||
"views.activitybar.title": "Roo Code",
|
||||
"views.contextMenu.label": "Roo Code",
|
||||
"views.terminalMenu.label": "Roo Code",
|
||||
|
|
@ -31,6 +32,7 @@
|
|||
"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.",
|
||||
"commands.preventCompletionWithOpenTodos.description": "Empêcher l'achèvement des tâches lorsqu'il y a des todos incomplets dans la liste de todos",
|
||||
"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)",
|
||||
|
|
@ -39,5 +41,6 @@
|
|||
"settings.autoImportSettingsPath.description": "Chemin d'accès à un fichier de configuration RooCode à importer automatiquement au démarrage de l'extension. Prend en charge les chemins absolus et les chemins relatifs au répertoire de base (par exemple, '~/Documents/roo-code-settings.json'). Laisser vide pour désactiver l'importation automatique.",
|
||||
"settings.useAgentRules.description": "Activer le chargement des fichiers AGENTS.md pour les règles spécifiques à l'agent (voir https://agent-rules.org/)",
|
||||
"settings.apiRequestTimeout.description": "Temps maximum en secondes d'attente pour les réponses de l'API (0 = pas de timeout, 1-3600s, par défaut : 600s). Des valeurs plus élevées sont recommandées pour les fournisseurs locaux comme LM Studio et Ollama qui peuvent nécessiter plus de temps de traitement.",
|
||||
"settings.newTaskRequireTodos.description": "Exiger le paramètre todos lors de la création de nouvelles tâches avec l'outil new_task",
|
||||
"settings.codeIndex.embeddingBatchSize.description": "La taille du lot pour les opérations d'embedding lors de l'indexation du code. Ajustez ceci selon les limites de votre fournisseur d'API. Par défaut, c'est 60."
|
||||
}
|
||||
|
|
|
|||