mirror of
https://github.com/cline/cline.git
synced 2026-09-04 20:02:30 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b04ff45c79 | |||
| 2e9fdb6726 | |||
| 48db9b1068 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixes the API Keys URL for Requesty
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Set gpt5 max tokens to 8_192 to fix 'context window exceeded' error
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix issue where fallback request to retrieve cost was not using correct auth token
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Remove deprecated GPT-4.5 Preview
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding OpenAI context window exceeded error handling
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adding safety guard for workspace root
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add CLINE_ACTIVE environment variable to Cline-managed terminals
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
calibrate input token counts when using anthropic models of sap ai core provider
|
||||
+1
-1
@@ -61,6 +61,6 @@ old_docs/**
|
||||
!assets/icons/**
|
||||
|
||||
# Ignore E2E build files
|
||||
e2e-build.mjs
|
||||
e2e-build.js
|
||||
e2e.vsix
|
||||
test-results/
|
||||
|
||||
@@ -1,35 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.25.2]
|
||||
|
||||
- Fix attempt_completion showing twice in chat due to partial logic not being handled correctly
|
||||
- Fix OpenRouter showing cline credits error after 402 response
|
||||
|
||||
## [3.25.1]
|
||||
|
||||
- Fix attempt_completion command showing twice in chat view when updating progress checklist
|
||||
- Fix bug where announcement banner could not be dismissed
|
||||
- Add GPT-OSS models to AWS Bedrock
|
||||
|
||||
## [3.25.0]
|
||||
|
||||
- **Focus Chain:** Automatically creates and maintains todo lists as you work with Cline, breaking down complex tasks into manageable steps with real-time progress tracking
|
||||
- **Auto Compact:** Intelligently manages conversation context to prevent token limit errors by automatically compacting older messages while preserving important context
|
||||
- **Deep Planning:** New `/deep-planning` slash command for structured 4-step implementation planning that integrates with Focus Chain for automatic progress tracking
|
||||
- Add support for 200k context window for Claude Sonnet 4 in OpenRouter and Cline providers
|
||||
- Add option to configure custom base URL for Requesty provider
|
||||
|
||||
## [3.24.0]
|
||||
|
||||
- Add OpenAI GPT-5 Chat(gpt-5-chat-latest)
|
||||
- Add custom browser arguments setting to allow passing flags to the Chrome executable for better headless compatibility.
|
||||
- Add 1m context window model support for claude sonnet 4
|
||||
- Fis the API Keys URL for Requesty
|
||||
- Set gpt5 max tokens to 8_192 to fix 'context window exceeded' error
|
||||
- Fix issue where fallback request to retrieve cost was not using correct auth token
|
||||
- Add OpenAI context window exceeded error handling
|
||||
- Calibrate input token counts when using anthropic models of sap ai core provider
|
||||
|
||||
## [3.23.0]
|
||||
|
||||
- Add caching support for Bedrock inferences using SAP AI Core and minor refactor
|
||||
|
||||
+4
-12
@@ -79,8 +79,6 @@
|
||||
"features/drag-and-drop",
|
||||
"features/plan-and-act",
|
||||
"features/slash-commands/workflows",
|
||||
"features/focus-chain",
|
||||
"features/auto-compact",
|
||||
"features/editing-messages",
|
||||
{
|
||||
"group": "@ Mentions",
|
||||
@@ -99,8 +97,7 @@
|
||||
"features/slash-commands/new-task",
|
||||
"features/slash-commands/new-rule",
|
||||
"features/slash-commands/smol",
|
||||
"features/slash-commands/report-bug",
|
||||
"features/slash-commands/deep-planning"
|
||||
"features/slash-commands/report-bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -149,14 +146,9 @@
|
||||
"pages": [
|
||||
"provider-config/anthropic",
|
||||
"provider-config/claude-code",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
"provider-config/aws-bedrock/api-key",
|
||||
"provider-config/aws-bedrock/iam-credentials",
|
||||
"provider-config/aws-bedrock/cli-profile"
|
||||
]
|
||||
},
|
||||
"provider-config/aws-bedrock-with-apikey-authentication",
|
||||
"provider-config/aws-bedrock-with-credentials-authentication",
|
||||
"provider-config/aws-bedrock-with-profile-authentication",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/litellm-and-cline-using-codestral",
|
||||
"provider-config/vscode-language-model-api",
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
title: "Automatic Context Summarization"
|
||||
sidebarTitle: "Auto Compact"
|
||||
---
|
||||
|
||||
When your conversation approaches the model's context window limit, Cline automatically summarizes it to free up space and keep working.
|
||||
|
||||
## How It Works
|
||||
|
||||
Cline monitors token usage during your conversation. When you're getting close to the limit, he:
|
||||
|
||||
1. Creates a comprehensive summary of everything that's happened
|
||||
2. Preserves all the technical details, code changes, and decisions
|
||||
3. Replaces the conversation history with the summary
|
||||
4. Continues exactly where he left off
|
||||
|
||||
You'll see a summarization tool call when this happens, showing the total cost like any other api call in the chat view.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
Previously, Cline would truncate older messages when hitting context limits. This meant losing important context from earlier in the conversation.
|
||||
|
||||
Now with summarization:
|
||||
- All technical decisions and code patterns are preserved
|
||||
- File changes and project context remain intact
|
||||
- Cline remembers everything he's done
|
||||
- You can work on much larger projects without interruption
|
||||
|
||||
<Tip>
|
||||
Context Summarization synergizes beautifully with [Focus Chain](/features/focus-chain). When Focus Chain is enabled, todo lists persist across summarizations. This means Cline can work on long-horizon tasks that span multiple context windows while staying on track with the todo list guiding him through each reset.
|
||||
</Tip>
|
||||
|
||||
## Technical Details
|
||||
|
||||
The summarization happens through your configured API provider using the same model you're already using. It leverages prompt caching to minimize costs.
|
||||
|
||||
1. Cline uses a [summarization prompt](https://github.com/cline/cline/blob/main/src/core/prompts/contextManagement.ts) to request a summary of the conversation.
|
||||
|
||||
2. Once the summary is generated, Cline replaces the conversation history with a [continuation prompt](https://github.com/cline/cline/blob/main/src/core/prompts/contextManagement.ts#L69) that asks Cline to keep working and provides the summary as context.
|
||||
|
||||
Different models have different context window thresholds for when auto-summarization kicks in. You can see how thresholds are determined in [context-window-utils.ts](https://github.com/cline/cline/blob/main/src/core/context/context-management/context-window-utils.ts).
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
Summarization leverages your existing prompt cache from the conversation, so it costs about the same as any other tool call.
|
||||
|
||||
Since most input tokens are already cached, you're primarily paying for the summary generation (output tokens), making it very cost-effective.
|
||||
|
||||
## Restoring Context with Checkpoints
|
||||
|
||||
You can use [checkpoints](/features/checkpoints) to restore your task state from before a summarization occurred. This means you never truly lose context - you can always roll back to previous versions of your conversation.
|
||||
|
||||
<Note>
|
||||
Editing a message before a summarization tool call will work similarly to a checkpoint, allowing you to restore the conversation to that point.
|
||||
</Note>
|
||||
@@ -11,4 +11,4 @@ Dragging and dropping workspace files into Cline will automatically create a [fi
|
||||
|
||||
### Supported File Types
|
||||
|
||||
Cline supports dragging external images, pdfs, csv, excel, and other text files from your file system, as well as files from your workspace.
|
||||
Cline supports dragging external images from your file system, as well as files from your workspace.
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
---
|
||||
title: "Focus Chain"
|
||||
sidebarTitle: "Focus Chain"
|
||||
---
|
||||
|
||||
Focus Chain is a task management enhancement feature in Cline that provides automatic todo list management with real-time progress tracking throughout your tasks.
|
||||
|
||||
This enables Cline to work on long-horizon tasks, seamlessly managing the context sent to LLMs, and keeping Cline on track across many context window resets.
|
||||
|
||||
<Tip>
|
||||
Focus Chain works particularly well with Cline's [Deep Planning slash command](/features/slash-commands/deep-planning), providing seamless progress tracking for implementation tasks created through the [planning process](/features/plan-and-act).
|
||||
</Tip>
|
||||
|
||||
## Key Features
|
||||
|
||||
### Automatic Todo List Generation
|
||||
|
||||
Cline analyzes your task and automatically creates a comprehensive todo list with:
|
||||
- Clear, actionable items in markdown checklist format
|
||||
- Logical breakdown of complex tasks into manageable steps
|
||||
- Real-time updates as work progresses
|
||||
|
||||
### User-Editable Todo Lists
|
||||
|
||||
Todo lists are stored as editable markdown files:
|
||||
- Direct editing through your preferred markdown editor
|
||||
- Automatic detection of changes you make
|
||||
- Seamless integration back into Cline's workflow
|
||||
- Quick access through the edit button in the task header
|
||||
|
||||
### Visual Progress Tracking
|
||||
|
||||
The task header displays clear progress indicators:
|
||||
- **Step counters** showing current progress (e.g., "3/8")
|
||||
- **Completed items** clearly marked with checkmarks
|
||||
- **Current work** highlighted with indicators
|
||||
- **Expandable view** to see the full todo list
|
||||
|
||||
### Smart Reminder System
|
||||
|
||||
Configurable reminders ensure todo lists stay current:
|
||||
- Default reminder every 6 messages (customizable 1-100)
|
||||
- Automatic prompts when switching from Plan Mode to Act Mode
|
||||
- User-triggered updates when todo lists are manually edited
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Steps>
|
||||
<Step title="Open Cline Settings">
|
||||
- Click the gear icon in the Cline sidebar
|
||||
- Navigate to the "Features" section
|
||||
</Step>
|
||||
<Step title="Enable Focus Chain">
|
||||
- Check "Enable Focus Chain"
|
||||
- Optionally adjust "Remind Cline Interval" (default: 6 messages)
|
||||
</Step>
|
||||
<Step title="Start a New Task">
|
||||
- Begin a new task
|
||||
- Cline will automatically start creating and managing todo lists
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
| Setting | Default | Range | Description |
|
||||
|---------|---------|-------|-------------|
|
||||
| Enable Focus Chain | Disabled | On/Off | Enables enhanced task progress tracking |
|
||||
| Remind Cline Interval | 6 | 1-100 messages | How often Cline updates the todo list |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
#### 1. Task Initiation
|
||||
|
||||
When you start a new task with Focus Chain enabled:
|
||||
|
||||
``` markdown User Request
|
||||
User: "Create a user authentication system for my React app"
|
||||
|
||||
Cline: [Analyzes request and creates todo list]
|
||||
```
|
||||
|
||||
#### 2. Todo List Created
|
||||
|
||||
Cline creates a comprehensive plan for the task, stored in a markdown file:
|
||||
|
||||
```markdown Todo List Created
|
||||
- [ ] Set up project structure
|
||||
- [ ] Install authentication dependencies
|
||||
- [ ] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password validation
|
||||
- [ ] Set up user database schema
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
```
|
||||
|
||||
#### 3. Progress Tracking
|
||||
|
||||
As Cline works, the task header shows real-time progress:
|
||||
|
||||
```markdown Todo List Header
|
||||
[3/8] Implement login functionality ⌄
|
||||
```
|
||||
|
||||
Click to expand and see the full list:
|
||||
|
||||
```markdown Full Todo List
|
||||
✓ Set up project structure
|
||||
✓ Install authentication dependencies
|
||||
✓ Create user registration component
|
||||
○ Implement login functionality ← Currently working
|
||||
○ Add password validation
|
||||
○ Set up user database schema
|
||||
○ Write authentication tests
|
||||
○ Deploy to staging environment
|
||||
```
|
||||
|
||||
#### 4. User Editing
|
||||
|
||||
Need to tweak the todo list? No problem.
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the todo list">
|
||||
Click the edit button in the expanded todo view
|
||||
</Step>
|
||||
<Step title="Edit the markdown file">
|
||||
A markdown file opens in your editor:
|
||||
|
||||
```markdown Editing Todo List
|
||||
# Focus Chain Todo List for Task abc123
|
||||
|
||||
<!-- Edit this markdown file to update your focus chain todo list -->
|
||||
<!-- Use - [ ] for incomplete items and - [x] for completed items -->
|
||||
|
||||
- [x] Set up project structure
|
||||
- [x] Install authentication dependencies (e.g., Firebase Auth)
|
||||
- [x] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password reset feature
|
||||
- [ ] Set up protected routes
|
||||
- [ ] Implement logout functionality
|
||||
- [ ] Add user profile page
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
|
||||
<!-- Save this file to update the task's todo list -->
|
||||
```
|
||||
</Step>
|
||||
<Step title="Make your changes">
|
||||
Add, remove, or reorder items as needed
|
||||
</Step>
|
||||
<Step title="Save the file">
|
||||
Cline automatically detects and uses your updates
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## File Structure
|
||||
|
||||
### Todo List Storage
|
||||
|
||||
Todo lists are stored as markdown files in your task directory:
|
||||
|
||||
``` markdown
|
||||
<VSCode Global Storage>/
|
||||
tasks/
|
||||
<taskId>/
|
||||
focus_chain_taskid_<taskId>.md
|
||||
... other task files
|
||||
```
|
||||
|
||||
### Markdown Format
|
||||
|
||||
Todo files use standard markdown checklist syntax:
|
||||
|
||||
```markdown Example Todo Syntax
|
||||
# Focus Chain Todo List for Task abc123
|
||||
|
||||
<!-- Edit this markdown file to update your focus chain todo list -->
|
||||
<!-- Use the format: - [ ] for incomplete items and - [x] for completed items -->
|
||||
|
||||
- [x] Set up project structure
|
||||
- [x] Install authentication dependencies
|
||||
- [ ] Create user registration component
|
||||
- [ ] Implement login functionality
|
||||
- [ ] Add password validation
|
||||
- [ ] Set up user database schema
|
||||
- [ ] Write authentication tests
|
||||
- [ ] Deploy to staging environment
|
||||
|
||||
<!-- Save this file and the todo list will be updated in the task -->
|
||||
```
|
||||
|
||||
|
||||
## Integration with Plan/Act Mode
|
||||
|
||||
Focus Chain works seamlessly with Cline's [Plan/Act mode](/features/plan-and-act):
|
||||
|
||||
- **Plan Mode**: Optional todo lists for presenting concrete steps
|
||||
- **Act Mode**: Automatic todo creation when switching from Plan Mode
|
||||
|
||||
<Tip>
|
||||
For complex projects, start in Plan Mode to discuss and refine your approach before switching to Act Mode for implementation.
|
||||
</Tip>
|
||||
|
||||
## Best Practices
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="For Effective Todo Lists">
|
||||
1. **Start with Clear Requests**
|
||||
- Provide detailed initial task descriptions
|
||||
- Include specific requirements and constraints
|
||||
- Mention any preferred technologies or approaches
|
||||
|
||||
2. **Review Generated Lists**
|
||||
- Check that Cline's breakdown aligns with your expectations
|
||||
- Verify that all important steps are included
|
||||
- Ensure the order makes sense for your project
|
||||
|
||||
3. **Edit When Needed**
|
||||
- Add missing steps you identify
|
||||
- Remove unnecessary items
|
||||
- Reorder steps for better workflow
|
||||
- Add more specific details to general items
|
||||
</Accordion>
|
||||
<Accordion title="For Complex Projects">
|
||||
1. **Use Plan Mode First**
|
||||
- Discuss the approach before implementation
|
||||
- Refine requirements through conversation
|
||||
- Switch to Act Mode when ready to begin work
|
||||
|
||||
2. **Break Down Large Tasks**
|
||||
- Split complex projects into smaller, manageable tasks
|
||||
- Create separate todo lists for different components
|
||||
- Focus on one major area at a time
|
||||
|
||||
3. **Regular Reviews**
|
||||
- Check progress periodically during long tasks
|
||||
- Update todo lists as requirements evolve
|
||||
- Communicate changes to Cline through edits
|
||||
</Accordion>
|
||||
<Accordion title="For Collaboration">
|
||||
1. **Share Todo Files**
|
||||
- Todo markdown files can be shared with team members
|
||||
- Include in version control for project documentation
|
||||
- Use as basis for project planning discussions
|
||||
|
||||
2. **Consistent Format**
|
||||
- Follow the standard markdown checklist format
|
||||
- Keep item descriptions clear and actionable
|
||||
- Use consistent terminology across todo lists
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Having issues? Try these quick fixes:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Todo list not updating?">
|
||||
- Check that Focus Chain is enabled in settings
|
||||
- Focus Chain may not work as well with smaller, less capable models
|
||||
- Ensure file permissions are correct in the task directory
|
||||
</Accordion>
|
||||
<Accordion title="Can't edit todo file?">
|
||||
- Verify your editor supports markdown
|
||||
- Check VSCode has write permissions for the directory
|
||||
</Accordion>
|
||||
<Accordion title="Progress not displaying?">
|
||||
- Ensure todo items use correct syntax (`- [ ]` and `- [x]`)
|
||||
- Verify the markdown file is properly formatted
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Still stuck? Use the [/reportbug](/features/slash-commands/report-bug) command in Cline to get help.
|
||||
|
||||
## Technical Details (for the curious)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="File Monitoring">
|
||||
- Real-time file watching detects changes to todo markdown files
|
||||
- Automatic synchronization between file edits and UI updates
|
||||
- Graceful handling of file creation, modification, and deletion
|
||||
</Accordion>
|
||||
<Accordion title="Progress Calculation">
|
||||
- Dynamic counting of completed vs. total todo items
|
||||
- Support for both `- [x]` and `- [X]` completion syntax
|
||||
- Unicode symbols (✓, ○) for enhanced visual display
|
||||
</Accordion>
|
||||
<Accordion title="Privacy Considerations">
|
||||
- Todo lists stored locally in VSCode workspace
|
||||
- No todo content transmitted to external services
|
||||
- Usage telemetry (can be disabled in settings)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
Focus Chain turns Cline into your personal project manager, keeping you on track and your tasks organized. Give it a try on your next project!
|
||||
@@ -1,153 +0,0 @@
|
||||
---
|
||||
title: "Deep Planning Command"
|
||||
sidebarTitle: "/deep-planning"
|
||||
---
|
||||
|
||||
`/deep-planning` transforms Cline into a meticulous architect who investigates your codebase, asks clarifying questions, and creates a comprehensive implementation plan before writing a single line of code.
|
||||
|
||||
When you use `/deep-planning`, Cline follows a four-step process that mirrors how senior developers approach complex features: thorough investigation, discussion & clarification of requirements, detailed planning, and structured task creation with progress tracking.
|
||||
|
||||
## The Four-Step Process
|
||||
|
||||
### Step 1: Silent Investigation
|
||||
|
||||
Cline becomes a detective, silently exploring your codebase to understand its structure, patterns, and constraints. He examines source files, analyzes import patterns, discovers class hierarchies, and identifies technical debt markers. No commentary, no narration - just focused research.
|
||||
|
||||
During this phase, Cline runs commands like:
|
||||
- Finding all class and function definitions across your codebase
|
||||
- Analyzing import patterns to understand dependencies
|
||||
- Discovering project structure and file organization
|
||||
- Identifying TODOs and technical debt
|
||||
|
||||
### Step 2: Discussion and Questions
|
||||
|
||||
Once Cline understands your codebase, he asks targeted questions that will shape the implementation. These aren't generic questions - they're specific to your project and the feature you're building.
|
||||
|
||||
Questions might cover:
|
||||
- Clarifying ambiguous requirements
|
||||
- Choosing between equally valid implementation approaches
|
||||
- Confirming assumptions about system behavior
|
||||
- Understanding preferences for technical decisions
|
||||
|
||||
### Step 3: Implementation Plan Document
|
||||
|
||||
Cline creates a structured markdown document (`implementation_plan.md`) that serves as your implementation blueprint. This isn't a vague outline - it's a detailed specification with exact file paths, function signatures, and implementation order.
|
||||
|
||||
The plan includes eight comprehensive sections:
|
||||
- **Overview**: The goal and high-level approach
|
||||
- **Types**: Complete type definitions and data structures
|
||||
- **Files**: Exact files to create, modify, or delete
|
||||
- **Functions**: New and modified functions with signatures
|
||||
- **Classes**: Class modifications and inheritance details
|
||||
- **Dependencies**: Package requirements and versions
|
||||
- **Testing**: Validation strategies and test requirements
|
||||
- **Implementation Order**: Step-by-step execution sequence
|
||||
|
||||
### Step 4: Implementation Task Creation
|
||||
|
||||
Cline creates a new task that references the plan document and includes trackable implementation steps. The task comes with specific commands to read each section of the plan, ensuring the implementing agent (whether that's you or Cline in Act Mode) can navigate the blueprint efficiently.
|
||||
|
||||
<Tip>
|
||||
Deep Planning works beautifully with [Focus Chain](/features/focus-chain). The implementation steps automatically become a todo list with real-time progress tracking, keeping complex projects organized and on track.
|
||||
</Tip>
|
||||
|
||||
## Using Deep Planning
|
||||
|
||||
Start a deep planning session by typing `/deep-planning` followed by your feature description:
|
||||
|
||||
```
|
||||
/deep-planning Add user authentication with JWT tokens and role-based access control
|
||||
```
|
||||
|
||||
Cline will begin his investigation immediately. You'll see him reading files and running commands to understand your codebase. Once he's gathered enough context, he'll engage you in discussion before creating the plan.
|
||||
|
||||
## Example Workflow
|
||||
|
||||
Here's how I use `/deep-planning` for a real feature:
|
||||
|
||||
<Steps>
|
||||
<Step title="Initiate Planning">
|
||||
I type `/deep-planning implement a caching layer for API responses`
|
||||
</Step>
|
||||
<Step title="Silent Investigation">
|
||||
Cline explores my codebase, examining:
|
||||
- Current API structure and endpoints
|
||||
- Existing data flow patterns
|
||||
- Database queries and performance bottlenecks
|
||||
- Configuration and environment setup
|
||||
</Step>
|
||||
<Step title="Targeted Discussion">
|
||||
Cline asks me:
|
||||
- "Should we use Redis or in-memory caching?"
|
||||
- "What's the acceptable cache staleness for user data?"
|
||||
- "Do you need cache invalidation webhooks?"
|
||||
</Step>
|
||||
<Step title="Plan Creation">
|
||||
Cline generates `implementation_plan.md` with:
|
||||
- Cache service class specifications
|
||||
- Redis connection configuration
|
||||
- Modified API endpoints with caching logic
|
||||
- Cache key generation strategies
|
||||
- TTL configurations for different data types
|
||||
</Step>
|
||||
<Step title="Task Generation">
|
||||
Cline creates a new task with:
|
||||
- Reference to the implementation plan
|
||||
- Commands to read specific sections
|
||||
- Trackable todo items for each implementation step
|
||||
- Request to switch to Act Mode for execution
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Integration with Plan/Act Mode
|
||||
|
||||
Deep Planning is designed to work seamlessly with [Plan/Act Mode](/features/plan-and-act):
|
||||
|
||||
- Use `/deep-planning` in Plan Mode for the investigation and planning phases
|
||||
- The generated task requests switching to Act Mode for implementation
|
||||
- Focus Chain automatically tracks progress through the implementation steps
|
||||
|
||||
This separation ensures planning stays focused on architecture while implementation stays focused on execution.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Deep Planning
|
||||
|
||||
Use `/deep-planning` for:
|
||||
- Features touching multiple parts of your codebase
|
||||
- Architectural changes requiring careful coordination
|
||||
- Complex integrations with external services
|
||||
- Refactoring efforts that need systematic execution
|
||||
- Any feature where you'd normally spend time whiteboarding
|
||||
|
||||
### Making the Most of Investigation
|
||||
|
||||
Let Cline complete his investigation thoroughly. The quality of the plan directly correlates with how well he understands your codebase. If you have specific areas he should examine, mention them in your initial request.
|
||||
|
||||
### Reviewing the Plan
|
||||
|
||||
Always review `implementation_plan.md` before starting implementation. The plan is comprehensive but not immutable - you can edit it directly if needed. Think of it as a collaborative document between you and Cline.
|
||||
|
||||
### Tracking Progress
|
||||
|
||||
With Focus Chain enabled, your implementation progress displays in the task header. Each completed step gets checked off automatically as Cline works through the plan, giving you real-time visibility into complex implementations.
|
||||
|
||||
## Inspiration
|
||||
|
||||
I use `/deep-planning` whenever I'm about to build something that would normally require a design document. Recent examples from my workflow:
|
||||
|
||||
- **Migrating authentication systems**: Deep Planning mapped every endpoint, identified all authentication touchpoints, and created a migration plan that avoided breaking changes.
|
||||
|
||||
- **Adding real-time features**: The plan covered WebSocket integration, event handling, state synchronization, and fallback mechanisms for disconnections.
|
||||
|
||||
- **Database schema refactoring**: Cline identified all affected queries, created migration scripts, and planned the rollout to minimize downtime.
|
||||
|
||||
- **API versioning implementation**: The plan detailed route changes, backward compatibility layers, deprecation notices, and client migration paths.
|
||||
|
||||
The power of `/deep-planning` is that it forces thoughtful architecture before implementation. It's like having a senior developer review your approach before you write code, except that developer has perfect knowledge of your entire codebase.
|
||||
|
||||
<Note>
|
||||
Deep Planning requires models with strong reasoning capabilities. It works best with the latest generation of models, like GPT-5, Claude 4, Gemini 2.5, or Grok 4. Smaller models may struggle with the comprehensive analysis required.
|
||||
</Note>
|
||||
|
||||
For simpler tasks that don't require extensive planning, consider using [/newtask](/features/slash-commands/new-task) to create focused tasks with context, or jump straight into implementation if the path forward is clear.
|
||||
@@ -55,8 +55,7 @@ Think of context like a whiteboard you and Cline share:
|
||||
- Each model has a fixed size:
|
||||
- Claude 3.5 Sonnet: 200,000 tokens
|
||||
- DeepSeek: 64,000 tokens
|
||||
- When the whiteboard is full, Cline automatically summarizes the conversation to free up space
|
||||
- [Learn about Automatic Context Summarization](/features/automatic-context-summarization)
|
||||
- When the whiteboard is full, you need to erase (clear context) to write more
|
||||
- [How Cline manages context under the hood](https://cline.bot/blog/understanding-the-new-context-window-progress-bar-in-cline)
|
||||
|
||||
⚠️ **Important**: Having a large context window (like Claude's 200k tokens) doesn't mean you should fill it completely. Just like a cluttered whiteboard, too much information can make it harder to focus on what's important.
|
||||
@@ -86,28 +85,7 @@ Cline provides a visual way to monitor your context window usage through a progr
|
||||
- Before starting complex tasks
|
||||
- When Cline seems to lose context
|
||||
|
||||
💡 **Tip**: With [Automatic Context Summarization](/features/automatic-context-summarization), Cline can now handle long conversations automatically. When combined with [Focus Chain](/features/focus-chain), you can work on complex projects that span multiple context windows without losing progress.
|
||||
|
||||
## Automatic Context Management
|
||||
|
||||
Cline now includes intelligent features to manage context automatically:
|
||||
|
||||
### Automatic Context Summarization
|
||||
|
||||
When your conversation approaches the context window limit, Cline automatically:
|
||||
- Creates a comprehensive summary of the conversation
|
||||
- Preserves all essential technical details and decisions
|
||||
- Seamlessly continues work without interruption
|
||||
- Maintains full task continuity
|
||||
|
||||
This means you can work on larger projects without manually managing context. [Learn more about Automatic Context Summarization](/features/automatic-context-summarization).
|
||||
|
||||
### Focus Chain Integration
|
||||
|
||||
When combined with [Focus Chain](/features/focus-chain), Cline can:
|
||||
- Maintain todo lists across context resets
|
||||
- Track progress through multiple summarizations
|
||||
- Keep working on long-horizon tasks without losing direction
|
||||
💡 **Tip**: Consider starting a fresh session when usage reaches 70-80% to maintain optimal performance.
|
||||
|
||||
## Working with Context Files
|
||||
|
||||
|
||||
+6
-7
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: "API Key (Simple Setup)"
|
||||
sidebarTitle: "API Key"
|
||||
description: "Set up AWS Bedrock with Cline using Bedrock API Keys. Simplest setup for individual developers to access frontier models."
|
||||
title: "AWS Bedrock"
|
||||
description: "Learn how to set up AWS Bedrock with Cline using credentials authentication. This guide covers AWS environment setup, regional access verification, and secure integration with the Cline VS Code extension."
|
||||
---
|
||||
|
||||
### Overview
|
||||
@@ -122,14 +121,14 @@ You can create a custom IAM policy with these permissions and attach it to your
|
||||
|
||||
### Conclusion
|
||||
|
||||
By following these steps, you can quickly integrate AWS Bedrock with the Cline VS Code extension to accelerate development:
|
||||
By following these steps, your enterprise team can securely integrate AWS Bedrock with the Cline VS Code extension to accelerate development:
|
||||
|
||||
1. **Prepare Your AWS Environment:** Create a Bedrock API Key with the necessary permissions.
|
||||
1. **Prepare Your AWS Environment:** Create or use a secure IAM role/user, attach the `AmazonBedrockLimitedAccess` policy, and ensure necessary permissions.
|
||||
2. **Verify Region and Model Access:** Confirm that your selected region supports your required models.
|
||||
3. **Configure Cline in VS Code:** Install and set up Cline with your AWS API Key and choose an appropriate model.
|
||||
3. **Configure Cline in VS Code:** Install and set up Cline with your AWS credentials and choose an appropriate model.
|
||||
4. **Implement Security and Monitoring:** Use best practices for IAM, network security, monitoring, and cost management.
|
||||
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html). Happy coding!
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your internal cloud team. Happy coding!
|
||||
|
||||
---
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: "IAM Credentials"
|
||||
sidebarTitle: "IAM Credentials"
|
||||
description: "Set up AWS Bedrock with Cline using IAM Access Key and Secret Key credentials. Best for enterprise environments with established IAM policies."
|
||||
title: "AWS Bedrock"
|
||||
description: "Learn how to set up AWS Bedrock with Cline using credentials authentication. This guide covers AWS environment setup, regional access verification, and secure integration with the Cline VS Code extension."
|
||||
---
|
||||
|
||||
### Overview
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: "CLI Profile (SSO)"
|
||||
sidebarTitle: "CLI Profile (SSO)"
|
||||
description: "Configure AWS Bedrock to use AWS CLI profiles for authentication with Cline. Best for SSO/federated roles and secure enterprise access."
|
||||
title: "AWS Bedrock w/ Profile Authentication"
|
||||
description: "Learn how to configure AWS Bedrock to use AWS Profiles for authentication with Cline, focusing on SSO/Federated roles for secure access."
|
||||
---
|
||||
|
||||
### Overview
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs"
|
||||
import * as esbuild from "esbuild"
|
||||
import path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import * as esbuild from "esbuild"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
@@ -170,7 +170,7 @@ const standaloneConfig = {
|
||||
const e2eBuildConfig = {
|
||||
...baseConfig,
|
||||
entryPoints: ["src/test/e2e/utils/build.ts"],
|
||||
outfile: `${destDir}/e2e-build.mjs`,
|
||||
outfile: `${destDir}/e2e-build.js`,
|
||||
external: ["@vscode/test-electron", "execa"],
|
||||
sourcemap: false,
|
||||
plugins: [aliasResolverPlugin, esbuildProblemMatcherPlugin],
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { OpenRouterHandler } from "../../src/api/providers/openrouter"
|
||||
import { OpenAiNativeHandler } from "../../src/api/providers/openai-native"
|
||||
import { ApiHandlerOptions } from "../../src/shared/api"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import {
|
||||
parseAssistantMessageV2,
|
||||
parseAssistantMessageV3,
|
||||
AssistantMessageContent,
|
||||
} from "./parsing/parse-assistant-message-06-06-25" // "../../src/core/assistant-message"
|
||||
import { constructNewFileContent as constructNewFileContent_06_06_25 } from "./diff-apply/diff-06-06-25"
|
||||
@@ -16,6 +18,7 @@ type ConstructNewFileContentFn = (diff: string, original: string, strict: boolea
|
||||
|
||||
const parsingFunctions: Record<string, ParseAssistantMessageFn> = {
|
||||
parseAssistantMessageV2: parseAssistantMessageV2,
|
||||
parseAssistantMessageV3: parseAssistantMessageV3,
|
||||
}
|
||||
|
||||
const diffEditingFunctions: Record<string, ConstructNewFileContentFn> = {
|
||||
|
||||
@@ -304,3 +304,621 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
export function parseAssistantMessageV3(assistantMessage: string): AssistantMessageContent[] {
|
||||
const contentBlocks: AssistantMessageContent[] = []
|
||||
let currentTextContentStart = 0 // Index where the current text block started
|
||||
let currentTextContent: TextContent | undefined = undefined
|
||||
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
|
||||
let currentToolUse: ToolUse | undefined = undefined
|
||||
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
|
||||
let currentParamName: ToolParamName | undefined = undefined
|
||||
|
||||
// Precompute tags for faster lookups
|
||||
const toolUseOpenTags = new Map<string, ToolUseName>()
|
||||
const toolParamOpenTags = new Map<string, ToolParamName>()
|
||||
for (const name of toolUseNames) {
|
||||
toolUseOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
for (const name of toolParamNames) {
|
||||
toolParamOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
|
||||
// Function calls format detection
|
||||
const isFunctionCallsOpen = "<function_calls>"
|
||||
const isFunctionCallsClose = "</function_calls>"
|
||||
const isInvokeStart = '<invoke name="'
|
||||
const isInvokeEnd = '">'
|
||||
const isInvokeClose = "</invoke>"
|
||||
const isParameterStart = '<parameter name="'
|
||||
const isParameterNameEnd = '">'
|
||||
const isParameterClose = "</parameter>"
|
||||
|
||||
// Variables for function calls parsing
|
||||
let inFunctionCalls = false
|
||||
let currentInvokeName = ""
|
||||
let currentParameterName = ""
|
||||
|
||||
const len = assistantMessage.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
const currentCharIndex = i
|
||||
|
||||
// --- State: Parsing Function Calls ---
|
||||
// Check for opening function_calls tag
|
||||
if (
|
||||
!inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsOpen.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsOpen, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
.trim()
|
||||
currentTextContent.partial = false
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
}
|
||||
|
||||
inFunctionCalls = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke start within function_calls
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName === "" &&
|
||||
!currentToolUse && // Don't create a new tool if we already have one
|
||||
currentCharIndex >= isInvokeStart.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeStart, currentCharIndex - isInvokeStart.length + 1)
|
||||
) {
|
||||
// Find the end of the invoke name
|
||||
const nameEndPos = assistantMessage.indexOf(isInvokeEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the invoke name
|
||||
currentInvokeName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
i = nameEndPos + isInvokeEnd.length - 1 // Skip to after the '">
|
||||
|
||||
// If this is an LS invoke, create a list_files tool
|
||||
if (currentInvokeName === "LS") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a Grep invoke, create a search_files tool
|
||||
if (currentInvokeName === "Grep") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "search_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Bash") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "execute_command",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Read") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "read_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Write") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "write_to_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "WebFetch") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "web_fetch",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AskQuestion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "ask_followup_question",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "UseMCPTool") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "use_mcp_tool",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AccessMCPResource") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "access_mcp_resource",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "ListCodeDefinitionNames") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_code_definition_names",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "PlanModeRespond") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "plan_mode_respond",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "LoadMcpDocumentation") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "load_mcp_documentation",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AttemptCompletion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "attempt_completion",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "BrowserAction") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "browser_action",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "NewTask") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "new_task",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a MultiEdit invoke, create a replace_in_file tool
|
||||
if (currentInvokeName === "MultiEdit") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "replace_in_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter start within invoke
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName === "" &&
|
||||
currentCharIndex >= isParameterStart.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterStart, currentCharIndex - isParameterStart.length + 1)
|
||||
) {
|
||||
// Find the end of the parameter name
|
||||
const nameEndPos = assistantMessage.indexOf(isParameterNameEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the parameter name
|
||||
currentParameterName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
currentParamValueStart = nameEndPos + isParameterNameEnd.length
|
||||
i = nameEndPos + isParameterNameEnd.length - 1 // Skip to after the '">'
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName !== "" &&
|
||||
currentCharIndex >= isParameterClose.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterClose, currentCharIndex - isParameterClose.length + 1)
|
||||
) {
|
||||
// Extract parameter value
|
||||
const value = assistantMessage.slice(currentParamValueStart, currentCharIndex - isParameterClose.length + 1).trim()
|
||||
|
||||
// Map parameter to tool params
|
||||
if (currentToolUse && currentInvokeName === "LS" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
// Default recursive to false - only show top level
|
||||
currentToolUse.params["recursive"] = "false"
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Read" && currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "PlanModeRespond" && currentParameterName === "response") {
|
||||
currentToolUse.params["response"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "WebFetch" && currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "ListCodeDefinitionNames" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "NewTask" && currentParameterName === "context") {
|
||||
currentToolUse.params["context"] = value
|
||||
}
|
||||
|
||||
// Map parameter to tool params for Grep
|
||||
if (currentToolUse && currentInvokeName === "Grep") {
|
||||
if (currentParameterName === "pattern") {
|
||||
currentToolUse.params["regex"] = value
|
||||
} else if (currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "include") {
|
||||
currentToolUse.params["file_pattern"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Bash") {
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
} else if (currentParameterName === "requires_approval") {
|
||||
currentToolUse.params["requires_approval"] = value === "true" ? "true" : "false"
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Write") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "content") {
|
||||
currentToolUse.params["content"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AskQuestion") {
|
||||
if (currentParameterName === "question") {
|
||||
currentToolUse.params["question"] = value
|
||||
} else if (currentParameterName === "options") {
|
||||
currentToolUse.params["options"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "UseMCPTool") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "tool_name") {
|
||||
currentToolUse.params["tool_name"] = value
|
||||
} else if (currentParameterName === "arguments") {
|
||||
currentToolUse.params["arguments"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AccessMCPResource") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "uri") {
|
||||
currentToolUse.params["uri"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AttemptCompletion") {
|
||||
if (currentParameterName === "result") {
|
||||
currentToolUse.params["result"] = value
|
||||
}
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "BrowserAction") {
|
||||
if (currentParameterName === "action") {
|
||||
currentToolUse.params["action"] = value
|
||||
} else if (currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
} else if (currentParameterName === "coordinate") {
|
||||
currentToolUse.params["coordinate"] = value
|
||||
} else if (currentParameterName === "text") {
|
||||
currentToolUse.params["text"] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Map parameter to tool params for MultiEdit
|
||||
if (currentToolUse && currentInvokeName === "MultiEdit") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "edits") {
|
||||
// Save the value to the diff parameter for replace_in_file
|
||||
currentToolUse.params["diff"] = value
|
||||
}
|
||||
}
|
||||
|
||||
currentParameterName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentCharIndex >= isInvokeClose.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeClose, currentCharIndex - isInvokeClose.length + 1)
|
||||
) {
|
||||
// If we have a tool use from this invoke, finalize it
|
||||
if (
|
||||
currentToolUse &&
|
||||
(currentInvokeName === "LS" ||
|
||||
currentInvokeName === "Grep" ||
|
||||
currentInvokeName === "Bash" ||
|
||||
currentInvokeName === "Read" ||
|
||||
currentInvokeName === "Write" ||
|
||||
currentInvokeName === "WebFetch" ||
|
||||
currentInvokeName === "AskQuestion" ||
|
||||
currentInvokeName === "UseMCPTool" ||
|
||||
currentInvokeName === "AccessMCPResource" ||
|
||||
currentInvokeName === "ListCodeDefinitionNames" ||
|
||||
currentInvokeName === "PlanModeRespond" ||
|
||||
currentInvokeName === "LoadMcpDocumentation" ||
|
||||
currentInvokeName === "AttemptCompletion" ||
|
||||
currentInvokeName === "BrowserAction" ||
|
||||
currentInvokeName === "NewTask" ||
|
||||
currentInvokeName === "MultiEdit")
|
||||
) {
|
||||
currentToolUse.partial = false
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined
|
||||
}
|
||||
currentInvokeName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for function_calls end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsClose.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsClose, currentCharIndex - isFunctionCallsClose.length + 1)
|
||||
) {
|
||||
inFunctionCalls = false
|
||||
currentTextContentStart = currentCharIndex + 1
|
||||
// Start a new text content block for any text after function_calls
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "",
|
||||
partial: true,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip normal parsing when inside function_calls
|
||||
if (inFunctionCalls) {
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Parameter ---
|
||||
if (currentToolUse && currentParamName) {
|
||||
const closeTag = `</${currentParamName}>`
|
||||
// Check if the string *ending* at index `i` matches the closing tag
|
||||
if (
|
||||
currentCharIndex >= closeTag.length - 1 &&
|
||||
assistantMessage.startsWith(
|
||||
closeTag,
|
||||
currentCharIndex - closeTag.length + 1, // Start checking from potential start of tag
|
||||
)
|
||||
) {
|
||||
// Found the closing tag for the parameter
|
||||
const value = assistantMessage
|
||||
.slice(
|
||||
currentParamValueStart, // Start after the opening tag
|
||||
currentCharIndex - closeTag.length + 1, // End before the closing tag
|
||||
)
|
||||
.trim()
|
||||
currentToolUse.params[currentParamName] = value
|
||||
currentParamName = undefined // Go back to parsing tool content
|
||||
// We don't continue loop here, need to check for tool close or other params at index i
|
||||
} else {
|
||||
continue // Still inside param value, move to next char
|
||||
}
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Use (but not a specific parameter) ---
|
||||
if (currentToolUse && !currentParamName) {
|
||||
// Ensure we are not inside a parameter already
|
||||
// Check if starting a new parameter
|
||||
let startedNewParam = false
|
||||
for (const [tag, paramName] of toolParamOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
currentParamName = paramName
|
||||
currentParamValueStart = currentCharIndex + 1 // Value starts after the tag
|
||||
startedNewParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (startedNewParam) {
|
||||
continue // Handled start of param, move to next char
|
||||
}
|
||||
|
||||
// Check if closing the current tool use
|
||||
const toolCloseTag = `</${currentToolUse.name}>`
|
||||
if (
|
||||
currentCharIndex >= toolCloseTag.length - 1 &&
|
||||
assistantMessage.startsWith(toolCloseTag, currentCharIndex - toolCloseTag.length + 1)
|
||||
) {
|
||||
// End of the tool use found
|
||||
// Special handling for content params *before* finalizing the tool
|
||||
const toolContentSlice = assistantMessage.slice(
|
||||
currentToolUseStart, // From after the tool opening tag
|
||||
currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag
|
||||
)
|
||||
|
||||
// Check if content parameter needs special handling (write_to_file/new_rule)
|
||||
// This check is important if the closing </content> tag was missed by the parameter parsing logic
|
||||
// (e.g., if content is empty or parsing logic prioritizes tool close)
|
||||
const contentParamName: ToolParamName = "content"
|
||||
if (
|
||||
currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */ &&
|
||||
toolContentSlice.includes(`<${contentParamName}>`)
|
||||
) {
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStart = toolContentSlice.indexOf(contentStartTag)
|
||||
// Use lastIndexOf for robustness against nested tags
|
||||
const contentEnd = toolContentSlice.lastIndexOf(contentEndTag)
|
||||
|
||||
if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) {
|
||||
const contentValue = toolContentSlice.slice(contentStart + contentStartTag.length, contentEnd).trim()
|
||||
currentToolUse.params[contentParamName] = contentValue
|
||||
}
|
||||
}
|
||||
|
||||
currentToolUse.partial = false // Mark as complete
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined // Reset state
|
||||
currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag
|
||||
continue // Move to next char
|
||||
}
|
||||
// If not starting a param and not closing the tool, continue accumulating tool content implicitly
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing Text / Looking for Tool Start ---
|
||||
if (!currentToolUse) {
|
||||
// Check if starting a new tool use
|
||||
let startedNewTool = false
|
||||
for (const [tag, toolName] of toolUseOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
currentTextContent.partial = false // Ended because tool started
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
} else {
|
||||
// Check for any text between the last block and this tag
|
||||
const potentialText = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text *might* have started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
if (potentialText.length > 0) {
|
||||
contentBlocks.push({
|
||||
type: "text",
|
||||
content: potentialText,
|
||||
partial: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Start the new tool use
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: {},
|
||||
partial: true, // Assume partial until closing tag is found
|
||||
}
|
||||
currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag
|
||||
startedNewTool = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (startedNewTool) {
|
||||
continue // Handled start of tool, move to next char
|
||||
}
|
||||
|
||||
// If not starting a tool, it must be text content
|
||||
if (!currentTextContent) {
|
||||
// Start a new text block if we aren't already in one
|
||||
currentTextContentStart = currentCharIndex // Text starts at the current character
|
||||
// Check if the current char is the start of potential text *immediately* after a tag
|
||||
// This needs the previous state - simpler to let slicing handle it later.
|
||||
// Resetting start index accurately is key.
|
||||
// It should be the index *after* the last processed tag.
|
||||
// The logic managing currentTextContentStart after closing tags handles this.
|
||||
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "", // Will be determined by slicing at the end or when a tool starts
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
// Continue accumulating text implicitly; content is extracted later.
|
||||
}
|
||||
} // End of loop
|
||||
|
||||
// --- Finalization after loop ---
|
||||
|
||||
// Finalize any open parameter within an open tool use
|
||||
if (currentToolUse && currentParamName) {
|
||||
currentToolUse.params[currentParamName] = assistantMessage
|
||||
.slice(currentParamValueStart) // From param start to end of string
|
||||
.trim()
|
||||
// Tool use remains partial
|
||||
}
|
||||
|
||||
// Finalize any open tool use (which might contain the finalized partial param)
|
||||
if (currentToolUse) {
|
||||
// Tool use is partial because the loop finished before its closing tag
|
||||
contentBlocks.push(currentToolUse)
|
||||
}
|
||||
// Finalize any trailing text content
|
||||
// Only possible if a tool use wasn't open at the very end
|
||||
else if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart) // From text start to end of string
|
||||
.trim()
|
||||
// Text is partial because the loop finished
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
}
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.25.0",
|
||||
"version": "3.23.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.25.0",
|
||||
"version": "3.23.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.25.2",
|
||||
"version": "3.23.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -362,8 +362,8 @@
|
||||
"test:unit": "TS_NODE_PROJECT='./tsconfig.unit-test.json' mocha",
|
||||
"test:coverage": "vscode-test --coverage",
|
||||
"e2e": "playwright test -c playwright.config.ts",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.mjs && playwright test",
|
||||
"test:e2e": "playwright install && vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"test:e2e:optimal": "vsce package --no-dependencies --allow-package-secrets sendgrid --out dist/e2e.vsix && node src/test/e2e/utils/build.js && playwright test",
|
||||
"install:all": "npm install && cd webview-ui && npm install",
|
||||
"dev:webview": "cd webview-ui && npm run dev",
|
||||
"build:webview": "cd webview-ui && npm run build",
|
||||
|
||||
@@ -6,25 +6,26 @@ const isWindow = process?.platform?.startsWith("win")
|
||||
export default defineConfig({
|
||||
workers: 1,
|
||||
retries: 1,
|
||||
forbidOnly: isCI,
|
||||
testDir: "src/test/e2e",
|
||||
testMatch: /.*\.test\.ts/,
|
||||
timeout: isCI || isWindow ? 40000 : 20000,
|
||||
expect: {
|
||||
timeout: isCI || isWindow ? 5000 : 2000,
|
||||
},
|
||||
fullyParallel: true,
|
||||
reporter: isCI ? [["github"], ["list"]] : [["list"]],
|
||||
use: {
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "setup test environment",
|
||||
testMatch: /global\.setup\.ts/,
|
||||
teardown: "cleanup test environment",
|
||||
},
|
||||
{
|
||||
name: "cleanup test environment",
|
||||
testMatch: /global\.teardown\.ts/,
|
||||
},
|
||||
{
|
||||
name: "e2e tests",
|
||||
testMatch: /.*\.test\.ts/,
|
||||
dependencies: ["setup test environment"],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -36,8 +36,6 @@ service AccountService {
|
||||
rpc getUserOrganizations(EmptyRequest) returns (UserOrganizationsResponse);
|
||||
|
||||
rpc setUserOrganization(UserOrganizationUpdateRequest) returns (Empty);
|
||||
|
||||
rpc openrouterAuthClicked(EmptyRequest) returns (Empty);
|
||||
}
|
||||
|
||||
message AuthStateChangedRequest {
|
||||
|
||||
@@ -42,7 +42,6 @@ message BrowserSettings {
|
||||
optional bool remote_browser_enabled = 3;
|
||||
optional string chrome_executable_path = 4;
|
||||
optional bool disable_tool_use = 5;
|
||||
optional string custom_args = 6;
|
||||
}
|
||||
|
||||
message UpdateBrowserSettingsRequest {
|
||||
@@ -52,5 +51,4 @@ message UpdateBrowserSettingsRequest {
|
||||
optional bool remote_browser_enabled = 4;
|
||||
optional string chrome_executable_path = 5;
|
||||
optional bool disable_tool_use = 6;
|
||||
optional string custom_args = 7;
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package cline;
|
||||
import "cline/common.proto";
|
||||
option java_package = "bot.cline.proto";
|
||||
option java_multiple_files = true;
|
||||
|
||||
// Service for running IDE commands, for example context menu actions,
|
||||
// commands, etc.
|
||||
// In contrast to the rest of the ProtoBus services, these are
|
||||
// intended to be called by the IDE directly instead of through the webview,
|
||||
// because they are triggered by interactions in the IDE.
|
||||
service CommandsService {
|
||||
rpc addToCline(CommandContext) returns (Empty);
|
||||
rpc fixWithCline(CommandContext) returns (Empty);
|
||||
rpc explainWithCline(CommandContext) returns (Empty);
|
||||
rpc improveWithCline(CommandContext) returns (Empty);
|
||||
}
|
||||
|
||||
message CommandContext {
|
||||
// The absolute path of the current file.
|
||||
optional string file_path = 1;
|
||||
// The selected source text.
|
||||
optional string selected_text = 2;
|
||||
// The language identifier for the current file.
|
||||
optional string language = 3;
|
||||
// Any diagnostic problems for the current file.
|
||||
repeated cline.Diagnostic diagnostics = 4;
|
||||
}
|
||||
@@ -73,32 +73,3 @@ message KeyValuePair {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message FileDiagnostics {
|
||||
string file_path = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
string message = 1;
|
||||
DiagnosticRange range = 2;
|
||||
DiagnosticSeverity severity = 3;
|
||||
optional string source = 4;
|
||||
}
|
||||
|
||||
message DiagnosticRange {
|
||||
DiagnosticPosition start = 1;
|
||||
DiagnosticPosition end = 2;
|
||||
}
|
||||
|
||||
message DiagnosticPosition {
|
||||
int32 line = 1;
|
||||
int32 character = 2;
|
||||
}
|
||||
|
||||
enum DiagnosticSeverity {
|
||||
DIAGNOSTIC_ERROR = 0;
|
||||
DIAGNOSTIC_WARNING = 1;
|
||||
DIAGNOSTIC_INFORMATION = 2;
|
||||
DIAGNOSTIC_HINT = 3;
|
||||
}
|
||||
|
||||
@@ -60,9 +60,6 @@ service FileService {
|
||||
|
||||
// Open a file in editor by a relative path
|
||||
rpc openFileRelativePath(StringRequest) returns (Empty);
|
||||
|
||||
// Opens or creates a focus chain checklist markdown file for editing
|
||||
rpc openFocusChainFile(StringRequest) returns (Empty);
|
||||
}
|
||||
|
||||
// Response for refreshRules operation
|
||||
|
||||
+32
-33
@@ -25,7 +25,7 @@ service ModelsService {
|
||||
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
|
||||
// Updates API configuration
|
||||
rpc updateApiConfigurationProto(UpdateApiConfigurationRequest) returns (Empty);
|
||||
// Refreshes and returns Groq models
|
||||
// Refreshes and returns Groq models
|
||||
rpc refreshGroqModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
// Refreshes and returns Baseten models
|
||||
rpc refreshBasetenModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
|
||||
@@ -205,38 +205,37 @@ message ModelsApiConfiguration {
|
||||
optional string open_ai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string requesty_base_url = 33;
|
||||
optional string together_api_key = 34;
|
||||
optional string fireworks_api_key = 35;
|
||||
optional int32 fireworks_model_max_completion_tokens = 36;
|
||||
optional int32 fireworks_model_max_tokens = 37;
|
||||
optional string qwen_api_key = 38;
|
||||
optional string doubao_api_key = 39;
|
||||
optional string mistral_api_key = 40;
|
||||
optional string azure_api_version = 41;
|
||||
optional string qwen_api_line = 42;
|
||||
optional string nebius_api_key = 43;
|
||||
optional string asksage_api_url = 44;
|
||||
optional string asksage_api_key = 45;
|
||||
optional string xai_api_key = 46;
|
||||
optional string sambanova_api_key = 47;
|
||||
optional string cerebras_api_key = 48;
|
||||
optional int32 request_timeout_ms = 49;
|
||||
optional string sap_ai_core_client_id = 50;
|
||||
optional string sap_ai_core_client_secret = 51;
|
||||
optional string sap_ai_resource_group = 52;
|
||||
optional string sap_ai_core_token_url = 53;
|
||||
optional string sap_ai_core_base_url = 54;
|
||||
optional string moonshot_api_key = 55;
|
||||
optional string moonshot_api_line = 56;
|
||||
optional string aws_authentication = 57;
|
||||
optional string aws_bedrock_api_key = 58;
|
||||
optional string cline_account_id = 59;
|
||||
optional string groq_api_key = 60;
|
||||
optional string hugging_face_api_key = 61;
|
||||
optional string huawei_cloud_maas_api_key = 62;
|
||||
optional string baseten_api_key = 63;
|
||||
optional string ollama_api_key = 64;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int32 fireworks_model_max_completion_tokens = 35;
|
||||
optional int32 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int32 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
optional string aws_authentication = 56;
|
||||
optional string aws_bedrock_api_key = 57;
|
||||
optional string cline_account_id = 58;
|
||||
optional string groq_api_key = 59;
|
||||
optional string hugging_face_api_key = 60;
|
||||
optional string huawei_cloud_maas_api_key = 61;
|
||||
optional string baseten_api_key = 62;
|
||||
optional string ollama_api_key = 63;
|
||||
|
||||
// Plan mode configurations
|
||||
optional ApiProvider plan_mode_api_provider = 100;
|
||||
|
||||
+25
-32
@@ -123,7 +123,6 @@ message UpdateSettingsRequest {
|
||||
optional string preferred_language = 14;
|
||||
optional OpenaiReasoningEffort openai_reasoning_effort = 15;
|
||||
optional bool strict_plan_mode_enabled = 16;
|
||||
optional FocusChainSettings focus_chain_settings = 17;
|
||||
}
|
||||
|
||||
// Complete API Configuration message
|
||||
@@ -161,32 +160,31 @@ message ApiConfiguration {
|
||||
optional string openai_native_api_key = 30;
|
||||
optional string deep_seek_api_key = 31;
|
||||
optional string requesty_api_key = 32;
|
||||
optional string requesty_base_url = 33;
|
||||
optional string together_api_key = 34;
|
||||
optional string fireworks_api_key = 35;
|
||||
optional int32 fireworks_model_max_completion_tokens = 36;
|
||||
optional int32 fireworks_model_max_tokens = 37;
|
||||
optional string qwen_api_key = 38;
|
||||
optional string doubao_api_key = 39;
|
||||
optional string mistral_api_key = 40;
|
||||
optional string azure_api_version = 41;
|
||||
optional string qwen_api_line = 42;
|
||||
optional string nebius_api_key = 43;
|
||||
optional string asksage_api_url = 44;
|
||||
optional string asksage_api_key = 45;
|
||||
optional string xai_api_key = 46;
|
||||
optional string sambanova_api_key = 47;
|
||||
optional string cerebras_api_key = 48;
|
||||
optional int32 request_timeout_ms = 49;
|
||||
optional string sap_ai_core_client_id = 50;
|
||||
optional string sap_ai_core_client_secret = 51;
|
||||
optional string sap_ai_resource_group = 52;
|
||||
optional string sap_ai_core_token_url = 53;
|
||||
optional string sap_ai_core_base_url = 54;
|
||||
optional string moonshot_api_key = 55;
|
||||
optional string moonshot_api_line = 56;
|
||||
optional string huawei_cloud_maas_api_key = 57;
|
||||
optional string ollama_api_key = 58;
|
||||
optional string together_api_key = 33;
|
||||
optional string fireworks_api_key = 34;
|
||||
optional int32 fireworks_model_max_completion_tokens = 35;
|
||||
optional int32 fireworks_model_max_tokens = 36;
|
||||
optional string qwen_api_key = 37;
|
||||
optional string doubao_api_key = 38;
|
||||
optional string mistral_api_key = 39;
|
||||
optional string azure_api_version = 40;
|
||||
optional string qwen_api_line = 41;
|
||||
optional string nebius_api_key = 42;
|
||||
optional string asksage_api_url = 43;
|
||||
optional string asksage_api_key = 44;
|
||||
optional string xai_api_key = 45;
|
||||
optional string sambanova_api_key = 46;
|
||||
optional string cerebras_api_key = 47;
|
||||
optional int32 request_timeout_ms = 48;
|
||||
optional string sap_ai_core_client_id = 49;
|
||||
optional string sap_ai_core_client_secret = 50;
|
||||
optional string sap_ai_resource_group = 51;
|
||||
optional string sap_ai_core_token_url = 52;
|
||||
optional string sap_ai_core_base_url = 53;
|
||||
optional string moonshot_api_key = 54;
|
||||
optional string moonshot_api_line = 55;
|
||||
optional string huawei_cloud_maas_api_key = 56;
|
||||
optional string ollama_api_key = 57;
|
||||
|
||||
// Plan mode configurations
|
||||
optional string plan_mode_api_provider = 100;
|
||||
@@ -250,11 +248,6 @@ message UpdateTerminalConnectionTimeoutRequest {
|
||||
optional int32 timeout_ms = 1;
|
||||
}
|
||||
|
||||
message FocusChainSettings {
|
||||
bool enabled = 1;
|
||||
int32 remind_cline_interval = 2;
|
||||
}
|
||||
|
||||
message UpdateTerminalConnectionTimeoutResponse {
|
||||
optional int32 timeout_ms = 1;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ enum ClineAsk {
|
||||
NEW_TASK = 13;
|
||||
CONDENSE = 14;
|
||||
REPORT_BUG = 15;
|
||||
SUMMARIZE_TASK = 16;
|
||||
}
|
||||
|
||||
// Enum for ClineSay types
|
||||
@@ -73,7 +72,6 @@ enum ClineSay {
|
||||
CHECKPOINT_CREATED = 24;
|
||||
LOAD_MCP_DOCUMENTATION = 25;
|
||||
INFO = 26;
|
||||
TASK_PROGRESS = 27;
|
||||
}
|
||||
|
||||
// Enum for ClineSayTool tool types
|
||||
|
||||
@@ -44,5 +44,34 @@ message GetDiagnosticsRequest {
|
||||
}
|
||||
|
||||
message GetDiagnosticsResponse {
|
||||
repeated cline.FileDiagnostics file_diagnostics = 1;
|
||||
repeated FileDiagnostics file_diagnostics = 1;
|
||||
}
|
||||
|
||||
message FileDiagnostics {
|
||||
string file_path = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
string message = 1;
|
||||
DiagnosticRange range = 2;
|
||||
DiagnosticSeverity severity = 3;
|
||||
optional string source = 4;
|
||||
}
|
||||
|
||||
message DiagnosticRange {
|
||||
DiagnosticPosition start = 1;
|
||||
DiagnosticPosition end = 2;
|
||||
}
|
||||
|
||||
message DiagnosticPosition {
|
||||
int32 line = 1;
|
||||
int32 character = 2;
|
||||
}
|
||||
|
||||
enum DiagnosticSeverity {
|
||||
DIAGNOSTIC_ERROR = 0;
|
||||
DIAGNOSTIC_WARNING = 1;
|
||||
DIAGNOSTIC_INFORMATION = 2;
|
||||
DIAGNOSTIC_HINT = 3;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu #x
|
||||
|
||||
# This installs the cline-core app to the user's home directory,
|
||||
# and starts the service.
|
||||
|
||||
CORE_DIR=~/.cline/core
|
||||
INSTALL_DIR=$CORE_DIR/0.0.1
|
||||
LOG_FILE=~/.cline/cline-core-service.log
|
||||
|
||||
ZIP_FILE=standalone.zip
|
||||
ZIP=dist-standalone/${ZIP_FILE}
|
||||
@@ -20,5 +18,4 @@ cd $INSTALL_DIR
|
||||
unp $ZIP_FILE > /dev/null
|
||||
|
||||
pkill -f cline-core.js || true
|
||||
|
||||
NODE_PATH=./node_modules DEV_WORKSPACE_FOLDER=/tmp/ node cline-core.js 2>&1 | tee $LOG_FILE
|
||||
NODE_PATH=./node_modules DEV_WORKSPACE_FOLDER=/tmp/ node cline-core.js
|
||||
|
||||
@@ -147,7 +147,6 @@ function createHandlerForProvider(
|
||||
})
|
||||
case "requesty":
|
||||
return new RequestyHandler({
|
||||
requestyBaseUrl: options.requestyBaseUrl,
|
||||
requestyApiKey: options.requestyApiKey,
|
||||
reasoningEffort: mode === "plan" ? options.planModeReasoningEffort : options.actModeReasoningEffort,
|
||||
thinkingBudgetTokens:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { Stream as AnthropicStream } from "@anthropic-ai/sdk/streaming"
|
||||
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, CLAUDE_SONNET_4_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { anthropicDefaultModelId, AnthropicModelId, anthropicModels, ApiHandlerOptions, ModelInfo } from "@shared/api"
|
||||
import { ApiHandler } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
interface AnthropicHandlerOptions {
|
||||
@@ -43,11 +43,7 @@ export class AnthropicHandler implements ApiHandler {
|
||||
|
||||
const model = this.getModel()
|
||||
let stream: AnthropicStream<Anthropic.RawMessageStreamEvent>
|
||||
|
||||
const modelId = model.id.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
? model.id.slice(0, -CLAUDE_SONNET_4_1M_SUFFIX.length)
|
||||
: model.id
|
||||
const enable1mContextWindow = model.id.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
const modelId = model.id
|
||||
|
||||
const budget_tokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = (modelId.includes("3-7") || modelId.includes("4-")) && budget_tokens !== 0 ? true : false
|
||||
@@ -121,15 +117,25 @@ export class AnthropicHandler implements ApiHandler {
|
||||
stream: true,
|
||||
},
|
||||
(() => {
|
||||
// 1m context window beta header
|
||||
if (enable1mContextWindow) {
|
||||
return {
|
||||
headers: {
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
// prompt caching: https://x.com/alexalbert__/status/1823751995901272068
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
|
||||
switch (modelId) {
|
||||
case "claude-sonnet-4-20250514":
|
||||
case "claude-opus-4-20250514":
|
||||
case "claude-opus-4-1-20250805":
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-3-opus-20240229":
|
||||
case "claude-3-haiku-20240307":
|
||||
return {
|
||||
headers: {
|
||||
"anthropic-beta": "prompt-caching-2024-07-31",
|
||||
},
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
})(),
|
||||
)
|
||||
|
||||
+12
-164
@@ -2,14 +2,13 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { withRetry } from "../retry"
|
||||
import { ApiHandler } from "../"
|
||||
import { convertToR1Format } from "../transform/r1-format"
|
||||
import { bedrockDefaultModelId, BedrockModelId, bedrockModels, CLAUDE_SONNET_4_1M_SUFFIX, ModelInfo } from "@shared/api"
|
||||
import { bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "@shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../utils/cost"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import {
|
||||
BedrockRuntimeClient,
|
||||
ConversationRole,
|
||||
ConverseCommand,
|
||||
ConverseStreamCommand,
|
||||
InvokeModelWithResponseStreamCommand,
|
||||
} from "@aws-sdk/client-bedrock-runtime"
|
||||
@@ -118,14 +117,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
@withRetry({ maxRetries: 4 })
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
const rawModelId = await this.getModelId()
|
||||
|
||||
const modelId = rawModelId.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
? rawModelId.slice(0, -CLAUDE_SONNET_4_1M_SUFFIX.length)
|
||||
: rawModelId
|
||||
|
||||
const enable1mContextWindow = rawModelId.endsWith(CLAUDE_SONNET_4_1M_SUFFIX)
|
||||
|
||||
const modelId = await this.getModelId()
|
||||
const model = this.getModel()
|
||||
|
||||
// This baseModelId is used to indicate the capabilities of the model.
|
||||
@@ -140,11 +132,6 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
return
|
||||
}
|
||||
|
||||
if (baseModelId.includes("openai")) {
|
||||
yield* this.createOpenAIMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a Deepseek model
|
||||
if (baseModelId.includes("deepseek")) {
|
||||
yield* this.createDeepseekMessage(systemPrompt, messages, modelId, model)
|
||||
@@ -152,7 +139,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
// Default: Use Anthropic Converse API for all Anthropic models
|
||||
yield* this.createAnthropicMessage(systemPrompt, messages, modelId, model, enable1mContextWindow)
|
||||
yield* this.createAnthropicMessage(systemPrompt, messages, modelId, model)
|
||||
}
|
||||
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
@@ -756,7 +743,6 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
enable1mContextWindow: boolean,
|
||||
): ApiStream {
|
||||
// Format messages for Anthropic model using unified formatter
|
||||
const formattedMessages = this.formatMessagesForConverseAPI(messages)
|
||||
@@ -787,18 +773,15 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
messages: messagesWithCache,
|
||||
system: systemMessages,
|
||||
inferenceConfig: this.getInferenceConfig(model.info, "anthropic"),
|
||||
additionalModelRequestFields: {
|
||||
// Add thinking configuration as per LangChain documentation
|
||||
...(reasoningOn && {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budget_tokens: budget_tokens,
|
||||
},
|
||||
}),
|
||||
...(enable1mContextWindow && {
|
||||
anthropic_beta: ["context-1m-2025-08-07"],
|
||||
}),
|
||||
},
|
||||
// Add thinking configuration as per LangChain documentation
|
||||
additionalModelRequestFields: reasoningOn
|
||||
? {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budget_tokens: budget_tokens,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
|
||||
// Execute the streaming request using unified handler
|
||||
@@ -975,139 +958,4 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
// Execute the streaming request using unified handler
|
||||
yield* this.executeConverseStream(command, model.info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message using OpenAI models through AWS Bedrock
|
||||
* Uses non-streaming Converse API and simulates streaming for models that don't support it
|
||||
*/
|
||||
private async *createOpenAIMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
|
||||
// Format messages for Converse API
|
||||
const formattedMessages = this.formatMessagesForConverseAPI(messages)
|
||||
|
||||
// Prepare system message
|
||||
const systemMessages = systemPrompt ? [{ text: systemPrompt }] : undefined
|
||||
|
||||
// Prepare the non-streaming Converse command
|
||||
const command = new ConverseCommand({
|
||||
modelId: modelId,
|
||||
messages: formattedMessages,
|
||||
system: systemMessages,
|
||||
inferenceConfig: {
|
||||
maxTokens: model.info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
// Track token usage
|
||||
const inputTokenEstimate = this.estimateInputTokens(systemPrompt, messages)
|
||||
let outputTokens = 0
|
||||
|
||||
// Execute the non-streaming request
|
||||
const response = await client.send(command)
|
||||
|
||||
// Extract the complete response text and reasoning content
|
||||
let fullText = ""
|
||||
let reasoningText = ""
|
||||
|
||||
if (response.output?.message?.content) {
|
||||
for (const contentBlock of response.output.message.content) {
|
||||
// Check for reasoning content first
|
||||
if ("reasoningContent" in contentBlock && contentBlock.reasoningContent) {
|
||||
// Handle nested reasoning structure
|
||||
const reasoning = contentBlock.reasoningContent
|
||||
if ("reasoningText" in reasoning && reasoning.reasoningText && "text" in reasoning.reasoningText) {
|
||||
reasoningText += reasoning.reasoningText.text
|
||||
}
|
||||
}
|
||||
// Handle regular text content
|
||||
else if ("text" in contentBlock && contentBlock.text) {
|
||||
fullText += contentBlock.text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have actual usage data from the response, use it
|
||||
if (response.usage) {
|
||||
const actualInputTokens = response.usage.inputTokens || inputTokenEstimate
|
||||
const actualOutputTokens = response.usage.outputTokens || this.estimateTokenCount(fullText + reasoningText)
|
||||
outputTokens = actualOutputTokens
|
||||
|
||||
// Report actual usage after processing content
|
||||
const actualCost = calculateApiCostOpenAI(model.info, actualInputTokens, actualOutputTokens, 0, 0)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: actualInputTokens,
|
||||
outputTokens: actualOutputTokens,
|
||||
totalCost: actualCost,
|
||||
}
|
||||
} else {
|
||||
// Estimate output tokens if not provided (includes both regular text and reasoning)
|
||||
outputTokens = this.estimateTokenCount(fullText + reasoningText)
|
||||
}
|
||||
|
||||
// Yield reasoning content first if present
|
||||
if (reasoningText) {
|
||||
const reasoningChunkSize = 1000 // Characters per chunk
|
||||
for (let i = 0; i < reasoningText.length; i += reasoningChunkSize) {
|
||||
const chunk = reasoningText.slice(i, Math.min(i + reasoningChunkSize, reasoningText.length))
|
||||
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate streaming by chunking the response text
|
||||
if (fullText) {
|
||||
const chunkSize = 1000 // Characters per chunk
|
||||
|
||||
for (let i = 0; i < fullText.length; i += chunkSize) {
|
||||
const chunk = fullText.slice(i, Math.min(i + chunkSize, fullText.length))
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report final usage if we didn't have actual usage data earlier
|
||||
if (!response.usage) {
|
||||
const finalCost = calculateApiCostOpenAI(model.info, inputTokenEstimate, outputTokens, 0, 0)
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: inputTokenEstimate,
|
||||
outputTokens: outputTokens,
|
||||
totalCost: finalCost,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error with OpenAI model via Converse API:", error)
|
||||
|
||||
// Try to extract more detailed error information
|
||||
let errorMessage = "Failed to process OpenAI model request"
|
||||
if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
// Check for specific AWS SDK errors
|
||||
if ("name" in error) {
|
||||
errorMessage = `${error.name}: ${error.message}`
|
||||
}
|
||||
}
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: `[ERROR] ${errorMessage}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { calculateApiCostOpenAI } from "@utils/cost"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
|
||||
interface RequestyHandlerOptions {
|
||||
requestyBaseUrl?: string
|
||||
requestyApiKey?: string
|
||||
reasoningEffort?: string
|
||||
thinkingBudgetTokens?: number
|
||||
@@ -41,7 +40,7 @@ export class RequestyHandler implements ApiHandler {
|
||||
}
|
||||
try {
|
||||
this.client = new OpenAI({
|
||||
baseURL: this.options.requestyBaseUrl || "https://router.requesty.ai/v1",
|
||||
baseURL: "https://router.requesty.ai/v1",
|
||||
apiKey: this.options.requestyApiKey,
|
||||
defaultHeaders: {
|
||||
"HTTP-Referer": "https://cline.bot",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CLAUDE_SONNET_4_1M_SUFFIX, ModelInfo, openRouterClaudeSonnet41mModelId } from "@shared/api"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { convertToOpenAiMessages } from "@api/transform/openai-format"
|
||||
import { convertToR1Format } from "@api/transform/r1-format"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
@@ -19,12 +19,6 @@ export async function createOpenRouterStream(
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const isClaudeSonnet41m = model.id === openRouterClaudeSonnet41mModelId
|
||||
if (isClaudeSonnet41m) {
|
||||
// remove the custom :1m suffix, to create the model id openrouter API expects
|
||||
model.id = model.id.slice(0, -CLAUDE_SONNET_4_1M_SUFFIX.length)
|
||||
}
|
||||
|
||||
// prompt caching: https://openrouter.ai/docs/prompt-caching
|
||||
// this was initially specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
// handles direct model.id match logic
|
||||
@@ -170,8 +164,6 @@ export async function createOpenRouterStream(
|
||||
...(isKimiK2
|
||||
? { provider: { order: ["groq", "together", "baseten", "parasail", "novita", "deepinfra"], allow_fallbacks: false } }
|
||||
: {}),
|
||||
// limit providers to only those that support the 1m context window
|
||||
...(isClaudeSonnet41m ? { provider: { order: ["anthropic", "amazon-bedrock"], allow_fallbacks: false } } : {}),
|
||||
})
|
||||
|
||||
return stream
|
||||
|
||||
+1
-2
@@ -30,8 +30,7 @@ export async function initialize(context: vscode.ExtensionContext): Promise<Webv
|
||||
const response = await HostProvider.env.getMachineId(EmptyRequest.create({}))
|
||||
distinctId = response.value
|
||||
} catch (e) {
|
||||
Logger.warn(`Failed to get machine ID: ${e instanceof Error ? e.message : String(e)}`)
|
||||
// PostHogProvider will fall back to uuid
|
||||
// ignore; PostHogProvider will fall back to uuid
|
||||
}
|
||||
}
|
||||
PostHogClientProvider.getInstance(distinctId)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { JSONParser } from "@streamparser/json"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
// Fallback type definition based on the error message: "Property 'value' is optional in type 'ParsedElementInfo'"
|
||||
type ParsedElementInfo = {
|
||||
value?: any
|
||||
key?: string | number
|
||||
parent?: any
|
||||
stack?: any[]
|
||||
}
|
||||
|
||||
export interface ReplacementItem {
|
||||
old_string: string
|
||||
new_string: string
|
||||
}
|
||||
|
||||
export interface ChangeLocation {
|
||||
startLine: number
|
||||
endLine: number
|
||||
startChar: number
|
||||
endChar: number
|
||||
}
|
||||
|
||||
export class StreamingJsonReplacer {
|
||||
private currentFileContent: string
|
||||
private parser: JSONParser
|
||||
private onContentUpdated: (newContent: string, isFinalItem: boolean, changeLocation?: ChangeLocation) => void
|
||||
private onErrorCallback: (error: Error) => void
|
||||
private itemsProcessed: number = 0
|
||||
private successfullyParsedItems: ReplacementItem[] = []
|
||||
|
||||
constructor(
|
||||
initialContent: string,
|
||||
onContentUpdatedCallback: (newContent: string, isFinalItem: boolean, changeLocation?: ChangeLocation) => void,
|
||||
onErrorCallback: (error: Error) => void,
|
||||
) {
|
||||
// Initialize log file path
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
||||
|
||||
this.currentFileContent = initialContent
|
||||
this.onContentUpdated = onContentUpdatedCallback
|
||||
this.onErrorCallback = onErrorCallback
|
||||
|
||||
this.parser = new JSONParser({ paths: ["$.*"] })
|
||||
|
||||
this.parser.onValue = (parsedElementInfo: ParsedElementInfo) => {
|
||||
const { value } = parsedElementInfo // Destructure to get value, which might be undefined
|
||||
|
||||
// This callback is triggered for each item matched by '$.replacements.*'
|
||||
if (value && typeof value === "object" && "old_string" in value && "new_string" in value) {
|
||||
const item = value as ReplacementItem // Value here is confirmed to be an object
|
||||
if (typeof item.old_string === "string" && typeof item.new_string === "string") {
|
||||
this.successfullyParsedItems.push(item) // Store the structurally valid item
|
||||
|
||||
if (this.currentFileContent.includes(item.old_string)) {
|
||||
// Calculate the change location before making the replacement
|
||||
const changeLocation = this.calculateChangeLocation(item.old_string, item.new_string)
|
||||
|
||||
const beforeLength = this.currentFileContent.length
|
||||
this.currentFileContent = this.currentFileContent.replace(item.old_string, item.new_string)
|
||||
const afterLength = this.currentFileContent.length
|
||||
|
||||
this.itemsProcessed++
|
||||
|
||||
// Notify that an item has been processed. The `isFinalItem` argument here is tricky
|
||||
// as we don't know from the parser alone if this is the *absolute* last item
|
||||
// until the stream ends. The caller (Task.ts) will manage the final update.
|
||||
// For now, we'll pass `false` and let Task.ts handle the final diff view update.
|
||||
this.onContentUpdated(this.currentFileContent, false, changeLocation)
|
||||
} else {
|
||||
const snippet = item.old_string.length > 50 ? item.old_string.substring(0, 47) + "..." : item.old_string
|
||||
const error = new Error(`Streaming Replacement failed: 'old_string' not found. Snippet: "${snippet}"`)
|
||||
this.onErrorCallback(error) // Call our own error callback
|
||||
}
|
||||
} else {
|
||||
const error = new Error(`Invalid item structure in replacements stream: ${JSON.stringify(item)}`)
|
||||
this.onErrorCallback(error) // Call our own error callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.parser.onError = (err: Error) => {
|
||||
// Propagate the error to the caller via the callback
|
||||
this.onErrorCallback(err)
|
||||
// Note: The @streamparser/json library might throw synchronously on write if onError is not set,
|
||||
// or if it re-throws. We'll ensure Task.ts wraps write/end in try-catch.
|
||||
}
|
||||
}
|
||||
|
||||
public write(jsonChunk: string): void {
|
||||
try {
|
||||
// Errors during write will be caught by the parser's onError or thrown.
|
||||
this.parser.write(jsonChunk)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentContent(): string {
|
||||
return this.currentFileContent
|
||||
}
|
||||
|
||||
public getSuccessfullyParsedItems(): ReplacementItem[] {
|
||||
return [...this.successfullyParsedItems] // Return a copy
|
||||
}
|
||||
|
||||
private calculateChangeLocation(oldStr: string, newStr: string): ChangeLocation {
|
||||
// Find the index where the old string starts
|
||||
const startIndex = this.currentFileContent.indexOf(oldStr)
|
||||
|
||||
if (startIndex === -1) {
|
||||
// This shouldn't happen since we already checked includes(), but just in case
|
||||
return { startLine: 0, endLine: 0, startChar: 0, endChar: 0 }
|
||||
}
|
||||
|
||||
// Calculate line numbers by counting newlines before the start index
|
||||
const contentBeforeStart = this.currentFileContent.substring(0, startIndex)
|
||||
|
||||
const startLine = (contentBeforeStart.match(/\n/g) || []).length
|
||||
// Calculate the end index after replacement
|
||||
const endIndex = startIndex + oldStr.length
|
||||
|
||||
const contentBeforeEnd = this.currentFileContent.substring(0, endIndex)
|
||||
|
||||
const endLine = (contentBeforeEnd.match(/\n/g) || []).length
|
||||
// Calculate character positions within their respective lines
|
||||
const lastNewlineBeforeStart = contentBeforeStart.lastIndexOf("\n")
|
||||
const startChar = lastNewlineBeforeStart === -1 ? startIndex : startIndex - lastNewlineBeforeStart - 1
|
||||
|
||||
const lastNewlineBeforeEnd = contentBeforeEnd.lastIndexOf("\n")
|
||||
|
||||
const endChar = lastNewlineBeforeEnd === -1 ? endIndex : endIndex - lastNewlineBeforeEnd - 1
|
||||
|
||||
const result = {
|
||||
startLine,
|
||||
endLine,
|
||||
startChar,
|
||||
endChar,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export type AssistantMessageContent = TextContent | ToolUse
|
||||
|
||||
export { parseAssistantMessageV2 } from "./parse-assistant-message"
|
||||
export { parseAssistantMessageV2, parseAssistantMessageV3 } from "./parse-assistant-message"
|
||||
|
||||
export interface TextContent {
|
||||
type: "text"
|
||||
@@ -25,7 +25,6 @@ export const toolUseNames = [
|
||||
"attempt_completion",
|
||||
"new_task",
|
||||
"condense",
|
||||
"summarize_task",
|
||||
"report_bug",
|
||||
"new_rule",
|
||||
"web_fetch",
|
||||
@@ -62,7 +61,6 @@ export const toolParamNames = [
|
||||
"api_request_output",
|
||||
"additional_context",
|
||||
"needs_more_exploration",
|
||||
"task_progress",
|
||||
] as const
|
||||
|
||||
export type ToolParamName = (typeof toolParamNames)[number]
|
||||
|
||||
@@ -234,3 +234,621 @@ export function parseAssistantMessageV2(assistantMessage: string): AssistantMess
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
export function parseAssistantMessageV3(assistantMessage: string): AssistantMessageContent[] {
|
||||
const contentBlocks: AssistantMessageContent[] = []
|
||||
let currentTextContentStart = 0 // Index where the current text block started
|
||||
let currentTextContent: TextContent | undefined = undefined
|
||||
let currentToolUseStart = 0 // Index *after* the opening tag of the current tool use
|
||||
let currentToolUse: ToolUse | undefined = undefined
|
||||
let currentParamValueStart = 0 // Index *after* the opening tag of the current param
|
||||
let currentParamName: ToolParamName | undefined = undefined
|
||||
|
||||
// Precompute tags for faster lookups
|
||||
const toolUseOpenTags = new Map<string, ToolUseName>()
|
||||
const toolParamOpenTags = new Map<string, ToolParamName>()
|
||||
for (const name of toolUseNames) {
|
||||
toolUseOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
for (const name of toolParamNames) {
|
||||
toolParamOpenTags.set(`<${name}>`, name)
|
||||
}
|
||||
|
||||
// Function calls format detection
|
||||
const isFunctionCallsOpen = "<function_calls>"
|
||||
const isFunctionCallsClose = "</function_calls>"
|
||||
const isInvokeStart = '<invoke name="'
|
||||
const isInvokeEnd = '">'
|
||||
const isInvokeClose = "</invoke>"
|
||||
const isParameterStart = '<parameter name="'
|
||||
const isParameterNameEnd = '">'
|
||||
const isParameterClose = "</parameter>"
|
||||
|
||||
// Variables for function calls parsing
|
||||
let inFunctionCalls = false
|
||||
let currentInvokeName = ""
|
||||
let currentParameterName = ""
|
||||
|
||||
const len = assistantMessage.length
|
||||
for (let i = 0; i < len; i++) {
|
||||
const currentCharIndex = i
|
||||
|
||||
// --- State: Parsing Function Calls ---
|
||||
// Check for opening function_calls tag
|
||||
if (
|
||||
!inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsOpen.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsOpen, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart, currentCharIndex - isFunctionCallsOpen.length + 1)
|
||||
.trim()
|
||||
currentTextContent.partial = false
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
}
|
||||
|
||||
inFunctionCalls = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke start within function_calls
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName === "" &&
|
||||
!currentToolUse && // Don't create a new tool if we already have one
|
||||
currentCharIndex >= isInvokeStart.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeStart, currentCharIndex - isInvokeStart.length + 1)
|
||||
) {
|
||||
// Find the end of the invoke name
|
||||
const nameEndPos = assistantMessage.indexOf(isInvokeEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the invoke name
|
||||
currentInvokeName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
i = nameEndPos + isInvokeEnd.length - 1 // Skip to after the '">
|
||||
|
||||
// If this is an LS invoke, create a list_files tool
|
||||
if (currentInvokeName === "LS") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a Grep invoke, create a search_files tool
|
||||
if (currentInvokeName === "Grep") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "search_files",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Bash") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "execute_command",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Read") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "read_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "Write") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "write_to_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "WebFetch") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "web_fetch",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AskQuestion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "ask_followup_question",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "UseMCPTool") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "use_mcp_tool",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AccessMCPResource") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "access_mcp_resource",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "ListCodeDefinitionNames") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "list_code_definition_names",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "PlanModeRespond") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "plan_mode_respond",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "LoadMcpDocumentation") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "load_mcp_documentation",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "AttemptCompletion") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "attempt_completion",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "BrowserAction") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "browser_action",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentInvokeName === "NewTask") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "new_task",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a MultiEdit invoke, create a replace_in_file tool
|
||||
if (currentInvokeName === "MultiEdit") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "replace_in_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter start within invoke
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName === "" &&
|
||||
currentCharIndex >= isParameterStart.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterStart, currentCharIndex - isParameterStart.length + 1)
|
||||
) {
|
||||
// Find the end of the parameter name
|
||||
const nameEndPos = assistantMessage.indexOf(isParameterNameEnd, currentCharIndex + 1)
|
||||
if (nameEndPos !== -1) {
|
||||
// Extract the parameter name
|
||||
currentParameterName = assistantMessage.slice(currentCharIndex + 1, nameEndPos)
|
||||
currentParamValueStart = nameEndPos + isParameterNameEnd.length
|
||||
i = nameEndPos + isParameterNameEnd.length - 1 // Skip to after the '">'
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check for parameter end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentParameterName !== "" &&
|
||||
currentCharIndex >= isParameterClose.length - 1 &&
|
||||
assistantMessage.startsWith(isParameterClose, currentCharIndex - isParameterClose.length + 1)
|
||||
) {
|
||||
// Extract parameter value
|
||||
const value = assistantMessage.slice(currentParamValueStart, currentCharIndex - isParameterClose.length + 1).trim()
|
||||
|
||||
// Map parameter to tool params
|
||||
if (currentToolUse && currentInvokeName === "LS" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
// Default recursive to false - only show top level
|
||||
currentToolUse.params["recursive"] = "false"
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Read" && currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "PlanModeRespond" && currentParameterName === "response") {
|
||||
currentToolUse.params["response"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "WebFetch" && currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "ListCodeDefinitionNames" && currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "NewTask" && currentParameterName === "context") {
|
||||
currentToolUse.params["context"] = value
|
||||
}
|
||||
|
||||
// Map parameter to tool params for Grep
|
||||
if (currentToolUse && currentInvokeName === "Grep") {
|
||||
if (currentParameterName === "pattern") {
|
||||
currentToolUse.params["regex"] = value
|
||||
} else if (currentParameterName === "path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "include") {
|
||||
currentToolUse.params["file_pattern"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Bash") {
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
} else if (currentParameterName === "requires_approval") {
|
||||
currentToolUse.params["requires_approval"] = value === "true" ? "true" : "false"
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "Write") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "content") {
|
||||
currentToolUse.params["content"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AskQuestion") {
|
||||
if (currentParameterName === "question") {
|
||||
currentToolUse.params["question"] = value
|
||||
} else if (currentParameterName === "options") {
|
||||
currentToolUse.params["options"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "UseMCPTool") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "tool_name") {
|
||||
currentToolUse.params["tool_name"] = value
|
||||
} else if (currentParameterName === "arguments") {
|
||||
currentToolUse.params["arguments"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AccessMCPResource") {
|
||||
if (currentParameterName === "server_name") {
|
||||
currentToolUse.params["server_name"] = value
|
||||
} else if (currentParameterName === "uri") {
|
||||
currentToolUse.params["uri"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "AttemptCompletion") {
|
||||
if (currentParameterName === "result") {
|
||||
currentToolUse.params["result"] = value
|
||||
}
|
||||
if (currentParameterName === "command") {
|
||||
currentToolUse.params["command"] = value
|
||||
}
|
||||
}
|
||||
|
||||
if (currentToolUse && currentInvokeName === "BrowserAction") {
|
||||
if (currentParameterName === "action") {
|
||||
currentToolUse.params["action"] = value
|
||||
} else if (currentParameterName === "url") {
|
||||
currentToolUse.params["url"] = value
|
||||
} else if (currentParameterName === "coordinate") {
|
||||
currentToolUse.params["coordinate"] = value
|
||||
} else if (currentParameterName === "text") {
|
||||
currentToolUse.params["text"] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Map parameter to tool params for MultiEdit
|
||||
if (currentToolUse && currentInvokeName === "MultiEdit") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "edits") {
|
||||
// Save the value to the diff parameter for replace_in_file
|
||||
currentToolUse.params["diff"] = value
|
||||
}
|
||||
}
|
||||
|
||||
currentParameterName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for invoke end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName !== "" &&
|
||||
currentCharIndex >= isInvokeClose.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeClose, currentCharIndex - isInvokeClose.length + 1)
|
||||
) {
|
||||
// If we have a tool use from this invoke, finalize it
|
||||
if (
|
||||
currentToolUse &&
|
||||
(currentInvokeName === "LS" ||
|
||||
currentInvokeName === "Grep" ||
|
||||
currentInvokeName === "Bash" ||
|
||||
currentInvokeName === "Read" ||
|
||||
currentInvokeName === "Write" ||
|
||||
currentInvokeName === "WebFetch" ||
|
||||
currentInvokeName === "AskQuestion" ||
|
||||
currentInvokeName === "UseMCPTool" ||
|
||||
currentInvokeName === "AccessMCPResource" ||
|
||||
currentInvokeName === "ListCodeDefinitionNames" ||
|
||||
currentInvokeName === "PlanModeRespond" ||
|
||||
currentInvokeName === "LoadMcpDocumentation" ||
|
||||
currentInvokeName === "AttemptCompletion" ||
|
||||
currentInvokeName === "BrowserAction" ||
|
||||
currentInvokeName === "NewTask" ||
|
||||
currentInvokeName === "MultiEdit")
|
||||
) {
|
||||
currentToolUse.partial = false
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined
|
||||
}
|
||||
currentInvokeName = ""
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for function_calls end
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentCharIndex >= isFunctionCallsClose.length - 1 &&
|
||||
assistantMessage.startsWith(isFunctionCallsClose, currentCharIndex - isFunctionCallsClose.length + 1)
|
||||
) {
|
||||
inFunctionCalls = false
|
||||
currentTextContentStart = currentCharIndex + 1
|
||||
// Start a new text content block for any text after function_calls
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "",
|
||||
partial: true,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip normal parsing when inside function_calls
|
||||
if (inFunctionCalls) {
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Parameter ---
|
||||
if (currentToolUse && currentParamName) {
|
||||
const closeTag = `</${currentParamName}>`
|
||||
// Check if the string *ending* at index `i` matches the closing tag
|
||||
if (
|
||||
currentCharIndex >= closeTag.length - 1 &&
|
||||
assistantMessage.startsWith(
|
||||
closeTag,
|
||||
currentCharIndex - closeTag.length + 1, // Start checking from potential start of tag
|
||||
)
|
||||
) {
|
||||
// Found the closing tag for the parameter
|
||||
const value = assistantMessage
|
||||
.slice(
|
||||
currentParamValueStart, // Start after the opening tag
|
||||
currentCharIndex - closeTag.length + 1, // End before the closing tag
|
||||
)
|
||||
.trim()
|
||||
currentToolUse.params[currentParamName] = value
|
||||
currentParamName = undefined // Go back to parsing tool content
|
||||
// We don't continue loop here, need to check for tool close or other params at index i
|
||||
} else {
|
||||
continue // Still inside param value, move to next char
|
||||
}
|
||||
}
|
||||
|
||||
// --- State: Parsing a Tool Use (but not a specific parameter) ---
|
||||
if (currentToolUse && !currentParamName) {
|
||||
// Ensure we are not inside a parameter already
|
||||
// Check if starting a new parameter
|
||||
let startedNewParam = false
|
||||
for (const [tag, paramName] of toolParamOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
currentParamName = paramName
|
||||
currentParamValueStart = currentCharIndex + 1 // Value starts after the tag
|
||||
startedNewParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (startedNewParam) {
|
||||
continue // Handled start of param, move to next char
|
||||
}
|
||||
|
||||
// Check if closing the current tool use
|
||||
const toolCloseTag = `</${currentToolUse.name}>`
|
||||
if (
|
||||
currentCharIndex >= toolCloseTag.length - 1 &&
|
||||
assistantMessage.startsWith(toolCloseTag, currentCharIndex - toolCloseTag.length + 1)
|
||||
) {
|
||||
// End of the tool use found
|
||||
// Special handling for content params *before* finalizing the tool
|
||||
const toolContentSlice = assistantMessage.slice(
|
||||
currentToolUseStart, // From after the tool opening tag
|
||||
currentCharIndex - toolCloseTag.length + 1, // To before the tool closing tag
|
||||
)
|
||||
|
||||
// Check if content parameter needs special handling (write_to_file/new_rule)
|
||||
// This check is important if the closing </content> tag was missed by the parameter parsing logic
|
||||
// (e.g., if content is empty or parsing logic prioritizes tool close)
|
||||
const contentParamName: ToolParamName = "content"
|
||||
if (
|
||||
currentToolUse.name === "write_to_file" /* || currentToolUse.name === "new_rule" */ &&
|
||||
toolContentSlice.includes(`<${contentParamName}>`)
|
||||
) {
|
||||
const contentStartTag = `<${contentParamName}>`
|
||||
const contentEndTag = `</${contentParamName}>`
|
||||
const contentStart = toolContentSlice.indexOf(contentStartTag)
|
||||
// Use lastIndexOf for robustness against nested tags
|
||||
const contentEnd = toolContentSlice.lastIndexOf(contentEndTag)
|
||||
|
||||
if (contentStart !== -1 && contentEnd !== -1 && contentEnd > contentStart) {
|
||||
const contentValue = toolContentSlice.slice(contentStart + contentStartTag.length, contentEnd).trim()
|
||||
currentToolUse.params[contentParamName] = contentValue
|
||||
}
|
||||
}
|
||||
|
||||
currentToolUse.partial = false // Mark as complete
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined // Reset state
|
||||
currentTextContentStart = currentCharIndex + 1 // Potential text starts after this tag
|
||||
continue // Move to next char
|
||||
}
|
||||
// If not starting a param and not closing the tool, continue accumulating tool content implicitly
|
||||
continue
|
||||
}
|
||||
|
||||
// --- State: Parsing Text / Looking for Tool Start ---
|
||||
if (!currentToolUse) {
|
||||
// Check if starting a new tool use
|
||||
let startedNewTool = false
|
||||
for (const [tag, toolName] of toolUseOpenTags.entries()) {
|
||||
if (currentCharIndex >= tag.length - 1 && assistantMessage.startsWith(tag, currentCharIndex - tag.length + 1)) {
|
||||
// End current text block if one was active
|
||||
if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
currentTextContent.partial = false // Ended because tool started
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
currentTextContent = undefined
|
||||
} else {
|
||||
// Check for any text between the last block and this tag
|
||||
const potentialText = assistantMessage
|
||||
.slice(
|
||||
currentTextContentStart, // From where text *might* have started
|
||||
currentCharIndex - tag.length + 1, // To before the tool tag starts
|
||||
)
|
||||
.trim()
|
||||
if (potentialText.length > 0) {
|
||||
contentBlocks.push({
|
||||
type: "text",
|
||||
content: potentialText,
|
||||
partial: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Start the new tool use
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: toolName,
|
||||
params: {},
|
||||
partial: true, // Assume partial until closing tag is found
|
||||
}
|
||||
currentToolUseStart = currentCharIndex + 1 // Tool content starts after the opening tag
|
||||
startedNewTool = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (startedNewTool) {
|
||||
continue // Handled start of tool, move to next char
|
||||
}
|
||||
|
||||
// If not starting a tool, it must be text content
|
||||
if (!currentTextContent) {
|
||||
// Start a new text block if we aren't already in one
|
||||
currentTextContentStart = currentCharIndex // Text starts at the current character
|
||||
// Check if the current char is the start of potential text *immediately* after a tag
|
||||
// This needs the previous state - simpler to let slicing handle it later.
|
||||
// Resetting start index accurately is key.
|
||||
// It should be the index *after* the last processed tag.
|
||||
// The logic managing currentTextContentStart after closing tags handles this.
|
||||
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "", // Will be determined by slicing at the end or when a tool starts
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
// Continue accumulating text implicitly; content is extracted later.
|
||||
}
|
||||
} // End of loop
|
||||
|
||||
// --- Finalization after loop ---
|
||||
|
||||
// Finalize any open parameter within an open tool use
|
||||
if (currentToolUse && currentParamName) {
|
||||
currentToolUse.params[currentParamName] = assistantMessage
|
||||
.slice(currentParamValueStart) // From param start to end of string
|
||||
.trim()
|
||||
// Tool use remains partial
|
||||
}
|
||||
|
||||
// Finalize any open tool use (which might contain the finalized partial param)
|
||||
if (currentToolUse) {
|
||||
// Tool use is partial because the loop finished before its closing tag
|
||||
contentBlocks.push(currentToolUse)
|
||||
}
|
||||
// Finalize any trailing text content
|
||||
// Only possible if a tool use wasn't open at the very end
|
||||
else if (currentTextContent) {
|
||||
currentTextContent.content = assistantMessage
|
||||
.slice(currentTextContentStart) // From text start to end of string
|
||||
.trim()
|
||||
// Text is partial because the loop finished
|
||||
if (currentTextContent.content.length > 0) {
|
||||
contentBlocks.push(currentTextContent)
|
||||
}
|
||||
}
|
||||
|
||||
return contentBlocks
|
||||
}
|
||||
|
||||
@@ -106,24 +106,7 @@ export class ContextManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether we should compact context window, based on token counts
|
||||
*/
|
||||
shouldCompactContextWindow(clineMessages: ClineMessage[], api: ApiHandler, previousApiReqIndex: number): boolean {
|
||||
if (previousApiReqIndex >= 0) {
|
||||
const previousRequest = clineMessages[previousApiReqIndex]
|
||||
if (previousRequest && previousRequest.text) {
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
|
||||
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
|
||||
const { maxAllowedSize } = getContextWindowInfo(api)
|
||||
return totalTokens >= maxAllowedSize
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* primary entry point for getting up to date context
|
||||
* primary entry point for getting up to date context & truncating when required
|
||||
*/
|
||||
async getNewContextMessagesAndMetadata(
|
||||
apiConversationHistory: Anthropic.Messages.MessageParam[],
|
||||
@@ -135,6 +118,63 @@ export class ContextManager {
|
||||
) {
|
||||
let updatedConversationHistoryDeletedRange = false
|
||||
|
||||
// If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
|
||||
if (previousApiReqIndex >= 0) {
|
||||
const previousRequest = clineMessages[previousApiReqIndex]
|
||||
if (previousRequest && previousRequest.text) {
|
||||
const timestamp = previousRequest.ts
|
||||
const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequest.text)
|
||||
const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
|
||||
const { maxAllowedSize } = getContextWindowInfo(api)
|
||||
|
||||
// This is the most reliable way to know when we're close to hitting the context window.
|
||||
if (totalTokens >= maxAllowedSize) {
|
||||
// Since the user may switch between models with different context windows, truncating half may not be enough (ie if switching from claude 200k to deepseek 64k, half truncation will only remove 100k tokens, but we need to remove much more)
|
||||
// So if totalTokens/2 is greater than maxAllowedSize, we truncate 3/4 instead of 1/2
|
||||
const keep = totalTokens / 2 > maxAllowedSize ? "quarter" : "half"
|
||||
|
||||
// we later check how many chars we trim to determine if we should still truncate history
|
||||
let [anyContextUpdates, uniqueFileReadIndices] = this.applyContextOptimizations(
|
||||
apiConversationHistory,
|
||||
conversationHistoryDeletedRange ? conversationHistoryDeletedRange[1] + 1 : 2,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
let needToTruncate = true
|
||||
if (anyContextUpdates) {
|
||||
// determine whether we've saved enough chars to not truncate
|
||||
const charactersSavedPercentage = this.calculateContextOptimizationMetrics(
|
||||
apiConversationHistory,
|
||||
conversationHistoryDeletedRange,
|
||||
uniqueFileReadIndices,
|
||||
)
|
||||
if (charactersSavedPercentage >= 0.3) {
|
||||
needToTruncate = false
|
||||
}
|
||||
}
|
||||
|
||||
if (needToTruncate) {
|
||||
// go ahead with truncation
|
||||
anyContextUpdates = this.applyStandardContextTruncationNoticeChange(timestamp) || anyContextUpdates
|
||||
|
||||
// NOTE: it's okay that we overwriteConversationHistory in resume task since we're only ever removing the last user message and not anything in the middle which would affect this range
|
||||
conversationHistoryDeletedRange = this.getNextTruncationRange(
|
||||
apiConversationHistory,
|
||||
conversationHistoryDeletedRange,
|
||||
keep,
|
||||
)
|
||||
|
||||
updatedConversationHistoryDeletedRange = true
|
||||
}
|
||||
|
||||
// if we alter the context history, save the updated version to disk
|
||||
if (anyContextUpdates) {
|
||||
await this.saveContextHistory(taskDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const truncatedConversationHistory = this.getAndAlterTruncatedMessages(
|
||||
apiConversationHistory,
|
||||
conversationHistoryDeletedRange,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import * as path from "path"
|
||||
import * as sinon from "sinon"
|
||||
import * as vscode from "vscode"
|
||||
import chokidar from "chokidar"
|
||||
import type { FileMetadataEntry, TaskMetadata } from "./ContextTrackerTypes"
|
||||
import { FileContextTracker } from "./FileContextTracker"
|
||||
import { Controller } from "@/core/controller"
|
||||
@@ -16,7 +15,6 @@ describe("FileContextTracker", () => {
|
||||
let mockController: Controller
|
||||
let mockWorkspace: sinon.SinonStub
|
||||
let mockFileSystemWatcher: any
|
||||
let chokidarWatchStub: sinon.SinonStub
|
||||
let tracker: FileContextTracker
|
||||
let taskId: string
|
||||
let mockTaskMetadata: TaskMetadata
|
||||
@@ -35,16 +33,16 @@ describe("FileContextTracker", () => {
|
||||
} as vscode.WorkspaceFolder,
|
||||
])
|
||||
|
||||
// Mock chokidar file watcher
|
||||
// Mock file system watcher
|
||||
mockFileSystemWatcher = {
|
||||
close: sandbox.stub().resolves(),
|
||||
on: sandbox.stub(),
|
||||
dispose: sandbox.stub(),
|
||||
onDidChange: sandbox.stub().returns({ dispose: () => {} }),
|
||||
}
|
||||
// Return the watcher itself for chaining
|
||||
mockFileSystemWatcher.on.returns(mockFileSystemWatcher)
|
||||
|
||||
// Stub chokidar.watch to return our mock watcher
|
||||
chokidarWatchStub = sandbox.stub(chokidar, "watch").returns(mockFileSystemWatcher as any)
|
||||
// Use a function replacement instead of a direct stub
|
||||
vscode.workspace.createFileSystemWatcher = function () {
|
||||
return mockFileSystemWatcher
|
||||
}
|
||||
|
||||
// Mock controller and context
|
||||
mockController = {
|
||||
@@ -189,13 +187,17 @@ describe("FileContextTracker", () => {
|
||||
it("should setup a file watcher for tracked files", async () => {
|
||||
const filePath = "src/test-file.ts"
|
||||
|
||||
// Create a spy to track if createFileSystemWatcher was called
|
||||
const createWatcherSpy = sinon.spy(vscode.workspace, "createFileSystemWatcher")
|
||||
|
||||
await tracker.trackFileContext(filePath, "read_tool")
|
||||
|
||||
// Verify chokidar.watch was called
|
||||
expect(chokidarWatchStub.called).to.be.true
|
||||
// Verify createFileSystemWatcher was called
|
||||
expect(createWatcherSpy.called).to.be.true
|
||||
createWatcherSpy.restore()
|
||||
|
||||
// Verify change listener was set up
|
||||
expect(mockFileSystemWatcher.on.called).to.be.true
|
||||
// Verify onDidChange was called to set up the change listener
|
||||
expect(mockFileSystemWatcher.onDidChange.called).to.be.true
|
||||
})
|
||||
|
||||
it("should track user edits when file watcher detects changes", async () => {
|
||||
@@ -211,8 +213,8 @@ describe("FileContextTracker", () => {
|
||||
// Create a spy on trackFileContext to verify it's called with the right parameters
|
||||
const trackFileContextSpy = sandbox.spy(tracker, "trackFileContext")
|
||||
|
||||
// Get the callback that was registered with chokidar "change" event
|
||||
const callback = mockFileSystemWatcher.on.firstCall.args[1]
|
||||
// Get the callback that was registered with onDidChange
|
||||
const callback = mockFileSystemWatcher.onDidChange.firstCall.args[0]
|
||||
|
||||
// Directly call the callback to simulate a file change event
|
||||
callback(vscode.Uri.file(path.resolve("/mock/workspace", filePath)))
|
||||
@@ -241,8 +243,8 @@ describe("FileContextTracker", () => {
|
||||
// Create a spy on trackFileContext to verify it's not called
|
||||
const trackFileContextSpy = sandbox.spy(tracker, "trackFileContext")
|
||||
|
||||
// Get the callback that was registered with chokidar "change" event
|
||||
const callback = mockFileSystemWatcher.on.firstCall.args[1]
|
||||
// Get the callback that was registered with onDidChange
|
||||
const callback = mockFileSystemWatcher.onDidChange.firstCall.args[0]
|
||||
|
||||
// Directly call the callback to simulate a file change event
|
||||
callback(vscode.Uri.file(path.resolve("/mock/workspace", filePath)))
|
||||
@@ -262,9 +264,9 @@ describe("FileContextTracker", () => {
|
||||
await tracker.trackFileContext(filePath, "read_tool")
|
||||
|
||||
// Call dispose
|
||||
await tracker.dispose()
|
||||
tracker.dispose()
|
||||
|
||||
// Verify the watcher was closed
|
||||
expect(mockFileSystemWatcher.close.called).to.be.true
|
||||
// Verify the watcher was disposed
|
||||
expect(mockFileSystemWatcher.dispose.called).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import chokidar, { FSWatcher } from "chokidar"
|
||||
import { getTaskMetadata, saveTaskMetadata } from "@core/storage/disk"
|
||||
import type { FileMetadataEntry } from "./ContextTrackerTypes"
|
||||
import type { ClineMessage } from "@shared/ExtensionMessage"
|
||||
@@ -27,7 +26,7 @@ export class FileContextTracker {
|
||||
readonly taskId: string
|
||||
|
||||
// File tracking and watching
|
||||
private fileWatchers = new Map<string, FSWatcher>()
|
||||
private fileWatchers = new Map<string, vscode.FileSystemWatcher>()
|
||||
private recentlyModifiedFiles = new Set<string>()
|
||||
private recentlyEditedByCline = new Set<string>()
|
||||
|
||||
@@ -51,21 +50,14 @@ export class FileContextTracker {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a chokidar file watcher for this specific file
|
||||
const resolvedFilePath = path.resolve(cwd, filePath)
|
||||
const watcher = chokidar.watch(resolvedFilePath, {
|
||||
persistent: true, // Keep process alive while watching
|
||||
ignoreInitial: true, // Don't emit events for existing files on startup
|
||||
atomic: true, // Handle atomic writes (editors that use temp files)
|
||||
awaitWriteFinish: {
|
||||
// Wait for writes to finish before emitting events
|
||||
stabilityThreshold: 100, // Wait 100ms for file size to stabilize
|
||||
pollInterval: 100, // Check every 100ms while waiting
|
||||
},
|
||||
})
|
||||
// Create a file system watcher for this specific file
|
||||
const fileUri = vscode.Uri.file(path.resolve(cwd, filePath))
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(
|
||||
new vscode.RelativePattern(path.dirname(fileUri.fsPath), path.basename(fileUri.fsPath)),
|
||||
)
|
||||
|
||||
// Track file changes
|
||||
watcher.on("change", () => {
|
||||
watcher.onDidChange(() => {
|
||||
if (this.recentlyEditedByCline.has(filePath)) {
|
||||
this.recentlyEditedByCline.delete(filePath) // This was an edit by Cline, no need to inform Cline
|
||||
} else {
|
||||
@@ -186,9 +178,10 @@ export class FileContextTracker {
|
||||
/**
|
||||
* Disposes all file watchers
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
const closePromises = Array.from(this.fileWatchers.values()).map((watcher) => watcher.close())
|
||||
await Promise.all(closePromises)
|
||||
dispose(): void {
|
||||
for (const watcher of this.fileWatchers.values()) {
|
||||
watcher.dispose()
|
||||
}
|
||||
this.fileWatchers.clear()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { openExternal } from "@/utils/env"
|
||||
|
||||
import { Controller } from ".."
|
||||
import { EmptyRequest, Empty } from "@shared/proto/cline/common"
|
||||
|
||||
/**
|
||||
* Initiates OpenRouter auth
|
||||
*/
|
||||
export async function openrouterAuthClicked(_: Controller, __: EmptyRequest): Promise<Empty> {
|
||||
const callbackUri = await HostProvider.get().getCallbackUri()
|
||||
const authUri = `https://openrouter.ai/auth?callback_url=${callbackUri}/openrouter`
|
||||
|
||||
await openExternal(authUri)
|
||||
|
||||
return {}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ export async function updateBrowserSettings(controller: Controller, request: Upd
|
||||
// Otherwise, fall back to mergedWithDefaults.
|
||||
"chromeExecutablePath" in request ? request.chromeExecutablePath : mergedWithDefaults.chromeExecutablePath,
|
||||
disableToolUse: request.disableToolUse === undefined ? mergedWithDefaults.disableToolUse : request.disableToolUse,
|
||||
customArgs: "customArgs" in request ? request.customArgs : mergedWithDefaults.customArgs,
|
||||
}
|
||||
|
||||
// Update global state with new settings
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { CommandContext, Empty } from "@/shared/proto/index.cline"
|
||||
import { telemetryService } from "@/services/posthog/PostHogClientProvider"
|
||||
import { getFileMentionFromPath } from "@/core/mentions"
|
||||
import { singleFileDiagnosticsToProblemsString } from "@/integrations/diagnostics"
|
||||
import { WebviewProvider } from "@/core/webview"
|
||||
import { sendAddToInputEventToClient } from "../ui/subscribeToAddToInput"
|
||||
|
||||
// 'Add to Cline' context menu in editor and code action
|
||||
// Inserts the selected code into the chat.
|
||||
export async function addToCline(controller: Controller, request: CommandContext): Promise<Empty> {
|
||||
if (!request.selectedText) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const filePath = request.filePath || ""
|
||||
const fileMention = await getFileMentionFromPath(filePath)
|
||||
|
||||
let input = `${fileMention}\n\`\`\`\n${request.selectedText}\n\`\`\``
|
||||
if (request.diagnostics.length) {
|
||||
const problemsString = await singleFileDiagnosticsToProblemsString(filePath, request.diagnostics)
|
||||
input += `\nProblems:\n${problemsString}`
|
||||
}
|
||||
|
||||
const lastActiveWebview = WebviewProvider.getLastActiveInstance()
|
||||
if (lastActiveWebview) {
|
||||
await sendAddToInputEventToClient(lastActiveWebview.getClientId(), input)
|
||||
}
|
||||
|
||||
console.log("addToCline", request.selectedText, filePath, request.language)
|
||||
telemetryService.captureButtonClick("codeAction_addToChat", controller.task?.ulid)
|
||||
|
||||
return {}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { CommandContext, Empty } from "@/shared/proto/index.cline"
|
||||
import { telemetryService } from "@/services/posthog/PostHogClientProvider"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ShowMessageType } from "@/shared/proto/index.host"
|
||||
import { getFileMentionFromPath } from "@/core/mentions"
|
||||
|
||||
export async function explainWithCline(controller: Controller, request: CommandContext): Promise<Empty> {
|
||||
if (!request.selectedText || !request.selectedText.trim()) {
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
})
|
||||
return {}
|
||||
}
|
||||
const fileMention = await getFileMentionFromPath(request.filePath || "")
|
||||
const prompt = `Explain the following code from ${fileMention}:
|
||||
\`\`\`${request.language}\n${request.selectedText}\n\`\`\``
|
||||
await controller.initTask(prompt)
|
||||
telemetryService.captureButtonClick("codeAction_explainCode", controller.task?.ulid)
|
||||
|
||||
return {}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { CommandContext, Empty } from "@/shared/proto/index.cline"
|
||||
import { telemetryService } from "@/services/posthog/PostHogClientProvider"
|
||||
import { getFileMentionFromPath } from "@/core/mentions"
|
||||
import { singleFileDiagnosticsToProblemsString } from "@/integrations/diagnostics"
|
||||
|
||||
export async function fixWithCline(controller: Controller, request: CommandContext): Promise<Empty> {
|
||||
const filePath = request.filePath || ""
|
||||
const fileMention = await getFileMentionFromPath(filePath)
|
||||
const problemsString = await singleFileDiagnosticsToProblemsString(filePath, request.diagnostics)
|
||||
|
||||
await controller.initTask(
|
||||
`Fix the following code in ${fileMention}
|
||||
\`\`\`\n${request.selectedText}\n\`\`\`\n\nProblems:\n${problemsString}`,
|
||||
)
|
||||
console.log("fixWithCline", request.selectedText, request.filePath, request.language, problemsString)
|
||||
|
||||
telemetryService.captureButtonClick("codeAction_fixWithCline", controller.task?.ulid)
|
||||
return {}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Controller } from "../index"
|
||||
import { CommandContext, Empty } from "@/shared/proto/index.cline"
|
||||
import { telemetryService } from "@/services/posthog/PostHogClientProvider"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ShowMessageType } from "@/shared/proto/index.host"
|
||||
import { getFileMentionFromPath } from "@/core/mentions"
|
||||
|
||||
export async function improveWithCline(controller: Controller, request: CommandContext): Promise<Empty> {
|
||||
if (!request.selectedText || !request.selectedText.trim()) {
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
})
|
||||
return {}
|
||||
}
|
||||
const fileMention = await getFileMentionFromPath(request.filePath || "")
|
||||
const prompt = `Improve the following code from ${fileMention} (e.g., suggest refactorings, optimizations, or better practices):
|
||||
\`\`\`${request.language}\n${request.selectedText}\n\`\`\``
|
||||
|
||||
await controller.initTask(prompt)
|
||||
|
||||
telemetryService.captureButtonClick("codeAction_improveCode", controller.task?.ulid)
|
||||
|
||||
return {}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Controller } from ".."
|
||||
import { openFile as openFileIntegration } from "@integrations/misc/open-file"
|
||||
import { Empty, StringRequest } from "../../../shared/proto/cline/common"
|
||||
import { ensureFocusChainFile, extractFocusChainListFromText } from "../../task/focus-chain/file-utils"
|
||||
import { telemetryService } from "../../../services/posthog/PostHogClientProvider"
|
||||
|
||||
/**
|
||||
* Opens or creates a focus chain checklist markdown file for editing
|
||||
* The file is stored at <globalStorage>/tasks/<taskId>/focus_chain_taskid_<taskId>.md
|
||||
*/
|
||||
export async function openFocusChainFile(controller: Controller, request: StringRequest): Promise<Empty> {
|
||||
if (!request.value) {
|
||||
throw new Error("Task ID is required")
|
||||
}
|
||||
|
||||
const taskId = request.value
|
||||
|
||||
// Get the current focus chain list from the task's most recent task_progress message
|
||||
let initialFocusChainContent: string | undefined
|
||||
const currentTask = controller.task
|
||||
if (currentTask) {
|
||||
// Get the task's message history and find the most recent task_progress message
|
||||
// TODO - can we decouple this from ClineMessages?
|
||||
const clineMessages = currentTask.messageStateHandler.getClineMessages()
|
||||
const lastProgressMessage = clineMessages
|
||||
.slice()
|
||||
.reverse()
|
||||
.find((m) => m.say === "task_progress")
|
||||
|
||||
if (lastProgressMessage && lastProgressMessage.text) {
|
||||
initialFocusChainContent = extractFocusChainListFromText(lastProgressMessage.text) || undefined
|
||||
}
|
||||
}
|
||||
|
||||
const focusChainFilePath = await ensureFocusChainFile(controller.context, taskId, initialFocusChainContent)
|
||||
telemetryService.captureFocusChainListOpened(taskId)
|
||||
await openFileIntegration(focusChainFilePath)
|
||||
|
||||
return Empty.create()
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import { UserInfo } from "@shared/UserInfo"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import axios from "axios"
|
||||
import fs from "fs/promises"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
@@ -30,6 +31,8 @@ import { ensureMcpServersDirectoryExists, ensureSettingsDirectoryExists, GlobalF
|
||||
import { Task } from "../task"
|
||||
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
|
||||
import { sendStateUpdate } from "./state/subscribeToState"
|
||||
import { sendAddToInputEvent, sendAddToInputEventToClient } from "./ui/subscribeToAddToInput"
|
||||
import { WebviewProvider } from "../webview"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -44,7 +47,6 @@ export class Controller {
|
||||
|
||||
mcpHub: McpHub
|
||||
accountService: ClineAccountService
|
||||
authService: AuthService
|
||||
readonly cacheService: CacheService
|
||||
|
||||
constructor(
|
||||
@@ -56,13 +58,13 @@ export class Controller {
|
||||
HostProvider.get().logToChannel("ClineProvider instantiated")
|
||||
this.accountService = ClineAccountService.getInstance()
|
||||
this.cacheService = new CacheService(context)
|
||||
this.authService = AuthService.getInstance(this)
|
||||
const authService = AuthService.getInstance(this)
|
||||
|
||||
// Initialize cache service asynchronously - critical for extension functionality
|
||||
this.cacheService
|
||||
.initialize()
|
||||
.then(() => {
|
||||
this.authService.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
authService.restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("CRITICAL: Failed to initialize CacheService - extension may not function properly:", error)
|
||||
@@ -160,8 +162,6 @@ export class Controller {
|
||||
const apiConfiguration = this.cacheService.getApiConfiguration()
|
||||
const autoApprovalSettings = this.cacheService.getGlobalStateKey("autoApprovalSettings")
|
||||
const browserSettings = this.cacheService.getGlobalStateKey("browserSettings")
|
||||
const focusChainSettings = this.cacheService.getGlobalStateKey("focusChainSettings")
|
||||
const focusChainFeatureFlagEnabled = this.cacheService.getGlobalStateKey("focusChainFeatureFlagEnabled")
|
||||
const preferredLanguage = this.cacheService.getGlobalStateKey("preferredLanguage")
|
||||
const openaiReasoningEffort = this.cacheService.getGlobalStateKey("openaiReasoningEffort")
|
||||
const mode = this.cacheService.getGlobalStateKey("mode")
|
||||
@@ -189,12 +189,6 @@ export class Controller {
|
||||
}
|
||||
this.cacheService.setGlobalState("autoApprovalSettings", updatedAutoApprovalSettings)
|
||||
}
|
||||
// Apply remote feature flag gate to focus chain settings
|
||||
const effectiveFocusChainSettings = {
|
||||
...(focusChainSettings || { enabled: true, remindClineInterval: 6 }),
|
||||
enabled: Boolean(focusChainSettings?.enabled) && Boolean(focusChainFeatureFlagEnabled),
|
||||
}
|
||||
|
||||
this.task = new Task(
|
||||
this,
|
||||
this.mcpHub,
|
||||
@@ -205,7 +199,6 @@ export class Controller {
|
||||
apiConfiguration,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
effectiveFocusChainSettings,
|
||||
preferredLanguage,
|
||||
openaiReasoningEffort,
|
||||
mode,
|
||||
@@ -309,7 +302,7 @@ export class Controller {
|
||||
|
||||
async handleAuthCallback(customToken: string, provider: string | null = null) {
|
||||
try {
|
||||
await this.authService.handleAuthCallback(customToken, provider ? provider : "google")
|
||||
await AuthService.getInstance(this).handleAuthCallback(customToken, provider ? provider : "google")
|
||||
|
||||
const clineProvider: ApiProvider = "cline"
|
||||
|
||||
@@ -509,6 +502,87 @@ export class Controller {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Context menus and code actions
|
||||
|
||||
async getFileMentionFromPath(filePath: string) {
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
return "@/" + filePath
|
||||
}
|
||||
const relativePath = path.relative(cwd, filePath)
|
||||
return "@/" + relativePath
|
||||
}
|
||||
|
||||
// 'Add to Cline' context menu in editor and code action
|
||||
async addSelectedCodeToChat(code: string, filePath: string, languageId: string, diagnostics?: vscode.Diagnostic[]) {
|
||||
// Post message to webview with the selected code
|
||||
const fileMention = await this.getFileMentionFromPath(filePath)
|
||||
|
||||
let input = `${fileMention}\n\`\`\`\n${code}\n\`\`\``
|
||||
if (diagnostics) {
|
||||
const problemsString = this.convertDiagnosticsToProblemsString(diagnostics)
|
||||
input += `\nProblems:\n${problemsString}`
|
||||
}
|
||||
|
||||
const lastActiveWebview = WebviewProvider.getLastActiveInstance()
|
||||
if (lastActiveWebview) {
|
||||
await sendAddToInputEventToClient(lastActiveWebview.getClientId(), input)
|
||||
}
|
||||
|
||||
console.log("addSelectedCodeToChat", code, filePath, languageId)
|
||||
}
|
||||
|
||||
// 'Add to Cline' context menu in Terminal
|
||||
async addSelectedTerminalOutputToChat(output: string, terminalName: string) {
|
||||
// Ensure the sidebar view is visible
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await setTimeoutPromise(100)
|
||||
await sendAddToInputEvent(`Terminal output:\n\`\`\`\n${output}\n\`\`\``)
|
||||
|
||||
console.log("addSelectedTerminalOutputToChat", output, terminalName)
|
||||
}
|
||||
|
||||
// 'Fix with Cline' in code actions
|
||||
async fixWithCline(code: string, filePath: string, languageId: string, diagnostics: vscode.Diagnostic[]) {
|
||||
// Ensure the sidebar view is visible
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await setTimeoutPromise(100)
|
||||
|
||||
const fileMention = await this.getFileMentionFromPath(filePath)
|
||||
const problemsString = this.convertDiagnosticsToProblemsString(diagnostics)
|
||||
await this.initTask(`Fix the following code in ${fileMention}\n\`\`\`\n${code}\n\`\`\`\n\nProblems:\n${problemsString}`)
|
||||
|
||||
console.log("fixWithCline", code, filePath, languageId, diagnostics, problemsString)
|
||||
}
|
||||
|
||||
convertDiagnosticsToProblemsString(diagnostics: vscode.Diagnostic[]) {
|
||||
let problemsString = ""
|
||||
for (const diagnostic of diagnostics) {
|
||||
let label: string
|
||||
switch (diagnostic.severity) {
|
||||
case vscode.DiagnosticSeverity.Error:
|
||||
label = "Error"
|
||||
break
|
||||
case vscode.DiagnosticSeverity.Warning:
|
||||
label = "Warning"
|
||||
break
|
||||
case vscode.DiagnosticSeverity.Information:
|
||||
label = "Information"
|
||||
break
|
||||
case vscode.DiagnosticSeverity.Hint:
|
||||
label = "Hint"
|
||||
break
|
||||
default:
|
||||
label = "Diagnostic"
|
||||
}
|
||||
const line = diagnostic.range.start.line + 1 // VSCode lines are 0-indexed
|
||||
const source = diagnostic.source ? `${diagnostic.source} ` : ""
|
||||
problemsString += `\n- [${source}${label}] Line ${line}: ${diagnostic.message}`
|
||||
}
|
||||
problemsString = problemsString.trim()
|
||||
return problemsString
|
||||
}
|
||||
|
||||
// Task history
|
||||
|
||||
async getTaskWithId(id: string): Promise<{
|
||||
@@ -577,8 +651,6 @@ export class Controller {
|
||||
const taskHistory = this.cacheService.getGlobalStateKey("taskHistory")
|
||||
const autoApprovalSettings = this.cacheService.getGlobalStateKey("autoApprovalSettings")
|
||||
const browserSettings = this.cacheService.getGlobalStateKey("browserSettings")
|
||||
const focusChainSettings = this.cacheService.getGlobalStateKey("focusChainSettings")
|
||||
const focusChainFeatureFlagEnabled = this.cacheService.getGlobalStateKey("focusChainFeatureFlagEnabled")
|
||||
const preferredLanguage = this.cacheService.getGlobalStateKey("preferredLanguage")
|
||||
const openaiReasoningEffort = this.cacheService.getGlobalStateKey("openaiReasoningEffort")
|
||||
const mode = this.cacheService.getGlobalStateKey("mode")
|
||||
@@ -595,9 +667,7 @@ export class Controller {
|
||||
const terminalReuseEnabled = this.cacheService.getGlobalStateKey("terminalReuseEnabled")
|
||||
const defaultTerminalProfile = this.cacheService.getGlobalStateKey("defaultTerminalProfile")
|
||||
const isNewUser = this.cacheService.getGlobalStateKey("isNewUser")
|
||||
const welcomeViewCompleted = Boolean(
|
||||
this.cacheService.getGlobalStateKey("welcomeViewCompleted") || this.authService.getInfo()?.user?.uid,
|
||||
)
|
||||
const welcomeViewCompleted = this.cacheService.getGlobalStateKey("welcomeViewCompleted")
|
||||
const mcpResponsesCollapsed = this.cacheService.getGlobalStateKey("mcpResponsesCollapsed")
|
||||
const terminalOutputLineLimit = this.cacheService.getGlobalStateKey("terminalOutputLineLimit")
|
||||
const localClineRulesToggles = this.cacheService.getWorkspaceStateKey("localClineRulesToggles")
|
||||
@@ -628,14 +698,11 @@ export class Controller {
|
||||
currentTaskItem,
|
||||
checkpointTrackerErrorMessage,
|
||||
clineMessages,
|
||||
currentFocusChainChecklist: this.task?.taskState.currentFocusChainChecklist || null,
|
||||
taskHistory: processedTaskHistory,
|
||||
shouldShowAnnouncement,
|
||||
platform,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
focusChainSettings,
|
||||
focusChainFeatureFlagEnabled,
|
||||
preferredLanguage,
|
||||
openaiReasoningEffort,
|
||||
mode,
|
||||
|
||||
@@ -6,8 +6,6 @@ import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { CLAUDE_SONNET_4_1M_TIERS, openRouterClaudeSonnet41mModelId } from "@/shared/api"
|
||||
import cloneDeep from "clone-deep"
|
||||
|
||||
/**
|
||||
* Refreshes the OpenRouter models and returns the updated model list
|
||||
@@ -142,14 +140,6 @@ export async function refreshOpenRouterModels(
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
|
||||
// add custom :1m model variant
|
||||
if (rawModel.id === "anthropic/claude-sonnet-4") {
|
||||
const claudeSonnet41mModelInfo = cloneDeep(modelInfo)
|
||||
claudeSonnet41mModelInfo.contextWindow = 1_000_000 // limiting providers to those that support 1m context window
|
||||
claudeSonnet41mModelInfo.tiers = CLAUDE_SONNET_4_1M_TIERS
|
||||
models[openRouterClaudeSonnet41mModelId] = claudeSonnet41mModelInfo
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid response from OpenRouter API")
|
||||
|
||||
@@ -11,8 +11,6 @@ import { convertProtoApiConfigurationToApiConfiguration } from "../../../shared/
|
||||
import { TelemetrySetting } from "@/shared/TelemetrySetting"
|
||||
import { OpenaiReasoningEffort } from "@/shared/storage/types"
|
||||
import { McpDisplayMode } from "@/shared/McpDisplayMode"
|
||||
import { telemetryService } from "../../../services/posthog/PostHogClientProvider"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
|
||||
/**
|
||||
* Updates multiple extension settings in a single request
|
||||
@@ -140,29 +138,6 @@ export async function updateSettings(controller: Controller, request: UpdateSett
|
||||
controller.cacheService.setGlobalState("strictPlanModeEnabled", request.strictPlanModeEnabled)
|
||||
}
|
||||
|
||||
// Update focus chain settings
|
||||
if (request.focusChainSettings !== undefined) {
|
||||
const remoteEnabled = controller.cacheService.getGlobalStateKey("focusChainFeatureFlagEnabled")
|
||||
if (remoteEnabled === false) {
|
||||
// No-op when feature flag disabled
|
||||
} else {
|
||||
const currentSettings = controller.cacheService.getGlobalStateKey("focusChainSettings")
|
||||
const wasEnabled = currentSettings?.enabled ?? false
|
||||
const isEnabled = request.focusChainSettings.enabled
|
||||
|
||||
const focusChainSettings = {
|
||||
enabled: isEnabled,
|
||||
remindClineInterval: request.focusChainSettings.remindClineInterval,
|
||||
}
|
||||
controller.cacheService.setGlobalState("focusChainSettings", focusChainSettings)
|
||||
|
||||
// Capture telemetry when setting changes
|
||||
if (wasEnabled !== isEnabled) {
|
||||
telemetryService.captureFocusChainToggle(isEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post updated state to webview
|
||||
await controller.postStateToWebview()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { McpMarketplaceCatalog } from "@shared/mcp"
|
||||
import { refreshOpenRouterModels } from "../models/refreshOpenRouterModels"
|
||||
import { refreshGroqModels } from "../models/refreshGroqModels"
|
||||
import { refreshBasetenModels } from "../models/refreshBasetenModels"
|
||||
import { featureFlagsService } from "@/services/posthog/PostHogClientProvider"
|
||||
|
||||
/**
|
||||
* Initialize webview when it launches
|
||||
@@ -182,15 +181,6 @@ export async function initializeWebview(controller: Controller, request: EmptyRe
|
||||
telemetryService.updateTelemetryState(isOptedIn)
|
||||
})
|
||||
|
||||
// Refresh focus chain remote flag on webview init
|
||||
featureFlagsService
|
||||
.getFocusChainEnabled()
|
||||
.then(async (enabled: boolean) => {
|
||||
controller.cacheService.setGlobalState("focusChainFeatureFlagEnabled", enabled)
|
||||
await controller.postStateToWebview()
|
||||
})
|
||||
.catch((err: any) => console.error("Failed to refresh focus chain remote flag on webview init", err))
|
||||
|
||||
return Empty.create({})
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize webview:", error)
|
||||
|
||||
@@ -15,7 +15,7 @@ import { openExternal } from "@utils/env"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
import { diagnosticsToProblemsString } from "@integrations/diagnostics"
|
||||
import { DiagnosticSeverity } from "@/shared/proto/index.cline"
|
||||
import { DiagnosticSeverity } from "@/shared/proto/index.host"
|
||||
|
||||
export async function openMention(mention?: string): Promise<void> {
|
||||
if (!mention) {
|
||||
@@ -44,15 +44,6 @@ export async function openMention(mention?: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFileMentionFromPath(filePath: string) {
|
||||
const cwd = await getCwd()
|
||||
if (!cwd) {
|
||||
return "@/" + filePath
|
||||
}
|
||||
const relativePath = path.relative(cwd, filePath)
|
||||
return "@/" + relativePath
|
||||
}
|
||||
|
||||
export async function parseMentions(
|
||||
text: string,
|
||||
cwd: string,
|
||||
|
||||
@@ -176,188 +176,3 @@ Usage:
|
||||
Below is the user's input when they indicated that they wanted to submit a Github issue.
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
export const deepPlanningToolResponse = () =>
|
||||
`<explicit_instructions type="deep-planning">
|
||||
Your task is to create a comprehensive implementation plan before writing any code. This process has four distinct steps that must be completed in order.
|
||||
|
||||
Your behavior should be methodical and thorough - take time to understand the codebase completely before making any recommendations. The quality of your investigation directly impacts the success of the implementation.
|
||||
|
||||
## STEP 1: Silent Investigation
|
||||
|
||||
<important>
|
||||
until explicitly instructed by the user to proceed with coding.
|
||||
You must thoroughly understand the existing codebase before proposing any changes.
|
||||
Perform your research without commentary or narration. Execute commands and read files without explaining what you're about to do. Only speak up if you have specific questions for the user.
|
||||
</important>
|
||||
|
||||
### Required Research Activities
|
||||
You must use the read_file tool to examine relevant source files, configuration files, and documentation. You must use terminal commands to gather information about the codebase structure and patterns. All terminal output must be piped to cat for visibility.
|
||||
|
||||
### Essential Terminal Commands
|
||||
Execute these commands to build your understanding. You must tailor them to the codebase and ensure the output is not overly verbose. These are only examples, the exact commands will differ depending on the codebase.
|
||||
|
||||
|
||||
# Discover project structure and file types
|
||||
find . -type f -name "*.py" -o -name "*.js" -o -name "*.ts" -o -name "*.java" -o -name "*.cpp" | head -30 | cat
|
||||
|
||||
# Find all class and function definitions
|
||||
grep -r "class\|function\|def\|interface\|struct" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" . | cat
|
||||
|
||||
# Analyze import patterns and dependencies
|
||||
grep -r "import\|from\|require\|#include" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" . | sort | uniq | cat
|
||||
|
||||
# Find dependency manifests
|
||||
find . -name "requirements*.txt" -o -name "package.json" -o -name "Cargo.toml" -o -name "pom.xml" -o -name "Gemfile" | xargs cat
|
||||
|
||||
# Identify technical debt and TODOs
|
||||
grep -r "TODO\|FIXME\|XXX\|HACK\|NOTE" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.cpp" . | cat
|
||||
|
||||
|
||||
## STEP 2: Discussion and Questions
|
||||
|
||||
Ask the user brief, targeted questions that will influence your implementation plan. Keep your questions concise and conversational. Ask only essential questions needed to create an accurate plan.
|
||||
|
||||
**Ask questions only when necessary for:**
|
||||
- Clarifying ambiguous requirements or specifications
|
||||
- Choosing between multiple equally valid implementation approaches
|
||||
- Confirming assumptions about existing system behavior or constraints
|
||||
- Understanding preferences for specific technical decisions that will affect the implementation
|
||||
|
||||
Your questions should be direct and specific. Avoid long explanations or multiple questions in one response.
|
||||
|
||||
## STEP 3: Create Implementation Plan Document
|
||||
|
||||
Create a structured markdown document containing your complete implementation plan. The document must follow this exact format with clearly marked sections:
|
||||
|
||||
### Document Structure Requirements
|
||||
|
||||
Your implementation plan must be saved as implementation_plan.md, and *must* be structured as follows:
|
||||
|
||||
|
||||
# Implementation Plan
|
||||
|
||||
[Overview]
|
||||
Single sentence describing the overall goal.
|
||||
|
||||
Multiple paragraphs outlining the scope, context, and high-level approach. Explain why this implementation is needed and how it fits into the existing system.
|
||||
|
||||
[Types]
|
||||
Single sentence describing the type system changes.
|
||||
|
||||
Detailed type definitions, interfaces, enums, or data structures with complete specifications. Include field names, types, validation rules, and relationships.
|
||||
|
||||
[Files]
|
||||
Single sentence describing file modifications.
|
||||
|
||||
Detailed breakdown:
|
||||
- New files to be created (with full paths and purpose)
|
||||
- Existing files to be modified (with specific changes)
|
||||
- Files to be deleted or moved
|
||||
- Configuration file updates
|
||||
|
||||
[Functions]
|
||||
Single sentence describing function modifications.
|
||||
|
||||
Detailed breakdown:
|
||||
- New functions (name, signature, file path, purpose)
|
||||
- Modified functions (exact name, current file path, required changes)
|
||||
- Removed functions (name, file path, reason, migration strategy)
|
||||
|
||||
[Classes]
|
||||
Single sentence describing class modifications.
|
||||
|
||||
Detailed breakdown:
|
||||
- New classes (name, file path, key methods, inheritance)
|
||||
- Modified classes (exact name, file path, specific modifications)
|
||||
- Removed classes (name, file path, replacement strategy)
|
||||
|
||||
[Dependencies]
|
||||
Single sentence describing dependency modifications.
|
||||
|
||||
Details of new packages, version changes, and integration requirements.
|
||||
|
||||
[Testing]
|
||||
Single sentence describing testing approach.
|
||||
|
||||
Test file requirements, existing test modifications, and validation strategies.
|
||||
|
||||
[Implementation Order]
|
||||
Single sentence describing the implementation sequence.
|
||||
|
||||
Numbered steps showing the logical order of changes to minimize conflicts and ensure successful integration.
|
||||
|
||||
|
||||
## STEP 4: Create Implementation Task
|
||||
|
||||
Use the new_task command to create a task for implementing the plan. The task must include a <task_progress> list that breaks down the implementation into trackable steps.
|
||||
|
||||
### Task Creation Requirements
|
||||
|
||||
Your new task should be self-contained and reference the plan document rather than requiring additional codebase investigation. Include these specific instructions in the task description:
|
||||
|
||||
**Plan Document Navigation Commands:**
|
||||
The implementation agent should use these commands to read specific sections of the implementation plan. You should adapt these examples to conform to the structure of the .md file you createdm, and explicitly provide them when creating the new task:
|
||||
|
||||
|
||||
# Read Overview section
|
||||
sed -n '/\[Overview\]/,/\[Types\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Types section
|
||||
sed -n '/\[Types\]/,/\[Files\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Files section
|
||||
sed -n '/\[Files\]/,/\[Functions\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Functions section
|
||||
sed -n '/\[Functions\]/,/\[Classes\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Classes section
|
||||
sed -n '/\[Classes\]/,/\[Dependencies\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Dependencies section
|
||||
sed -n '/\[Dependencies\]/,/\[Testing\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Testing section
|
||||
sed -n '/\[Testing\]/,/\[Implementation Order\]/p' implementation_plan.md | head -n 1 | cat
|
||||
|
||||
# Read Implementation Order section
|
||||
sed -n '/\[Implementation Order\]/,$p' implementation_plan.md | cat
|
||||
|
||||
|
||||
**Task Progress Format:**
|
||||
<IMPORTANT>
|
||||
You absolutely must include the task_progress contents in context when creating the new task. When providing it, do not wrap it in XML tags- instead provide it like this:
|
||||
|
||||
|
||||
task_progress Items:
|
||||
- [ ] Step 1: Brief description of first implementation step
|
||||
- [ ] Step 2: Brief description of second implementation step
|
||||
- [ ] Step 3: Brief description of third implementation step
|
||||
- [ ] Step N: Brief description of final implementation step
|
||||
|
||||
|
||||
You also MUST include the path to the markdown file you have created in your new task prompt. You should do this as follows:
|
||||
|
||||
Refer to @path/to/file/markdown.md for a complete breakdown of the task requirements and steps. You should periodically read this file again.
|
||||
|
||||
|
||||
|
||||
### Mode Switching
|
||||
|
||||
When creating the new task, request a switch to "act mode" if you are currently in "plan mode". This ensures the implementation agent operates in execution mode rather than planning mode.
|
||||
</IMPORTANT>
|
||||
|
||||
## Quality Standards
|
||||
|
||||
You must be specific with exact file paths, function names, and class names. You must be comprehensive and avoid assuming implicit understanding. You must be practical and consider real-world constraints and edge cases. You must use precise technical language and avoid ambiguity.
|
||||
|
||||
Your implementation plan should be detailed enough that another developer could execute it without additional investigation.
|
||||
|
||||
---
|
||||
|
||||
**Execute all four steps in sequence. Your role is to plan thoroughly, not to implement. Code creation begins only after the new task is created and you receive explicit instruction to proceed.**
|
||||
|
||||
Below is the user's input when they indicated that they wanted to create a comprehensive implementation plan.
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
export const summarizeTask = () =>
|
||||
`<explicit_instructions type="summarize_task">
|
||||
The current conversation is rapidly running out of context. Now, your urgent task is to create a comprehensive detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
|
||||
This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context. You MUST ONLY respond to this message by using the summarize_task tool call.
|
||||
|
||||
Before providing your final summary, wrap your analysis in <thinking> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
|
||||
1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
|
||||
- The user's explicit requests and intents
|
||||
- Your approach to addressing the user's requests
|
||||
- Key decisions, technical concepts and code patterns
|
||||
- Specific details like file names, full code snippets, function signatures, file edits, etc
|
||||
2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
|
||||
|
||||
Your summary should include the following sections:
|
||||
1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
|
||||
2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
|
||||
3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
|
||||
4. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
|
||||
5. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
|
||||
6. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
|
||||
7. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests without confirming with the user first.
|
||||
If there is a next step, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no drift in task interpretation.
|
||||
8. You should pay special attention to the most recent user message, as it indicates the user's most recent intent, if applicable.
|
||||
|
||||
Usage:
|
||||
<summarize_task>
|
||||
<context>Your detailed summary</context>
|
||||
</summarize_task>
|
||||
|
||||
Here's an example of how your output should be structured:
|
||||
|
||||
<example>
|
||||
<thinking>
|
||||
[Your thought process, ensuring all points are covered thoroughly and accurately]
|
||||
</thinking>
|
||||
<summarize_task>
|
||||
<context>
|
||||
1. Primary Request and Intent:
|
||||
[Detailed description]
|
||||
2. Key Technical Concepts:
|
||||
- [Concept 1]
|
||||
- [Concept 2]
|
||||
- [...]
|
||||
3. Files and Code Sections:
|
||||
- [File Name 1]
|
||||
- [Summary of why this file is important]
|
||||
- [Summary of the changes made to this file, if any]
|
||||
- [Important Code Snippet]
|
||||
- [File Name 2]
|
||||
- [Important Code Snippet]
|
||||
- [...]
|
||||
4. Problem Solving:
|
||||
[Description of solved problems and ongoing troubleshooting]
|
||||
5. Pending Tasks:
|
||||
- [Task 1]
|
||||
- [Task 2]
|
||||
- [...]
|
||||
6. Current Work:
|
||||
[Precise description of current work]
|
||||
7. Optional Next Step:
|
||||
[Optional Next step to take]
|
||||
</context>
|
||||
</summarize_task>
|
||||
</example>
|
||||
|
||||
</explicit_instructions>\n
|
||||
`
|
||||
|
||||
export const continuationPrompt = (summaryText: string) => `
|
||||
This session is being continued from a previous conversation that ran out of context. The conversation is summarized below:
|
||||
${summaryText}.
|
||||
|
||||
Please continue the conversation from where we left it off without asking the user any further questions. Continue with the last task that you were asked to work on. Pay special attention to the most recent user message when responding rather than the initial task message, if applicable.
|
||||
If the most recent user's message starts with "/newtask", "/smol", "/compact", "/newrule", or "/reportbug", you should indicate to the user that they will need to run this command again.
|
||||
`
|
||||
@@ -0,0 +1,346 @@
|
||||
import { getShell } from "@utils/shell"
|
||||
import os from "os"
|
||||
import osName from "os-name"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
|
||||
import {
|
||||
createAntmlToolPrompt,
|
||||
createSimpleXmlToolPrompt,
|
||||
toolDefinitionToSimpleXml,
|
||||
} from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
import { bashToolDefinition } from "@core/tools/bashTool"
|
||||
import { readToolDefinition } from "@core/tools/readTool"
|
||||
import { writeToolDefinition } from "@core/tools/writeTool"
|
||||
import { lsToolDefinition } from "@core/tools/lsTool"
|
||||
import { grepToolDefinition } from "@core/tools/grepTool"
|
||||
import { webFetchToolDefinition } from "@core/tools/webFetchTool"
|
||||
import { askQuestionToolDefinition } from "@core/tools/askQuestionTool"
|
||||
import { useMCPToolDefinition } from "@core/tools/useMcpTool"
|
||||
import { listCodeDefinitionNamesToolDefinition } from "@core/tools/listCodeDefinitionNamesTool"
|
||||
import { accessMcpResourceToolDefinition } from "@core/tools/accessMcpResourceTool"
|
||||
import { planModeRespondToolDefinition } from "@core/tools/planModeRespondTool"
|
||||
import { loadMcpDocumentationToolDefinition } from "@core/tools/loadMcpDocumentationTool"
|
||||
import { attemptCompletionToolDefinition } from "@core/tools/attemptCompletionTool"
|
||||
import { browserActionToolDefinition } from "@core/tools/browserActionTool"
|
||||
import { newTaskToolDefinition } from "@core/tools/newTaskTool"
|
||||
import { editToolDefinition } from "@/core/tools/editTool"
|
||||
|
||||
export const SYSTEM_PROMPT_CLAUDE4_EXPERIMENTAL = async (
|
||||
cwd: string,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
) => {
|
||||
const bashTool = bashToolDefinition(cwd)
|
||||
const readTool = readToolDefinition(cwd)
|
||||
const writeTool = writeToolDefinition(cwd)
|
||||
const listCodeDefinitionNamesTool = listCodeDefinitionNamesToolDefinition(cwd)
|
||||
const loadMcpDocumentationTool = loadMcpDocumentationToolDefinition(
|
||||
useMCPToolDefinition.name,
|
||||
accessMcpResourceToolDefinition.name,
|
||||
)
|
||||
const browserActionTool = browserActionToolDefinition(browserSettings)
|
||||
|
||||
const systemPrompt = `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
|
||||
====
|
||||
|
||||
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.
|
||||
|
||||
|
||||
MultiEdit Tool: Makes multiple changes to a single file in one operation
|
||||
|
||||
<function_calls>
|
||||
<invoke name="MultiEdit">
|
||||
<parameter name="file_path">/path/to/file</parameter>
|
||||
<parameter name="edits">[
|
||||
{"old_string": "first text to replace", "new_string": "new text 1"},
|
||||
{"old_string": "second text to replace", "new_string": "new text 2"}
|
||||
]</parameter>
|
||||
</invoke>
|
||||
</function_calls>
|
||||
|
||||
Parameters:
|
||||
- file_path (required): Absolute path to the file to modify
|
||||
- edits (required): Array of edit operations, each containing:
|
||||
- old_string (required): Exact text to replace
|
||||
- new_string (required): The replacement text
|
||||
|
||||
# Tool Use Guidelines
|
||||
|
||||
1. In <thinking> tags, assess what information you already have and what information you need to proceed with the task.
|
||||
2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. It's critical that you think about each available tool and use the one that best fits the current step in the task.
|
||||
3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result.
|
||||
4. Formulate your tool use using the XML format specified for each tool.
|
||||
5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include:
|
||||
- Information about whether the tool succeeded or failed, along with any reasons for failure.
|
||||
- Linter errors that may have arisen due to the changes you made, which you'll need to address.
|
||||
- New terminal output in reaction to the changes, which you may need to consider or act upon.
|
||||
- Any other relevant feedback or information related to the tool use.
|
||||
6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user.
|
||||
|
||||
It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to:
|
||||
1. Confirm the success of each step before proceeding.
|
||||
2. Address any issues or errors that arise immediately.
|
||||
3. Adapt your approach based on new information or unexpected results.
|
||||
4. Ensure that each action builds correctly on the previous ones.
|
||||
|
||||
By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.
|
||||
|
||||
====
|
||||
|
||||
MCP SERVERS
|
||||
|
||||
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
||||
|
||||
# Connected MCP Servers
|
||||
|
||||
When a server is connected, you can use the server's tools via the \`${useMCPToolDefinition.name}\` tool, and access the server's resources via the \`${accessMcpResourceToolDefinition.name}\` tool.
|
||||
|
||||
${
|
||||
mcpHub.getServers().length > 0
|
||||
? `${mcpHub
|
||||
.getServers()
|
||||
.filter((server) => server.status === "connected")
|
||||
.map((server) => {
|
||||
const tools = server.tools
|
||||
?.map((tool) => {
|
||||
const schemaStr = tool.inputSchema
|
||||
? ` Input Schema:
|
||||
${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}`
|
||||
: ""
|
||||
|
||||
return `- ${tool.name}: ${tool.description}\n${schemaStr}`
|
||||
})
|
||||
.join("\n\n")
|
||||
|
||||
const templates = server.resourceTemplates
|
||||
?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`)
|
||||
.join("\n")
|
||||
|
||||
const resources = server.resources
|
||||
?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`)
|
||||
.join("\n")
|
||||
|
||||
const config = JSON.parse(server.config)
|
||||
|
||||
return (
|
||||
`## ${server.name}` +
|
||||
(config.command
|
||||
? ` (\`${config.command}${config.args && Array.isArray(config.args) ? ` ${config.args.join(" ")}` : ""}\`)`
|
||||
: "") +
|
||||
(tools ? `\n\n### Available Tools\n${tools}` : "") +
|
||||
(templates ? `\n\n### Resource Templates\n${templates}` : "") +
|
||||
(resources ? `\n\n### Direct Resources\n${resources}` : "")
|
||||
)
|
||||
})
|
||||
.join("\n\n")}`
|
||||
: "(No MCP servers currently connected)"
|
||||
}
|
||||
|
||||
====
|
||||
|
||||
EDITING FILES
|
||||
|
||||
You have access to two tools for working with files: **${writeTool.name}** and **${editToolDefinition.name}**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications.
|
||||
|
||||
# ${writeTool.name}
|
||||
|
||||
## Purpose
|
||||
|
||||
- Create a new file, or overwrite the entire contents of an existing file.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Initial file creation, such as when scaffolding a new project.
|
||||
- Overwriting large boilerplate files where you want to replace the entire content at once.
|
||||
- When the complexity or number of changes would make ${editToolDefinition.name} unwieldy or error-prone.
|
||||
- When you need to completely restructure a file's content or change its fundamental organization.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
- Using ${writeTool.name} requires providing the file's complete final content.
|
||||
- If you only need to make small changes to an existing file, consider using ${editToolDefinition.name} instead to avoid unnecessarily rewriting the entire file.
|
||||
- While ${writeTool.name} should not be your default choice, don't hesitate to use it when the situation truly calls for it.
|
||||
|
||||
# ${editToolDefinition.name}
|
||||
|
||||
## Purpose
|
||||
|
||||
- Make targeted edits to specific parts of an existing file without overwriting the entire file.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Small, localized changes like updating a few lines, function implementations, changing variable names, modifying a section of text, etc.
|
||||
- Targeted improvements where only specific portions of the file's content needs to be altered.
|
||||
- Especially useful for long files where much of the file will remain unchanged.
|
||||
|
||||
## Advantages
|
||||
|
||||
- More efficient for minor edits, since you don't need to supply the entire file content.
|
||||
- Reduces the chance of errors that can occur when overwriting large files.
|
||||
|
||||
# Choosing the Appropriate Tool
|
||||
|
||||
- **Default to ${editToolDefinition.name}** for most changes. It's the safer, more precise option that minimizes potential issues.
|
||||
- **Use ${writeTool.name}** when:
|
||||
- Creating new files
|
||||
- The changes are so extensive that using ${editToolDefinition.name} would be more complex or risky
|
||||
- You need to completely reorganize or restructure a file
|
||||
- The file is relatively small and the changes affect most of its content
|
||||
- You're generating boilerplate or template files
|
||||
|
||||
# Auto-formatting Considerations
|
||||
|
||||
- After using either ${writeTool.name} or ${editToolDefinition.name}, the user's editor may automatically format the file
|
||||
- This auto-formatting may modify the file contents, for example:
|
||||
- Breaking single lines into multiple lines
|
||||
- Adjusting indentation to match project style (e.g. 2 spaces vs 4 spaces vs tabs)
|
||||
- Converting single quotes to double quotes (or vice versa based on project preferences)
|
||||
- Organizing imports (e.g. sorting, grouping by type)
|
||||
- Adding/removing trailing commas in objects and arrays
|
||||
- Enforcing consistent brace style (e.g. same-line vs new-line)
|
||||
- Standardizing semicolon usage (adding or removing based on style)
|
||||
- The ${writeTool.name} and ${editToolDefinition.name} tool responses will include the final state of the file after any auto-formatting
|
||||
- Use this final state as your reference point for any subsequent edits. This is ESPECIALLY important when crafting SEARCH blocks for ${editToolDefinition.name} which require the content to match what's in the file exactly.
|
||||
|
||||
# Workflow Tips
|
||||
|
||||
1. Before editing, assess the scope of your changes and decide which tool to use.
|
||||
2. For major overhauls or initial file creation, rely on ${writeTool.name}.
|
||||
3. Once the file has been edited with either ${writeTool.name} or ${editToolDefinition.name}, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
|
||||
4. All edits are applied in sequence, in the order they are provided
|
||||
5. All edits must be valid for the operation to succeed - if any edit fails, none will be applied
|
||||
6. Do not make more than 4 replacements in a single ${editToolDefinition.name} call, as this can lead to errors and make it difficult to track changes. If you need to make more than 4 changes, consider breaking them into multiple ${editToolDefinition.name} calls.
|
||||
7. Make sure a single old_str in a ${editToolDefinition.name} call is no more than 4 lines, as too many lines can lead to errors. If you need to replace a larger section, break it into smaller blocks.
|
||||
|
||||
By thoughtfully selecting between ${writeTool.name} and ${editToolDefinition.name}, you can make your file editing process smoother, safer, and more efficient.
|
||||
|
||||
====
|
||||
|
||||
ACT MODE V.S. PLAN MODE
|
||||
|
||||
In each user message, the environment_details will specify the current mode. There are two modes:
|
||||
|
||||
- ACT MODE: In this mode, you have access to all tools EXCEPT the ${planModeRespondToolDefinition.name} tool.
|
||||
- In ACT MODE, you use tools to accomplish the user's task. Once you've completed the user's task, you use the ${attemptCompletionToolDefinition.name} tool to present the result of the task to the user.
|
||||
- PLAN MODE: In this special mode, you have access to the ${planModeRespondToolDefinition.name} tool.
|
||||
- In PLAN MODE, the goal is to gather information and get context to create a detailed plan for accomplishing the task, which the user will review and approve before they switch you to ACT MODE to implement the solution.
|
||||
- In PLAN MODE, when you need to converse with the user or present a plan, you should use the ${planModeRespondToolDefinition.name} tool to deliver your response directly, rather than using <thinking> tags to analyze when to respond. Do not talk about using ${planModeRespondToolDefinition.name} - just use it directly to share your thoughts and provide helpful answers.
|
||||
|
||||
## What is PLAN MODE?
|
||||
|
||||
- While you are usually in ACT MODE, the user may switch to PLAN MODE in order to have a back and forth with you to plan how to best accomplish the task.
|
||||
- When starting in PLAN MODE, depending on the user's request, you may need to do some information gathering e.g. using ${readTool.name} or ${grepToolDefinition.name} to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task.
|
||||
- Once you've gained more context about the user's request, you should architect a detailed plan for how you will accomplish the task.
|
||||
- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.
|
||||
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
||||
|
||||
====
|
||||
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
supportsBrowserUse ? ", use the browser" : ""
|
||||
}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use ${grepToolDefinition.name} to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the ${listCodeDefinitionNamesTool.name} tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use ${listCodeDefinitionNamesTool.name} to get further insight using source code definitions for files located in relevant directories, then ${readTool.name} to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the ${editToolDefinition.name} tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use ${grepToolDefinition.name} to ensure you update other files as needed.
|
||||
- You can use the ${bashTool.name} tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
supportsBrowserUse
|
||||
? `\n- You can use the ${browserActionTool.name} tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use the ${bashTool.name} tool to run the site locally, then use ${browserActionTool.name} to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser.`
|
||||
: ""
|
||||
}
|
||||
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
|
||||
|
||||
====
|
||||
|
||||
RULES
|
||||
|
||||
- Your current working directory is: ${cwd.toPosix()}
|
||||
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the ${bashTool.name} tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
|
||||
- When using the ${grepToolDefinition.name} tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the ${grepToolDefinition.name} tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use ${readTool.name} to examine the full context of interesting matches before using ${editToolDefinition.name} to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when creating files, as the ${writeTool.name} tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
|
||||
- When you want to modify a file, use the ${editToolDefinition.name} or ${writeTool.name} tool directly with the desired changes. You do not need to display the changes before using the tool.
|
||||
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the ${attemptCompletionToolDefinition.name} tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
|
||||
- You are only allowed to ask the user questions using the ${askQuestionToolDefinition.name} tool. Use this tool only when you need additional details to complete a task, and be sure to use a clear and concise question that will help you move forward with the task. However if you can use the available tools to avoid having to ask the user questions, you should do so. For example, if the user mentions a file that may be in an outside directory like the Desktop, you should use the ${lsToolDefinition.name} tool to list the files in the Desktop and check if the file they are talking about is there, rather than asking the user to provide the file path themselves.
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ${askQuestionToolDefinition.name} tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the ${readTool.name} tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
|
||||
supportsBrowserUse
|
||||
? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the ${browserActionTool.name} tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over ${browserActionTool.name}.`
|
||||
: ""
|
||||
}
|
||||
- NEVER end ${attemptCompletionToolDefinition.name} result with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
||||
- You are STRICTLY FORBIDDEN from starting your messages with "Great", "Certainly", "Okay", "Sure". You should NOT be conversational in your responses, but rather direct and to the point. For example you should NOT say "Great, I've updated the CSS" but instead something like "I've updated the CSS". It is important you be clear and technical in your messages.
|
||||
- When presented with images, utilize your vision capabilities to thoroughly examine them and extract meaningful information. Incorporate these insights into your thought process as you accomplish the user's task.
|
||||
- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details.
|
||||
- Before executing commands, check the "Actively Running Terminals" section in environment_details. If present, consider how these active processes might impact your task. For example, if a local development server is already running, you wouldn't need to start it again. If no active terminals are listed, proceed with command execution as normal.
|
||||
- When using the ${editToolDefinition.name} tool, you must include complete lines
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
supportsBrowserUse
|
||||
? ` Then if you want to test your work, you might use ${browserActionTool.name} to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser.`
|
||||
: ""
|
||||
}
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.
|
||||
|
||||
====
|
||||
|
||||
SYSTEM INFORMATION
|
||||
|
||||
Operating System: ${osName()}
|
||||
Default Shell: ${getShell()}
|
||||
Home Directory: ${os.homedir().toPosix()}
|
||||
Current Working Directory: ${cwd.toPosix()}
|
||||
|
||||
====
|
||||
|
||||
If the user asks for help or wants to give feedback inform them of the following:
|
||||
- To give feedback, users should report the issue using the /reportbug slash command in the chat.
|
||||
|
||||
When the user directly asks about Cline (eg 'can Cline do...', 'does Cline have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the ${webFetchToolDefinition.name} tool to gather information to answer the question from Cline docs at https://docs.cline.bot.
|
||||
- The available sub-pages are \`getting-started\` (Intro for new coders, installing Cline and dev essentials), \`model-selection\` (Model Selection Guide, Custom Model Configs, Bedrock, Vertex, Codestral, LM Studio, Ollama), \`features\` (Auto approve, Checkpoints, Cline rules, Drag & Drop, Plan & Act, Workflows, etc), \`task-management\` (Task and Context Management in Cline), \`prompt-engineering\` (Improving your prompting skills, Prompt Engineering Guide), \`cline-tools\` (Cline Tools Reference Guide, New Task Tool, Remote Browser Support, Slash Commands), \`mcp\` (MCP Overview, Adding/Configuring Servers, Transport Mechanisms, MCP Dev Protocol), \`enterprise\` (Cloud provider integration, Security concerns, Custom instructions), \`more-info\` (Telemetry and other reference content)
|
||||
- Example: https://docs.cline.bot/features/auto-approve
|
||||
|
||||
====
|
||||
|
||||
OBJECTIVE
|
||||
|
||||
You accomplish a given task iteratively, breaking it down into clear steps and working through them methodically.
|
||||
|
||||
1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
|
||||
2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. You will be informed on the work completed and what's remaining as you go.
|
||||
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ${askQuestionToolDefinition.name} tool. DO NOT ask for more information on optional parameters if it is not provided.
|
||||
4. Once you've completed the user's task, you must use the ${attemptCompletionToolDefinition.name} tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
|
||||
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
|
||||
|
||||
const tools = [
|
||||
readTool,
|
||||
writeTool,
|
||||
editToolDefinition,
|
||||
askQuestionToolDefinition,
|
||||
planModeRespondToolDefinition,
|
||||
bashTool,
|
||||
lsToolDefinition,
|
||||
grepToolDefinition,
|
||||
webFetchToolDefinition,
|
||||
listCodeDefinitionNamesTool,
|
||||
useMCPToolDefinition,
|
||||
accessMcpResourceToolDefinition,
|
||||
loadMcpDocumentationTool,
|
||||
newTaskToolDefinition,
|
||||
]
|
||||
if (supportsBrowserUse) {
|
||||
tools.push(browserActionTool)
|
||||
}
|
||||
|
||||
return createAntmlToolPrompt(tools, true, systemPrompt)
|
||||
}
|
||||
@@ -5,15 +5,15 @@ import { McpHub } from "@services/mcp/McpHub"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
|
||||
export const SYSTEM_PROMPT_CLAUDE4 = async (
|
||||
cwd: string,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
focusChainSettings : boolean
|
||||
cwd: string,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
) => {
|
||||
|
||||
return `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
return `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
|
||||
====
|
||||
|
||||
TOOL USE
|
||||
|
||||
@@ -33,9 +33,6 @@ For example:
|
||||
|
||||
<read_file>
|
||||
<path>src/main.js</path>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</read_file>
|
||||
|
||||
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
||||
@@ -47,27 +44,19 @@ Description: Request to execute a CLI command on the system. Use this when you n
|
||||
Parameters:
|
||||
- command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
|
||||
- requires_approval: (required) A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<execute_command>
|
||||
<command>Your command here</command>
|
||||
<requires_approval>true or false</requires_approval>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</execute_command>
|
||||
|
||||
## read_file
|
||||
Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
|
||||
Parameters:
|
||||
- path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()})
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<read_file>
|
||||
<path>File path here</path>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</read_file>
|
||||
|
||||
## write_to_file
|
||||
@@ -75,16 +64,12 @@ Description: Request to write content to a file at the specified path. If the fi
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
<content>
|
||||
Your file content here
|
||||
</content>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</write_to_file>
|
||||
|
||||
## replace_in_file
|
||||
@@ -115,16 +100,12 @@ Parameters:
|
||||
4. Special operations:
|
||||
* To move code: Use two SEARCH/REPLACE blocks (one to delete from original + one to insert at new location)
|
||||
* To delete code: Use empty REPLACE section
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<replace_in_file>
|
||||
<path>File path here</path>
|
||||
<diff>
|
||||
Search and replace blocks here
|
||||
</diff>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
|
||||
## list_files
|
||||
@@ -135,25 +116,19 @@ Parameters:
|
||||
Usage:
|
||||
<list_files>
|
||||
<path>Directory path here</path>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
<recursive>true or false (optional)</recursive>
|
||||
</list_files>
|
||||
|
||||
## list_code_definition_names
|
||||
Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.
|
||||
Parameters:
|
||||
- path: (required) The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for.
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<list_code_definition_names>
|
||||
<path>Directory path here</path>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</list_code_definition_names>${
|
||||
supportsBrowserUse
|
||||
? `
|
||||
supportsBrowserUse
|
||||
? `
|
||||
|
||||
## browser_action
|
||||
Description: Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action.
|
||||
@@ -181,18 +156,14 @@ Parameters:
|
||||
* Example: <coordinate>450,300</coordinate>
|
||||
- text: (optional) Use this for providing the text for the \`type\` action.
|
||||
* Example: <text>Hello, world!</text>
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<browser_action>
|
||||
<action>Action to perform (e.g., launch, click, type, scroll_down, scroll_up, close)</action>
|
||||
<url>URL to launch the browser at (optional)</url>
|
||||
<coordinate>x,y coordinates (optional)</coordinate>
|
||||
<text>Text to type (optional)</text>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</browser_action>`
|
||||
: ""
|
||||
: ""
|
||||
}
|
||||
|
||||
## web_fetch
|
||||
@@ -218,7 +189,6 @@ Parameters:
|
||||
- server_name: (required) The name of the MCP server providing the tool
|
||||
- tool_name: (required) The name of the tool to execute
|
||||
- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<use_mcp_tool>
|
||||
<server_name>server name here</server_name>
|
||||
@@ -229,9 +199,6 @@ Usage:
|
||||
"param2": "value2"
|
||||
}
|
||||
</arguments>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</use_mcp_tool>
|
||||
|
||||
## access_mcp_resource
|
||||
@@ -239,14 +206,10 @@ Description: Request to access a resource provided by a connected MCP server. Re
|
||||
Parameters:
|
||||
- server_name: (required) The name of the MCP server providing the resource
|
||||
- uri: (required) The URI identifying the specific resource to access
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<access_mcp_resource>
|
||||
<server_name>server name here</server_name>
|
||||
<uri>resource URI here</uri>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</access_mcp_resource>
|
||||
|
||||
## search_files
|
||||
@@ -278,16 +241,11 @@ Array of options here (optional), e.g. ["Option 1", "Option 2", "Option 3"]
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. Optionally you may provide a CLI command to showcase the result of your work. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
${focusChainSettings ? `If you were using task_progress to update the task progress, you must include the completed list in the result as well.` : "" }
|
||||
Parameters:
|
||||
- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.
|
||||
- command: (optional) A CLI command to execute to show a live demo of the result to the user. For example, use \`open index.html\` to display a created html website, or \`open localhost:3000\` to display a locally running development server. But DO NOT use commands like \`echo\` or \`cat\` that merely print text. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<attempt_completion>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (required if you used task_progress in previous tool uses)
|
||||
</task_progress>` : "" }
|
||||
<result>
|
||||
Your final result description here
|
||||
</result>
|
||||
@@ -315,14 +273,10 @@ However, if while writing your response you realize you actually need to do more
|
||||
Parameters:
|
||||
- response: (required) The response to provide to the user. Do not try to use tools in this parameter, this is simply a chat response. (You MUST use the response parameter, do not simply place the response text directly within <plan_mode_respond> tags.)
|
||||
- needs_more_exploration: (optional) Set to true if while formulating your response that you found you need to do more exploration with tools, for example reading files. (Remember, you can explore the project with tools like read_file in PLAN MODE without the user having to toggle to ACT MODE.) Defaults to false if not specified.
|
||||
${focusChainSettings ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }Usage:
|
||||
Usage:
|
||||
<plan_mode_respond>
|
||||
<response>Your response here</response>
|
||||
<needs_more_exploration>true or false (optional, but you MUST set to true if in <response> you need to read files or use other exploration tools)</needs_more_exploration>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
Checklist here (If you have presented the user with concrete steps or requirements, you can optionally include a todo list outlining these steps.)
|
||||
</task_progress>` : "" }
|
||||
</plan_mode_respond>
|
||||
|
||||
## load_mcp_documentation
|
||||
@@ -339,12 +293,6 @@ Usage:
|
||||
<execute_command>
|
||||
<command>npm run dev</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Run command to start server
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</execute_command>
|
||||
|
||||
## Example 2: Requesting to create a new file
|
||||
@@ -367,12 +315,6 @@ ${focusChainSettings ? `<task_progress>
|
||||
"version": "1.0.0"
|
||||
}
|
||||
</content>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</write_to_file>
|
||||
|
||||
## Example 3: Creating a new task
|
||||
@@ -439,12 +381,6 @@ return (
|
||||
<div>
|
||||
+++++++ REPLACE
|
||||
</diff>
|
||||
${focusChainSettings ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</replace_in_file>
|
||||
|
||||
|
||||
@@ -499,21 +435,8 @@ It is crucial to proceed step-by-step, waiting for the user's message after each
|
||||
|
||||
By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.
|
||||
|
||||
${focusChainSettings ? `====
|
||||
|
||||
AUTOMATIC TODO LIST MANAGEMENT
|
||||
|
||||
The system automatically manages todo lists to help track task progress:
|
||||
|
||||
- Every 10th API request, you will be prompted to review and update the current todo list if one exists
|
||||
- When switching from PLAN MODE to ACT MODE, you should create a comprehensive todo list for the task
|
||||
- Todo list updates should be done silently using the task_progress parameter - do not announce these updates to the user
|
||||
- Use standard Markdown checklist format: "- [ ]" for incomplete items and "- [x]" for completed items
|
||||
- The system will automatically include todo list context in your prompts when appropriate
|
||||
- Focus on creating actionable, meaningful steps rather than granular technical details
|
||||
|
||||
====
|
||||
` : "" }
|
||||
|
||||
MCP SERVERS
|
||||
|
||||
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
||||
@@ -523,31 +446,31 @@ The Model Context Protocol (MCP) enables communication between the system and lo
|
||||
When a server is connected, you can use the server's tools via the \`use_mcp_tool\` tool, and access the server's resources via the \`access_mcp_resource\` tool.
|
||||
|
||||
${
|
||||
mcpHub.getServers().length > 0
|
||||
? `${mcpHub
|
||||
.getServers()
|
||||
.filter((server) => server.status === "connected")
|
||||
.map((server) => {
|
||||
const tools = server.tools
|
||||
?.map((tool) => {
|
||||
const schemaStr = tool.inputSchema
|
||||
? ` Input Schema:
|
||||
mcpHub.getServers().length > 0
|
||||
? `${mcpHub
|
||||
.getServers()
|
||||
.filter((server) => server.status === "connected")
|
||||
.map((server) => {
|
||||
const tools = server.tools
|
||||
?.map((tool) => {
|
||||
const schemaStr = tool.inputSchema
|
||||
? ` Input Schema:
|
||||
${JSON.stringify(tool.inputSchema, null, 2).split("\n").join("\n ")}`
|
||||
: ""
|
||||
: ""
|
||||
|
||||
return `- ${tool.name}: ${tool.description}\n${schemaStr}`
|
||||
})
|
||||
.join("\n\n")
|
||||
return `- ${tool.name}: ${tool.description}\n${schemaStr}`
|
||||
})
|
||||
.join("\n\n")
|
||||
|
||||
const templates = server.resourceTemplates
|
||||
?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`)
|
||||
.join("\n")
|
||||
const templates = server.resourceTemplates
|
||||
?.map((template) => `- ${template.uriTemplate} (${template.name}): ${template.description}`)
|
||||
.join("\n")
|
||||
|
||||
const resources = server.resources
|
||||
?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`)
|
||||
.join("\n")
|
||||
const resources = server.resources
|
||||
?.map((resource) => `- ${resource.uri} (${resource.name}): ${resource.description}`)
|
||||
.join("\n")
|
||||
|
||||
const config = JSON.parse(server.config)
|
||||
const config = JSON.parse(server.config)
|
||||
|
||||
return (
|
||||
`## ${server.name}` +
|
||||
@@ -657,47 +580,21 @@ In each user message, the environment_details will specify the current mode. The
|
||||
- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.
|
||||
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
||||
|
||||
${focusChainSettings ? `====
|
||||
|
||||
UPDATING TASK PROGRESS
|
||||
|
||||
Every tool use supports an optional task_progress parameter that allows you to provide an updated checklist to keep the user informed of your overall progress on the task. This should be used regularly throughout the task to keep the user informed of completed and remaining steps. Before using the attempt_completion tool, ensure the final checklist item is checked off to indicate task completion.
|
||||
|
||||
- You probably wouldn't use this while in PLAN mode until the user has approved your plan and switched you to ACT mode.
|
||||
- Use standard Markdown checklist format: "- [ ]" for incomplete items and "- [x]" for completed items
|
||||
- Provide the whole checklist of steps you intend to complete in the task, and keep the checkboxes updated as you make progress. It's okay to rewrite this checklist as needed if it becomes invalid due to scope changes or new information.
|
||||
- Keep items focused on meaningful progress milestones rather than minor technical details. The checklist should not so granular that minor implementation details clutter the progress tracking.
|
||||
- If you are creating this checklist for the first time, and the tool use completes the first step in the checklist, make sure to mark it as completed in your parameter input since this checklist will be displayed after this tool use is completed.
|
||||
- For simple tasks, short checklists with even a single item are acceptable. For complex tasks, avoid making the checklist too long or verbose.
|
||||
- If a checklist is being used, be sure to update it any time a step has been completed.
|
||||
|
||||
Example:
|
||||
<execute_command>
|
||||
<command>npm install react</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>
|
||||
</execute_command>
|
||||
|
||||
====
|
||||
` : "" }
|
||||
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
supportsBrowserUse ? ", use the browser" : ""
|
||||
supportsBrowserUse ? ", use the browser" : ""
|
||||
}, read and edit files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you need to further explore directories such as outside the current working directory, you can use the list_files tool. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use search_files to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the list_code_definition_names tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use list_code_definition_names to get further insight using source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use search_files to ensure you update other files as needed.
|
||||
- You can use the execute_command tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
supportsBrowserUse
|
||||
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
|
||||
: ""
|
||||
supportsBrowserUse
|
||||
? "\n- You can use the browser_action tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use execute_command to run the site locally, then use browser_action to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser."
|
||||
: ""
|
||||
}
|
||||
- You have access to MCP servers that may provide additional tools and resources. Each server may provide different capabilities that you can use to accomplish tasks more effectively.
|
||||
|
||||
@@ -730,9 +627,9 @@ RULES
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ask_followup_question tool to request the user to copy and paste it back to you.
|
||||
- The user may provide a file's contents directly in their message, in which case you shouldn't use the read_file tool to get the file contents again since you already have it.
|
||||
- Your goal is to try to accomplish the user's task, NOT engage in a back and forth conversation.${
|
||||
supportsBrowserUse
|
||||
? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.`
|
||||
: ""
|
||||
supportsBrowserUse
|
||||
? `\n- The user may ask generic non-development tasks, such as "what\'s the latest news" or "look up the weather in San Diego", in which case you might use the browser_action tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action.`
|
||||
: ""
|
||||
}
|
||||
- NEVER end attempt_completion result with a question or request to engage in further conversation! Formulate the end of your result in a way that is final and does not require further input from the user.
|
||||
- You are STRICTLY FORBIDDEN from starting your messages with "Great", "Certainly", "Okay", "Sure". You should NOT be conversational in your responses, but rather direct and to the point. For example you should NOT say "Great, I've updated the CSS" but instead something like "I've updated the CSS". It is important you be clear and technical in your messages.
|
||||
@@ -743,9 +640,9 @@ RULES
|
||||
- When using the replace_in_file tool, if you use multiple SEARCH/REPLACE blocks, list them in the order they appear in the file. For example if you need to make changes to both line 10 and line 50, first include the SEARCH/REPLACE block for line 10, followed by the SEARCH/REPLACE block for line 50.
|
||||
- When using the replace_in_file tool, Do NOT add extra characters to the markers (e.g., ------- SEARCH> is INVALID). Do NOT forget to use the closing +++++++ REPLACE marker. Do NOT modify the marker format in any way. Malformed XML will cause complete tool failure and break the entire editing process.
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
supportsBrowserUse
|
||||
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
|
||||
: ""
|
||||
supportsBrowserUse
|
||||
? " Then if you want to test your work, you might use browser_action to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser."
|
||||
: ""
|
||||
}
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.
|
||||
|
||||
@@ -769,41 +666,41 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
||||
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
|
||||
4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
|
||||
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
|
||||
}
|
||||
}
|
||||
|
||||
export function addUserInstructions(
|
||||
globalClineRulesFileInstructions?: string,
|
||||
localClineRulesFileInstructions?: string,
|
||||
localCursorRulesFileInstructions?: string,
|
||||
localCursorRulesDirInstructions?: string,
|
||||
localWindsurfRulesFileInstructions?: string,
|
||||
clineIgnoreInstructions?: string,
|
||||
preferredLanguageInstructions?: string,
|
||||
globalClineRulesFileInstructions?: string,
|
||||
localClineRulesFileInstructions?: string,
|
||||
localCursorRulesFileInstructions?: string,
|
||||
localCursorRulesDirInstructions?: string,
|
||||
localWindsurfRulesFileInstructions?: string,
|
||||
clineIgnoreInstructions?: string,
|
||||
preferredLanguageInstructions?: string,
|
||||
) {
|
||||
let customInstructions = ""
|
||||
if (preferredLanguageInstructions) {
|
||||
customInstructions += preferredLanguageInstructions + "\n\n"
|
||||
}
|
||||
if (globalClineRulesFileInstructions) {
|
||||
customInstructions += globalClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localClineRulesFileInstructions) {
|
||||
customInstructions += localClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localCursorRulesFileInstructions) {
|
||||
customInstructions += localCursorRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localCursorRulesDirInstructions) {
|
||||
customInstructions += localCursorRulesDirInstructions + "\n\n"
|
||||
}
|
||||
if (localWindsurfRulesFileInstructions) {
|
||||
customInstructions += localWindsurfRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (clineIgnoreInstructions) {
|
||||
customInstructions += clineIgnoreInstructions
|
||||
}
|
||||
let customInstructions = ""
|
||||
if (preferredLanguageInstructions) {
|
||||
customInstructions += preferredLanguageInstructions + "\n\n"
|
||||
}
|
||||
if (globalClineRulesFileInstructions) {
|
||||
customInstructions += globalClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localClineRulesFileInstructions) {
|
||||
customInstructions += localClineRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localCursorRulesFileInstructions) {
|
||||
customInstructions += localCursorRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (localCursorRulesDirInstructions) {
|
||||
customInstructions += localCursorRulesDirInstructions + "\n\n"
|
||||
}
|
||||
if (localWindsurfRulesFileInstructions) {
|
||||
customInstructions += localWindsurfRulesFileInstructions + "\n\n"
|
||||
}
|
||||
if (clineIgnoreInstructions) {
|
||||
customInstructions += clineIgnoreInstructions
|
||||
}
|
||||
|
||||
return `
|
||||
return `
|
||||
====
|
||||
|
||||
USER'S CUSTOM INSTRUCTIONS
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
function escapeXml(text: string): string {
|
||||
// Anything that could be interpreted as markup has to be entity-encoded
|
||||
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||
}
|
||||
|
||||
export interface ToolDefinition {
|
||||
name: string
|
||||
description?: string
|
||||
descriptionForAgent?: string
|
||||
inputSchema: {
|
||||
type: string
|
||||
properties: Record<string, any>
|
||||
required?: string[]
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single tool definition (JSON schema) to the <function> tag format.
|
||||
* This is for *defining* the tool, not calling it.
|
||||
* @param toolDef The tool definition object
|
||||
* @returns The tool definition as a JSON string wrapped in <function> tags
|
||||
*/
|
||||
export function toolDefinitionToAntmlDefinition(toolDef: ToolDefinition): string {
|
||||
// Restructure the parameters object to match the expected order
|
||||
const { type, properties, required, ...rest } = toolDef.inputSchema
|
||||
const parameters = {
|
||||
properties,
|
||||
required,
|
||||
type,
|
||||
...rest,
|
||||
}
|
||||
|
||||
const functionDef = {
|
||||
description: toolDef.descriptionForAgent || toolDef.description || "",
|
||||
name: toolDef.name,
|
||||
parameters,
|
||||
}
|
||||
|
||||
// 1. Create a custom JSON string with the exact format we want
|
||||
let rawJson = `{"description": "${functionDef.description}", "name": "${functionDef.name}", "parameters": {`
|
||||
|
||||
// Add properties
|
||||
rawJson += `"properties": {`
|
||||
const propEntries = Object.entries(parameters.properties)
|
||||
propEntries.forEach(([propName, propDef], index) => {
|
||||
rawJson += `"${propName}": {`
|
||||
rawJson += `"description": "${(propDef as any).description || ""}", `
|
||||
rawJson += `"type": "${(propDef as any).type || "string"}"`
|
||||
rawJson += `}`
|
||||
if (index < propEntries.length - 1) {
|
||||
rawJson += ", "
|
||||
}
|
||||
})
|
||||
rawJson += `}, `
|
||||
|
||||
// Add required
|
||||
rawJson += `"required": ${JSON.stringify(parameters.required || [])}, `
|
||||
|
||||
// Add type
|
||||
rawJson += `"type": "object"`
|
||||
|
||||
// Close parameters and the whole object
|
||||
rawJson += `}}`
|
||||
|
||||
// 2. Escape <, > and & so the JSON can sit INSIDE the XML tag safely.
|
||||
// (Quotes don’t need escaping - they’re not markup.)
|
||||
const safeJson = escapeXml(rawJson)
|
||||
|
||||
// 3. Return wrapped in <function> tags
|
||||
return `<function>${safeJson}</function>`
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts multiple tool definitions to the complete <functions> block.
|
||||
* This is for *defining* the tools.
|
||||
* @param toolDefs Array of tool definition objects
|
||||
* @returns Complete <functions> block with all tool definitions
|
||||
*/
|
||||
export function toolDefinitionsToAntmlDefinitions(toolDefs: ToolDefinition[]): string {
|
||||
const functionTags = toolDefs.map(toolDefinitionToAntmlDefinition)
|
||||
return `Here are the functions available in JSONSchema format:
|
||||
<functions>
|
||||
${functionTags.join("\n")}
|
||||
</functions>`
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an example of an ANTML tool call for a given tool definition.
|
||||
* This is for *calling* a tool.
|
||||
* @param toolDef The tool definition object
|
||||
* @param exampleValues Optional example values for parameters
|
||||
* @returns Example ANTML function call string
|
||||
*/
|
||||
export function toolDefinitionToAntmlCallExample(toolDef: ToolDefinition, exampleValues: Record<string, any> = {}): string {
|
||||
const props = toolDef.inputSchema.properties ?? {}
|
||||
|
||||
const paramLines = Object.keys(props).length
|
||||
? Object.entries(props)
|
||||
.map(([name]) => {
|
||||
const value = exampleValues[name] ?? `$${name.toUpperCase()}` // placeholder
|
||||
// Don't escape XML here - the example should show raw format
|
||||
return `<parameter name="${name}">${value}</parameter>`
|
||||
})
|
||||
.join("\n")
|
||||
: ""
|
||||
|
||||
// Only include one invoke block
|
||||
return ["<function_calls>", `<invoke name="${toolDef.name}">`, paramLines, "</invoke>", "</function_calls>"]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a complete system prompt section for tools in ANTML format,
|
||||
* including instructions and tool definitions.
|
||||
* @param toolDefs Array of tool definition objects
|
||||
* @param includeInstructions Whether to include the standard tool calling instructions
|
||||
* @returns Complete system prompt section for ANTML tools
|
||||
*/
|
||||
export function createAntmlToolPrompt(toolDefs: ToolDefinition[], includeInstructions = true, systemPrompt = ""): string {
|
||||
if (toolDefs.length === 0) {
|
||||
if (!includeInstructions) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const noToolsMessage = [
|
||||
"In this environment you have access to a set of tools you can use to answer the user's question.",
|
||||
'You can invoke functions by writing a "<function_calls>" block like the following as part of your reply to the user:',
|
||||
"<function_calls>",
|
||||
'<invoke name="$FUNCTION_NAME">',
|
||||
'<parameter name="$PARAMETER_NAME">$PARAMETER_VALUE</parameter>',
|
||||
"...",
|
||||
"</invoke>",
|
||||
"</function_calls>",
|
||||
"",
|
||||
"String and scalar parameters should be specified as is, while lists and objects should use JSON format.",
|
||||
"",
|
||||
"However, no tools are currently available.",
|
||||
].join("\n")
|
||||
|
||||
return noToolsMessage
|
||||
}
|
||||
|
||||
let prompt = ""
|
||||
|
||||
if (includeInstructions) {
|
||||
const instructionLines = [
|
||||
"In this environment you have access to a set of tools you can use to answer the user's question.",
|
||||
'You can invoke functions by writing a "<function_calls>" block like the following as part of your reply to the user:',
|
||||
"<function_calls>",
|
||||
'<invoke name="$FUNCTION_NAME">',
|
||||
'<parameter name="$PARAMETER_NAME">$PARAMETER_VALUE</parameter>',
|
||||
"...",
|
||||
"</invoke>",
|
||||
"</function_calls>",
|
||||
"",
|
||||
"String and scalar parameters should be specified as is, while lists and objects should use JSON format.",
|
||||
"",
|
||||
]
|
||||
prompt += instructionLines.join("\n")
|
||||
}
|
||||
|
||||
prompt += toolDefinitionsToAntmlDefinitions(toolDefs)
|
||||
|
||||
if (includeInstructions) {
|
||||
const closingInstructions = [
|
||||
"",
|
||||
"",
|
||||
systemPrompt,
|
||||
"",
|
||||
"",
|
||||
"Answer the user's request using the relevant tool(s), if they are available. Check that all the required parameters for each tool call are provided or can reasonably be inferred from context. IF there are no relevant tools or there are missing values for required parameters, ask the user to supply these values; otherwise proceed with the tool calls. If the user provides a specific value for a parameter (for example provided in quotes), make sure to use that value EXACTLY. DO NOT make up values for or ask about optional parameters. Carefully analyze descriptive terms in the request as they may indicate required parameter values that should be included even if not explicitly quoted.",
|
||||
]
|
||||
prompt += closingInstructions.join("\n")
|
||||
}
|
||||
|
||||
return prompt // Don't trim - preserve exact formatting
|
||||
}
|
||||
|
||||
// --- SimpleXML Functions (Cline's internal format) ---
|
||||
|
||||
/**
|
||||
* Converts a single tool definition to the SimpleXML format
|
||||
* as used by Cline's current system prompts for non-ANTML models.
|
||||
* @param toolDef The tool definition object
|
||||
* @returns The tool definition formatted for SimpleXML usage
|
||||
*/
|
||||
export function toolDefinitionToSimpleXml(toolDef: ToolDefinition): string {
|
||||
const description = toolDef.descriptionForAgent || toolDef.description || ""
|
||||
const properties = toolDef.inputSchema.properties || {}
|
||||
const required = toolDef.inputSchema.required || []
|
||||
|
||||
let parameterDocs = ""
|
||||
if (Object.keys(properties).length > 0) {
|
||||
parameterDocs = "Parameters:\n"
|
||||
for (const [paramName, paramDef] of Object.entries(properties)) {
|
||||
const isRequired = required.includes(paramName)
|
||||
const requiredText = isRequired ? "(required)" : "(optional)"
|
||||
const paramDescription = (paramDef as any).description || "No description."
|
||||
parameterDocs += `- ${paramName}: ${requiredText} ${paramDescription}\n`
|
||||
}
|
||||
}
|
||||
|
||||
const exampleParams = Object.keys(properties)
|
||||
.map((paramName) => `<${paramName}>${paramName} value here</${paramName}>`)
|
||||
.join("\n")
|
||||
|
||||
const usageExample = `Usage:
|
||||
<${toolDef.name}>
|
||||
${exampleParams.length > 0 ? exampleParams + "\n" : ""}</${toolDef.name}>`
|
||||
|
||||
return `## ${toolDef.name}
|
||||
Description: ${description}
|
||||
${parameterDocs.trim()}
|
||||
${usageExample}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts multiple tool definitions to the complete SimpleXML format.
|
||||
* @param toolDefs Array of tool definition objects
|
||||
* @returns Complete tools documentation in SimpleXML format
|
||||
*/
|
||||
export function toolDefinitionsToSimpleXml(toolDefs: ToolDefinition[]): string {
|
||||
const toolDocs = toolDefs.map((toolDef) => toolDefinitionToSimpleXml(toolDef))
|
||||
return `# Tools
|
||||
|
||||
${toolDocs.join("\n\n")}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a complete system prompt section for tools in SimpleXML format.
|
||||
* @param toolDefs Array of tool definition objects
|
||||
* @param includeInstructions Whether to include the standard tool calling instructions
|
||||
* @returns Complete system prompt section for SimpleXML tools
|
||||
*/
|
||||
export function createSimpleXmlToolPrompt(toolDefs: ToolDefinition[], includeInstructions: boolean = true): string {
|
||||
if (toolDefs.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
let prompt = ""
|
||||
|
||||
if (includeInstructions) {
|
||||
prompt += `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.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
||||
|
||||
<tool_name>
|
||||
<parameter1_name>value1</parameter1_name>
|
||||
<parameter2_name>value2</parameter2_name>
|
||||
...
|
||||
</tool_name>
|
||||
|
||||
For example:
|
||||
|
||||
<read_file>
|
||||
<path>src/main.js</path>
|
||||
</read_file>
|
||||
|
||||
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
||||
`
|
||||
}
|
||||
|
||||
prompt += toolDefinitionsToSimpleXml(toolDefs)
|
||||
|
||||
if (includeInstructions) {
|
||||
prompt += `
|
||||
|
||||
# Tool Use Guidelines
|
||||
|
||||
1. Choose the most appropriate tool based on the task and the tool descriptions provided.
|
||||
2. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively.
|
||||
3. Formulate your tool use using the XML format specified for each tool.
|
||||
4. After each tool use, the user will respond with the result of that tool use.
|
||||
5. ALWAYS wait for user confirmation after each tool use before proceeding.`
|
||||
}
|
||||
return prompt.trimEnd()
|
||||
}
|
||||
+12
-105
@@ -3,20 +3,24 @@ import os from "os"
|
||||
import osName from "os-name"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
import { SYSTEM_PROMPT_CLAUDE4_EXPERIMENTAL } from "@core/prompts/model_prompts/claude4-experimental"
|
||||
import { SYSTEM_PROMPT_CLAUDE4 } from "@core/prompts/model_prompts/claude4"
|
||||
import { USE_EXPERIMENTAL_CLAUDE4_FEATURES } from "@core/task/index"
|
||||
|
||||
export const SYSTEM_PROMPT = async (
|
||||
cwd: string,
|
||||
supportsBrowserUse: boolean,
|
||||
mcpHub: McpHub,
|
||||
browserSettings: BrowserSettings,
|
||||
focusChainSettings: FocusChainSettings,
|
||||
isNextGenModel: boolean = false,
|
||||
) => {
|
||||
if (isNextGenModel) {
|
||||
return SYSTEM_PROMPT_CLAUDE4(cwd, supportsBrowserUse, mcpHub, browserSettings, focusChainSettings.enabled)
|
||||
}
|
||||
if (isNextGenModel && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
return SYSTEM_PROMPT_CLAUDE4_EXPERIMENTAL(cwd, supportsBrowserUse, mcpHub, browserSettings)
|
||||
}
|
||||
|
||||
if (isNextGenModel) {
|
||||
return SYSTEM_PROMPT_CLAUDE4(cwd, supportsBrowserUse, mcpHub, browserSettings)
|
||||
}
|
||||
|
||||
return `You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
|
||||
@@ -40,9 +44,6 @@ For example:
|
||||
|
||||
<read_file>
|
||||
<path>src/main.js</path>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</read_file>
|
||||
|
||||
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
||||
@@ -54,27 +55,19 @@ Description: Request to execute a CLI command on the system. Use this when you n
|
||||
Parameters:
|
||||
- command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
|
||||
- requires_approval: (required) A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<execute_command>
|
||||
<command>Your command here</command>
|
||||
<requires_approval>true or false</requires_approval>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : ""}
|
||||
</execute_command>
|
||||
|
||||
## read_file
|
||||
Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
|
||||
Parameters:
|
||||
- path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()})
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<read_file>
|
||||
<path>File path here</path>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</read_file>
|
||||
|
||||
## write_to_file
|
||||
@@ -82,16 +75,12 @@ Description: Request to write content to a file at the specified path. If the fi
|
||||
Parameters:
|
||||
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
|
||||
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<write_to_file>
|
||||
<path>File path here</path>
|
||||
<content>
|
||||
Your file content here
|
||||
</content>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</write_to_file>
|
||||
|
||||
## replace_in_file
|
||||
@@ -122,16 +111,12 @@ Parameters:
|
||||
4. Special operations:
|
||||
* To move code: Use two SEARCH/REPLACE blocks (one to delete from original + one to insert at new location)
|
||||
* To delete code: Use empty REPLACE section
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<replace_in_file>
|
||||
<path>File path here</path>
|
||||
<diff>
|
||||
Search and replace blocks here
|
||||
</diff>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
|
||||
|
||||
@@ -196,16 +181,12 @@ Parameters:
|
||||
* Example: <coordinate>450,300</coordinate>
|
||||
- text: (optional) Use this for providing the text for the \`type\` action.
|
||||
* Example: <text>Hello, world!</text>
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<browser_action>
|
||||
<action>Action to perform (e.g., launch, click, type, scroll_down, scroll_up, close)</action>
|
||||
<url>URL to launch the browser at (optional)</url>
|
||||
<coordinate>x,y coordinates (optional)</coordinate>
|
||||
<text>Text to type (optional)</text>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</browser_action>`
|
||||
: ""
|
||||
}
|
||||
@@ -216,7 +197,6 @@ Parameters:
|
||||
- server_name: (required) The name of the MCP server providing the tool
|
||||
- tool_name: (required) The name of the tool to execute
|
||||
- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schema
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<use_mcp_tool>
|
||||
<server_name>server name here</server_name>
|
||||
@@ -227,9 +207,6 @@ Usage:
|
||||
"param2": "value2"
|
||||
}
|
||||
</arguments>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</use_mcp_tool>
|
||||
|
||||
## access_mcp_resource
|
||||
@@ -237,14 +214,10 @@ Description: Request to access a resource provided by a connected MCP server. Re
|
||||
Parameters:
|
||||
- server_name: (required) The name of the MCP server providing the resource
|
||||
- uri: (required) The URI identifying the specific resource to access
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<access_mcp_resource>
|
||||
<server_name>server name here</server_name>
|
||||
<uri>resource URI here</uri>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (optional)
|
||||
</task_progress>` : "" }
|
||||
</access_mcp_resource>
|
||||
|
||||
## ask_followup_question
|
||||
@@ -263,16 +236,11 @@ Array of options here (optional), e.g. ["Option 1", "Option 2", "Option 3"]
|
||||
## attempt_completion
|
||||
Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. Optionally you may provide a CLI command to showcase the result of your work. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.
|
||||
${focusChainSettings.enabled ? `If you were using task_progress to update the task progress, you must include the completed list in the result as well.` : "" }
|
||||
Parameters:
|
||||
- result: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.
|
||||
- command: (optional) A CLI command to execute to show a live demo of the result to the user. For example, use \`open index.html\` to display a created html website, or \`open localhost:3000\` to display a locally running development server. But DO NOT use commands like \`echo\` or \`cat\` that merely print text. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
|
||||
${focusChainSettings.enabled ? `- task_progress: A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<attempt_completion>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (required if you used task_progress in previous tool uses)
|
||||
</task_progress>` : "" }
|
||||
<result>
|
||||
Your final result description here
|
||||
</result>
|
||||
@@ -300,14 +268,10 @@ However, if while writing your response you realize you actually need to do more
|
||||
Parameters:
|
||||
- response: (required) The response to provide to the user. Do not try to use tools in this parameter, this is simply a chat response. (You MUST use the response parameter, do not simply place the response text directly within <plan_mode_respond> tags.)
|
||||
- needs_more_exploration: (optional) Set to true if while formulating your response that you found you need to do more exploration with tools, for example reading files. (Remember, you can explore the project with tools like read_file in PLAN MODE without the user having to toggle to ACT MODE.) Defaults to false if not specified.
|
||||
${focusChainSettings.enabled ? `- task_progress: (optional) A checklist showing task progress after this tool use is completed. (See 'Updating Task Progress' section for more details)` : "" }
|
||||
Usage:
|
||||
<plan_mode_respond>
|
||||
<response>Your response here</response>
|
||||
<needs_more_exploration>true or false (optional, but you MUST set to true if in <response> you need to read files or use other exploration tools)</needs_more_exploration>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
Checklist here (If you have presented the user with concrete steps or requirements, you can optionally include a todo list outlining these steps.)
|
||||
</task_progress>` : "" }
|
||||
</plan_mode_respond>
|
||||
|
||||
## load_mcp_documentation
|
||||
@@ -324,12 +288,6 @@ Usage:
|
||||
<execute_command>
|
||||
<command>npm run dev</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Run command to start server
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</execute_command>
|
||||
|
||||
## Example 2: Requesting to create a new file
|
||||
@@ -352,12 +310,6 @@ ${focusChainSettings.enabled ? `<task_progress>
|
||||
"version": "1.0.0"
|
||||
}
|
||||
</content>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</write_to_file>
|
||||
|
||||
## Example 3: Creating a new task
|
||||
@@ -424,12 +376,6 @@ return (
|
||||
<div>
|
||||
+++++++ REPLACE
|
||||
</diff>
|
||||
${focusChainSettings.enabled ? `<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>` : "" }
|
||||
</replace_in_file>
|
||||
|
||||
|
||||
@@ -484,21 +430,8 @@ It is crucial to proceed step-by-step, waiting for the user's message after each
|
||||
|
||||
By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work.
|
||||
|
||||
${focusChainSettings.enabled ? `===
|
||||
|
||||
AUTOMATIC TODO LIST MANAGEMENT
|
||||
|
||||
The system automatically manages todo lists to help track task progress:
|
||||
|
||||
- Every 10th API request, you will be prompted to review and update the current todo list if one exists
|
||||
- When switching from PLAN MODE to ACT MODE, you should create a comprehensive todo list for the task
|
||||
- Todo list updates should be done silently using the task_progress parameter - do not announce these updates to the user
|
||||
- Use standard Markdown checklist format: "- [ ]" for incomplete items and "- [x]" for completed items
|
||||
- The system will automatically include todo list context in your prompts when appropriate
|
||||
- Focus on creating actionable, meaningful steps rather than granular technical details
|
||||
|
||||
====
|
||||
`: "" }
|
||||
|
||||
MCP SERVERS
|
||||
|
||||
The Model Context Protocol (MCP) enables communication between the system and locally running MCP servers that provide additional tools and resources to extend your capabilities.
|
||||
@@ -642,34 +575,8 @@ In each user message, the environment_details will specify the current mode. The
|
||||
- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.
|
||||
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
||||
|
||||
${focusChainSettings.enabled ? `====
|
||||
|
||||
UPDATING TASK PROGRESS
|
||||
|
||||
Every tool use supports an optional task_progress parameter that allows you to provide an updated checklist to keep the user informed of your overall progress on the task. This should be used regularly throughout the task to keep the user informed of completed and remaining steps. Before using the attempt_completion tool, ensure the final checklist item is checked off to indicate task completion.
|
||||
|
||||
- You probably wouldn't use this while in PLAN mode until the user has approved your plan and switched you to ACT mode.
|
||||
- Use standard Markdown checklist format: "- [ ]" for incomplete items and "- [x]" for completed items
|
||||
- Provide the whole checklist of steps you intend to complete in the task, and keep the checkboxes updated as you make progress. It's okay to rewrite this checklist as needed if it becomes invalid due to scope changes or new information.
|
||||
- Keep items focused on meaningful progress milestones rather than minor technical details. The checklist should not be so granular that minor implementation details clutter the progress tracking.
|
||||
- If you are creating this checklist for the first time, and the tool use completes the first step in the checklist, make sure to mark it as completed in your parameter input since this checklist will be displayed after this tool use is completed.
|
||||
- For simple tasks, short checklists with even a single item are acceptable. For complex tasks, avoid making the checklist too long or verbose.
|
||||
- If a checklist is being used, be sure to update it any time a step has been completed.
|
||||
|
||||
Example:
|
||||
<execute_command>
|
||||
<command>npm install react</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
<task_progress>
|
||||
- [x] Set up project structure
|
||||
- [x] Install dependencies
|
||||
- [ ] Create components
|
||||
- [ ] Test application
|
||||
</task_progress>
|
||||
</execute_command>
|
||||
|
||||
====
|
||||
` : "" }
|
||||
|
||||
CAPABILITIES
|
||||
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files, view source code definitions, regex search${
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
newTaskToolResponse,
|
||||
condenseToolResponse,
|
||||
newRuleToolResponse,
|
||||
reportBugToolResponse,
|
||||
deepPlanningToolResponse,
|
||||
} from "../prompts/commands"
|
||||
import { newTaskToolResponse, condenseToolResponse, newRuleToolResponse, reportBugToolResponse } from "../prompts/commands"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import fs from "fs/promises"
|
||||
|
||||
@@ -17,7 +11,7 @@ export async function parseSlashCommands(
|
||||
localWorkflowToggles: ClineRulesToggles,
|
||||
globalWorkflowToggles: ClineRulesToggles,
|
||||
): Promise<{ processedText: string; needsClinerulesFileCheck: boolean }> {
|
||||
const SUPPORTED_DEFAULT_COMMANDS = ["newtask", "smol", "compact", "newrule", "reportbug", "deep-planning"]
|
||||
const SUPPORTED_DEFAULT_COMMANDS = ["newtask", "smol", "compact", "newrule", "reportbug"]
|
||||
|
||||
const commandReplacements: Record<string, string> = {
|
||||
newtask: newTaskToolResponse(),
|
||||
@@ -25,7 +19,6 @@ export async function parseSlashCommands(
|
||||
compact: condenseToolResponse(),
|
||||
newrule: newRuleToolResponse(),
|
||||
reportbug: reportBugToolResponse(),
|
||||
"deep-planning": deepPlanningToolResponse(),
|
||||
}
|
||||
|
||||
// this currently allows matching prepended whitespace prior to /slash-command
|
||||
|
||||
@@ -4,7 +4,6 @@ import { CACHE_SERVICE_NOT_INITIALIZED } from "./error-messages"
|
||||
import type { ExtensionContext } from "vscode"
|
||||
import { readStateFromDisk } from "./utils/state-helpers"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@/shared/AutoApprovalSettings"
|
||||
import { DEFAULT_FOCUS_CHAIN_SETTINGS } from "@shared/FocusChainSettings"
|
||||
|
||||
/**
|
||||
* Interface for persistence error event data
|
||||
@@ -214,7 +213,6 @@ export class CacheService {
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyBaseUrl,
|
||||
togetherApiKey,
|
||||
qwenApiKey,
|
||||
doubaoApiKey,
|
||||
@@ -381,7 +379,6 @@ export class CacheService {
|
||||
awsAuthentication,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
requestyBaseUrl,
|
||||
openAiBaseUrl,
|
||||
openAiHeaders,
|
||||
ollamaBaseUrl,
|
||||
@@ -627,7 +624,6 @@ export class CacheService {
|
||||
openAiNativeApiKey,
|
||||
deepSeekApiKey,
|
||||
requestyApiKey,
|
||||
requestyBaseUrl,
|
||||
togetherApiKey,
|
||||
qwenApiKey,
|
||||
doubaoApiKey,
|
||||
@@ -662,7 +658,6 @@ export class CacheService {
|
||||
huggingFaceApiKey,
|
||||
huaweiCloudMaasApiKey,
|
||||
requestTimeoutMs,
|
||||
authNonce,
|
||||
// Plan mode configurations
|
||||
planModeApiProvider,
|
||||
planModeApiModelId,
|
||||
@@ -732,8 +727,6 @@ export class CacheService {
|
||||
autoApprovalSettings: state.autoApprovalSettings || DEFAULT_AUTO_APPROVAL_SETTINGS,
|
||||
globalClineRulesToggles: state.globalClineRulesToggles,
|
||||
browserSettings: state.browserSettings,
|
||||
focusChainSettings: state.focusChainSettings || DEFAULT_FOCUS_CHAIN_SETTINGS,
|
||||
focusChainFeatureFlagEnabled: state.focusChainFeatureFlagEnabled,
|
||||
preferredLanguage: state.preferredLanguage,
|
||||
openaiReasoningEffort: state.openaiReasoningEffort,
|
||||
mode: state.mode,
|
||||
@@ -750,8 +743,6 @@ export class CacheService {
|
||||
defaultTerminalProfile: state.defaultTerminalProfile,
|
||||
globalWorkflowToggles: state.globalWorkflowToggles,
|
||||
taskHistory: state.taskHistory,
|
||||
lastShownAnnouncementId: state.lastShownAnnouncementId,
|
||||
mcpMarketplaceCatalog: state.mcpMarketplaceCatalog,
|
||||
|
||||
// Plan mode configuration updates
|
||||
planModeApiProvider,
|
||||
@@ -821,10 +812,8 @@ export class CacheService {
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsAuthentication,
|
||||
awsBedrockApiKey,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
requestyBaseUrl,
|
||||
openAiBaseUrl,
|
||||
openAiHeaders,
|
||||
ollamaBaseUrl,
|
||||
@@ -847,7 +836,7 @@ export class CacheService {
|
||||
sapAiCoreTokenUrl,
|
||||
sapAiResourceGroup,
|
||||
claudeCodePath,
|
||||
} satisfies GlobalState
|
||||
}
|
||||
|
||||
// Populate global state cache directly
|
||||
Object.assign(this.globalStateCache, globalStateFields)
|
||||
@@ -883,10 +872,9 @@ export class CacheService {
|
||||
nebiusApiKey,
|
||||
sapAiCoreClientId,
|
||||
sapAiCoreClientSecret,
|
||||
authNonce,
|
||||
huggingFaceApiKey,
|
||||
huaweiCloudMaasApiKey,
|
||||
} satisfies Secrets
|
||||
}
|
||||
|
||||
// Populate secrets cache directly
|
||||
Object.assign(this.secretsCache, secretsFields)
|
||||
@@ -950,7 +938,6 @@ export class CacheService {
|
||||
awsAuthentication: this.globalStateCache["awsAuthentication"],
|
||||
vertexProjectId: this.globalStateCache["vertexProjectId"],
|
||||
vertexRegion: this.globalStateCache["vertexRegion"],
|
||||
requestyBaseUrl: this.globalStateCache["requestyBaseUrl"],
|
||||
openAiBaseUrl: this.globalStateCache["openAiBaseUrl"],
|
||||
openAiHeaders: this.globalStateCache["openAiHeaders"] || {},
|
||||
ollamaBaseUrl: this.globalStateCache["ollamaBaseUrl"],
|
||||
|
||||
@@ -9,7 +9,6 @@ import { HistoryItem } from "@/shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "@/shared/AutoApprovalSettings"
|
||||
import { Mode, OpenaiReasoningEffort } from "@/shared/storage/types"
|
||||
import { McpMarketplaceCatalog } from "@/shared/mcp"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
|
||||
export type SecretKey =
|
||||
| "apiKey"
|
||||
@@ -58,7 +57,6 @@ export type GlobalStateKey =
|
||||
| "vertexRegion"
|
||||
| "lastShownAnnouncementId"
|
||||
| "taskHistory"
|
||||
| "requestyBaseUrl"
|
||||
| "openAiBaseUrl"
|
||||
| "openAiHeaders"
|
||||
| "ollamaBaseUrl"
|
||||
@@ -100,8 +98,6 @@ export type GlobalStateKey =
|
||||
| "sapAiResourceGroup"
|
||||
| "claudeCodePath"
|
||||
| "strictPlanModeEnabled"
|
||||
| "focusChainSettings"
|
||||
| "focusChainFeatureFlagEnabled"
|
||||
// Settings around plan/act and ephemeral model configuration
|
||||
| "preferredLanguage"
|
||||
| "openaiReasoningEffort"
|
||||
@@ -180,7 +176,6 @@ export interface GlobalState {
|
||||
vertexRegion: string | undefined
|
||||
lastShownAnnouncementId: string | undefined
|
||||
taskHistory: HistoryItem[]
|
||||
requestyBaseUrl: string | undefined
|
||||
openAiBaseUrl: string | undefined
|
||||
openAiHeaders: Record<string, string>
|
||||
ollamaBaseUrl: string | undefined
|
||||
@@ -225,8 +220,6 @@ export interface GlobalState {
|
||||
preferredLanguage: string
|
||||
openaiReasoningEffort: OpenaiReasoningEffort
|
||||
mode: Mode
|
||||
focusChainSettings: FocusChainSettings
|
||||
focusChainFeatureFlagEnabled: boolean
|
||||
// Plan mode configurations
|
||||
planModeApiProvider: ApiProvider
|
||||
planModeApiModelId: string | undefined
|
||||
|
||||
@@ -27,7 +27,6 @@ export async function readStateFromDisk(context: ExtensionContext) {
|
||||
const vertexProjectId = context.globalState.get("vertexProjectId") as string | undefined
|
||||
const vertexRegion = context.globalState.get("vertexRegion") as string | undefined
|
||||
const openAiBaseUrl = context.globalState.get("openAiBaseUrl") as string | undefined
|
||||
const requestyBaseUrl = context.globalState.get("requestyBaseUrl") as string | undefined
|
||||
const openAiHeaders = context.globalState.get("openAiHeaders") as Record<string, string> | undefined
|
||||
const ollamaBaseUrl = context.globalState.get("ollamaBaseUrl") as string | undefined
|
||||
const ollamaApiOptionsCtxNum = context.globalState.get("ollamaApiOptionsCtxNum") as string | undefined
|
||||
@@ -262,7 +261,6 @@ export async function readStateFromDisk(context: ExtensionContext) {
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
requestyBaseUrl,
|
||||
openAiApiKey,
|
||||
openAiHeaders: openAiHeaders || {},
|
||||
ollamaBaseUrl,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { AssistantMessageContent } from "@core/assistant-message"
|
||||
import { StreamingJsonReplacer } from "@core/assistant-message/diff-json"
|
||||
import { ClineAskResponse } from "@shared/WebviewMessage"
|
||||
|
||||
export class TaskState {
|
||||
@@ -18,6 +19,10 @@ export class TaskState {
|
||||
presentAssistantMessageLocked = false
|
||||
presentAssistantMessageHasPendingUpdates = false
|
||||
|
||||
// Claude 4 experimental JSON streaming
|
||||
streamingJsonReplacer?: StreamingJsonReplacer
|
||||
lastProcessedJsonLength: number = 0
|
||||
|
||||
// Ask/Response handling
|
||||
askResponse?: ClineAskResponse
|
||||
askResponseText?: string
|
||||
@@ -48,17 +53,8 @@ export class TaskState {
|
||||
// Task Initialization
|
||||
isInitialized = false
|
||||
|
||||
// Focus Chain / Todo List Management
|
||||
apiRequestCount: number = 0
|
||||
apiRequestsSinceLastTodoUpdate: number = 0
|
||||
currentFocusChainChecklist: string | null = null
|
||||
todoListWasUpdatedByUser: boolean = false
|
||||
|
||||
// Task Abort / Cancellation
|
||||
abort: boolean = false
|
||||
didFinishAbortingStream = false
|
||||
abandoned = false
|
||||
|
||||
// Auto-context summarization
|
||||
currentlySummarizing: boolean = false
|
||||
}
|
||||
|
||||
+223
-167
@@ -11,13 +11,12 @@ import { ApiHandler } from "@api/index"
|
||||
import { FileContextTracker } from "@core/context/context-tracking/FileContextTracker"
|
||||
import { ClineIgnoreController } from "@core/ignore/ClineIgnoreController"
|
||||
import { DiffViewProvider } from "@integrations/editor/DiffViewProvider"
|
||||
import { processFilesIntoText } from "@integrations/misc/extract-text"
|
||||
import { extractTextFromFile, processFilesIntoText } from "@integrations/misc/extract-text"
|
||||
import { BrowserSession } from "@services/browser/BrowserSession"
|
||||
import { UrlContentFetcher } from "@services/browser/UrlContentFetcher"
|
||||
import { McpHub } from "@services/mcp/McpHub"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
import {
|
||||
BrowserAction,
|
||||
BrowserActionResult,
|
||||
@@ -32,19 +31,26 @@ import {
|
||||
COMPLETION_RESULT_CHANGES_FLAG,
|
||||
} from "@shared/ExtensionMessage"
|
||||
import { ClineAskResponse } from "@shared/WebviewMessage"
|
||||
import { extractFileContent } from "@integrations/misc/extract-file-content"
|
||||
import { extractFileContent, FileContentResult } from "@integrations/misc/extract-file-content"
|
||||
import { COMMAND_REQ_APP_STRING } from "@shared/combineCommandSequences"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { modelDoesntSupportWebp, isNextGenModelFamily } from "@utils/model-utils"
|
||||
import {
|
||||
isClaude4ModelFamily,
|
||||
isGemini2dot5ModelFamily,
|
||||
isGrok4ModelFamily,
|
||||
modelDoesntSupportWebp,
|
||||
isNextGenModelFamily,
|
||||
} from "@utils/model-utils"
|
||||
import { fixModelHtmlEscaping, removeInvalidChars } from "@utils/string"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import os from "os"
|
||||
import * as path from "path"
|
||||
import { serializeError } from "serialize-error"
|
||||
import * as vscode from "vscode"
|
||||
import { ToolResponse } from "."
|
||||
import { ToolResponse, USE_EXPERIMENTAL_CLAUDE4_FEATURES } from "."
|
||||
import { ToolParamName, ToolUse, ToolUseName } from "../assistant-message"
|
||||
import { constructNewFileContent } from "../assistant-message/diff"
|
||||
import { ChangeLocation, StreamingJsonReplacer } from "../assistant-message/diff-json"
|
||||
import { ContextManager } from "../context/context-management/ContextManager"
|
||||
import { loadMcpDocumentation } from "../prompts/loadMcpDocumentation"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
@@ -55,7 +61,6 @@ import { MessageStateHandler } from "./message-state"
|
||||
import { AutoApprove } from "./tools/autoApprove"
|
||||
import { showNotificationForApprovalIfAutoApprovalEnabled } from "./utils"
|
||||
import { Mode } from "@shared/storage/types"
|
||||
import { continuationPrompt } from "../prompts/contextManagement"
|
||||
|
||||
export class ToolExecutor {
|
||||
private autoApprover: AutoApprove
|
||||
@@ -90,7 +95,6 @@ export class ToolExecutor {
|
||||
// Configuration & Settings
|
||||
private autoApprovalSettings: AutoApprovalSettings,
|
||||
private browserSettings: BrowserSettings,
|
||||
private focusChainSettings: FocusChainSettings,
|
||||
private cwd: string,
|
||||
private taskId: string,
|
||||
private ulid: string,
|
||||
@@ -120,7 +124,6 @@ export class ToolExecutor {
|
||||
private removeLastPartialMessageIfExistsWithType: (type: "ask" | "say", askOrSay: ClineAsk | ClineSay) => Promise<void>,
|
||||
private executeCommandTool: (command: string) => Promise<[boolean, any]>,
|
||||
private doesLatestTaskCompletionHaveNewChanges: () => Promise<boolean>,
|
||||
private updateFCListFromToolResponse: (taskProgress: string | undefined) => Promise<void>,
|
||||
) {
|
||||
this.autoApprover = new AutoApprove(autoApprovalSettings)
|
||||
}
|
||||
@@ -154,15 +157,23 @@ export class ToolExecutor {
|
||||
if (typeof content === "string") {
|
||||
const resultText = content || "(tool did not return anything)"
|
||||
|
||||
// Non-Claude 4: Use traditional format with header
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: `${this.toolDescription(block)} Result:`,
|
||||
})
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: resultText,
|
||||
})
|
||||
if (isNextGenModel && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
// Claude 4 family: Use function_results format
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: `<function_results>\n${resultText}\n</function_results>`,
|
||||
})
|
||||
} else {
|
||||
// Non-Claude 4: Use traditional format with header
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: `${this.toolDescription(block)} Result:`,
|
||||
})
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: resultText,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.taskState.userMessageContent.push(...content)
|
||||
}
|
||||
@@ -206,8 +217,6 @@ export class ToolExecutor {
|
||||
return `[${block.name} for creating a new task]`
|
||||
case "condense":
|
||||
return `[${block.name}]`
|
||||
case "summarize_task":
|
||||
return `[${block.name}]`
|
||||
case "report_bug":
|
||||
return `[${block.name}]`
|
||||
case "new_rule":
|
||||
@@ -302,6 +311,131 @@ export class ToolExecutor {
|
||||
return text.replace(tagRegex, "")
|
||||
}
|
||||
|
||||
// Handle streaming JSON replacement for Claude 4 model family
|
||||
private async handleStreamingJsonReplacement(
|
||||
block: any,
|
||||
relPath: string,
|
||||
currentFullJson: string,
|
||||
): Promise<{ shouldBreak: boolean; newContent?: string; error?: string }> {
|
||||
// Calculate the delta - what's new since last time
|
||||
const newJsonChunk = currentFullJson.substring(this.taskState.lastProcessedJsonLength)
|
||||
if (block.partial) {
|
||||
// Initialize on first chunk
|
||||
if (!this.taskState.streamingJsonReplacer) {
|
||||
if (!this.diffViewProvider.isEditing) {
|
||||
await this.diffViewProvider.open(relPath)
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
const onContentUpdated = (newContent: string, _isFinalItem: boolean, changeLocation?: ChangeLocation) => {
|
||||
// Update diff view incrementally
|
||||
this.diffViewProvider.update(newContent, false, changeLocation)
|
||||
}
|
||||
|
||||
const onError = (error: Error) => {
|
||||
console.error("StreamingJsonReplacer error:", error)
|
||||
console.log("Failed StreamingJsonReplacer update:")
|
||||
// Handle error: push tool result, cleanup
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: formatResponse.toolError(`JSON replacement error: ${error.message}`),
|
||||
})
|
||||
this.taskState.didAlreadyUseTool = true
|
||||
this.taskState.userMessageContentReady = true
|
||||
this.taskState.streamingJsonReplacer = undefined
|
||||
this.taskState.lastProcessedJsonLength = 0
|
||||
throw error
|
||||
}
|
||||
|
||||
this.taskState.streamingJsonReplacer = new StreamingJsonReplacer(
|
||||
this.diffViewProvider.originalContent || "",
|
||||
onContentUpdated,
|
||||
onError,
|
||||
)
|
||||
this.taskState.lastProcessedJsonLength = 0
|
||||
}
|
||||
|
||||
// Feed only the new chunk
|
||||
if (newJsonChunk.length > 0) {
|
||||
try {
|
||||
this.taskState.streamingJsonReplacer.write(newJsonChunk)
|
||||
this.taskState.lastProcessedJsonLength = currentFullJson.length
|
||||
} catch (e) {
|
||||
// Handle write error
|
||||
return { shouldBreak: true, error: `Write error: ${e}` }
|
||||
}
|
||||
}
|
||||
|
||||
return { shouldBreak: true } // Wait for more chunks
|
||||
} else {
|
||||
// Final chunk (!block.partial)
|
||||
if (!this.taskState.streamingJsonReplacer) {
|
||||
// JSON came all at once, initialize
|
||||
if (!this.diffViewProvider.isEditing) {
|
||||
await this.diffViewProvider.open(relPath)
|
||||
}
|
||||
|
||||
// Initialize StreamingJsonReplacer for non-streaming case
|
||||
const onContentUpdated = (newContent: string, _isFinalItem: boolean, changeLocation?: ChangeLocation) => {
|
||||
// Update diff view incrementally
|
||||
this.diffViewProvider.update(newContent, false, changeLocation)
|
||||
}
|
||||
|
||||
const onError = (error: Error) => {
|
||||
console.error("StreamingJsonReplacer error:", error)
|
||||
// Handle error
|
||||
this.taskState.userMessageContent.push({
|
||||
type: "text",
|
||||
text: formatResponse.toolError(`JSON replacement error: ${error.message}`),
|
||||
})
|
||||
this.taskState.didAlreadyUseTool = true
|
||||
this.taskState.userMessageContentReady = true
|
||||
throw error
|
||||
}
|
||||
|
||||
this.taskState.streamingJsonReplacer = new StreamingJsonReplacer(
|
||||
this.diffViewProvider.originalContent || "",
|
||||
onContentUpdated,
|
||||
onError,
|
||||
)
|
||||
|
||||
// Write the entire JSON at once
|
||||
this.taskState.streamingJsonReplacer.write(currentFullJson)
|
||||
|
||||
// Get the final content
|
||||
const newContent = this.taskState.streamingJsonReplacer.getCurrentContent()
|
||||
|
||||
// Cleanup
|
||||
this.taskState.streamingJsonReplacer = undefined
|
||||
this.taskState.lastProcessedJsonLength = 0
|
||||
|
||||
// Update diff view with final content
|
||||
await this.diffViewProvider.update(newContent, true)
|
||||
|
||||
return { shouldBreak: false, newContent }
|
||||
}
|
||||
|
||||
// Feed final delta
|
||||
if (newJsonChunk.length > 0) {
|
||||
this.taskState.streamingJsonReplacer.write(newJsonChunk)
|
||||
}
|
||||
|
||||
const newContent = this.taskState.streamingJsonReplacer.getCurrentContent()
|
||||
|
||||
// Get final list of replacements
|
||||
const allReplacements = this.taskState.streamingJsonReplacer.getSuccessfullyParsedItems()
|
||||
|
||||
// Cleanup
|
||||
this.taskState.streamingJsonReplacer = undefined
|
||||
this.taskState.lastProcessedJsonLength = 0
|
||||
|
||||
// Update diff view with final content
|
||||
await this.diffViewProvider.update(newContent, true)
|
||||
|
||||
return { shouldBreak: false, newContent }
|
||||
}
|
||||
}
|
||||
|
||||
public async executeTool(block: ToolUse): Promise<void> {
|
||||
if (this.taskState.didRejectTool) {
|
||||
// ignore any tool content after user has rejected tool once
|
||||
@@ -390,35 +524,62 @@ export class ToolExecutor {
|
||||
await this.diffViewProvider.open(relPath)
|
||||
}
|
||||
|
||||
try {
|
||||
newContent = await constructNewFileContent(
|
||||
diff,
|
||||
this.diffViewProvider.originalContent || "",
|
||||
!block.partial,
|
||||
)
|
||||
} catch (error) {
|
||||
await this.say("diff_error", relPath)
|
||||
const currentFullJson = block.params.diff
|
||||
// Check if we should use streaming (e.g., for specific models)
|
||||
const isNextGenModel = isNextGenModelFamily(this.api)
|
||||
// Going through claude family of models
|
||||
if (isNextGenModel && USE_EXPERIMENTAL_CLAUDE4_FEATURES && currentFullJson) {
|
||||
const streamingResult = await this.handleStreamingJsonReplacement(block, relPath, currentFullJson)
|
||||
|
||||
// Extract error type from error message if possible, or use a generic type
|
||||
const errorType =
|
||||
error instanceof Error && error.message.includes("does not match anything")
|
||||
? "search_not_found"
|
||||
: "other_diff_error"
|
||||
if (streamingResult.error) {
|
||||
await this.say("diff_error", relPath)
|
||||
this.pushToolResult(formatResponse.toolError(streamingResult.error), block)
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.diffViewProvider.reset()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
|
||||
// Add telemetry for diff edit failure
|
||||
telemetryService.captureDiffEditFailure(this.ulid, this.api.getModel().id, errorType)
|
||||
if (streamingResult.shouldBreak) {
|
||||
break // Wait for more chunks or handle initialization
|
||||
}
|
||||
|
||||
this.pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`${(error as Error)?.message}\n\n` +
|
||||
formatResponse.diffError(relPath, this.diffViewProvider.originalContent),
|
||||
),
|
||||
block,
|
||||
)
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.diffViewProvider.reset()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
// If we get here, we have the final content
|
||||
if (streamingResult.newContent) {
|
||||
newContent = streamingResult.newContent
|
||||
// Continue with approval flow...
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
newContent = await constructNewFileContent(
|
||||
diff,
|
||||
this.diffViewProvider.originalContent || "",
|
||||
!block.partial,
|
||||
)
|
||||
} catch (error) {
|
||||
await this.say("diff_error", relPath)
|
||||
|
||||
// Extract error type from error message if possible, or use a generic type
|
||||
const errorType =
|
||||
error instanceof Error && error.message.includes("does not match anything")
|
||||
? "search_not_found"
|
||||
: "other_diff_error"
|
||||
|
||||
// Add telemetry for diff edit failure
|
||||
telemetryService.captureDiffEditFailure(this.ulid, this.api.getModel().id, errorType)
|
||||
|
||||
this.pushToolResult(
|
||||
formatResponse.toolError(
|
||||
`${(error as Error)?.message}\n\n` +
|
||||
formatResponse.diffError(relPath, this.diffViewProvider.originalContent),
|
||||
),
|
||||
block,
|
||||
)
|
||||
await this.diffViewProvider.revertChanges()
|
||||
await this.diffViewProvider.reset()
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (content) {
|
||||
newContent = content
|
||||
@@ -635,10 +796,6 @@ export class ToolExecutor {
|
||||
|
||||
await this.diffViewProvider.reset()
|
||||
|
||||
if (!block.partial && this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
|
||||
break
|
||||
@@ -728,10 +885,6 @@ export class ToolExecutor {
|
||||
this.taskState.userMessageContent.push(result.imageBlock)
|
||||
}
|
||||
|
||||
if (!block.partial && this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -809,11 +962,6 @@ export class ToolExecutor {
|
||||
telemetryService.captureToolUsage(this.ulid, block.name, this.api.getModel().id, false, true)
|
||||
}
|
||||
this.pushToolResult(result, block)
|
||||
|
||||
if (!block.partial && this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -886,11 +1034,6 @@ export class ToolExecutor {
|
||||
telemetryService.captureToolUsage(this.ulid, block.name, this.api.getModel().id, false, true)
|
||||
}
|
||||
this.pushToolResult(result, block)
|
||||
|
||||
if (!block.partial) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -975,11 +1118,6 @@ export class ToolExecutor {
|
||||
telemetryService.captureToolUsage(this.ulid, block.name, this.api.getModel().id, false, true)
|
||||
}
|
||||
this.pushToolResult(results, block)
|
||||
|
||||
if (!block.partial) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
@@ -1154,11 +1292,6 @@ export class ToolExecutor {
|
||||
),
|
||||
block,
|
||||
)
|
||||
|
||||
if (!block.partial) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
case "close":
|
||||
@@ -1679,63 +1812,6 @@ export class ToolExecutor {
|
||||
break
|
||||
}
|
||||
}
|
||||
case "summarize_task": {
|
||||
const context: string | undefined = block.params.context
|
||||
try {
|
||||
if (block.partial) {
|
||||
// Show streaming summary generation in tool UI
|
||||
const partialMessage = JSON.stringify({
|
||||
tool: "summarizeTask",
|
||||
content: this.removeClosingTag(block, "context", context),
|
||||
} satisfies ClineSayTool)
|
||||
|
||||
await this.say("tool", partialMessage, undefined, undefined, block.partial)
|
||||
break
|
||||
} else {
|
||||
if (!context) {
|
||||
this.taskState.consecutiveMistakeCount++
|
||||
this.pushToolResult(await this.sayAndCreateMissingParamError("summarize_task", "context"), block)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
this.taskState.consecutiveMistakeCount = 0
|
||||
|
||||
// Show completed summary in tool UI
|
||||
const completeMessage = JSON.stringify({
|
||||
tool: "summarizeTask",
|
||||
content: context,
|
||||
} satisfies ClineSayTool)
|
||||
|
||||
await this.say("tool", completeMessage, undefined, undefined, false)
|
||||
|
||||
// Use the continuationPrompt to format the tool result
|
||||
this.pushToolResult(formatResponse.toolResult(continuationPrompt(context)), block)
|
||||
|
||||
const apiConversationHistory = this.messageStateHandler.getApiConversationHistory()
|
||||
const keepStrategy = "none"
|
||||
|
||||
// clear the context history at this point in time. note that this will not include the assistant message
|
||||
// for summarizing, which we will need to delete later
|
||||
this.taskState.conversationHistoryDeletedRange = this.contextManager.getNextTruncationRange(
|
||||
apiConversationHistory,
|
||||
this.taskState.conversationHistoryDeletedRange,
|
||||
keepStrategy,
|
||||
)
|
||||
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
await this.contextManager.triggerApplyStandardContextTruncationNoticeChange(
|
||||
Date.now(),
|
||||
await ensureTaskDirectoryExists(this.context, this.taskId),
|
||||
)
|
||||
}
|
||||
await this.saveCheckpoint()
|
||||
this.taskState.currentlySummarizing = true
|
||||
break
|
||||
} catch (error) {
|
||||
await this.handleError("summarizing context window", error, block)
|
||||
await this.saveCheckpoint()
|
||||
break
|
||||
}
|
||||
}
|
||||
case "condense": {
|
||||
const context: string | undefined = block.params.context
|
||||
try {
|
||||
@@ -2155,8 +2231,6 @@ export class ToolExecutor {
|
||||
),
|
||||
block,
|
||||
)
|
||||
// Reset the flag after using it to prevent it from persisting
|
||||
this.taskState.didRespondToPlanAskBySwitchingMode = false
|
||||
} else {
|
||||
// if we didn't switch to ACT MODE, then we can just send the user_feedback message
|
||||
this.pushToolResult(
|
||||
@@ -2165,10 +2239,6 @@ export class ToolExecutor {
|
||||
)
|
||||
}
|
||||
|
||||
if (!block.partial && this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
|
||||
//
|
||||
break
|
||||
}
|
||||
@@ -2229,27 +2299,25 @@ export class ToolExecutor {
|
||||
// const secondLastMessage = this.clineMessages.at(-2)
|
||||
// NOTE: we do not want to auto approve a command run as part of the attempt_completion tool
|
||||
if (lastMessage && lastMessage.ask === "command") {
|
||||
// we are not going to stream the attempt_completion's command anymore since we might also need to send out a task_progress message before waiting for the user to approve the command, so the tool call checks everything on the progress check list.
|
||||
// update command
|
||||
// await this.ask("command", this.removeClosingTag(block, "command", command), block.partial).catch(
|
||||
// () => {},
|
||||
// )
|
||||
await this.ask("command", this.removeClosingTag(block, "command", command), block.partial).catch(
|
||||
() => {},
|
||||
)
|
||||
} else {
|
||||
// Now that we don't stream a command, we shouldn't be completing the attempt_completion tool in the block.partial conditional, and instead do it when partial is false below
|
||||
//
|
||||
// last message is completion_result, we have command string, which means we have the result as well, so finish it (doesn't have to exist yet)
|
||||
// await this.say(
|
||||
// "completion_result",
|
||||
// this.removeClosingTag(block, "result", result),
|
||||
// undefined,
|
||||
// undefined,
|
||||
// false,
|
||||
// )
|
||||
// await this.saveCheckpoint(true)
|
||||
// await addNewChangesFlagToLastCompletionResultMessage()
|
||||
// await this.ask("command", this.removeClosingTag(block, "command", command), block.partial).catch(
|
||||
// () => {},
|
||||
// )
|
||||
// last message is completion_result
|
||||
// we have command string, which means we have the result as well, so finish it (doesn't have to exist yet)
|
||||
await this.say(
|
||||
"completion_result",
|
||||
this.removeClosingTag(block, "result", result),
|
||||
undefined,
|
||||
undefined,
|
||||
false,
|
||||
)
|
||||
await this.saveCheckpoint(true)
|
||||
await addNewChangesFlagToLastCompletionResultMessage()
|
||||
await this.ask("command", this.removeClosingTag(block, "command", command), block.partial).catch(
|
||||
() => {},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// no command, still outputting partial result
|
||||
@@ -2285,17 +2353,9 @@ export class ToolExecutor {
|
||||
await this.saveCheckpoint(true)
|
||||
await addNewChangesFlagToLastCompletionResultMessage()
|
||||
telemetryService.captureTaskCompleted(this.ulid)
|
||||
|
||||
if (this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
} else {
|
||||
// we already sent a command message, meaning the complete completion message has also been sent
|
||||
await this.saveCheckpoint(true)
|
||||
|
||||
if (this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
}
|
||||
|
||||
// complete command message
|
||||
@@ -2318,10 +2378,6 @@ export class ToolExecutor {
|
||||
await this.saveCheckpoint(true)
|
||||
await addNewChangesFlagToLastCompletionResultMessage()
|
||||
telemetryService.captureTaskCompleted(this.ulid)
|
||||
|
||||
if (this.focusChainSettings.enabled) {
|
||||
await this.updateFCListFromToolResponse(block.params.task_progress)
|
||||
}
|
||||
}
|
||||
|
||||
// we already sent completion_result says, an empty string asks relinquishes control over button and field
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import * as fs from "fs/promises"
|
||||
import * as path from "path"
|
||||
import * as vscode from "vscode"
|
||||
import { ensureTaskDirectoryExists } from "../../storage/disk"
|
||||
|
||||
/**
|
||||
* Generate the standard file path for a task's focusChain markdown file
|
||||
*/
|
||||
export function getFocusChainFilePath(taskDir: string, taskId: string): string {
|
||||
return path.join(taskDir, `focus_chain_taskid_${taskId}.md`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the standard markdown content structure for a focusChain file
|
||||
*/
|
||||
export function createFocusChainMarkdownContent(taskId: string, focusChainList: string): string {
|
||||
return `# Focus Chain List for Task ${taskId}
|
||||
|
||||
<!-- Edit this markdown file to update your focus chain focusChain list -->
|
||||
<!-- Use the format: - [ ] for incomplete items and - [x] for completed items -->
|
||||
|
||||
${focusChainList}
|
||||
|
||||
<!-- Save this file and the focusChain list will be updated in the task -->`
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract focusChain items from text content (markdown or message text)
|
||||
* Returns array of lines that match focusChain item format
|
||||
*/
|
||||
export function extractFocusChainItemsFromText(text: string): string[] {
|
||||
const lines = text.split("\n")
|
||||
return lines.filter((line) => {
|
||||
const trimmed = line.trim()
|
||||
return trimmed.startsWith("- [ ]") || trimmed.startsWith("- [x]") || trimmed.startsWith("- [X]")
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract focusChain items and return as joined string, or null if no items found
|
||||
*/
|
||||
export function extractFocusChainListFromText(text: string): string | null {
|
||||
const focusChainLines = extractFocusChainItemsFromText(text)
|
||||
return focusChainLines.length > 0 ? focusChainLines.join("\n") : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a focusChain file exists, creating it with provided content if it doesn't exist
|
||||
* Returns the file path
|
||||
*/
|
||||
export async function ensureFocusChainFile(
|
||||
context: vscode.ExtensionContext,
|
||||
taskId: string,
|
||||
initialFocusChainContent?: string,
|
||||
): Promise<string> {
|
||||
const taskDir = await ensureTaskDirectoryExists(context, taskId)
|
||||
const focusChainFilePath = getFocusChainFilePath(taskDir, taskId)
|
||||
|
||||
// Check if file exists
|
||||
let fileExists = false
|
||||
try {
|
||||
await fs.access(focusChainFilePath)
|
||||
fileExists = true
|
||||
} catch {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
// Create file if it doesn't exist
|
||||
if (!fileExists) {
|
||||
const focusChainContent =
|
||||
initialFocusChainContent ||
|
||||
`- [ ] Example checklist item
|
||||
- [ ] Another checklist item
|
||||
- [x] Completed example item`
|
||||
|
||||
const fileContent = createFocusChainMarkdownContent(taskId, focusChainContent)
|
||||
await fs.writeFile(focusChainFilePath, fileContent, "utf8")
|
||||
}
|
||||
|
||||
return focusChainFilePath
|
||||
}
|
||||
@@ -1,514 +0,0 @@
|
||||
import * as vscode from "vscode"
|
||||
import * as fs from "fs/promises"
|
||||
import { writeFile } from "../../../utils/fs"
|
||||
import { ensureTaskDirectoryExists } from "../../storage/disk"
|
||||
import { TaskState } from "../TaskState"
|
||||
import { Mode } from "../../../shared/storage/types"
|
||||
import { ClineSay } from "../../../shared/ExtensionMessage"
|
||||
import { HostProvider } from "../../../hosts/host-provider"
|
||||
import { SubscribeToFileRequest, FileChangeEvent_ChangeType } from "../../../shared/proto/host/watch"
|
||||
import { telemetryService, featureFlagsService } from "@services/posthog/PostHogClientProvider"
|
||||
import { parseFocusChainListCounts } from "./utils"
|
||||
import {
|
||||
getFocusChainFilePath,
|
||||
createFocusChainMarkdownContent,
|
||||
extractFocusChainListFromText,
|
||||
extractFocusChainItemsFromText,
|
||||
} from "./file-utils"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
import { CacheService } from "../../storage/CacheService"
|
||||
|
||||
export interface FocusChainDependencies {
|
||||
taskId: string
|
||||
taskState: TaskState
|
||||
mode: Mode
|
||||
context: vscode.ExtensionContext
|
||||
cacheService: CacheService
|
||||
postStateToWebview: () => Promise<void>
|
||||
say: (type: ClineSay, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<undefined>
|
||||
focusChainSettings: FocusChainSettings
|
||||
}
|
||||
|
||||
export class FocusChainManager {
|
||||
private taskId: string
|
||||
private taskState: TaskState
|
||||
private mode: Mode
|
||||
private context: vscode.ExtensionContext
|
||||
private cacheService: CacheService
|
||||
private postStateToWebview: () => Promise<void>
|
||||
private say: (type: ClineSay, text?: string, images?: string[], files?: string[], partial?: boolean) => Promise<undefined>
|
||||
private focusChainFileWatcherCancel?: () => void
|
||||
private hasTrackedFirstProgress = false
|
||||
private focusChainSettings: FocusChainSettings
|
||||
private fileUpdateDebounceTimer?: NodeJS.Timeout
|
||||
|
||||
constructor(dependencies: FocusChainDependencies) {
|
||||
this.taskId = dependencies.taskId
|
||||
this.taskState = dependencies.taskState
|
||||
this.mode = dependencies.mode
|
||||
this.context = dependencies.context
|
||||
this.cacheService = dependencies.cacheService
|
||||
this.postStateToWebview = dependencies.postStateToWebview
|
||||
this.say = dependencies.say
|
||||
this.focusChainSettings = dependencies.focusChainSettings
|
||||
|
||||
this.initializeRemoteFeatureFlags().catch((err) =>
|
||||
console.error("Failed to initialize focus chain remote feature flags", err),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and caches PostHog remote feature flag for focus chain.
|
||||
* Updates global state with the current feature flag value and refreshes the webview.
|
||||
* This method is called during FocusChainManager initialization.
|
||||
* @returns Promise<void> - Resolves when feature flag is updated, logs errors on failure
|
||||
*/
|
||||
private async initializeRemoteFeatureFlags(): Promise<void> {
|
||||
try {
|
||||
const enabled = await featureFlagsService.getFocusChainEnabled()
|
||||
this.cacheService.setGlobalState("focusChainFeatureFlagEnabled", enabled)
|
||||
await this.postStateToWebview()
|
||||
} catch (error) {
|
||||
console.error("Error initializing focus chain remote feature flags:", error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local mode state to reflect the current Plan/Act mode.
|
||||
* Called when the task switches between planning and execution modes.
|
||||
* @param mode - The new Mode value ("plan" or "act")
|
||||
* @returns void - No return value
|
||||
*/
|
||||
public updateMode(mode: Mode) {
|
||||
this.mode = mode
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a file watcher to monitor changes to the focus chain list markdown file.
|
||||
* Automatically updates the UI when the file is created, modified, or deleted by external editors.
|
||||
* @requires this.taskId, this.context to be initialized
|
||||
* @returns Promise<void> - Resolves when watcher is set up, logs errors if setup fails
|
||||
*/
|
||||
public async setupFocusChainFileWatcher() {
|
||||
try {
|
||||
const taskDir = await ensureTaskDirectoryExists(this.context, this.taskId)
|
||||
const focusChainFilePath = getFocusChainFilePath(taskDir, this.taskId)
|
||||
|
||||
this.focusChainFileWatcherCancel = HostProvider.watch.subscribeToFile(
|
||||
SubscribeToFileRequest.create({
|
||||
path: focusChainFilePath,
|
||||
}),
|
||||
{
|
||||
onResponse: async (response) => {
|
||||
switch (response.type) {
|
||||
case FileChangeEvent_ChangeType.CHANGED:
|
||||
await this.updateFCListFromMarkdownFileAndNotifyUI()
|
||||
break
|
||||
case FileChangeEvent_ChangeType.CREATED:
|
||||
await this.updateFCListFromMarkdownFileAndNotifyUI()
|
||||
break
|
||||
case FileChangeEvent_ChangeType.DELETED:
|
||||
this.taskState.currentFocusChainChecklist = null
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(`[Task ${this.taskId}] Failed to watch todo file:`, error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log(`[Task ${this.taskId}] Todo file watcher completed`)
|
||||
},
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`[Task ${this.taskId}] Failed to setup todo file watcher:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current focus chain list from the markdown file and updates the UI with any changes.
|
||||
* Uses debouncing (300ms) to prevent excessive updates and only notifies the webview when content actually changes.
|
||||
* @requires File watcher to be active and markdown file to exist
|
||||
* @returns Promise<void> - Updates taskState.currentFocusChainChecklist and calls postStateToWebview()
|
||||
*/
|
||||
private async updateFCListFromMarkdownFileAndNotifyUI() {
|
||||
if (this.fileUpdateDebounceTimer) {
|
||||
clearTimeout(this.fileUpdateDebounceTimer)
|
||||
}
|
||||
|
||||
// Debounce file watcher to prevent false positives
|
||||
this.fileUpdateDebounceTimer = setTimeout(async () => {
|
||||
try {
|
||||
const markdownTodoList = await this.readFocusChainFromDisk()
|
||||
if (markdownTodoList) {
|
||||
const previousList = this.taskState.currentFocusChainChecklist
|
||||
|
||||
// Only update if the content actually changed
|
||||
if (previousList !== markdownTodoList) {
|
||||
this.taskState.currentFocusChainChecklist = markdownTodoList
|
||||
this.taskState.todoListWasUpdatedByUser = true
|
||||
|
||||
await this.postStateToWebview()
|
||||
telemetryService.captureFocusChainListWritten(this.taskId)
|
||||
} else {
|
||||
console.log(
|
||||
`[Task ${this.taskId}] Focus Chain List: File watcher triggered but content unchanged, skipping update`,
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Task ${this.taskId}] Error updating focuss chain list from markdown file:`, error)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates contextual instructions for focus chain list creation and management based on current task state.
|
||||
* Returns formatted markdown instructions that guide the AI on when and how to update progress tracking.
|
||||
* @requires this.taskState with current focus chain list state and API request counts
|
||||
* @returns string - Formatted markdown instructions for focus chain list management, varies by context
|
||||
*/
|
||||
public generateFocusChainInstructions(): string {
|
||||
// Prompt for initial list creation
|
||||
const listInstructionsInitial = `\n
|
||||
# TODO LIST CREATION REQUIRED - ACT MODE ACTIVATED\n
|
||||
\n
|
||||
**You've just switched from PLAN MODE to ACT MODE!**\n
|
||||
\n
|
||||
** IMMEDIATE ACTION REQUIRED:**\n
|
||||
1. Create a comprehensive todo list in your NEXT tool call\n
|
||||
2. Use the task_progress parameter to provide the list\n
|
||||
3. Format each item using markdown checklist syntax:\n
|
||||
- [ ] For tasks to be done\n
|
||||
- [x] For any tasks already completed\n
|
||||
\n
|
||||
**Your todo list should include:**\n
|
||||
- All major implementation steps\n
|
||||
- Testing and validation tasks\n
|
||||
- Documentation updates if needed\n
|
||||
- Final verification steps\n
|
||||
\n
|
||||
**Example format:**\n\
|
||||
- [ ] Set up project structure\n
|
||||
- [ ] Implement core functionality\n
|
||||
- [ ] Add error handling\n-
|
||||
- [ ] Write tests\n
|
||||
- [ ] Test implementation\n
|
||||
- [ ] Document changes\n
|
||||
\n
|
||||
**Remember:** Keeping the todo list updated helps track progress and ensures nothing is missed.`
|
||||
|
||||
// For when recommending but not requiring a list
|
||||
const listInstructionsRecommended = `\n
|
||||
1. Include the task_progress parameter in your next tool call\n
|
||||
2. Create a comprehensive checklist of all steps needed\n
|
||||
3. Use markdown format: - [ ] for incomplete, - [x] for complete\n
|
||||
\n
|
||||
**Benefits of creating a todo list now:**\n
|
||||
- Clear roadmap for implementation\n
|
||||
- Progress tracking throughout the task\n
|
||||
- Nothing gets forgotten or missed\n
|
||||
- Users can see, monitor, and edit the plan\n
|
||||
\n
|
||||
**Example structure:**\n\`\`\`\n
|
||||
- [ ] Analyze requirements\n
|
||||
- [ ] Set up necessary files\n
|
||||
- [ ] Implement main functionality\n
|
||||
- [ ] Handle edge cases\n
|
||||
- [ ] Test the implementation\n
|
||||
- [ ] Verify results\n\`\`\`\n
|
||||
\n
|
||||
Keeping the todo list updated helps track progress and ensures nothing is missed.`
|
||||
|
||||
// Prompt for reminders to update the list periodically
|
||||
const listInstrunctionsReminder = `\n
|
||||
1. To create or update a todo list, include the task_progress parameter in the next tool call\n
|
||||
2. Review each item and update its status:\n
|
||||
- Mark completed items with: - [x]\n
|
||||
- Keep incomplete items as: - [ ]\n
|
||||
- Add new items if you discover additional steps\n
|
||||
3. Modify the list as needed:\n
|
||||
- Add any new steps you've discovered\n
|
||||
- Reorder if the sequence has changed\n
|
||||
4. Ensure the list accurately reflects the current state\n
|
||||
\n
|
||||
**Remember:** Keeping the todo list updated helps track progress and ensures nothing is missed.`
|
||||
|
||||
// If list exists already exists, we need to remind it to update rather than demand initialization
|
||||
if (this.taskState.currentFocusChainChecklist) {
|
||||
// Parse the current list for counts/stats
|
||||
const { totalItems, completedItems } = parseFocusChainListCounts(this.taskState.currentFocusChainChecklist)
|
||||
const percentComplete = totalItems > 0 ? Math.round((completedItems / totalItems) * 100) : 0
|
||||
|
||||
const introUpdateRequired =
|
||||
"# TODO LIST UPDATE REQUIRED - You MUST include the task_progress parameter in your NEXT tool call."
|
||||
const listCurrentProgress = `**Current Progress: ${completedItems}/${totalItems} items completed (${percentComplete}%)**`
|
||||
const userHasUpdatedList =
|
||||
"**CRITICAL INFORMATION:** The user has modified this todo list - review ALL changes carefully"
|
||||
|
||||
// If user has updated the list, inform the model (and provide latest copy)
|
||||
if (this.taskState.todoListWasUpdatedByUser) {
|
||||
return `\n\n
|
||||
${introUpdateRequired}\n
|
||||
${listCurrentProgress}\n
|
||||
\n
|
||||
${this.taskState.currentFocusChainChecklist}\n
|
||||
${userHasUpdatedList}\n
|
||||
${listInstrunctionsReminder}\n
|
||||
`
|
||||
|
||||
// If there are no user changes, proceed with reminders based on list progress
|
||||
} else {
|
||||
let progressBasedMessageStub = ""
|
||||
// If there are items on the list, but none have been completed yet, remind the model to update the list when appropriate
|
||||
if (completedItems === 0 && totalItems > 0) {
|
||||
progressBasedMessageStub =
|
||||
"\n\n**Note:** No items are marked complete yet. As you work through the task, remember to mark items as complete when finished."
|
||||
} else if (percentComplete >= 25 && percentComplete < 50) {
|
||||
progressBasedMessageStub = `\n\n**Note:** ${percentComplete}% of items are complete.`
|
||||
} else if (percentComplete >= 50 && percentComplete < 75) {
|
||||
progressBasedMessageStub = `\n\n**Note:** ${percentComplete}% of items are complete. Proceed with the task.`
|
||||
} else if (percentComplete >= 75) {
|
||||
progressBasedMessageStub = `\n\n**Note:** ${percentComplete}% of items are complete! Focus on finishing the remaining items.`
|
||||
}
|
||||
// Every item on the list has been completed. Hooray!
|
||||
else if (completedItems === totalItems && totalItems > 0) {
|
||||
progressBasedMessageStub = `\n\n**🎉 EXCELLENT! All ${totalItems} items have been completed!**
|
||||
|
||||
**Completed Items:**
|
||||
${this.taskState.currentFocusChainChecklist}
|
||||
|
||||
**Next Steps:**
|
||||
- If the task is fully complete and meets all requirements, use attempt_completion
|
||||
- If you've discovered additional work that wasn't in the original scope (new features, improvements, edge cases, etc.), create a new task_progress list with those items
|
||||
- If there are related tasks or follow-up items the user might want, you can suggest them in a new checklist
|
||||
|
||||
**Remember:** Only use attempt_completion if you're confident the task is truly finished. If there's any remaining work, create a new focus chain list to track it.`
|
||||
}
|
||||
|
||||
// Return with progress-based stub
|
||||
return `\n
|
||||
${introUpdateRequired}\n
|
||||
${listCurrentProgress}\n
|
||||
${this.taskState.currentFocusChainChecklist}\n
|
||||
\n
|
||||
${listInstrunctionsReminder}\n
|
||||
${progressBasedMessageStub}\n
|
||||
`
|
||||
}
|
||||
}
|
||||
// When switching from Plan to Act, request that a new list be generated
|
||||
else if (this.taskState.didRespondToPlanAskBySwitchingMode) {
|
||||
return `${listInstructionsInitial}`
|
||||
}
|
||||
|
||||
// When in plan mode, lists are optional. TODO - May want to improve this soft prompt approach in a future version
|
||||
else if (this.mode === "plan") {
|
||||
return `\n
|
||||
# Todo List (Optional - Plan Mode)\n
|
||||
\n
|
||||
While in PLAN MODE, if you've outlined concrete steps or requirements for the user, you may include a preliminary todo list using the task_progress parameter.\n
|
||||
Reminder on how to use the task_progress parameter:\n
|
||||
${listInstrunctionsReminder}`
|
||||
} else {
|
||||
// Check if we're early in the task
|
||||
const isEarlyInTask = this.taskState.apiRequestCount < 10
|
||||
if (isEarlyInTask) {
|
||||
return `\n
|
||||
# TODO LIST RECOMMENDED
|
||||
When starting a new task, it is recommended to create a todo list.
|
||||
\n
|
||||
${listInstructionsRecommended}\n`
|
||||
} else {
|
||||
return `\n
|
||||
# TODO LIST \n
|
||||
You've made ${this.taskState.apiRequestCount} API requests without a todo list. Consider creating one to track remaining work.\n
|
||||
\n
|
||||
${listInstrunctionsReminder}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the focus chain list from the task's markdown file on disk and extracts the checklist content.
|
||||
* Returns the raw focus chain list string if found, or null if the file doesn't exist or contains no valid todos.
|
||||
* @requires this.taskId and this.context to locate the task directory
|
||||
* @returns Promise<string | null> - focus chain list content as string, or null if file missing/invalid
|
||||
* @throws Returns null on file read errors (file not found, permission issues)
|
||||
*/
|
||||
private async readFocusChainFromDisk(): Promise<string | null> {
|
||||
try {
|
||||
const taskDir = await ensureTaskDirectoryExists(this.context, this.taskId)
|
||||
const todoFilePath = getFocusChainFilePath(taskDir, this.taskId)
|
||||
const markdownContent = await fs.readFile(todoFilePath, "utf8")
|
||||
const todoList = extractFocusChainListFromText(markdownContent)
|
||||
|
||||
if (todoList) {
|
||||
const todoLines = extractFocusChainItemsFromText(markdownContent)
|
||||
return todoList
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
// File doesn't exist or can't be read, return null
|
||||
console.log(`[Task ${this.taskId}] focus chain list: Could not load from markdown file: ${error}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the provided focus chain list to the task's markdown file on disk with proper formatting.
|
||||
* Creates the full markdown document structure and triggers file watchers to update the UI.
|
||||
* @param todoList - Raw focus chain list string with markdown checklist items
|
||||
* @requires this.taskId and this.context for file path generation
|
||||
* @returns Promise<void> - Resolves when file is written successfully
|
||||
* @throws Error if file write fails (disk full, permissions, etc.)
|
||||
*/
|
||||
private async writeFocusChainToDisk(todoList: string): Promise<void> {
|
||||
try {
|
||||
const taskDir = await ensureTaskDirectoryExists(this.context, this.taskId)
|
||||
const todoFilePath = getFocusChainFilePath(taskDir, this.taskId)
|
||||
const fileContent = createFocusChainMarkdownContent(this.taskId, todoList)
|
||||
await writeFile(todoFilePath, fileContent, "utf8")
|
||||
} catch (error) {
|
||||
console.error(`[Task ${this.taskId}] focus chain list: FILE WRITE FAILED - Error:`, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes focus chain list updates from the AI model's task_progress parameter and persists them to disk.
|
||||
* Handles telemetry tracking for progress updates and falls back to reading existing files if no update provided.
|
||||
* Also manages the apiRequestsSinceLastTodoUpdate counter and includes comprehensive error handling.
|
||||
* @param taskProgress - Optional focus chain list string from AI model's task_progress parameter
|
||||
* @requires this.taskState, this.say method, and telemetryService to be available
|
||||
* @returns Promise<void> - Updates taskState.currentFocusChainChecklist and sends UI messages
|
||||
*/
|
||||
public async updateFCListFromToolResponse(taskProgress: string | undefined) {
|
||||
try {
|
||||
// Reset the counter if task_progress was provided
|
||||
if (taskProgress && taskProgress.trim()) {
|
||||
this.taskState.apiRequestsSinceLastTodoUpdate = 0
|
||||
}
|
||||
|
||||
// If model provides task_progress update, write it to the markdown file
|
||||
if (taskProgress && taskProgress.trim()) {
|
||||
const previousList = this.taskState.currentFocusChainChecklist
|
||||
this.taskState.currentFocusChainChecklist = taskProgress.trim()
|
||||
console.debug(
|
||||
`[Task ${this.taskId}] focus chain list: LLM provided focus chain list update via task_progress parameter. Length ${previousList?.length || 0} > ${this.taskState.currentFocusChainChecklist.length}`,
|
||||
)
|
||||
|
||||
// Parse focus chain list counts for telemetry
|
||||
const { totalItems, completedItems } = parseFocusChainListCounts(taskProgress.trim())
|
||||
|
||||
// Track first progress creation
|
||||
if (!this.hasTrackedFirstProgress && totalItems > 0) {
|
||||
telemetryService.captureFocusChainProgressFirst(this.taskId, totalItems)
|
||||
this.hasTrackedFirstProgress = true
|
||||
}
|
||||
// Track progress updates (only if not the first, and has items)
|
||||
else if (this.hasTrackedFirstProgress && totalItems > 0) {
|
||||
telemetryService.captureFocusChainProgressUpdate(this.taskId, totalItems, completedItems)
|
||||
}
|
||||
|
||||
// Write the model's update to the markdown file
|
||||
try {
|
||||
await this.writeFocusChainToDisk(taskProgress.trim())
|
||||
|
||||
// Send the task_progress message to the UI immediately
|
||||
await this.say("task_progress", taskProgress.trim())
|
||||
} catch (error) {
|
||||
console.error(`[Task ${this.taskId}] focus chain list: Failed to write to markdown file:`, error)
|
||||
// Fall back to creating a task_progress message directly if file write fails
|
||||
await this.say("task_progress", taskProgress.trim())
|
||||
console.log(`[Task ${this.taskId}] focus chain list: Sent fallback task_progress message to UI`)
|
||||
}
|
||||
} else {
|
||||
// No model update provided, check if markdown file exists and load it
|
||||
const markdownTodoList = await this.readFocusChainFromDisk()
|
||||
if (markdownTodoList) {
|
||||
const previousList = this.taskState.currentFocusChainChecklist
|
||||
this.taskState.currentFocusChainChecklist = markdownTodoList
|
||||
|
||||
// Create a task_progress message to display the focus chain list in the UI
|
||||
await this.say("task_progress", markdownTodoList)
|
||||
} else {
|
||||
console.debug(`[Task ${this.taskId}] focus chain list: No valid task progress to update with`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Task ${this.taskId}] focus chain list: Error in updateFCListFromToolResponse:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates multiple conditions to determine if focus chain list instructions should be included in the AI prompt.
|
||||
* Returns true when in plan mode, after mode switches, when user edits exist, or at reminder intervals.
|
||||
* @requires this.mode, this.taskState, and this.focusChainSettings to be initialized
|
||||
* @returns boolean - True if instructions should be included in AI prompt, false otherwise
|
||||
*/
|
||||
public shouldIncludeFocusChainInstructions(): boolean {
|
||||
// Always include when in Plan mode
|
||||
const inPlanMode = this.mode === "plan"
|
||||
// Always include when switching from Plan > Act
|
||||
const justSwitchedFromPlanMode = this.taskState.didRespondToPlanAskBySwitchingMode
|
||||
// Always include when user had edited the list manually
|
||||
const userUpdatedList = this.taskState.todoListWasUpdatedByUser
|
||||
// Include when reaching the reminder interval, configured by settings
|
||||
const reachedReminderInterval =
|
||||
this.taskState.apiRequestsSinceLastTodoUpdate >= this.focusChainSettings.remindClineInterval
|
||||
// Include on first API request or if list does not exist
|
||||
const isFirstApiRequest = this.taskState.apiRequestCount === 1 && !this.taskState.currentFocusChainChecklist
|
||||
// Include if no list has been created and multiple requests have completed
|
||||
const hasNoTodoListAfterMultipleRequests =
|
||||
!this.taskState.currentFocusChainChecklist && this.taskState.apiRequestCount >= 2
|
||||
|
||||
const shouldInclude =
|
||||
reachedReminderInterval ||
|
||||
justSwitchedFromPlanMode ||
|
||||
userUpdatedList ||
|
||||
inPlanMode ||
|
||||
isFirstApiRequest ||
|
||||
hasNoTodoListAfterMultipleRequests
|
||||
|
||||
return shouldInclude
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the current focus chain list for incomplete items when a task is marked as complete.
|
||||
* Captures telemetry data about unfinished progress items to help improve the focus chain system.
|
||||
* @requires this.focusChainSettings.enabled and this.taskState.currentFocusChainChecklist to exist
|
||||
* @returns void - Sends telemetry data if incomplete items found, no return value
|
||||
*/
|
||||
public checkIncompleteProgressOnCompletion() {
|
||||
if (this.focusChainSettings.enabled && this.taskState.currentFocusChainChecklist) {
|
||||
const { totalItems, completedItems } = parseFocusChainListCounts(this.taskState.currentFocusChainChecklist)
|
||||
|
||||
// Only track if there are items and not all are marked as completed
|
||||
if (totalItems > 0 && completedItems < totalItems) {
|
||||
const incompleteItems = totalItems - completedItems
|
||||
telemetryService.captureFocusChainIncompleteOnCompletion(this.taskId, totalItems, completedItems, incompleteItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs cleanup operations when the focus chain manager is no longer needed.
|
||||
* Cancels active file watchers and clears any pending debounce timers to prevent memory leaks.
|
||||
* @requires No parameters needed
|
||||
* @returns void - Cleans up timers and watchers, no return value
|
||||
*/
|
||||
public dispose() {
|
||||
if (this.fileUpdateDebounceTimer) {
|
||||
clearTimeout(this.fileUpdateDebounceTimer)
|
||||
this.fileUpdateDebounceTimer = undefined
|
||||
}
|
||||
|
||||
if (this.focusChainFileWatcherCancel && typeof this.focusChainFileWatcherCancel === "function") {
|
||||
this.focusChainFileWatcherCancel()
|
||||
this.focusChainFileWatcherCancel = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
export interface TodoListCounts {
|
||||
totalItems: number
|
||||
completedItems: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a focus chain list string and returns counts of total and completed items
|
||||
* @param todoList The focus chain list string to parse
|
||||
* @returns Object with totalItems and completedItems counts
|
||||
*/
|
||||
export function parseFocusChainListCounts(todoList: string): TodoListCounts {
|
||||
const lines = todoList.split("\n")
|
||||
let totalItems = 0
|
||||
let completedItems = 0
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim()
|
||||
if (trimmed.startsWith("- [ ]") || trimmed.startsWith("- [x]") || trimmed.startsWith("- [X]")) {
|
||||
totalItems++
|
||||
if (trimmed.startsWith("- [x]") || trimmed.startsWith("- [X]")) {
|
||||
completedItems++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { totalItems, completedItems }
|
||||
}
|
||||
+13
-150
@@ -4,7 +4,7 @@ import { ShowMessageType } from "@/shared/proto/index.host"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandler, buildApiHandler } from "@api/index"
|
||||
import { ApiStream } from "@api/transform/stream"
|
||||
import { parseAssistantMessageV2, ToolUseName } from "@core/assistant-message"
|
||||
import { parseAssistantMessageV2, parseAssistantMessageV3, ToolUseName } from "@core/assistant-message"
|
||||
import { checkContextWindowExceededError } from "@core/context/context-management/context-error-handling"
|
||||
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
|
||||
import { ContextManager } from "@core/context/context-management/ContextManager"
|
||||
@@ -50,7 +50,6 @@ import { ApiConfiguration } from "@shared/api"
|
||||
import { findLast, findLastIndex } from "@shared/array"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
import { FocusChainSettings } from "@shared/FocusChainSettings"
|
||||
import { combineApiRequests } from "@shared/combineApiRequests"
|
||||
import { combineCommandSequences } from "@shared/combineCommandSequences"
|
||||
import { ClineApiReqCancelReason, ClineApiReqInfo, ClineAsk, ClineMessage, ClineSay } from "@shared/ExtensionMessage"
|
||||
@@ -81,8 +80,8 @@ import { showChangedFilesDiff } from "./multifile-diff"
|
||||
import { TaskState } from "./TaskState"
|
||||
import { ToolExecutor } from "./ToolExecutor"
|
||||
import { updateApiReqMsg } from "./utils"
|
||||
import { FocusChainManager } from "./focus-chain"
|
||||
import { summarizeTask } from "@core/prompts/contextManagement"
|
||||
|
||||
export const USE_EXPERIMENTAL_CLAUDE4_FEATURES = false
|
||||
|
||||
export type ToolResponse = string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>
|
||||
type UserContent = Array<Anthropic.ContentBlockParam>
|
||||
@@ -118,9 +117,6 @@ export class Task {
|
||||
private fileContextTracker: FileContextTracker
|
||||
private modelContextTracker: ModelContextTracker
|
||||
|
||||
// Focus Chain
|
||||
private FocusChainManager?: FocusChainManager
|
||||
|
||||
// Callbacks
|
||||
private updateTaskHistory: (historyItem: HistoryItem) => Promise<HistoryItem[]>
|
||||
private postStateToWebview: () => Promise<void>
|
||||
@@ -133,7 +129,6 @@ export class Task {
|
||||
// User chat state
|
||||
autoApprovalSettings: AutoApprovalSettings
|
||||
browserSettings: BrowserSettings
|
||||
focusChainSettings: FocusChainSettings
|
||||
preferredLanguage: string
|
||||
openaiReasoningEffort: OpenaiReasoningEffort
|
||||
mode: Mode
|
||||
@@ -150,7 +145,6 @@ export class Task {
|
||||
apiConfiguration: ApiConfiguration,
|
||||
autoApprovalSettings: AutoApprovalSettings,
|
||||
browserSettings: BrowserSettings,
|
||||
focusChainSettings: FocusChainSettings,
|
||||
preferredLanguage: string,
|
||||
openaiReasoningEffort: OpenaiReasoningEffort,
|
||||
mode: Mode,
|
||||
@@ -199,7 +193,6 @@ export class Task {
|
||||
this.diffViewProvider = HostProvider.get().createDiffViewProvider()
|
||||
this.autoApprovalSettings = autoApprovalSettings
|
||||
this.browserSettings = browserSettings
|
||||
this.focusChainSettings = focusChainSettings
|
||||
this.preferredLanguage = preferredLanguage
|
||||
this.openaiReasoningEffort = openaiReasoningEffort
|
||||
this.mode = mode
|
||||
@@ -242,20 +235,6 @@ export class Task {
|
||||
this.fileContextTracker = new FileContextTracker(controller, this.taskId)
|
||||
this.modelContextTracker = new ModelContextTracker(controller.context, this.taskId)
|
||||
|
||||
// Initialize focus chain manager only if enabled
|
||||
if (this.focusChainSettings.enabled) {
|
||||
this.FocusChainManager = new FocusChainManager({
|
||||
taskId: this.taskId,
|
||||
taskState: this.taskState,
|
||||
mode: this.mode,
|
||||
context: this.getContext(),
|
||||
cacheService: this.cacheService,
|
||||
postStateToWebview: this.postStateToWebview,
|
||||
say: this.say.bind(this),
|
||||
focusChainSettings: this.focusChainSettings,
|
||||
})
|
||||
}
|
||||
|
||||
// Prepare effective API configuration
|
||||
const effectiveApiConfiguration: ApiConfiguration = {
|
||||
...apiConfiguration,
|
||||
@@ -317,13 +296,6 @@ export class Task {
|
||||
this.startTask(task, images, files)
|
||||
}
|
||||
|
||||
// Set up focus chain file watcher (async, runs in background) only if focus chain is enabled
|
||||
if (this.FocusChainManager) {
|
||||
this.FocusChainManager.setupFocusChainFileWatcher().catch((error) => {
|
||||
console.error(`[Task ${this.taskId}] Failed to setup focus chain file watcher:`, error)
|
||||
})
|
||||
}
|
||||
|
||||
// initialize telemetry
|
||||
if (historyItem) {
|
||||
// Open task from history
|
||||
@@ -348,7 +320,6 @@ export class Task {
|
||||
this.cacheService,
|
||||
this.autoApprovalSettings,
|
||||
this.browserSettings,
|
||||
this.focusChainSettings,
|
||||
cwd,
|
||||
this.taskId,
|
||||
this.ulid,
|
||||
@@ -361,16 +332,12 @@ export class Task {
|
||||
this.removeLastPartialMessageIfExistsWithType.bind(this),
|
||||
this.executeCommandTool.bind(this),
|
||||
this.doesLatestTaskCompletionHaveNewChanges.bind(this),
|
||||
this.FocusChainManager?.updateFCListFromToolResponse.bind(this.FocusChainManager) || (async () => {}),
|
||||
)
|
||||
}
|
||||
|
||||
public updateMode(mode: Mode): void {
|
||||
this.mode = mode
|
||||
this.toolExecutor.updateMode(mode)
|
||||
if (this.FocusChainManager) {
|
||||
this.FocusChainManager.updateMode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
public updateStrictPlanMode(strictPlanModeEnabled: boolean): void {
|
||||
@@ -720,7 +687,6 @@ export class Task {
|
||||
text?: string
|
||||
images?: string[]
|
||||
files?: string[]
|
||||
askTs?: number
|
||||
}> {
|
||||
// If this Cline instance was aborted by the provider, then the only thing keeping us alive is a promise still running in the background, in which case we don't want to send its result to the webview as it is attached to a new instance of Cline now. So we can safely ignore the result of any active promises, and this class will be deallocated. (Although we set Cline = undefined in provider, that simply removes the reference to this instance, but the instance is still alive until this promise resolves or rejects.)
|
||||
if (this.taskState.abort) {
|
||||
@@ -731,7 +697,6 @@ export class Task {
|
||||
const clineMessages = this.messageStateHandler.getClineMessages()
|
||||
const lastMessage = clineMessages.at(-1)
|
||||
const lastMessageIndex = clineMessages.length - 1
|
||||
|
||||
const isUpdatingPreviousPartial =
|
||||
lastMessage && lastMessage.partial && lastMessage.type === "ask" && lastMessage.ask === type
|
||||
if (partial) {
|
||||
@@ -1208,11 +1173,6 @@ export class Task {
|
||||
}
|
||||
|
||||
async abortTask() {
|
||||
// Check for incomplete progress before aborting
|
||||
if (this.FocusChainManager) {
|
||||
this.FocusChainManager.checkIncompleteProgressOnCompletion()
|
||||
}
|
||||
|
||||
this.taskState.abort = true // will stop any autonomously running promises
|
||||
this.terminalManager.disposeAll()
|
||||
this.urlContentFetcher.closeBrowser()
|
||||
@@ -1224,9 +1184,6 @@ export class Task {
|
||||
await this.diffViewProvider.revertChanges()
|
||||
// Clear the notification callback when task is aborted
|
||||
this.mcpHub.clearNotificationCallback()
|
||||
if (this.FocusChainManager) {
|
||||
this.FocusChainManager.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
// Checkpoints
|
||||
@@ -1653,14 +1610,7 @@ export class Task {
|
||||
const supportsBrowserUse = modelSupportsBrowserUse && !disableBrowserTool // only enable browser use if the model supports it and the user hasn't disabled it
|
||||
|
||||
const isNextGenModel = isNextGenModelFamily(this.api)
|
||||
let systemPrompt = await SYSTEM_PROMPT(
|
||||
this.cwd,
|
||||
supportsBrowserUse,
|
||||
this.mcpHub,
|
||||
this.browserSettings,
|
||||
this.focusChainSettings,
|
||||
isNextGenModel,
|
||||
)
|
||||
let systemPrompt = await SYSTEM_PROMPT(this.cwd, supportsBrowserUse, this.mcpHub, this.browserSettings, isNextGenModel)
|
||||
|
||||
const preferredLanguage = getLanguageKey(this.preferredLanguage as LanguageDisplay)
|
||||
const preferredLanguageInstructions =
|
||||
@@ -1943,10 +1893,6 @@ export class Task {
|
||||
throw new Error("Cline instance aborted")
|
||||
}
|
||||
|
||||
// Increment API request counter for focus chain list management
|
||||
this.taskState.apiRequestCount++
|
||||
this.taskState.apiRequestsSinceLastTodoUpdate++
|
||||
|
||||
// Used to know what models were used in the task if user wants to export metadata for error reporting purposes
|
||||
const { modelId, providerId } = await this.getCurrentProviderInfo()
|
||||
if (providerId && modelId) {
|
||||
@@ -2140,63 +2086,7 @@ export class Task {
|
||||
// No explicit UI message here, error message will be in ExtensionState.
|
||||
}
|
||||
|
||||
// when we initially trigger the context cleanup, we will be increasing the context window size, so we need some state `currentlySummarizing`
|
||||
// to store whether we have already started the context summarization flow, so we don't attempt to summarize again. additionally, immediately
|
||||
// post summarizing we need to increment the conversationHistoryDeletedRange to mask out the summarization-trigger user & assistant response messaages
|
||||
let shouldCompact = false
|
||||
if (this.taskState.currentlySummarizing) {
|
||||
this.taskState.currentlySummarizing = false
|
||||
|
||||
if (this.taskState.conversationHistoryDeletedRange) {
|
||||
const [start, end] = this.taskState.conversationHistoryDeletedRange
|
||||
const apiHistory = this.messageStateHandler.getApiConversationHistory()
|
||||
|
||||
// we want to increment the deleted range to remove the pre-summarization tool call output, with additional safety check
|
||||
const safeEnd = Math.min(end + 2, apiHistory.length - 1)
|
||||
if (end + 2 <= safeEnd) {
|
||||
this.taskState.conversationHistoryDeletedRange = [start, end + 2]
|
||||
await this.messageStateHandler.saveClineMessagesAndUpdateHistory()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
shouldCompact = this.contextManager.shouldCompactContextWindow(
|
||||
this.messageStateHandler.getClineMessages(),
|
||||
this.api,
|
||||
previousApiReqIndex,
|
||||
)
|
||||
|
||||
// There is an edge case where the summarize_task tool call completes but the user cancels the next request before it finishes
|
||||
// this will result in this.taskState.currentlySummarizing being false, and we also failed to update the context window token
|
||||
// estimate, which require a full new message to be completed along with gathering the latest usage block. A proxy for whether
|
||||
// we just summarized would be to check the number of in-range messages, which itself has some extreme edge case (e.g. what if
|
||||
// first+second user messages take up entire context-window, but in this case there's already an issue). TODO: Examine other
|
||||
// approaches such as storing this.taskState.currentlySummarizing on disk in the clineMessages. This was intentionally not done
|
||||
// for now to prevent additional disk from needing to be used.
|
||||
// The worse case scenario is effectively cline summarizing a summary, which is bad UX, but doesn't break other logic.
|
||||
if (shouldCompact && this.taskState.conversationHistoryDeletedRange) {
|
||||
const apiHistory = this.messageStateHandler.getApiConversationHistory()
|
||||
const activeMessageCount = apiHistory.length - this.taskState.conversationHistoryDeletedRange[1] - 1
|
||||
|
||||
// IMPORTANT - we didn't append this next user message yet so the last message in this array is an assistant message
|
||||
// that's why we are comparing to an even number of messages (0, 2) rather than odd (1, 3)
|
||||
if (activeMessageCount <= 2) {
|
||||
shouldCompact = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let parsedUserContent: UserContent
|
||||
let environmentDetails: string
|
||||
let clinerulesError: boolean
|
||||
|
||||
// when summarizing the context window, we do not want to inject updated to the context
|
||||
if (shouldCompact) {
|
||||
parsedUserContent = userContent
|
||||
environmentDetails = ""
|
||||
clinerulesError = false
|
||||
} else {
|
||||
;[parsedUserContent, environmentDetails, clinerulesError] = await this.loadContext(userContent, includeFileDetails)
|
||||
}
|
||||
const [parsedUserContent, environmentDetails, clinerulesError] = await this.loadContext(userContent, includeFileDetails)
|
||||
|
||||
// error handling if the user uses the /newrule command & their .clinerules is a file, for file read operations didnt work properly
|
||||
if (clinerulesError === true) {
|
||||
@@ -2208,14 +2098,7 @@ export class Task {
|
||||
|
||||
userContent = parsedUserContent
|
||||
// add environment details as its own text block, separate from tool results
|
||||
// do not add environment details to the message which we are compacting the context window
|
||||
if (!shouldCompact) {
|
||||
userContent.push({ type: "text", text: environmentDetails })
|
||||
}
|
||||
|
||||
if (shouldCompact) {
|
||||
userContent.push({ type: "text", text: summarizeTask() })
|
||||
}
|
||||
userContent.push({ type: "text", text: environmentDetails })
|
||||
|
||||
await this.messageStateHandler.addToApiConversationHistory({
|
||||
role: "user",
|
||||
@@ -2347,8 +2230,12 @@ export class Task {
|
||||
assistantMessage += chunk.text
|
||||
// parse raw assistant message into content blocks
|
||||
const prevLength = this.taskState.assistantMessageContent.length
|
||||
|
||||
this.taskState.assistantMessageContent = parseAssistantMessageV2(assistantMessage)
|
||||
const isNextGenModel = isNextGenModelFamily(this.api)
|
||||
if (isNextGenModel && USE_EXPERIMENTAL_CLAUDE4_FEATURES) {
|
||||
this.taskState.assistantMessageContent = parseAssistantMessageV3(assistantMessage)
|
||||
} else {
|
||||
this.taskState.assistantMessageContent = parseAssistantMessageV2(assistantMessage)
|
||||
}
|
||||
|
||||
if (this.taskState.assistantMessageContent.length > prevLength) {
|
||||
this.taskState.userMessageContentReady = false // new content we need to present, reset to false in case previous content set this to true
|
||||
@@ -2510,19 +2397,7 @@ export class Task {
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Offer the user a chance to retry this API request
|
||||
const { response } = await this.ask(
|
||||
"api_req_failed",
|
||||
"No assistant message was received. Would you like to retry the request?",
|
||||
)
|
||||
|
||||
if (response === "yesButtonClicked") {
|
||||
// Signal the loop to continue (i.e., do not end), so it will attempt again
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns early to avoid retry since user dismissed
|
||||
// Returns early to avoid retry since no assistant message was received
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2594,18 +2469,6 @@ export class Task {
|
||||
clinerulesError = await ensureLocalClineDirExists(this.cwd, GlobalFileNames.clineRules)
|
||||
}
|
||||
|
||||
// Add focu chain list instructions if needed
|
||||
if (this.FocusChainManager?.shouldIncludeFocusChainInstructions()) {
|
||||
const focusChainInstructions = this.FocusChainManager.generateFocusChainInstructions()
|
||||
processedUserContent.push({
|
||||
type: "text",
|
||||
text: focusChainInstructions,
|
||||
})
|
||||
|
||||
this.taskState.apiRequestsSinceLastTodoUpdate = 0
|
||||
this.taskState.todoListWasUpdatedByUser = false
|
||||
}
|
||||
|
||||
// Return all results
|
||||
return [processedUserContent, environmentDetails, clinerulesError]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const descriptionForAgent = `Request to access a resource provided by a connected MCP server. Resources represent data sources that can be used as context, such as files, API responses, or system information.`
|
||||
|
||||
export const accessMcpResourceToolDefinition = {
|
||||
name: "AccessMCPResource",
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
server_name: {
|
||||
type: "string",
|
||||
description: "The name of the MCP server providing the resource",
|
||||
},
|
||||
uri: {
|
||||
type: "string",
|
||||
description: "The URI identifying the specific resource to access",
|
||||
},
|
||||
},
|
||||
required: ["server_name", "uri"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const askQuestionToolName = "AskQuestion"
|
||||
|
||||
const descriptionForAgent = `Ask the user a question to gather additional information needed to complete the task. This tool should be used when you encounter ambiguities, need clarification, or require more details to proceed effectively. It allows for interactive problem-solving by enabling direct communication with the user. Use this tool judiciously to maintain a balance between gathering necessary information and avoiding excessive back-and-forth.`
|
||||
|
||||
export const askQuestionToolDefinition: ToolDefinition = {
|
||||
name: askQuestionToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
question: {
|
||||
type: "string",
|
||||
description:
|
||||
"The question to ask the user. This should be a clear, specific question that addresses the information you need.",
|
||||
},
|
||||
options: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
description:
|
||||
"An array of 2-5 options for the user to choose from. Each option should be a string describing a possible answer. You may not always need to provide options, but it may be helpful in many cases where it can save the user from having to type out a response manually. IMPORTANT: NEVER include an option to toggle to Act mode, as this would be something you need to direct the user to do manually themselves if needed.",
|
||||
},
|
||||
},
|
||||
required: ["question"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const attemptCompletionToolName = "AttemptCompletion"
|
||||
|
||||
const descriptionForAgent = `After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. Optionally you may provide a CLI command to showcase the result of your work. The user may respond with feedback if they are not satisfied with the result, which you can use to make improvements and try again.
|
||||
IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in <thinking></thinking> tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool.`
|
||||
|
||||
export const attemptCompletionToolDefinition: ToolDefinition = {
|
||||
name: attemptCompletionToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
result: {
|
||||
type: "string",
|
||||
description:
|
||||
"The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance.",
|
||||
},
|
||||
command: {
|
||||
type: "string",
|
||||
description:
|
||||
"A CLI command to execute to show a live demo of the result to the user. For example, use `open index.html` to display a created html website, or `open localhost:3000` to display a locally running development server. But DO NOT use commands like `echo` or `cat` that merely print text. This command should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.",
|
||||
},
|
||||
},
|
||||
required: ["result"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
export const bashToolName = "Bash"
|
||||
|
||||
const CO_AUTHORED_COMMIT_MSG = `\uD83E\uDD16 Generated with [Cline](https://docs.cline.bot)
|
||||
|
||||
Co-Authored-By: Cline <noreply@cline.bot>`
|
||||
const CO_AUTHORED_PR_MSG = `\uD83E\uDD16 Generated with [Cline](https://docs.cline.bot)`
|
||||
|
||||
const descriptionForAgent = (
|
||||
cwd: string,
|
||||
) => `Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${cwd.toPosix()}.
|
||||
|
||||
# Committing changes with git
|
||||
|
||||
When the user asks you to create a new git commit, follow these steps carefully:
|
||||
|
||||
1. You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. ALWAYS run the following bash commands in parallel, each using the Bash tool:
|
||||
- Run a git status command to see all untracked files.
|
||||
- Run a git diff command to see both staged and unstaged changes that will be committed.
|
||||
- Run a git log command to see recent commit messages, so that you can follow this repository's commit message style.
|
||||
|
||||
2. Analyze all staged changes (both previously staged and newly added) and draft a commit message. Wrap your analysis process in <commit_analysis> tags:
|
||||
|
||||
<commit_analysis>
|
||||
- List the files that have been changed or added
|
||||
- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
|
||||
- Brainstorm the purpose or motivation behind these changes
|
||||
- Assess the impact of these changes on the overall project
|
||||
- Check for any sensitive information that shouldn't be committed
|
||||
- Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"
|
||||
- Ensure your language is clear, concise, and to the point
|
||||
- Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
|
||||
- Ensure the message is not generic (avoid words like "Update" or "Fix" without context)
|
||||
- Review the draft message to ensure it accurately reflects the changes and their purpose
|
||||
</commit_analysis>
|
||||
|
||||
3. If the commit fails due to pre-commit hook changes, retry the commit ONCE to include these automated changes. If it fails again, it usually means a pre-commit hook is preventing the commit. If the commit succeeds but you notice that files were modified by the pre-commit hook, you MUST amend your commit to include them.
|
||||
|
||||
Important notes:
|
||||
- Use the git context at the start of this conversation to determine which files are relevant to your commit. Be careful not to stage and commit files (e.g. with \\\`git add .\\\`) that aren't relevant to your commit.
|
||||
- NEVER update the git config
|
||||
- DO NOT run additional commands to read or explore code, beyond what is available in the git context
|
||||
- DO NOT push to the remote repository
|
||||
- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.
|
||||
- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit
|
||||
- Ensure your commit message is meaningful and concise. It should explain the purpose of the changes, not just describe them.
|
||||
- Return an empty response - the user will see the git output directly
|
||||
- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:
|
||||
<example>
|
||||
git commit -m "\$(cat <<'EOF'
|
||||
Commit message here.
|
||||
|
||||
\${CO_AUTHORED_COMMIT_MSG}
|
||||
EOF
|
||||
)"
|
||||
</example>
|
||||
|
||||
# Creating pull requests
|
||||
Use the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.
|
||||
|
||||
IMPORTANT: When the user asks you to create a pull request, follow these steps carefully:
|
||||
|
||||
1. Gather information
|
||||
- Run a git status command to see all untracked files
|
||||
- Run a git diff command to see both staged and unstaged changes that will be committed
|
||||
- Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote
|
||||
- Run a git log command and \\\`git diff main...HEAD\\\` to understand the full commit history for the current branch (from the time it diverged from the \\\`main\\\` branch)
|
||||
|
||||
2. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (NOT just the latest commit, but ALL commits that will be included in the pull request!!!), and draft a pull request summary. Wrap your analysis process in <pr_analysis> tags:
|
||||
|
||||
<pr_analysis>
|
||||
- List the commits since diverging from the main branch
|
||||
- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
|
||||
- Brainstorm the purpose or motivation behind these changes
|
||||
- Assess the impact of these changes on the overall project
|
||||
- Do not use tools to explore code, beyond what is available in the git context
|
||||
- Check for any sensitive information that shouldn't be committed
|
||||
- Draft a concise (1-2 bullet points) pull request summary that focuses on the "why" rather than the "what"
|
||||
- Ensure the summary accurately reflects all changes since diverging from the main branch
|
||||
- Ensure your language is clear, concise, and to the point
|
||||
- Ensure the summary accurately reflects the changes and their purpose (ie. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
|
||||
- Ensure the summary is not generic (avoid words like "Update" or "Fix" without context)
|
||||
- Review the draft summary to ensure it accurately reflects the changes and their purpose
|
||||
</pr_analysis>
|
||||
|
||||
<example>
|
||||
gh pr create \
|
||||
--title "the pr title" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
<1-3 bullet points>
|
||||
|
||||
## Test plan
|
||||
[Checklist of TODOs for testing the pull request...]
|
||||
|
||||
${CO_AUTHORED_PR_MSG}
|
||||
EOF
|
||||
)"
|
||||
</example>
|
||||
|
||||
Important:
|
||||
- NEVER update the git config
|
||||
- Return the PR URL when you're done, so the user can see it
|
||||
|
||||
# Other common operations
|
||||
- View comments on a Github PR: gh api repos/foo/bar/pulls/123/comments`
|
||||
|
||||
export const bashToolDefinition = (cwd: string) => ({
|
||||
name: bashToolName,
|
||||
descriptionForAgent: descriptionForAgent(cwd),
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
command: {
|
||||
type: "string",
|
||||
description:
|
||||
"The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.",
|
||||
},
|
||||
requires_approval: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.",
|
||||
},
|
||||
},
|
||||
required: ["command", "requires_approval"],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
|
||||
export const browserActionToolName = "BrowserAction"
|
||||
|
||||
const descriptionForAgent = (
|
||||
browserSettings: BrowserSettings,
|
||||
) => `Request to interact with a Puppeteer-controlled browser. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action.
|
||||
- The sequence of actions **must always start with** launching the browser at a URL, and **must always end with** closing the browser. If you need to visit a new URL that is not possible to navigate to from the current webpage, you must first close the browser, then launch again at the new URL.
|
||||
- While the browser is active, only the \`${browserActionToolName}\` tool can be used. No other tools should be called during this time. You may proceed to use other tools only after closing the browser. For example if you run into an error and need to fix a file, you must close the browser, then use other tools to make the necessary changes, then re-launch the browser to verify the result.
|
||||
- The browser window has a resolution of **${browserSettings.viewport.width}x${browserSettings.viewport.height}** pixels. When performing any click actions, ensure the coordinates are within this resolution range.
|
||||
- Before clicking on any elements such as icons, links, or buttons, you must consult the provided screenshot of the page to determine the coordinates of the element. The click should be targeted at the **center of the element**, not on its edges.`
|
||||
|
||||
export const browserActionToolDefinition = (browserSettings: BrowserSettings): ToolDefinition => ({
|
||||
name: browserActionToolName,
|
||||
descriptionForAgent: descriptionForAgent(browserSettings),
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
action: {
|
||||
type: "string",
|
||||
description: `The action to perform. The available actions are:
|
||||
* launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**.
|
||||
- Use with the \`url\` parameter to provide the URL.
|
||||
- Ensure the URL is valid and includes the appropriate protocol (e.g. http://localhost:3000/page, file:///path/to/file.html, etc.)
|
||||
* click: Click at a specific x,y coordinate.
|
||||
- Use with the \`coordinate\` parameter to specify the location.
|
||||
- Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot.
|
||||
* type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text.
|
||||
- Use with the \`text\` parameter to provide the string to type.
|
||||
* scroll_down: Scroll down the page by one page height.
|
||||
* scroll_up: Scroll up the page by one page height.
|
||||
* close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**.
|
||||
- Example: \`<action>close</action>\``,
|
||||
},
|
||||
url: {
|
||||
type: "string",
|
||||
description: `Use this for providing the URL for the \`launch\` action.
|
||||
Example: <url>https://example.com</url>`,
|
||||
},
|
||||
coordinate: {
|
||||
type: "string",
|
||||
description: `The X and Y coordinates for the \`click\` action. Coordinates should be within the **${browserSettings.viewport.width}x${browserSettings.viewport.height}** resolution.
|
||||
Example: <coordinate>450,300</coordinate>`,
|
||||
},
|
||||
text: {
|
||||
type: "string",
|
||||
description: `Use this for providing the text for the \`type\` action.
|
||||
Example: <text>Hello, world!</text>`,
|
||||
},
|
||||
},
|
||||
required: ["action"],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const editToolDefinition: ToolDefinition = {
|
||||
name: "MultiEdit",
|
||||
descriptionForAgent:
|
||||
"Makes multiple changes to a single file in one operation. Use this tool to edit files by providing the exact text to replace and the new text.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: "Absolute path to the file to modify",
|
||||
},
|
||||
edits: {
|
||||
type: "array",
|
||||
description: "Array of edit operations, each containing old_string and new_string",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
old_string: {
|
||||
type: "string",
|
||||
description: "Exact text to replace",
|
||||
},
|
||||
new_string: {
|
||||
type: "string",
|
||||
description: "The replacement text",
|
||||
},
|
||||
},
|
||||
required: ["old_string", "new_string"],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ["file_path", "edits"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const grepToolDefinition: ToolDefinition = {
|
||||
name: "Grep",
|
||||
descriptionForAgent: `- Fast content search tool that works with any codebase size
|
||||
- Searches file contents using regular expressions
|
||||
- Supports full regex syntax (eg. "log.*Error", "function\\\\s+\\\\w+", etc.)
|
||||
- Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}")
|
||||
- Returns file paths with at least one match
|
||||
- Use this tool when you need to find files containing specific patterns`,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
pattern: {
|
||||
type: "string",
|
||||
description: "The regular expression pattern to search for in file contents",
|
||||
},
|
||||
path: {
|
||||
type: "string",
|
||||
description: "The directory to search in.",
|
||||
},
|
||||
include: {
|
||||
type: "string",
|
||||
description: "File pattern to filter which files to search (e.g., '*.js' for JavaScript files)",
|
||||
},
|
||||
},
|
||||
required: ["pattern", "path"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
const descriptionForAgent = `Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.`
|
||||
|
||||
export const listCodeDefinitionNamesToolDefinition = (cwd: string) => ({
|
||||
name: "ListCodeDefinitionNames",
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: `The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for.`,
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const loadMcpDocumentationToolName = "LoadMcpDocumentation"
|
||||
|
||||
const descriptionForAgent = (useMCPToolName: string, accessMcpResourceToolName: string) =>
|
||||
`Load documentation about creating MCP servers. This tool should be used when the user requests to create or install an MCP server (the user may ask you something along the lines of "add a tool" that does some function, in other words to create an MCP server that provides tools and resources that may connect to external APIs for example. You have the ability to create an MCP server and add it to a configuration file that will then expose the tools and resources for you to use with \`${useMCPToolName}\` and \`${accessMcpResourceToolName}\`). The documentation provides detailed information about the MCP server creation process, including setup instructions, best practices, and examples.`
|
||||
|
||||
export const loadMcpDocumentationToolDefinition = (
|
||||
useMCPToolName: string,
|
||||
accessMcpResourceToolName: string,
|
||||
): ToolDefinition => ({
|
||||
name: loadMcpDocumentationToolName,
|
||||
descriptionForAgent: descriptionForAgent(useMCPToolName, accessMcpResourceToolName),
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const lsToolDefinition: ToolDefinition = {
|
||||
name: "LS",
|
||||
descriptionForAgent:
|
||||
"Lists files and directories in a given path. The path parameter must be an absolute path, not a relative path. You should generally prefer the Glob and Grep tools, if you know which directories to search.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "The path of the directory to list contents for",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const newTaskToolName = "NewTask"
|
||||
|
||||
const descriptionForAgent = `Request to create a new task with preloaded context covering the conversation with the user up to this point and key information for continuing with the new task. With this tool, you will create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions, with a focus on the most relevant information required for the new task.
|
||||
Among other important areas of focus, this summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing with the new task. The user will be presented with a preview of your generated context and can choose to create a new task or keep chatting in the current conversation. The user may choose to start a new task at any point.`
|
||||
|
||||
export const newTaskToolDefinition: ToolDefinition = {
|
||||
name: newTaskToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
context: {
|
||||
type: "string",
|
||||
description: `The context to preload the new task with. If applicable based on the current task, this should include:
|
||||
1. Current Work: Describe in detail what was being worked on prior to this request to create a new task. Pay special attention to the more recent messages / conversation.
|
||||
2. Key Technical Concepts: List all important technical concepts, technologies, coding conventions, and frameworks discussed, which might be relevant for the new task.
|
||||
3. Relevant Files and Code: If applicable, enumerate specific files and code sections examined, modified, or created for the task continuation. Pay special attention to the most recent messages and changes.
|
||||
4. Problem Solving: Document problems solved thus far and any ongoing troubleshooting efforts.
|
||||
5. Pending Tasks and Next Steps: Outline all pending tasks that you have explicitly been asked to work on, as well as list the next steps you will take for all outstanding work, if applicable. Include code snippets where they add clarity. For any next steps, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no information loss in context between tasks. It's important to be detailed here.`,
|
||||
},
|
||||
},
|
||||
required: ["context"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const planModeRespondToolName = "PlanModeRespond"
|
||||
|
||||
const descriptionForAgent = `Respond to the user's inquiry in an effort to plan a solution to the user's task. This tool should be used when you need to provide a response to a question or statement from the user about how you plan to accomplish the task. This tool is only available in PLAN MODE. The environment_details will specify the current mode, if it is not PLAN MODE then you should not use this tool. Depending on the user's message, you may ask questions to get clarification about the user's request, architect a solution to the task, and to brainstorm ideas with the user. For example, if the user's task is to create a website, you may start by asking some clarifying questions, then present a detailed plan for how you will accomplish the task given the context, and perhaps engage in a back and forth to finalize the details before the user switches you to ACT MODE to implement the solution.`
|
||||
|
||||
export const planModeRespondToolDefinition: ToolDefinition = {
|
||||
name: planModeRespondToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
response: {
|
||||
type: "string",
|
||||
description:
|
||||
"The response to provide to the user. Do not try to use tools in this parameter, this is simply a chat response. (You MUST use the response parameter)",
|
||||
},
|
||||
},
|
||||
required: ["response"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
const DEFAULT_LINE_LIMIT = 2000
|
||||
const MAX_LINE_LENGTH = 2000
|
||||
|
||||
const descriptionForAgent = `Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.`
|
||||
|
||||
export const readToolDefinition = (cwd: string) => ({
|
||||
name: "Read",
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: `The path of the file to read (relative to the current working directory ${cwd.toPosix()})`,
|
||||
},
|
||||
},
|
||||
required: ["file_path"],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const useMCPToolName = "UseMCPTool"
|
||||
|
||||
const descriptionForAgent = `Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.`
|
||||
|
||||
export const useMCPToolDefinition: ToolDefinition = {
|
||||
name: useMCPToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
server_name: {
|
||||
type: "string",
|
||||
description: "The name of the MCP server providing the tool",
|
||||
},
|
||||
tool_name: {
|
||||
type: "string",
|
||||
description: "The name of the tool to execute",
|
||||
},
|
||||
arguments: {
|
||||
type: "object",
|
||||
description: "A JSON object containing the tool's input parameters, following the tool's input schema",
|
||||
},
|
||||
},
|
||||
required: ["server_name", "tool_name", "arguments"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ToolDefinition } from "@core/prompts/model_prompts/jsonToolToXml"
|
||||
|
||||
export const webFetchToolName = "WebFetch"
|
||||
|
||||
const descriptionForAgent = `
|
||||
- Fetches content from a specified URL and processes into markdown
|
||||
- Takes a URL as input
|
||||
- Fetches the URL content, converts HTML to markdown
|
||||
- Use this tool when you need to retrieve and analyze web content
|
||||
|
||||
Usage notes:
|
||||
- IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions.
|
||||
- The URL must be a fully-formed valid URL
|
||||
- HTTP URLs will be automatically upgraded to HTTPS
|
||||
- This tool is read-only and does not modify any files
|
||||
`
|
||||
|
||||
export const webFetchToolDefinition: ToolDefinition = {
|
||||
name: webFetchToolName,
|
||||
descriptionForAgent,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
url: {
|
||||
type: "string",
|
||||
format: "url",
|
||||
description: "The URL to fetch content from",
|
||||
},
|
||||
},
|
||||
required: ["url"],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const descriptionForAgent = (
|
||||
cwd: string,
|
||||
) => `Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
||||
|
||||
Usage:
|
||||
- The file_path parameter must be an relative path to the current working directory: ${cwd.toPosix()}
|
||||
- This tool will overwrite the existing file if there is one at the provided path.
|
||||
- If this is an existing file, you MUST use the Read tool first to read the file's contents.
|
||||
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
||||
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.`
|
||||
|
||||
export const writeToolDefinition = (cwd: string) => ({
|
||||
name: "Write",
|
||||
descriptionForAgent: descriptionForAgent(cwd),
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: `The path of the file to write to (relative to the current working directory ${cwd.toPosix()})`,
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
description:
|
||||
"The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.",
|
||||
},
|
||||
},
|
||||
required: ["file_path", "content"],
|
||||
},
|
||||
})
|
||||
@@ -13,6 +13,8 @@ import { HostProvider } from "@/hosts/host-provider"
|
||||
import { ShowMessageType } from "@/shared/proto/host/window"
|
||||
|
||||
export abstract class WebviewProvider {
|
||||
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
||||
public static readonly tabPanelId = "claude-dev.TabPanelProvider"
|
||||
private static activeInstances: Set<WebviewProvider> = new Set()
|
||||
private static clientIdMap = new Map<WebviewProvider, string>()
|
||||
controller: Controller
|
||||
@@ -22,6 +24,7 @@ export abstract class WebviewProvider {
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
|
||||
private readonly providerType: WebviewProviderType,
|
||||
) {
|
||||
WebviewProvider.activeInstances.add(this)
|
||||
@@ -55,11 +58,15 @@ export abstract class WebviewProvider {
|
||||
}
|
||||
|
||||
public static getActiveInstance(): WebviewProvider | undefined {
|
||||
return Array.from(WebviewProvider.activeInstances).find((instance) => instance.isActive())
|
||||
return Array.from(WebviewProvider.activeInstances).find((instance) => {
|
||||
const webview = instance.getWebview()
|
||||
if (webview && webview.viewType === "claude-dev.TabPanelProvider" && "active" in webview) {
|
||||
return webview.active === true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
protected abstract isActive(): boolean
|
||||
|
||||
public static getAllInstances(): WebviewProvider[] {
|
||||
return Array.from(WebviewProvider.activeInstances)
|
||||
}
|
||||
@@ -108,6 +115,21 @@ export abstract class WebviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and sets up the webview when it's first created.
|
||||
*
|
||||
* @param webviewView - The webview view or panel instance to be resolved
|
||||
* @returns A promise that resolves when the webview has been fully initialized
|
||||
*/
|
||||
abstract resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel): Promise<void>
|
||||
|
||||
/**
|
||||
* Gets the current webview instance.
|
||||
*
|
||||
* @returns The webview instance (WebviewView, WebviewPanel, or similar)
|
||||
*/
|
||||
abstract getWebview(): any
|
||||
|
||||
/**
|
||||
* Converts a local URI to a webview URI that can be used within the webview.
|
||||
*
|
||||
@@ -156,6 +178,14 @@ export abstract class WebviewProvider {
|
||||
// don't forget to add font-src ${webview.cspSource};
|
||||
const codiconsUri = this.getExtensionUri("node_modules", "@vscode", "codicons", "dist", "codicon.css")
|
||||
|
||||
// const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js"))
|
||||
|
||||
// const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css"))
|
||||
// const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "vscode.css"))
|
||||
|
||||
// // Same for stylesheet
|
||||
// const stylesheetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.css"))
|
||||
|
||||
// Use a nonce to only allow a specific script to be run.
|
||||
/*
|
||||
content security policy of your webview to only allow scripts that have a specific nonce
|
||||
@@ -319,6 +349,9 @@ export abstract class WebviewProvider {
|
||||
* @returns A URI pointing to the file/resource
|
||||
*/
|
||||
private getExtensionUri(...pathList: string[]): Uri {
|
||||
if (!this.getWebview()) {
|
||||
throw Error("webview is not initialized.")
|
||||
}
|
||||
return this.getWebviewUri(Uri.joinPath(this.context.extensionUri, ...pathList))
|
||||
}
|
||||
}
|
||||
|
||||
+118
-43
@@ -5,6 +5,7 @@ import { DIFF_VIEW_URI_SCHEME } from "@hosts/vscode/VscodeDiffViewProvider"
|
||||
import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/cline/ui"
|
||||
import assert from "node:assert"
|
||||
import { setTimeout as setTimeoutPromise } from "node:timers/promises"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as vscode from "vscode"
|
||||
import { sendAccountButtonClickedEvent } from "./core/controller/ui/subscribeToAccountButtonClicked"
|
||||
import { sendChatButtonClickedEvent } from "./core/controller/ui/subscribeToChatButtonClicked"
|
||||
@@ -23,13 +24,7 @@ import { vscodeHostBridgeClient } from "@/hosts/vscode/hostbridge/client/host-gr
|
||||
import { readTextFromClipboard, writeTextToClipboard } from "@/utils/env"
|
||||
import type { ExtensionContext } from "vscode"
|
||||
import { initialize, tearDown } from "./common"
|
||||
import { addToCline } from "./core/controller/commands/addToCline"
|
||||
import { explainWithCline } from "./core/controller/commands/explainWithCline"
|
||||
import { fixWithCline } from "./core/controller/commands/fixWithCline"
|
||||
import { improveWithCline } from "./core/controller/commands/improveWithCline"
|
||||
import { sendAddToInputEvent } from "./core/controller/ui/subscribeToAddToInput"
|
||||
import { sendFocusChatInputEvent } from "./core/controller/ui/subscribeToFocusChatInput"
|
||||
import { focusChatInput, getContextForCommand } from "./hosts/vscode/commandUtils"
|
||||
import { VscodeDiffViewProvider } from "./hosts/vscode/VscodeDiffViewProvider"
|
||||
import { VscodeWebviewProvider } from "./hosts/vscode/VscodeWebviewProvider"
|
||||
import { GitCommitGenerator } from "./integrations/git/commit-message-generator"
|
||||
@@ -62,7 +57,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.executeCommand("setContext", "cline.isDevMode", IS_DEV && IS_DEV === "true")
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(VscodeWebviewProvider.SIDEBAR_ID, sidebarWebview, {
|
||||
vscode.window.registerWebviewViewProvider(WebviewProvider.sideBarId, sidebarWebview, {
|
||||
webviewOptions: { retainContextWhenHidden: true },
|
||||
}),
|
||||
)
|
||||
@@ -124,7 +119,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
Logger.log("Opening Cline in new tab")
|
||||
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
|
||||
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
|
||||
const tabWebview = HostProvider.get().createWebviewProvider(WebviewProviderType.TAB) as VscodeWebviewProvider
|
||||
const tabWebview = HostProvider.get().createWebviewProvider(WebviewProviderType.TAB)
|
||||
//const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
|
||||
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
|
||||
|
||||
@@ -135,7 +130,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two
|
||||
|
||||
const panel = vscode.window.createWebviewPanel(VscodeWebviewProvider.TAB_PANEL_ID, "Cline", targetCol, {
|
||||
const panel = vscode.window.createWebviewPanel(WebviewProvider.tabPanelId, "Cline", targetCol, {
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [context.extensionUri],
|
||||
@@ -241,6 +236,42 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
})
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.addToChat", async (range?: vscode.Range, diagnostics?: vscode.Diagnostic[]) => {
|
||||
await vscode.commands.executeCommand("cline.focusChatInput") // Ensure Cline is visible and input focused
|
||||
const activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
const clientId = activeWebview?.getClientId()
|
||||
await pWaitFor(() => !!activeWebview)
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor || !clientId) {
|
||||
return
|
||||
}
|
||||
|
||||
await sendFocusChatInputEvent(clientId)
|
||||
|
||||
// Use provided range if available, otherwise use current selection
|
||||
// (vscode command passes an argument in the first param by default, so we need to ensure it's a Range object)
|
||||
const textRange = range instanceof vscode.Range ? range : editor.selection
|
||||
const selectedText = editor.document.getText(textRange)
|
||||
|
||||
if (!selectedText) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the file path and language ID
|
||||
const filePath = editor.document.uri.fsPath
|
||||
const languageId = editor.document.languageId
|
||||
|
||||
await activeWebview?.controller.addSelectedCodeToChat(
|
||||
selectedText,
|
||||
filePath,
|
||||
languageId,
|
||||
Array.isArray(diagnostics) ? diagnostics : undefined,
|
||||
)
|
||||
telemetryService.captureButtonClick("codeAction_addToChat", activeWebview?.controller.task?.ulid)
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.addTerminalOutputToChat", async () => {
|
||||
const terminal = vscode.window.activeTerminal
|
||||
@@ -265,12 +296,24 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// No terminal content was copied (either nothing selected or some error)
|
||||
return
|
||||
}
|
||||
// Ensure the sidebar view is visible
|
||||
await focusChatInput()
|
||||
|
||||
await sendAddToInputEvent(`Terminal output:\n\`\`\`\n${terminalContents}\n\`\`\``)
|
||||
// [Optional] Any additional logic to process multi-line content can remain here
|
||||
// For example:
|
||||
/*
|
||||
const lines = terminalContents.split("\n")
|
||||
const lastLine = lines.pop()?.trim()
|
||||
if (lastLine) {
|
||||
let i = lines.length - 1
|
||||
while (i >= 0 && !lines[i].trim().startsWith(lastLine)) {
|
||||
i--
|
||||
}
|
||||
terminalContents = lines.slice(Math.max(i, 0)).join("\n")
|
||||
}
|
||||
*/
|
||||
|
||||
console.log("addSelectedTerminalOutputToChat", terminalContents, terminal.name)
|
||||
// Send to sidebar provider
|
||||
const visibleWebview = WebviewProvider.getVisibleInstance()
|
||||
await visibleWebview?.controller.addSelectedTerminalOutputToChat(terminalContents, terminal.name)
|
||||
} catch (error) {
|
||||
// Ensure clipboard is restored even if an error occurs
|
||||
await writeTextToClipboard(tempCopyBuffer)
|
||||
@@ -283,6 +326,10 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}),
|
||||
)
|
||||
|
||||
const CONTEXT_LINES_TO_EXPAND = 3
|
||||
const START_OF_LINE_CHAR_INDEX = 0
|
||||
const LINE_COUNT_ADJUSTMENT_FOR_ZERO_INDEXING = 1
|
||||
|
||||
// Register code action provider
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCodeActionsProvider(
|
||||
@@ -295,10 +342,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
range: vscode.Range,
|
||||
context: vscode.CodeActionContext,
|
||||
): vscode.CodeAction[] {
|
||||
const CONTEXT_LINES_TO_EXPAND = 3
|
||||
const START_OF_LINE_CHAR_INDEX = 0
|
||||
const LINE_COUNT_ADJUSTMENT_FOR_ZERO_INDEXING = 1
|
||||
|
||||
const actions: vscode.CodeAction[] = []
|
||||
const editor = vscode.window.activeTextEditor // Get active editor for selection check
|
||||
|
||||
@@ -381,41 +424,76 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
),
|
||||
)
|
||||
|
||||
// Register the command handlers
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.addToChat", async (range?: vscode.Range, diagnostics?: vscode.Diagnostic[]) => {
|
||||
const context = await getContextForCommand(range, diagnostics)
|
||||
if (!context) {
|
||||
return
|
||||
}
|
||||
await addToCline(context.controller, context.commandContext)
|
||||
}),
|
||||
)
|
||||
// Register the command handler
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.fixWithCline", async (range: vscode.Range, diagnostics: vscode.Diagnostic[]) => {
|
||||
const context = await getContextForCommand(range, diagnostics)
|
||||
if (!context) {
|
||||
// Add this line to focus the chat input first
|
||||
await vscode.commands.executeCommand("cline.focusChatInput")
|
||||
// Wait for a webview instance to become available after focusing
|
||||
await pWaitFor(() => !!WebviewProvider.getLastActiveInstance())
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
await fixWithCline(context.controller, context.commandContext)
|
||||
|
||||
const selectedText = editor.document.getText(range)
|
||||
const filePath = editor.document.uri.fsPath
|
||||
const languageId = editor.document.languageId
|
||||
|
||||
// Send to last active instance with diagnostics
|
||||
const activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
await activeWebview?.controller.fixWithCline(selectedText, filePath, languageId, diagnostics)
|
||||
telemetryService.captureButtonClick("codeAction_fixWithCline", activeWebview?.controller.task?.ulid)
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.explainCode", async (range: vscode.Range) => {
|
||||
const context = await getContextForCommand(range)
|
||||
if (!context) {
|
||||
await vscode.commands.executeCommand("cline.focusChatInput") // Ensure Cline is visible and input focused
|
||||
await pWaitFor(() => !!WebviewProvider.getLastActiveInstance())
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
await explainWithCline(context.controller, context.commandContext)
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to explain.",
|
||||
})
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
const activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
const fileMention = activeWebview?.controller.getFileMentionFromPath(filePath) || filePath
|
||||
const prompt = `Explain the following code from ${fileMention}:\n\`\`\`${editor.document.languageId}\n${selectedText}\n\`\`\``
|
||||
await activeWebview?.controller.initTask(prompt)
|
||||
telemetryService.captureButtonClick("codeAction_explainCode", activeWebview?.controller.task?.ulid)
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.improveCode", async (range: vscode.Range) => {
|
||||
const context = await getContextForCommand(range)
|
||||
if (!context) {
|
||||
await vscode.commands.executeCommand("cline.focusChatInput") // Ensure Cline is visible and input focused
|
||||
await pWaitFor(() => !!WebviewProvider.getLastActiveInstance())
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
await improveWithCline(context.controller, context.commandContext)
|
||||
const selectedText = editor.document.getText(range)
|
||||
if (!selectedText.trim()) {
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message: "Please select some code to improve.",
|
||||
})
|
||||
return
|
||||
}
|
||||
const filePath = editor.document.uri.fsPath
|
||||
const activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
const fileMention = activeWebview?.controller.getFileMentionFromPath(filePath) || filePath
|
||||
const prompt = `Improve the following code from ${fileMention} (e.g., suggest refactorings, optimizations, or better practices):\n\`\`\`${editor.document.languageId}\n${selectedText}\n\`\`\``
|
||||
await activeWebview?.controller.initTask(prompt)
|
||||
telemetryService.captureButtonClick("codeAction_improveCode", activeWebview?.controller.task?.ulid)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -423,7 +501,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cline.focusChatInput", async () => {
|
||||
// Fast path: check for existing active instance
|
||||
let activeWebview = WebviewProvider.getLastActiveInstance() as VscodeWebviewProvider
|
||||
let activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
|
||||
if (activeWebview) {
|
||||
// Instance exists - just reveal and focus it
|
||||
@@ -440,7 +518,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
WebviewProvider.setLastActiveControllerId(null)
|
||||
|
||||
// Check for existing tab instances first (cheaper than focusing sidebar)
|
||||
const tabInstances = WebviewProvider.getTabInstances() as VscodeWebviewProvider[]
|
||||
const tabInstances = WebviewProvider.getTabInstances()
|
||||
if (tabInstances.length > 0) {
|
||||
activeWebview = tabInstances[tabInstances.length - 1]
|
||||
} else {
|
||||
@@ -449,11 +527,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Small delay for focus to complete
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
activeWebview = WebviewProvider.getSidebarInstance() as VscodeWebviewProvider
|
||||
if (!activeWebview) {
|
||||
// Last resort: create new tab
|
||||
activeWebview = (await openClineInNewTab()) as VscodeWebviewProvider
|
||||
}
|
||||
// Last resort: create new tab
|
||||
activeWebview = WebviewProvider.getSidebarInstance() || (await openClineInNewTab())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+23
-65
@@ -6,10 +6,6 @@ import { SharedUriHandler } from "@/services/uri/SharedUriHandler"
|
||||
|
||||
const SERVER_TIMEOUT = 10 * 60 * 1000 // 10 minutes
|
||||
|
||||
const PORT_RANGE_START = 48801
|
||||
const PORT_RANGE_END = 48811
|
||||
const PORTS: number[] = Array.from({ length: PORT_RANGE_END - PORT_RANGE_START + 1 }, (_, i) => PORT_RANGE_START + i)
|
||||
|
||||
/**
|
||||
* Handles OAuth authentication flow by creating a local server to receive tokens.
|
||||
*/
|
||||
@@ -61,62 +57,38 @@ export class AuthHandler {
|
||||
}
|
||||
|
||||
private async createServer(): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const server = http.createServer(this.handleRequest.bind(this))
|
||||
|
||||
// Try to bind on a port from the allowed range
|
||||
for (const port of PORTS) {
|
||||
try {
|
||||
await this.tryListenOnPort(server, port)
|
||||
|
||||
const address = server.address()
|
||||
if (!address) {
|
||||
console.error("AuthHandler: Failed to get server address")
|
||||
this.server = null
|
||||
this.port = 0
|
||||
this.serverCreationPromise = null
|
||||
reject(new Error("Failed to get server address"))
|
||||
return
|
||||
}
|
||||
|
||||
// Get the assigned port and set up the server
|
||||
this.port = (address as AddressInfo).port
|
||||
this.server = server
|
||||
console.log("AuthHandler: Server started on port", this.port)
|
||||
this.updateTimeout()
|
||||
this.serverCreationPromise = null
|
||||
|
||||
// Attach a general error logger for visibility after successful bind
|
||||
server.on("error", (error) => {
|
||||
console.error("AuthHandler: Server error", error)
|
||||
})
|
||||
|
||||
resolve()
|
||||
return
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException
|
||||
if (err?.code === "EADDRINUSE") {
|
||||
console.warn(`AuthHandler: Port ${port} in use, trying next...`)
|
||||
continue
|
||||
}
|
||||
console.error("AuthHandler: Server error", error)
|
||||
// Use callback to ensure server is ready before getting address
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const address = server.address()
|
||||
if (!address) {
|
||||
console.error("AuthHandler: Failed to get server address")
|
||||
this.server = null
|
||||
this.port = 0
|
||||
this.serverCreationPromise = null
|
||||
reject(error)
|
||||
reject(new Error("Failed to get server address"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, all ports in the range are occupied
|
||||
console.error(`AuthHandler: No available port in range ${PORT_RANGE_START}-${PORT_RANGE_END}`)
|
||||
this.server = null
|
||||
this.port = 0
|
||||
this.serverCreationPromise = null
|
||||
reject(
|
||||
new Error(`No available port found for local auth callback (tried ${PORT_RANGE_START}-${PORT_RANGE_END}).`),
|
||||
)
|
||||
// Get the assigned port and set up the server
|
||||
this.port = (address as AddressInfo).port
|
||||
this.server = server
|
||||
console.log("AuthHandler: Server started on port", this.port)
|
||||
this.updateTimeout()
|
||||
this.serverCreationPromise = null
|
||||
resolve()
|
||||
})
|
||||
|
||||
server.on("error", (error) => {
|
||||
console.error("AuthHandler: Server error", error)
|
||||
this.server = null
|
||||
this.port = 0
|
||||
this.serverCreationPromise = null
|
||||
reject(error)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("AuthHandler: Failed to create server", error)
|
||||
this.server = null
|
||||
@@ -127,20 +99,6 @@ export class AuthHandler {
|
||||
})
|
||||
}
|
||||
|
||||
private tryListenOnPort(server: Server, port: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onError = (error: NodeJS.ErrnoException) => {
|
||||
server.off("error", onError)
|
||||
reject(error)
|
||||
}
|
||||
server.once("error", onError)
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
server.off("error", onError)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private updateTimeout(): void {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId)
|
||||
|
||||
+6
-2
@@ -23,7 +23,11 @@ export class ExternalWebviewProvider extends WebviewProvider {
|
||||
override isVisible() {
|
||||
return true
|
||||
}
|
||||
protected override isActive(): boolean {
|
||||
return true
|
||||
override getWebview() {
|
||||
return {}
|
||||
}
|
||||
|
||||
override resolveWebviewView(_: any): Promise<void> {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,19 @@ export class HostProvider {
|
||||
logToChannel,
|
||||
getCallbackUri,
|
||||
)
|
||||
|
||||
// If telemetry was created early, update its machineId now that hostbridge is ready
|
||||
try {
|
||||
const { PostHogClientProvider } = require("@/services/posthog/PostHogClientProvider")
|
||||
if (PostHogClientProvider?.isInitialized?.()) {
|
||||
PostHogClientProvider.getInstance().updateMachineIdAsync?.()
|
||||
}
|
||||
} catch (err) {
|
||||
const msg = `[Telemetry] skipped PostHog update: ${String(err)}`
|
||||
if (HostProvider.isInitialized()) {
|
||||
HostProvider.get().logToChannel(msg)
|
||||
}
|
||||
}
|
||||
return HostProvider.instance
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@ https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/c
|
||||
*/
|
||||
|
||||
export class VscodeWebviewProvider extends WebviewProvider implements vscode.WebviewViewProvider {
|
||||
// Used in package.json as the view's id. This value cannot be changed due to how vscode caches
|
||||
// views based on their id, and updating the id would break existing instances of the extension.
|
||||
public static readonly SIDEBAR_ID = "claude-dev.SidebarProvider"
|
||||
public static readonly TAB_PANEL_ID = "claude-dev.TabPanelProvider"
|
||||
|
||||
private webview?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private disposables: vscode.Disposable[] = []
|
||||
|
||||
@@ -34,36 +29,20 @@ export class VscodeWebviewProvider extends WebviewProvider implements vscode.Web
|
||||
}
|
||||
return this.webview.webview.asWebviewUri(uri)
|
||||
}
|
||||
|
||||
override getCspSource() {
|
||||
if (!this.webview) {
|
||||
throw new Error("Webview not initialized")
|
||||
}
|
||||
return this.webview.webview.cspSource
|
||||
}
|
||||
|
||||
protected isActive() {
|
||||
if (this.webview && this.webview.viewType === VscodeWebviewProvider.TAB_PANEL_ID && "active" in this.webview) {
|
||||
return this.webview.active === true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override isVisible() {
|
||||
return this.webview?.visible || false
|
||||
}
|
||||
|
||||
public getWebview(): vscode.WebviewView | vscode.WebviewPanel | undefined {
|
||||
override getWebview() {
|
||||
return this.webview
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and sets up the webview when it's first created.
|
||||
*
|
||||
* @param webviewView - The webview view or panel instance to be resolved
|
||||
* @returns A promise that resolves when the webview has been fully initialized
|
||||
*/
|
||||
public async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel): Promise<void> {
|
||||
override async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) {
|
||||
this.webview = webviewView
|
||||
|
||||
webviewView.webview.options = {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { CommandContext } from "@/shared/proto/index.cline"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as vscode from "vscode"
|
||||
import { Controller } from "../../core/controller"
|
||||
import { WebviewProvider } from "../../core/webview"
|
||||
import { convertVscodeDiagnostics } from "./hostbridge/workspace/getDiagnostics"
|
||||
|
||||
/**
|
||||
* Gets the context needed for VSCode commands that interact with the editor
|
||||
* @param range Optional range to use instead of current selection
|
||||
* @param vscodeDiagnostics Optional diagnostics to include
|
||||
* @returns Context object with controller, selected text, file info, and problems
|
||||
*/
|
||||
export async function getContextForCommand(
|
||||
range?: vscode.Range,
|
||||
vscodeDiagnostics?: vscode.Diagnostic[],
|
||||
): Promise<
|
||||
| undefined
|
||||
| {
|
||||
controller: Controller
|
||||
commandContext: CommandContext
|
||||
}
|
||||
> {
|
||||
const activeWebview = await focusChatInput()
|
||||
if (!activeWebview) {
|
||||
return
|
||||
}
|
||||
// Use the controller from the last active instance
|
||||
const controller = activeWebview.controller
|
||||
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
return
|
||||
}
|
||||
// Use provided range if available, otherwise use current selection
|
||||
// (vscode command passes an argument in the first param by default, so we need to ensure it's a Range object)
|
||||
const textRange = range instanceof vscode.Range ? range : editor.selection
|
||||
const selectedText = editor.document.getText(textRange)
|
||||
|
||||
const filePath = editor.document.uri.fsPath
|
||||
const language = editor.document.languageId
|
||||
const diagnostics = convertVscodeDiagnostics(vscodeDiagnostics || [])
|
||||
const commandContext: CommandContext = {
|
||||
selectedText,
|
||||
filePath,
|
||||
diagnostics,
|
||||
language,
|
||||
}
|
||||
return { controller, commandContext }
|
||||
}
|
||||
|
||||
export async function focusChatInput(): Promise<WebviewProvider | undefined> {
|
||||
await vscode.commands.executeCommand("cline.focusChatInput")
|
||||
|
||||
// Wait for a webview instance to become available after focusing
|
||||
await pWaitFor(() => !!WebviewProvider.getLastActiveInstance())
|
||||
const activeWebview = WebviewProvider.getLastActiveInstance()
|
||||
if (!activeWebview) {
|
||||
console.error("No active webview to receive command")
|
||||
return
|
||||
}
|
||||
|
||||
return activeWebview
|
||||
}
|
||||
@@ -73,6 +73,7 @@ export class GrpcHandler {
|
||||
)
|
||||
|
||||
// Call the streaming handler directly
|
||||
console.log(`[DEBUG] Streaming gRPC host call to ${service}.${method} req:${requestId}`)
|
||||
try {
|
||||
await this.handleStreamingRequest(service, method, request, requestId)
|
||||
} catch (error) {
|
||||
|
||||
@@ -45,6 +45,7 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
|
||||
) => {
|
||||
// Use handleRequest with streaming callbacks
|
||||
const requestId = uuidv4()
|
||||
console.log(`[DEBUG] Streaming gRPC host call to ${service.fullName}.${methodKey} req:${requestId}`)
|
||||
|
||||
// We need to await the promise and then return the cancel function
|
||||
return (async () => {
|
||||
@@ -79,8 +80,10 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
|
||||
client[methodKey as keyof GrpcClientType<T>] = ((request: any) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const requestId = uuidv4()
|
||||
console.log(`[DEBUG] gRPC host call to ${service.fullName}.${methodKey} req:${requestId}`)
|
||||
try {
|
||||
const response = await grpcHandler.handleRequest(service.fullName, methodKey, request, requestId)
|
||||
console.log(`[DEBUG] gRPC host resp to ${service.fullName}.${methodKey} req:${requestId}`)
|
||||
|
||||
// Check if the response is a function (streaming)
|
||||
if (typeof response === "function") {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user