mirror of
https://github.com/cline/cline.git
synced 2026-09-11 16:42:40 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676da8982c | ||
|
|
d51da744c0 | ||
|
|
7fca787e70 | ||
|
|
9207e8bb30 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Refactor Anthropic handler to use metadata for reasoning support
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix cline auth for bedrock provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Prevent duplicate error messages during streaming for Diff Edit tool when Parallel Tool Calling is not enabled.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Exclude files without extensions (and dotfiles) from getDiffSet results if they are binary
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: preserve file endings and trailing newlines across all edit tools
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Cerebras rate limiting by using conservative max_tokens (16K) instead of model maximum.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix issues where platform-based content was not displayed correctly.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix banner carousel styling and dismiss functionality
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed Auto Compact not triggering when using Claude Code provider. Short model aliases like "sonnet" and "opus" are now correctly recognized as Claude 4+ models.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Workspace and Favorites history filters to work independently instead of being mutually exclusive
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed connection failures with remote MCP servers that return 404 instead of 405 for SSE stream checks. This was causing "Failed to open SSE stream: Not Found" errors after the v3.46.0 SDK upgrade.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve the auth state tracking and reduce logouts caused by errors
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: correct typos in gemini system prompt overrides
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
feat(prompts): modify prompts for parallel tool usage in claude and gemini 3 models
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Refactor Bedrock provider to use metadata for reasoning support
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
add multi-root workspace support to cline CLI
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Stop automatically opening Cline sidebar on extension update - only show a notification
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
add supportsReasoning property to Baseten models
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
bump go version
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
show slash command autocompletion in the cli
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add background edit mode with webview diff view.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix sapaicore security issue
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix regression that broke JSON parsing for SAP AI Core provider in native API mode for claude models
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: Fetch remote config values from the cache
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Baseten model selector issue in model picker modal mode
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Replace current diff edit tools with Apply Patch tool for GPT-5+ models
|
||||
@@ -1,173 +0,0 @@
|
||||
name: Claude Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
# Manual trigger for backfilling existing issues. Run from terminal:
|
||||
# gh workflow run claude-issue-triage.yml -f issue_number=1234
|
||||
# Or batch process:
|
||||
# gh issue list --state open --limit 10 --json number --jq '.[].number' | while read num; do
|
||||
# gh workflow run claude-issue-triage.yml -f issue_number=$num
|
||||
# sleep 60
|
||||
# done
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to triage'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-issue-triage:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
# SECURITY: These permissions are intentionally restrictive.
|
||||
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
|
||||
# - issues: write -> Claude can comment and add labels (the only write access needed)
|
||||
# - pull-requests: read -> Claude can view PR context but CANNOT create PRs
|
||||
# This ensures that even if a malicious user attempts prompt injection via issue content,
|
||||
# Claude cannot modify repository code, create branches, or open PRs.
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Issue Response & Triage
|
||||
id: triage
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed_non_write_users: "*"
|
||||
# Allow all tools - security is enforced by GitHub permissions above (contents: read, issues: write)
|
||||
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
|
||||
prompt: |
|
||||
You're a GitHub issue first responder for the open source Cline repository.
|
||||
|
||||
**Issue:** #${{ github.event.issue.number || inputs.issue_number }}
|
||||
**Title:** ${{ github.event.issue.title || 'See issue details below' }}
|
||||
**Author:** @${{ github.event.issue.user.login || 'See issue details below' }}
|
||||
|
||||
## Your job
|
||||
|
||||
Investigate this issue thoroughly, then post a single helpful comment that helps the user and gives maintainers the context they need.
|
||||
|
||||
## Investigation
|
||||
|
||||
Start by reading the full issue:
|
||||
gh issue view ${{ github.event.issue.number || inputs.issue_number }}
|
||||
|
||||
### Search for duplicates and related issues
|
||||
|
||||
Search thoroughly for existing issues that match this one:
|
||||
gh issue list --search "<keywords from the issue>" --state all --limit 30
|
||||
gh issue list --search "<error messages>" --state all --limit 20
|
||||
gh issue list --search "<affected feature/component>" --state all --limit 20
|
||||
|
||||
For each relevant issue you find, read it including its comments:
|
||||
gh issue view <number> --comments
|
||||
|
||||
You're looking for:
|
||||
- **Duplicates**: Issues describing the same problem. Link to them and explain why you think they're duplicates. If closed, check how they were resolved - the solution might apply here.
|
||||
- **Related issues**: Similar problems or context that could help. Pull useful information from their comments (workarounds others found, debugging steps that helped, maintainer explanations). Link to them and explain the connection.
|
||||
|
||||
If there are closed issues with solutions, surface those solutions prominently - this might immediately solve the user's problem.
|
||||
|
||||
### Analyze recent changes (ALWAYS DO THIS)
|
||||
|
||||
Many issues are regressions from recent releases. **Always** check what changed recently:
|
||||
gh release list --limit 10
|
||||
gh pr list --state merged --limit 50 --json number,title,mergedAt,author,body
|
||||
|
||||
Look for PRs merged in the last few weeks that might correlate with the issue. If you find a likely connection:
|
||||
gh pr view <number>
|
||||
gh pr diff <number>
|
||||
git log --since="1 month ago" --oneline -- <relevant paths>
|
||||
git show <commit>
|
||||
|
||||
**Always include your findings in your comment:**
|
||||
- If you find a regression, call it out explicitly: which PR/commit likely caused it, who authored it, what changed, and suggest a fix direction if you can see one.
|
||||
- If you don't find anything related, still mention it: "I analyzed recent PRs and releases but didn't find any changes that seem related to this issue."
|
||||
|
||||
### Search the codebase
|
||||
|
||||
Find the relevant code:
|
||||
- Use grep/find to locate code related to the issue
|
||||
- Key areas: `src/api/` (providers/models), `src/core/prompts/` (tools/prompts), platform-specific code for VS Code vs JetBrains
|
||||
|
||||
### Find documentation
|
||||
|
||||
Cline docs are at **https://docs.cline.bot/** and built with Mintlify from the `docs/` directory.
|
||||
|
||||
The URL structure maps directly to the file structure:
|
||||
- `docs/getting-started/selecting-your-model.mdx` → https://docs.cline.bot/getting-started/selecting-your-model
|
||||
- `docs/troubleshooting.mdx` → https://docs.cline.bot/troubleshooting
|
||||
- Headings become anchors: `## Which Model` → `#which-model`
|
||||
|
||||
Search the `docs/` directory to find relevant documentation, then construct URLs to link users to:
|
||||
```bash
|
||||
ls docs/
|
||||
grep -r "keyword" docs/ --include="*.mdx" -l
|
||||
```
|
||||
|
||||
### Identify subject matter experts
|
||||
|
||||
For issues that clearly need engineering attention:
|
||||
git log --since="6 months ago" --format="%an" -- <relevant paths> | sort | uniq -c | sort -rn | head -5
|
||||
|
||||
Cross-reference with GitHub usernames. Include in your response (@mention, do NOT assign):
|
||||
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username1 | Authored PR #X which modified this area |
|
||||
| @username2 | Primary contributor to affected file |
|
||||
|
||||
## Weak model detection
|
||||
|
||||
Many issues are caused by users running small or non-frontier models that don't tool-call reliably. Signs include:
|
||||
- Model failing to use tools correctly
|
||||
- Nonsensical or malformed responses
|
||||
- User is running a small/local model or older model version
|
||||
|
||||
If this looks like a weak model issue, kindly suggest they try reproducing with Claude Sonnet and report back if it persists. Link to https://docs.cline.bot/getting-started/selecting-your-model if helpful. Still label and triage normally.
|
||||
|
||||
## Your comment
|
||||
|
||||
Write a single comment as a helpful community member. Be conversational, not robotic. Include what's relevant:
|
||||
|
||||
- **Helpful response** - Answer their question, suggest a fix, provide a workaround. If you found solutions in related closed issues, surface those prominently.
|
||||
- **Duplicates and related issues** - Link to any you found and explain why they're duplicates/related. Summarize useful context from their comments.
|
||||
- **Regression analysis** - If this looks like a regression, explain what change likely caused it, link to the PR/commit, and tag the author.
|
||||
- **Clarifying questions** - If you need more info, ask specific questions. Don't ask for things already provided.
|
||||
- **SME table** - Include the table above if this needs engineering attention. Don't tag people for questions with obvious answers or weak-model issues.
|
||||
- **Context for maintainers** - Relevant code paths, what you found. Keep it concise.
|
||||
- **Docs links** - If there's relevant documentation, link to it naturally in your response as a recommendation (e.g., "For more details, check out [the Ollama setup guide](url)"). Do NOT add a "Sources" section at the end - integrate doc links into your response where they're helpful.
|
||||
- **Possible Duplicates section** - ALWAYS include a "Possible Duplicates" section at the end of your comment listing issues that might be duplicates so maintainers can quickly close if appropriate. If none found, say "No obvious duplicates found."
|
||||
|
||||
## Labels
|
||||
First, retrieve all available labels and read their descriptions to understand what each is for:
|
||||
gh label list --json name,description --limit 100
|
||||
|
||||
Then apply the appropriate labels based on your analysis. Only use labels from the list above—do not create new labels.
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "label1,label2"
|
||||
|
||||
If your regression analysis found a likely culprit (a recent PR/commit that probably caused this issue), add the "Regression" label:
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Regression"
|
||||
|
||||
IMPORTANT: After posting your comment, add the "Bot Responded" label to indicate this issue has received an automated response:
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Bot Responded"
|
||||
|
||||
## Remember
|
||||
|
||||
- **This is a one-time automated response** - you will NOT see their reply or respond again. Never say things like "I can help you", "let me know", "once I have that info", or "I can give you more targeted help" - you won't be there to follow up. If you ask clarifying questions, frame them for the maintainers who will follow up, e.g., "If you can share X, that would help the maintainers diagnose this."
|
||||
- Don't be formulaic. Respond to what the issue actually needs.
|
||||
- Surface solutions from past issues - often the fastest path to helping.
|
||||
- Connecting regressions to specific changes is extremely valuable.
|
||||
- Link issues with #number so they're clickable.
|
||||
@@ -1,272 +0,0 @@
|
||||
name: Claude PR Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review]
|
||||
# Manual trigger for backfilling existing PRs. Run from terminal:
|
||||
# gh workflow run claude-pr-review.yml -f pr_number=1234
|
||||
# Or batch process open PRs:
|
||||
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
|
||||
# gh workflow run claude-pr-review.yml -f pr_number=$num
|
||||
# sleep 60
|
||||
# done
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to review'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-pr-review:
|
||||
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
|
||||
if: |
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
|
||||
# SECURITY: These permissions are intentionally restrictive.
|
||||
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
|
||||
# - pull-requests: write -> Claude can post reviews and inline suggestions
|
||||
# - issues: read -> Claude can search for related issues
|
||||
# NOTE: Even with pull-requests: write, Claude CANNOT merge PRs because branch protection
|
||||
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get PR number
|
||||
id: pr
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run PR Review
|
||||
id: review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed_non_write_users: "*"
|
||||
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
|
||||
prompt: |
|
||||
You're a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
|
||||
|
||||
PR: #${{ steps.pr.outputs.number }}
|
||||
|
||||
## Gather context
|
||||
|
||||
```bash
|
||||
# Get full PR details
|
||||
gh pr view ${{ steps.pr.outputs.number }} --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
|
||||
|
||||
# Get the diff
|
||||
gh pr diff ${{ steps.pr.outputs.number }}
|
||||
|
||||
# Check CI status
|
||||
gh pr checks ${{ steps.pr.outputs.number }}
|
||||
|
||||
# Get existing review comments (to understand context and your previous feedback)
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments --jq '.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'
|
||||
|
||||
# Get conversation comments
|
||||
gh pr view ${{ steps.pr.outputs.number }} --comments
|
||||
```
|
||||
|
||||
If this is a re-review (workflow_dispatch event):
|
||||
Read your previous comments carefully. Understand what you asked for before.
|
||||
Check if new commits or comments address your previous feedback.
|
||||
|
||||
## Check contributing guidelines
|
||||
|
||||
Flag (but don't block) if:
|
||||
- Missing changeset - For user-facing changes, check if there's a `.changeset/` file:
|
||||
```bash
|
||||
gh pr diff ${{ steps.pr.outputs.number }} --name-only | grep '.changeset/' || echo "No changeset found"
|
||||
```
|
||||
If missing, ask them to run `npm run changeset`
|
||||
- Missing tests - New features should have tests
|
||||
|
||||
## Find related issues and PRs
|
||||
|
||||
Search thoroughly for context that might help with the review:
|
||||
|
||||
```bash
|
||||
# Find related issues for context
|
||||
gh issue list --search "<keywords from the PR>" --state all --limit 30
|
||||
gh issue list --search "<error messages or feature names>" --state all --limit 20
|
||||
|
||||
# Find similar PRs for reference
|
||||
gh pr list --search "<keywords>" --state all --limit 30
|
||||
```
|
||||
|
||||
For each relevant issue or PR you find, read it including comments:
|
||||
```bash
|
||||
gh issue view <number> --comments
|
||||
gh pr view <number> --comments
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Open issues this PR might fix that weren't linked in the description
|
||||
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
|
||||
- Context from maintainer discussions that could inform your review
|
||||
|
||||
## Find subject matter experts
|
||||
|
||||
For files changed in this PR, find who knows the code best:
|
||||
```bash
|
||||
# Get files changed
|
||||
gh pr diff ${{ steps.pr.outputs.number }} --name-only
|
||||
|
||||
# For each relevant path, find contributors
|
||||
git log --since="6 months ago" --format="%an" -- <path> | sort | uniq -c | sort -rn | head -5
|
||||
```
|
||||
|
||||
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
|
||||
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username1 | Authored PR #X which modified this area |
|
||||
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
|
||||
| @username3 | Reviewed similar PR #Y with extensive feedback |
|
||||
|
||||
## Deep code review
|
||||
|
||||
This is the most important part. Don't just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
|
||||
|
||||
Step 1: Understand the intent
|
||||
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
|
||||
|
||||
Step 2: Form your own opinion first
|
||||
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
|
||||
|
||||
Step 3: Compare approaches
|
||||
Now look at their implementation. How does it compare to what you would have done?
|
||||
- Is their approach better in some ways? Note what they did well.
|
||||
- Is their approach missing something? Be specific about what and why.
|
||||
- Are there edge cases they haven't considered?
|
||||
- Does it follow the patterns established in similar parts of the codebase?
|
||||
|
||||
Step 4: Look at the bigger picture
|
||||
- What other files or systems does this change interact with?
|
||||
- Could this break anything else?
|
||||
- Is there additional work needed beyond this PR to complete the feature?
|
||||
- Does this fit well with the overall architecture?
|
||||
|
||||
Step 5: Find reference implementations
|
||||
Look for similar changes in the codebase:
|
||||
```bash
|
||||
git log --oneline --all --grep="<relevant keywords>" | head -20
|
||||
git log --oneline -- <similar files> | head -20
|
||||
```
|
||||
|
||||
If this is adding a new API provider, look at how other providers are implemented.
|
||||
If this is adding a new feature, look at how similar features were added.
|
||||
Note where their implementation aligns with or diverges from established patterns.
|
||||
|
||||
Step 6: Standard code review checks
|
||||
- DRY: Is there duplicated code that could be extracted?
|
||||
- Error handling: Are errors handled appropriately?
|
||||
- Security: Any injection risks, credential exposure, unsafe dependencies?
|
||||
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
|
||||
- Types: Is TypeScript used correctly? Any unsafe type assertions?
|
||||
- Naming: Are variables and functions named clearly?
|
||||
- Comments: Is complex logic explained? Are there outdated comments?
|
||||
|
||||
## Inline code suggestions
|
||||
|
||||
For specific code improvements, use GitHub's suggestion syntax via `gh api`.
|
||||
This creates suggestions the author can commit with one click.
|
||||
|
||||
Single-line suggestion:
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
|
||||
-f event="COMMENT" \
|
||||
-f body="" \
|
||||
-F comments='[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"line": 42,
|
||||
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
Multi-line suggestion (replacing lines 40-45):
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
|
||||
-f event="COMMENT" \
|
||||
-f body="" \
|
||||
-F comments='[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"start_line": 40,
|
||||
"line": 45,
|
||||
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
|
||||
|
||||
## Post your review
|
||||
|
||||
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
|
||||
|
||||
Start with a warm thank you for their contribution. Be conversational, not robotic.
|
||||
|
||||
Include what's relevant:
|
||||
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author's intent, why they made the changes, how they implemented it, and what files/systems are affected. Don't just summarize - explain.
|
||||
- Related issues/PRs you found that provide useful context (link to them)
|
||||
- Your review findings (issues to address, suggestions, etc.)
|
||||
- Clear next steps for the author
|
||||
|
||||
Include a "For Maintainers" section with:
|
||||
- Anything else useful to help the maintainer resolve this PR
|
||||
- Related issues/PRs with context on why they're relevant
|
||||
- Open issues this PR might fix that weren't linked in the description
|
||||
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
|
||||
- SME table - who should review this and why
|
||||
|
||||
For the SME table:
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username | Primary contributor to affected files |
|
||||
|
||||
## Update labels
|
||||
|
||||
Add appropriate labels based on your analysis:
|
||||
```bash
|
||||
gh label list --json name,description --limit 100
|
||||
gh pr edit ${{ steps.pr.outputs.number }} --add-label "label1,label2"
|
||||
```
|
||||
|
||||
When done, add the reviewed label:
|
||||
```bash
|
||||
gh pr edit ${{ steps.pr.outputs.number }} --add-label "Bot Reviewed"
|
||||
```
|
||||
|
||||
## Remember
|
||||
|
||||
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like "let me know if you have questions", "I can help you with", or "feel free to ask" - you won't be there to follow up. Frame any questions for the maintainers who will follow up.
|
||||
- Be helpful and welcoming - Many contributors are new to the project
|
||||
- Be specific - Point to exact lines and suggest fixes, don't give vague feedback
|
||||
- Think deeply - Don't just surface-level review, understand the intent and evaluate the approach
|
||||
- Use inline suggestions - Make it easy for authors to accept changes
|
||||
- You're a first-pass reviewer - A human maintainer will do final approval
|
||||
@@ -8,14 +8,12 @@ tmp
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
.husky/_/
|
||||
|
||||
pnpm-lock.yaml
|
||||
|
||||
.clineignore
|
||||
.venv
|
||||
.actrc
|
||||
CLAUDE.local.md
|
||||
|
||||
webview-ui/src/**/*.js
|
||||
webview-ui/src/**/*.js.map
|
||||
@@ -30,8 +28,6 @@ coverage-unit
|
||||
*evals.env
|
||||
.env
|
||||
|
||||
.worktrees
|
||||
|
||||
## Generated files ##
|
||||
src/generated/
|
||||
src/shared/proto/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
.gitignore
|
||||
@@ -0,0 +1,8 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
webview-ui/node_modules
|
||||
|
||||
# Generated proto files
|
||||
src/generated/
|
||||
src/shared/proto/
|
||||
webview-ui/src/services/grpc-client.ts
|
||||
@@ -1,33 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [3.46.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove GLM 4.6 from free models
|
||||
|
||||
|
||||
## [3.46.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added GLM 4.7 model
|
||||
- Enhanced background terminal execution with command tracking, log file output, zombie process prevention (10-minute timeout), and clickable log paths in UI
|
||||
- Apply Patch tool for GPT-5+ models (replacing current diff edit tools)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Duplicate error messages during streaming for Diff Edit tool when Parallel Tool Calling is not enabled
|
||||
- Banner carousel styling and dismiss functionality
|
||||
- Typos in Gemini system prompt overrides
|
||||
- Model picker favorites ordering, star toggle, and keyboard navigation for OpenRouter and Vercel AI Gateway providers
|
||||
- Fetch remote config values from the cache
|
||||
|
||||
### Refactored
|
||||
|
||||
- Anthropic handler to use metadata for reasoning support
|
||||
- Bedrock provider to use metadata for reasoning support
|
||||
|
||||
## [3.45.1]
|
||||
|
||||
- Fixed MCP settings race condition where toggling auto-approve or changing timeout settings would cause the UI to flash and revert
|
||||
|
||||
@@ -14,10 +14,6 @@ This file is the secret sauce for working effectively in this codebase. It captu
|
||||
|
||||
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
|
||||
|
||||
## Miscellaneous
|
||||
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
|
||||
- When creating PRs, if the change is user-facing and significant enough to warrant a changelog entry, run `npm run changeset` and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.
|
||||
|
||||
## gRPC/Protobuf Communication
|
||||
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 96 KiB |
+1
-3
@@ -278,7 +278,6 @@
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/onboarding",
|
||||
"enterprise-solutions/sso-setup",
|
||||
"enterprise-solutions/team-management/managing-members",
|
||||
{
|
||||
"group": "SaaS Provider Configuration",
|
||||
@@ -318,8 +317,7 @@
|
||||
"pages": [
|
||||
"enterprise-solutions/monitoring/overview",
|
||||
"enterprise-solutions/monitoring/telemetry",
|
||||
"enterprise-solutions/monitoring/opentelemetry",
|
||||
"enterprise-solutions/monitoring/opentelemetry_override"
|
||||
"enterprise-solutions/monitoring/opentelemetry"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ Check which models are available in your region first. Some newer models might n
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/AWS%20Remote%20Config.gif"
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/AWS%20Remote%20Config.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ If you don't have AWS credentials yet, reach out to your IT or cloud team to get
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
|
||||
@@ -42,45 +42,76 @@ Cline supports three OTLP export protocols:
|
||||
- **HTTP/protobuf**
|
||||
- **HTTP/JSON**
|
||||
|
||||
### Export Destinations
|
||||
|
||||
You can export to:
|
||||
- **Console** (for testing)
|
||||
- **OTLP endpoint** (your own collector or observability platform)
|
||||
|
||||
## Configuration
|
||||
|
||||
OpenTelemetry is configured using [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works) from the [dashboard](https://app.cline.bot/dashboard/organization?tab=settings).
|
||||
OpenTelemetry is configured using environment variables before launching Cline.
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Enable OpenTelemetry, configure an OTLP endpoint and select a protocol:
|
||||
Enable OpenTelemetry and configure an OTLP endpoint:
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_main_options.png"
|
||||
/>
|
||||
</Frame>
|
||||
```bash
|
||||
# Enable OpenTelemetry
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
|
||||
If you're using gRPC, you can opt out of TLS.
|
||||
# Configure metrics and logs export
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
|
||||
Once the collector has been configured, you can enable logs and/or metrics collection. At least one of them needs to be enabled.
|
||||
# Set your OTLP endpoint
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
|
||||
|
||||
You only need to configure it further if you need an advanced configuration.
|
||||
# Optional: Set protocol (default is grpc)
|
||||
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`1` or `true`) | Disabled |
|
||||
| `OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
|
||||
| `OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
|
||||
| `OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
You can add custom protocols and endpoints for both, logs and metrics. You can also configure the metrics export interval, and the logs batch size, batch timeout and max queue size.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_metrics_and_logs.png"
|
||||
/>
|
||||
</Frame>
|
||||
**Separate endpoints for metrics and logs:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
|
||||
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
|
||||
```
|
||||
|
||||
**Custom headers for authentication:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
|
||||
```
|
||||
|
||||
Finally, if your collector needs authentication headers, you can add key value pairs in the headers section.
|
||||
**Multiple exporters (console + OTLP):**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console,otlp
|
||||
export OTEL_LOGS_EXPORTER=console,otlp
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_headers.png"
|
||||
/>
|
||||
</Frame>
|
||||
**Export intervals:**
|
||||
```bash
|
||||
# Metrics export interval in milliseconds (default: 60000)
|
||||
export OTEL_METRIC_EXPORT_INTERVAL=30000
|
||||
|
||||
# Logs batch size and timeout
|
||||
export OTEL_LOG_BATCH_SIZE=512
|
||||
export OTEL_LOG_BATCH_TIMEOUT=5000
|
||||
export OTEL_LOG_MAX_QUEUE_SIZE=2048
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
@@ -88,46 +119,73 @@ Finally, if your collector needs authentication headers, you can add key value p
|
||||
|
||||
Export to Datadog using their OTLP endpoint:
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_datadog_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
|
||||
```
|
||||
|
||||
### New Relic
|
||||
|
||||
Export to New Relic:
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_relic_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
|
||||
```
|
||||
|
||||
### Grafana Cloud
|
||||
|
||||
Export to Grafana Cloud:
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_grafana_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
|
||||
```
|
||||
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
To test your configuration, log in to your account, perform some actions in a task, wait for the export interval, and verify that the data has arrived at your collector.
|
||||
Test your configuration with console output before sending to a real endpoint:
|
||||
|
||||
```bash
|
||||
# Enable console output to see what data would be exported
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
Then launch Cline and check the console output for metrics and logs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you aren’t getting any data in your collector, the easiest way to verify your integration is to enable the developer tools in your editor.
|
||||
### No Data Being Exported
|
||||
|
||||
To do this, open the [webview developer tools](https://code.visualstudio.com/api/extension-guides/webview#inspecting-and-debugging-webviews).
|
||||
1. **Verify OpenTelemetry is enabled:**
|
||||
```bash
|
||||
echo $OTEL_TELEMETRY_ENABLED
|
||||
```
|
||||
Should output `1` or `true`
|
||||
|
||||
Once you’ve done so, if you perform some actions that trigger metrics and/or logs (such as doing a task with Cline),
|
||||
you will see error logs if any error occurs when sending the data to your collector.
|
||||
2. **Check exporters are configured:**
|
||||
```bash
|
||||
echo $OTEL_METRICS_EXPORTER
|
||||
echo $OTEL_LOGS_EXPORTER
|
||||
```
|
||||
|
||||
If you don't see any logs, enable [debug mode](#debug-mode).
|
||||
3. **Test with console exporter first:**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
### Connection Errors
|
||||
|
||||
@@ -136,7 +194,10 @@ If you don't see any logs, enable [debug mode](#debug-mode).
|
||||
curl -v https://your-otlp-endpoint:4317
|
||||
```
|
||||
|
||||
2. **Check if insecure mode is needed** by opting out of TLS
|
||||
2. **Check if insecure mode is needed:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_INSECURE=true
|
||||
```
|
||||
|
||||
3. **Verify authentication headers:**
|
||||
Double-check your API keys and authentication headers are correct
|
||||
@@ -146,7 +207,7 @@ If you don't see any logs, enable [debug mode](#debug-mode).
|
||||
Enable debug logging to see detailed OpenTelemetry information:
|
||||
|
||||
```bash
|
||||
TEL_DEBUG_DIAGNOSTICS=true code .
|
||||
export TEL_DEBUG_DIAGNOSTICS=true
|
||||
```
|
||||
|
||||
This will output detailed information about:
|
||||
@@ -157,7 +218,7 @@ This will output detailed information about:
|
||||
|
||||
## What Gets Exported
|
||||
|
||||
When OpenTelemetry is enabled, Cline exports:
|
||||
When Opentelemetry is enabled, Cline exports:
|
||||
|
||||
### Metrics
|
||||
- Feature usage counts
|
||||
@@ -177,9 +238,9 @@ Exported data is already anonymous and doesn't include code content, file paths,
|
||||
## Limitations
|
||||
|
||||
Current OpenTelemetry support in Cline:
|
||||
- ✅ OTLP metrics export (gRPC, HTTP)
|
||||
- ✅ OTLP logs export (gRPC, HTTP)
|
||||
- ✅ Basic configuration via [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works)
|
||||
- ✅ OTLP metrics export (console, gRPC, HTTP)
|
||||
- ✅ OTLP logs export (console, gRPC, HTTP)
|
||||
- ✅ Basic configuration via environment variables
|
||||
- ❌ Distributed tracing (not yet implemented)
|
||||
- ❌ Custom instrumentation API (not yet exposed)
|
||||
- ❌ Sampling configuration (uses defaults)
|
||||
|
||||
@@ -1,266 +0,0 @@
|
||||
---
|
||||
title: "OpenTelemetry Integration Override"
|
||||
sidebarTitle: "OpenTelemetry Override"
|
||||
description: "Export Cline telemetry to your observability platform using OpenTelemetry Protocol (OTLP)"
|
||||
---
|
||||
|
||||
Cline includes opt-in OpenTelemetry support for exporting metrics and logs to your own observability infrastructure using the OpenTelemetry Protocol (OTLP).
|
||||
|
||||
<Note>
|
||||
OpenTelemetry integration is **optional** and intended for advanced users with existing observability infrastructure. Most users won't need this feature.
|
||||
</Note>
|
||||
|
||||
## What is OpenTelemetry?
|
||||
|
||||
[OpenTelemetry](https://opentelemetry.io/) is an industry-standard observability framework that provides a unified way to collect and export telemetry data (metrics, logs, and traces).
|
||||
|
||||
Cline's OpenTelemetry support allows you to:
|
||||
- Export telemetry to your own systems
|
||||
- Integrate with observability platforms like Datadog, New Relic, Grafana Cloud, etc.
|
||||
- Maintain full control over your monitoring data
|
||||
- Use your organization's existing monitoring infrastructure
|
||||
|
||||
## Supported Features
|
||||
|
||||
Cline supports OpenTelemetry's **OTLP (OpenTelemetry Protocol)** export with:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Metrics Export" icon="chart-bar">
|
||||
Export metrics about Cline usage, performance, and errors
|
||||
</Card>
|
||||
|
||||
<Card title="Logs Export" icon="file-lines">
|
||||
Export structured logs for debugging and analysis
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Export Formats
|
||||
|
||||
Cline supports three OTLP export protocols:
|
||||
|
||||
- **gRPC** (default, recommended)
|
||||
- **HTTP/protobuf**
|
||||
- **HTTP/JSON**
|
||||
|
||||
### Export Destinations
|
||||
|
||||
You can export to:
|
||||
- **Console** (for testing)
|
||||
- **OTLP endpoint** (your own collector or observability platform)
|
||||
|
||||
## Configuration
|
||||
|
||||
OpenTelemetry is configured using environment variables before launching Cline.
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Enable OpenTelemetry and configure an OTLP endpoint:
|
||||
|
||||
```bash
|
||||
# Enable OpenTelemetry
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
|
||||
# Configure metrics and logs export
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
|
||||
# Set your OTLP endpoint
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
|
||||
|
||||
# Optional: Set protocol (default is grpc)
|
||||
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`1` or `true`) | Disabled |
|
||||
| `OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
|
||||
| `OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
|
||||
| `OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
**Separate endpoints for metrics and logs:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
|
||||
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
|
||||
```
|
||||
|
||||
**Custom headers for authentication:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
|
||||
```
|
||||
|
||||
**Multiple exporters (console + OTLP):**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console,otlp
|
||||
export OTEL_LOGS_EXPORTER=console,otlp
|
||||
```
|
||||
|
||||
**Export intervals:**
|
||||
```bash
|
||||
# Metrics export interval in milliseconds (default: 60000)
|
||||
export OTEL_METRIC_EXPORT_INTERVAL=30000
|
||||
|
||||
# Logs batch size and timeout
|
||||
export OTEL_LOG_BATCH_SIZE=512
|
||||
export OTEL_LOG_BATCH_TIMEOUT=5000
|
||||
export OTEL_LOG_MAX_QUEUE_SIZE=2048
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Datadog
|
||||
|
||||
Export to Datadog using their OTLP endpoint:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
|
||||
```
|
||||
|
||||
### New Relic
|
||||
|
||||
Export to New Relic:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
|
||||
```
|
||||
|
||||
### Grafana Cloud
|
||||
|
||||
Export to Grafana Cloud:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
|
||||
```
|
||||
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
Test your configuration with console output before sending to a real endpoint:
|
||||
|
||||
```bash
|
||||
# Enable console output to see what data would be exported
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
Then launch Cline and check the console output for metrics and logs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Data Being Exported
|
||||
|
||||
1. **Verify OpenTelemetry is enabled:**
|
||||
```bash
|
||||
echo $OTEL_TELEMETRY_ENABLED
|
||||
```
|
||||
Should output `1` or `true`
|
||||
|
||||
2. **Check exporters are configured:**
|
||||
```bash
|
||||
echo $OTEL_METRICS_EXPORTER
|
||||
echo $OTEL_LOGS_EXPORTER
|
||||
```
|
||||
|
||||
3. **Test with console exporter first:**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
### Connection Errors
|
||||
|
||||
1. **Verify endpoint is accessible:**
|
||||
```bash
|
||||
curl -v https://your-otlp-endpoint:4317
|
||||
```
|
||||
|
||||
2. **Check if insecure mode is needed:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_INSECURE=true
|
||||
```
|
||||
|
||||
3. **Verify authentication headers:**
|
||||
Double-check your API keys and authentication headers are correct
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging to see detailed OpenTelemetry information:
|
||||
|
||||
```bash
|
||||
export TEL_DEBUG_DIAGNOSTICS=true
|
||||
```
|
||||
|
||||
This will output detailed information about:
|
||||
- Configuration being used
|
||||
- Exporters being created
|
||||
- Connection attempts
|
||||
- Export successes/failures
|
||||
|
||||
## What Gets Exported
|
||||
|
||||
When OpenTelemetry is enabled, Cline exports:
|
||||
|
||||
### Metrics
|
||||
- Feature usage counts
|
||||
- Task execution metrics
|
||||
- Error rates and types
|
||||
- Performance measurements
|
||||
|
||||
### Logs
|
||||
- System events
|
||||
- Error logs with context
|
||||
- Operational information
|
||||
|
||||
<Warning>
|
||||
Exported data is already anonymous and doesn't include code content, file paths, or sensitive information. However, you're responsible for securing the data once exported to your systems.
|
||||
</Warning>
|
||||
|
||||
## Limitations
|
||||
|
||||
Current OpenTelemetry support in Cline:
|
||||
- ✅ OTLP metrics export (console, gRPC, HTTP)
|
||||
- ✅ OTLP logs export (console, gRPC, HTTP)
|
||||
- ✅ Basic configuration via environment variables
|
||||
- ❌ Distributed tracing (not yet implemented)
|
||||
- ❌ Custom instrumentation API (not yet exposed)
|
||||
- ❌ Sampling configuration (uses defaults)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Test First**: Always test with console exporter before sending to production
|
||||
2. **Secure Credentials**: Never hardcode API keys; use secure environment variable management
|
||||
3. **Monitor Costs**: Be aware of data ingestion costs with your observability platform
|
||||
4. **Start Simple**: Begin with metrics only, add logs if needed
|
||||
5. **Use Compression**: OTLP supports compression; check if your endpoint requires it
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
|
||||
Configure simple built-in telemetry
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry Docs" icon="book" href="https://opentelemetry.io/docs/">
|
||||
Learn more about OpenTelemetry
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -8,20 +8,14 @@ Cline includes optional monitoring capabilities for organizations that want to t
|
||||
|
||||
## Monitoring Options
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
|
||||
Export metrics and logs to your own observability backends
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry Override" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry_override">
|
||||
Export to your own observability backends through environment variables (advanced)
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
|
||||
Built-in anonymous usage tracking that helps improve Cline (opt-in)
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
|
||||
Export metrics and logs to your own observability backends (advanced)
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Cline Telemetry
|
||||
|
||||
@@ -22,10 +22,6 @@ Your IdP administrator will receive an email with a link to register their organ
|
||||
|
||||
### Step 2: Configure Your Identity Provider
|
||||
|
||||
<Info>
|
||||
For a short overview of where SSO configuration lives (Cline dashboard vs WorkOS vs your IdP), see [SSO Setup](/enterprise-solutions/sso-setup).
|
||||
</Info>
|
||||
|
||||
Connect your identity provider (IdP) to WorkOS:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: "SSO Setup"
|
||||
sidebarTitle: "SSO Setup"
|
||||
description: "Configure Single Sign-On (SSO) for Cline Enterprise via WorkOS AuthKit."
|
||||
---
|
||||
|
||||
## Overview
|
||||
Cline Enterprise integrates with your identity provider (IdP) via **WorkOS AuthKit** for SSO.
|
||||
|
||||
This page describes, at a high level, how SSO is set up for Cline Enterprise using WorkOS AuthKit.
|
||||
|
||||
If you haven’t completed initial onboarding, start with [Onboarding](/enterprise-solutions/onboarding).
|
||||
|
||||
## Where setup happens
|
||||
SSO setup spans two places:
|
||||
|
||||
1) **Cline Dashboard (app.cline.bot)**
|
||||
- Where you sign in and verify SSO works for your organization.
|
||||
|
||||
2) **WorkOS dashboard**
|
||||
- Where your IdP connection is configured (AuthKit → Connections). Your designated admin receives access to this during enterprise onboarding.
|
||||
|
||||
## Using the Cline Dashboard
|
||||
Use the Cline Dashboard at https://app.cline.bot to:
|
||||
- complete sign-in and onboarding flows
|
||||
- verify users can authenticate via SSO
|
||||
|
||||
## Configure your IdP connection in WorkOS
|
||||
|
||||
During enterprise onboarding, your designated admin will receive an invitation email from WorkOS with a link to access your organization's WorkOS dashboard.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/workos-invite-email.png" alt="WorkOS invitation email example" />
|
||||
</Frame>
|
||||
|
||||
To connect your IdP to Cline Enterprise, configure your identity provider in **WorkOS AuthKit**:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
2. Click **Add Connection**
|
||||
3. Select your identity provider (e.g., Okta, Microsoft Entra ID/Azure AD, Google Workspace, Generic SAML/OIDC)
|
||||
4. Follow the provider-specific instructions in WorkOS
|
||||
|
||||
WorkOS’s UI and required fields vary by provider. For details, follow WorkOS documentation:
|
||||
- https://workos.com/docs/authkit/sso
|
||||
|
||||
## Keycloak note (IdP compatibility)
|
||||
Cline Enterprise’s default SSO integration is **via WorkOS**.
|
||||
|
||||
If you use **Keycloak** as your IdP, the supported path is to configure Keycloak in WorkOS as a **Generic SAML** or **Generic OIDC** provider (using the settings WorkOS requests for those provider types).
|
||||
|
||||
## Verification
|
||||
After configuring WorkOS:
|
||||
|
||||
1) Attempt an SSO sign-in from https://app.cline.bot.
|
||||
2) Confirm the sign-in completes (you are redirected back successfully).
|
||||
|
||||
## Troubleshooting
|
||||
- **Redirect URI mismatch**: confirm the redirect/callback URL configured in WorkOS matches what was provided during your Cline Enterprise onboarding.
|
||||
|
||||
For additional troubleshooting guidance, refer to WorkOS documentation:
|
||||
- https://workos.com/docs/authkit/sso
|
||||
@@ -13,6 +13,46 @@ Cline is your AI assistant that can:
|
||||
- Automate repetitive tasks
|
||||
- Integrate with external tools
|
||||
|
||||
## First Steps
|
||||
|
||||
1. **Start a Task**
|
||||
|
||||
- Type your request in the chat
|
||||
- Example: "Create a new React component called Header"
|
||||
|
||||
2. **Provide Context**
|
||||
|
||||
- Use @ mentions to add files, folders, or URLs
|
||||
- Example: "@file:src/components/App.tsx"
|
||||
|
||||
3. **Review Changes**
|
||||
- Cline will show diffs before making changes
|
||||
- You can edit or reject changes
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **File Editing**
|
||||
|
||||
- Create new files
|
||||
- Modify existing code
|
||||
- Search and replace across files
|
||||
|
||||
2. **Terminal Commands**
|
||||
|
||||
- Run npm commands
|
||||
- Start development servers
|
||||
- Install dependencies
|
||||
|
||||
3. **Code Analysis**
|
||||
|
||||
- Find and fix errors
|
||||
- Refactor code
|
||||
- Add documentation
|
||||
|
||||
4. **Browser Integration**
|
||||
- Test web pages
|
||||
- Capture screenshots
|
||||
- Inspect console logs
|
||||
|
||||
## Available Tools
|
||||
|
||||
@@ -44,7 +84,6 @@ Cline has access to the following tools for various tasks:
|
||||
- `ask_followup_question`: Ask user for clarification
|
||||
- `attempt_completion`: Present final results
|
||||
|
||||
|
||||
Each tool has specific parameters and usage patterns. Here are some examples:
|
||||
|
||||
- Create a new file (write_to_file):
|
||||
|
||||
@@ -12,19 +12,6 @@ sidebarTitle: "/deep-planning"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Demo Video
|
||||
|
||||
Watch Deep Planning in action as Cline investigates a codebase, asks clarifying questions, and generates a comprehensive implementation plan:
|
||||
|
||||
<Frame>
|
||||
<video
|
||||
muted
|
||||
controls
|
||||
playsInline
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/Cline-Deep-Planning-Demo.mp4"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
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
|
||||
|
||||
Generated
+1423
-2311
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -13,7 +13,7 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.249"
|
||||
"mintlify": "^4.2.23"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
|
||||
@@ -6,17 +6,12 @@ description: "Complete guide to resolving terminal integration issues in Cline"
|
||||
|
||||
This guide helps you resolve terminal integration issues in Cline. Terminal integration is crucial for Cline to execute commands and read their output, enabling it to understand errors, test results, and command responses.
|
||||
|
||||
## Try This First: Background Execution Mode
|
||||
<Tip>
|
||||
If you're experiencing terminal issues, try switching to a simpler shell like `bash` in the Cline settings, under "Terminal Settings"
|
||||
|
||||
The simplest fix for most terminal issues is switching to **Background Execution Mode**:
|
||||
This resolves most terminal integration problems.
|
||||
|
||||
1. Click **Settings** (top right of Cline chat)
|
||||
2. Go to **Terminal Settings**
|
||||
3. Set **Terminal Execution Mode** → **Background Exec**
|
||||
|
||||
This runs commands in a background process instead of VSCode's terminal, bypassing most integration issues. The guide below is for users who specifically need VSCode's integrated terminal.
|
||||
|
||||
---
|
||||
</Tip>
|
||||
|
||||
## Quick Diagnosis Flowchart
|
||||
|
||||
|
||||
@@ -4,21 +4,7 @@ sidebarTitle: "Terminal Quick Fixes"
|
||||
description: "Quick solutions for common terminal issues"
|
||||
---
|
||||
|
||||
## Try This First: Background Execution Mode
|
||||
|
||||
The simplest fix for most terminal issues is switching to **Background Execution Mode**:
|
||||
|
||||
1. Click **Settings** (top right of Cline chat)
|
||||
2. Go to **Terminal Settings**
|
||||
3. Set **Terminal Execution Mode** → **Background Exec**
|
||||
|
||||
This runs commands in a background process instead of VSCode's terminal, bypassing most integration issues.
|
||||
|
||||
---
|
||||
|
||||
## Other Fixes
|
||||
|
||||
If you need VSCode's integrated terminal, try these:
|
||||
**Here is a list of common fixes, starting with the most applicable:**
|
||||
|
||||
- **Switch to bash** (solves most instances)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
streamlit==1.43.2
|
||||
streamlit>=1.28.0
|
||||
plotly>=5.17.0
|
||||
pandas>=2.0.0
|
||||
numpy>=1.24.0
|
||||
|
||||
Generated
+69
-176
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.46.1",
|
||||
"version": "3.45.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.46.1",
|
||||
"version": "3.45.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.37.0",
|
||||
@@ -20,7 +20,7 @@
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@modelcontextprotocol/sdk": "^1.11.1",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/core": "^2.1.0",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "^0.56.0",
|
||||
@@ -68,7 +68,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"json5": "^2.2.3",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
@@ -1187,6 +1186,7 @@
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -2649,6 +2649,7 @@
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.9.15",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.8",
|
||||
"@types/node": ">=12.12.47"
|
||||
@@ -2684,18 +2685,6 @@
|
||||
"@grpc/grpc-js": "^1.8.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.19.7",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz",
|
||||
"integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.14.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"hono": "^4"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/external-editor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
|
||||
@@ -3228,12 +3217,12 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.25.1",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.1.tgz",
|
||||
"integrity": "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ==",
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz",
|
||||
"integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.19.7",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"content-type": "^1.0.5",
|
||||
@@ -3243,26 +3232,20 @@
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"jose": "^6.1.1",
|
||||
"json-schema-typed": "^8.0.2",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
"raw-body": "^3.0.0",
|
||||
"zod": "^3.25 || ^4.0",
|
||||
"zod-to-json-schema": "^3.25.0"
|
||||
"zod": "^3.23.8",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cfworker/json-schema": "^4.1.1",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
"@cfworker/json-schema": "^4.1.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@cfworker/json-schema": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3300,6 +3283,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -4915,8 +4899,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4929,8 +4912,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4943,8 +4925,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4957,8 +4938,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4971,8 +4951,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.4",
|
||||
@@ -4985,8 +4964,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.4",
|
||||
@@ -4999,8 +4977,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.4",
|
||||
@@ -5013,8 +4990,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5027,8 +5003,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5041,8 +5016,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5055,8 +5029,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5069,8 +5042,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5083,8 +5055,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5097,8 +5068,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5111,8 +5081,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5125,8 +5094,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.4",
|
||||
@@ -5139,8 +5107,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.4",
|
||||
@@ -5153,8 +5120,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5167,8 +5133,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5181,8 +5146,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.4",
|
||||
@@ -5195,8 +5159,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.4",
|
||||
@@ -5209,8 +5172,7 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
]
|
||||
},
|
||||
"node_modules/@sap-ai-sdk/ai-api": {
|
||||
"version": "2.1.0",
|
||||
@@ -6467,60 +6429,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.5.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.5.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.0.5",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz",
|
||||
@@ -6763,8 +6671,7 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/get-folder-size": {
|
||||
"version": "3.0.4",
|
||||
@@ -6789,6 +6696,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz",
|
||||
"integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -7486,6 +7394,7 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -8252,6 +8161,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"caniuse-lite": "^1.0.30001741",
|
||||
@@ -9455,7 +9365,8 @@
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1342118",
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.2.0",
|
||||
@@ -11440,16 +11351,6 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.1.tgz",
|
||||
"integrity": "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"dev": true,
|
||||
@@ -12431,19 +12332,11 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
|
||||
"integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
@@ -12504,16 +12397,9 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-typed": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
|
||||
"integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -12533,12 +12419,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
|
||||
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
|
||||
"version": "9.0.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^4.0.1",
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
@@ -12554,6 +12438,23 @@
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken/node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jszip": {
|
||||
"version": "3.10.1",
|
||||
"license": "(MIT OR GPL-3.0-or-later)",
|
||||
@@ -12581,12 +12482,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
|
||||
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^2.0.1",
|
||||
"jwa": "^2.0.0",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
@@ -12662,6 +12561,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
@@ -15542,7 +15442,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -15563,7 +15462,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -16231,7 +16129,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
|
||||
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -17668,7 +17565,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.3"
|
||||
@@ -17685,7 +17581,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
@@ -18049,6 +17944,7 @@
|
||||
"version": "5.5.3",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -18308,7 +18204,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -18383,7 +18278,6 @@
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
@@ -19079,17 +18973,16 @@
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zod-to-json-schema": {
|
||||
"version": "3.25.0",
|
||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz",
|
||||
"integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==",
|
||||
"version": "3.24.4",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25 || ^4"
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-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.46.1",
|
||||
"version": "3.45.1",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
@@ -463,7 +463,7 @@
|
||||
"@grpc/grpc-js": "^1.9.15",
|
||||
"@grpc/reflection": "^1.0.4",
|
||||
"@mistralai/mistralai": "^1.5.0",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@modelcontextprotocol/sdk": "^1.11.1",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/core": "^2.1.0",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "^0.56.0",
|
||||
@@ -511,7 +511,6 @@
|
||||
"image-size": "^2.0.2",
|
||||
"isbinaryfile": "^5.0.2",
|
||||
"jschardet": "^3.1.4",
|
||||
"json5": "^2.2.3",
|
||||
"mammoth": "^1.11.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"nice-grpc": "^2.1.12",
|
||||
|
||||
@@ -19,8 +19,6 @@ service AccountService {
|
||||
// Clears API keys and user state.
|
||||
rpc accountLogoutClicked(EmptyRequest) returns (Empty);
|
||||
|
||||
rpc fetchAuth(EmptyRequest) returns (Empty);
|
||||
|
||||
// Subscribe to auth status update events (when authentication state changes)
|
||||
rpc subscribeToAuthStatusUpdate(EmptyRequest) returns (stream AuthState);
|
||||
|
||||
@@ -55,10 +53,6 @@ message AuthStateChangedRequest {
|
||||
|
||||
message AuthState {
|
||||
optional UserInfo user = 1;
|
||||
bool has_session_data = 2;
|
||||
bool pending = 3;
|
||||
optional int64 next_retry_at = 4;
|
||||
optional string error = 5;
|
||||
}
|
||||
|
||||
// User's information
|
||||
|
||||
+4
-2
@@ -104,15 +104,17 @@ async function showVersionUpdateAnnouncement(context: vscode.ExtensionContext) {
|
||||
if (!previousVersion || currentVersion !== previousVersion) {
|
||||
Logger.log(`Cline version changed: ${previousVersion} -> ${currentVersion}. First run or update detected.`)
|
||||
|
||||
// Check if there's a new announcement to show
|
||||
// Use the same condition as announcements: focus when there's a new announcement to show
|
||||
const lastShownAnnouncementId = context.globalState.get<string>("lastShownAnnouncementId")
|
||||
const latestAnnouncementId = getLatestAnnouncementId()
|
||||
|
||||
if (lastShownAnnouncementId !== latestAnnouncementId) {
|
||||
// Show notification when there's a new announcement (major/minor updates or fresh installs)
|
||||
// Focus Cline when there's a new announcement to show (major/minor updates or fresh installs)
|
||||
const message = previousVersion
|
||||
? `Cline has been updated to v${currentVersion}`
|
||||
: `Welcome to Cline v${currentVersion}`
|
||||
await HostProvider.workspace.openClineSidebarPanel({})
|
||||
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.INFORMATION,
|
||||
message,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ModelInfo } from "@shared/api"
|
||||
import OpenAI from "openai"
|
||||
import { ApiHandler, CommonApiHandlerOptions } from "../index"
|
||||
import { withRetry } from "../retry"
|
||||
import { sanitizeAnthropicMessages } from "../transform/anthropic-format"
|
||||
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
@@ -145,15 +144,12 @@ export class AIhubmixHandler implements ApiHandler {
|
||||
const client = this.ensureAnthropicClient()
|
||||
const modelId = this.options.modelId || "claude-3-5-sonnet-20241022"
|
||||
|
||||
// Sanitize messages to remove Cline-specific fields like call_id that are not allowed by Anthropic API
|
||||
const sanitizedMessages = sanitizeAnthropicMessages(messages, false)
|
||||
|
||||
const stream = await client.messages.create({
|
||||
model: modelId,
|
||||
temperature: 0,
|
||||
max_tokens: this.options.modelInfo?.maxTokens || 8192,
|
||||
system: [{ text: systemPrompt, type: "text" }],
|
||||
messages: sanitizedMessages,
|
||||
messages,
|
||||
stream: true,
|
||||
})
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@ interface CerebrasHandlerOptions extends CommonApiHandlerOptions {
|
||||
apiModelId?: string
|
||||
}
|
||||
|
||||
// Conservative max_tokens for Cerebras to avoid premature rate limiting.
|
||||
// Cerebras rate limiter estimates token consumption using max_completion_tokens upfront,
|
||||
// so requesting the model maximum (e.g., 64K) reserves that quota even if actual usage is low.
|
||||
// 16K is sufficient for most agentic tool use while preserving rate limit headroom.
|
||||
const CEREBRAS_DEFAULT_MAX_TOKENS = 16_384
|
||||
|
||||
export class CerebrasHandler implements ApiHandler {
|
||||
private options: CerebrasHandlerOptions
|
||||
private client: Cerebras | undefined
|
||||
@@ -117,7 +111,7 @@ export class CerebrasHandler implements ApiHandler {
|
||||
messages: cerebrasMessages,
|
||||
temperature: 0,
|
||||
stream: true,
|
||||
max_tokens: CEREBRAS_DEFAULT_MAX_TOKENS,
|
||||
max_tokens: this.getModel().info.maxTokens,
|
||||
})
|
||||
|
||||
// Handle streaming response
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { ChatMessage, OrchestrationClient, OrchestrationModuleConfig } from "@sap-ai-sdk/orchestration"
|
||||
import { ModelInfo, SapAiCoreModelId, sapAiCoreDefaultModelId, sapAiCoreModels } from "@shared/api"
|
||||
import axios from "axios"
|
||||
import JSON5 from "json5"
|
||||
import OpenAI from "openai"
|
||||
import { ClineStorageMessage } from "@/shared/messages/content"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
@@ -893,8 +892,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
|
||||
try {
|
||||
// Parse the incoming JSON data from the stream
|
||||
// Using JSON5 to handle relaxed JSON syntax (e.g., single quotes)
|
||||
const data = JSON5.parse(jsonData)
|
||||
const data = JSON.parse(jsonData)
|
||||
|
||||
// Handle metadata (token usage)
|
||||
if (data.metadata?.usage) {
|
||||
|
||||
@@ -48,11 +48,7 @@ export function convertAnthropicContentToGemini(content: string | ClineStorageMe
|
||||
},
|
||||
}
|
||||
case "thinking":
|
||||
return {
|
||||
text: block.thinking,
|
||||
thought: true,
|
||||
thoughtSignature: block.signature || GEMINI_DUMMY_THOUGHT_SIGNATURE,
|
||||
}
|
||||
return { text: block.thinking, thought: true, thoughtSignature: block.signature }
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -176,18 +176,11 @@ export function convertToOpenAiMessages(
|
||||
// Process tool use messages
|
||||
const tool_calls: OpenAI.Chat.ChatCompletionMessageToolCall[] = toolMessages.map((toolMessage) => {
|
||||
const toolDetails = toolMessage.reasoning_details
|
||||
if (toolDetails) {
|
||||
if (toolDetails?.length) {
|
||||
if (Array.isArray(toolDetails)) {
|
||||
// For Gemini: reasoning details must be linkable back to the tool call.
|
||||
// Sometimes OpenRouter/Gemini returns entries without `id`; those poison the next request.
|
||||
// Keep only entries with an id matching the tool call id.
|
||||
// See: https://github.com/cline/cline/issues/8214
|
||||
const validDetails = toolDetails.filter((detail: any) => detail?.id === toolMessage.id)
|
||||
if (validDetails.length > 0) reasoningDetails.push(...validDetails)
|
||||
reasoningDetails.push(...toolDetails)
|
||||
} else {
|
||||
// Single reasoning detail - only include if it has matching id
|
||||
const detail = toolDetails as any
|
||||
if (detail?.id === toolMessage.id) reasoningDetails.push(toolDetails)
|
||||
reasoningDetails.push(toolDetails)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,17 +200,13 @@ export function convertToOpenAiMessages(
|
||||
const hasMeaningfulContent = content !== undefined && content.trim() !== ""
|
||||
const finalContent = hasMeaningfulContent ? content : hasToolCalls ? null : undefined
|
||||
|
||||
const consolidatedReasoningDetails =
|
||||
reasoningDetails.length > 0 ? consolidateReasoningDetails(reasoningDetails as any) : []
|
||||
|
||||
openAiMessages.push({
|
||||
role: "assistant",
|
||||
content: finalContent,
|
||||
// Cannot be an empty array. API expects an array with minimum length 1, and will respond with an error if it's empty
|
||||
tool_calls: tool_calls?.length > 0 ? tool_calls : undefined,
|
||||
// Only include reasoning_details when non-empty; sending [] can trigger provider validation issues.
|
||||
// @ts-ignore-next-line
|
||||
reasoning_details: consolidatedReasoningDetails.length > 0 ? consolidatedReasoningDetails : undefined,
|
||||
reasoning_details: reasoningDetails.length > 0 ? consolidateReasoningDetails(reasoningDetails) : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -256,11 +245,6 @@ function consolidateReasoningDetails(reasoningDetails: ReasoningDetail[]): Reaso
|
||||
const groupedByIndex = new Map<number, ReasoningDetail[]>()
|
||||
|
||||
for (const detail of reasoningDetails) {
|
||||
// Drop corrupted encrypted reasoning blocks that would otherwise trigger:
|
||||
// "Invalid input: expected string, received undefined" for reasoning_details.*.data
|
||||
// See: https://github.com/cline/cline/issues/8214
|
||||
if (detail.type === "reasoning.encrypted" && !detail.data) continue
|
||||
|
||||
const index = detail.index ?? 0
|
||||
if (!groupedByIndex.has(index)) {
|
||||
groupedByIndex.set(index, [])
|
||||
|
||||
@@ -36,46 +36,6 @@ export async function createOpenRouterStream(
|
||||
model.id = model.id.slice(0, -CLAUDE_SONNET_1M_SUFFIX.length)
|
||||
}
|
||||
|
||||
// Gemini models require thought signatures for tool calls. When switching providers mid-conversation,
|
||||
// historical tool calls may not include Gemini/OpenRouter reasoning details, which can poison the next request.
|
||||
// Bandaid: for Gemini only, drop tool_calls that lack reasoning_details and their paired tool messages.
|
||||
if (model.id.includes("gemini")) {
|
||||
const droppedToolCallIds = new Set<string>()
|
||||
const sanitized: OpenAI.Chat.ChatCompletionMessageParam[] = []
|
||||
|
||||
for (const msg of openAiMessages) {
|
||||
if (msg.role === "assistant") {
|
||||
const anyMsg = msg as any
|
||||
const toolCalls = anyMsg.tool_calls
|
||||
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
||||
const reasoningDetails = anyMsg.reasoning_details
|
||||
const hasReasoningDetails = Array.isArray(reasoningDetails) && reasoningDetails.length > 0
|
||||
if (!hasReasoningDetails) {
|
||||
for (const tc of toolCalls) {
|
||||
if (tc?.id) droppedToolCallIds.add(tc.id)
|
||||
}
|
||||
// Keep any textual content, but drop the tool_calls themselves.
|
||||
if (anyMsg.content) {
|
||||
sanitized.push({ role: "assistant", content: anyMsg.content } as any)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.role === "tool") {
|
||||
const anyMsg = msg as any
|
||||
if (anyMsg.tool_call_id && droppedToolCallIds.has(anyMsg.tool_call_id)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sanitized.push(msg)
|
||||
}
|
||||
|
||||
openAiMessages = sanitized
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -105,9 +65,6 @@ export async function createOpenRouterStream(
|
||||
case "anthropic/claude-3-haiku:beta":
|
||||
case "anthropic/claude-3-opus":
|
||||
case "anthropic/claude-3-opus:beta":
|
||||
case "minimax/minimax-m2":
|
||||
case "minimax/minimax-m2.1":
|
||||
case "minimax/minimax-m2.1-lightning":
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: [
|
||||
|
||||
@@ -31,9 +31,8 @@ export async function createVercelAIGatewayStream(
|
||||
}
|
||||
|
||||
const isAnthropicModel = model.id.startsWith("anthropic/")
|
||||
const isMinimaxModel = model.id.startsWith("minimax/")
|
||||
|
||||
if (isAnthropicModel || isMinimaxModel) {
|
||||
if (isAnthropicModel) {
|
||||
openAiMessages[0] = {
|
||||
role: "system",
|
||||
content: systemPrompt,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { EmptyRequest } from "@shared/proto/cline/common"
|
||||
import { Empty } from "@shared/proto/cline/common"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
* Handles triggering restoring the auth data
|
||||
* @param controller The controller instance
|
||||
* @param _request The empty request object
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function fetchAuth(_: Controller, _request: EmptyRequest): Promise<Empty> {
|
||||
await AuthService.getInstance().restoreRefreshTokenAndRetrieveAuthInfo()
|
||||
return Empty.create({})
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import fs from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { ensureCacheDirectoryExists, GlobalFileNames } from "@core/storage/disk"
|
||||
import { ANTHROPIC_MAX_THINKING_BUDGET, ModelInfo } from "@shared/api"
|
||||
import { ModelInfo } from "@shared/api"
|
||||
import { fileExistsAtPath } from "@utils/fs"
|
||||
import { parsePrice } from "@utils/model-utils"
|
||||
import axios from "axios"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { getAxiosSettings } from "@/shared/net"
|
||||
import { basetenModels } from "../../../shared/api"
|
||||
import { Controller } from ".."
|
||||
@@ -22,7 +22,22 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
|
||||
const models: Record<string, Partial<ModelInfo> & { supportedFeatures?: string[] }> = {}
|
||||
try {
|
||||
if (basetenApiKey) {
|
||||
if (!basetenApiKey) {
|
||||
// Don't throw an error, just use static models, althought this might be slightly out of date
|
||||
for (const [modelId, modelInfo] of Object.entries(basetenModels)) {
|
||||
models[modelId] = {
|
||||
maxTokens: modelInfo.maxTokens,
|
||||
contextWindow: modelInfo.contextWindow,
|
||||
supportsImages: modelInfo.supportsImages,
|
||||
supportsPromptCache: modelInfo.supportsPromptCache,
|
||||
inputPrice: modelInfo.inputPrice,
|
||||
outputPrice: modelInfo.outputPrice,
|
||||
cacheWritesPrice: (modelInfo as any).cacheWritesPrice || 0,
|
||||
cacheReadsPrice: (modelInfo as any).cacheReadsPrice || 0,
|
||||
description: (modelInfo as any).description || `${modelId} model`,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ensure the API key is properly formatted
|
||||
const cleanApiKey = basetenApiKey.trim()
|
||||
if (!cleanApiKey) {
|
||||
@@ -39,9 +54,9 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
...getAxiosSettings(),
|
||||
})
|
||||
|
||||
const rawModels = response?.data?.data
|
||||
if (response.data?.data) {
|
||||
const rawModels = response.data.data
|
||||
|
||||
if (rawModels && Array.isArray(rawModels)) {
|
||||
for (const rawModel of rawModels) {
|
||||
// Filter out non-chat models and validate model capabilities
|
||||
if (!isValidChatModel(rawModel)) {
|
||||
@@ -50,9 +65,6 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
|
||||
// Check if we have static pricing information for this model
|
||||
const staticModelInfo = basetenModels[rawModel.id as keyof typeof basetenModels]
|
||||
const supportThinking = rawModel?.supported_features?.some(
|
||||
(p: string) => p === "reasoning_effort" || p === "reasoning",
|
||||
)
|
||||
|
||||
const modelInfo: Partial<ModelInfo> & { supportedFeatures?: string[] } = {
|
||||
maxTokens: rawModel.max_completion_tokens || staticModelInfo?.maxTokens,
|
||||
@@ -65,23 +77,15 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
cacheReadsPrice: staticModelInfo?.cacheReadsPrice || 0,
|
||||
description: generateModelDescription(rawModel, staticModelInfo),
|
||||
supportedFeatures: rawModel.supported_features || [],
|
||||
supportsReasoning: supportThinking || false,
|
||||
// If thinking is supported, set maxBudget with a default value as a placeholder
|
||||
// to ensure it has a valid thinkingConfig that lets the application know thinking is supported.
|
||||
thinkingConfig: supportThinking ? { maxBudget: ANTHROPIC_MAX_THINKING_BUDGET } : undefined,
|
||||
}
|
||||
|
||||
models[rawModel.id] = modelInfo
|
||||
}
|
||||
} else {
|
||||
console.error("Invalid response from Baseten API")
|
||||
}
|
||||
// Cache the fetched models to disk
|
||||
await fs.writeFile(basetenModelsFilePath, JSON.stringify(models))
|
||||
}
|
||||
|
||||
// If no API key is set or models is empty, throw an error to trigger fallback
|
||||
if (Object.keys(models).length === 0) {
|
||||
throw new Error("No Baseten API key set or no models fetched")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching Baseten models:", error)
|
||||
|
||||
@@ -125,8 +129,6 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
cacheWritesPrice: (modelInfo as any).cacheWritesPrice || 0,
|
||||
cacheReadsPrice: (modelInfo as any).cacheReadsPrice || 0,
|
||||
description: (modelInfo as any).description || `${modelId} model`,
|
||||
supportsReasoning: modelInfo.supportsReasoning || false,
|
||||
thinkingConfig: modelInfo.supportsReasoning ? { maxBudget: ANTHROPIC_MAX_THINKING_BUDGET } : undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,8 +149,6 @@ export async function refreshBasetenModels(controller: Controller): Promise<Reco
|
||||
cacheReadsPrice: model.cacheReadsPrice ?? 0,
|
||||
description: model.description ?? "",
|
||||
tiers: model.tiers,
|
||||
supportsReasoning: model.supportsReasoning || false,
|
||||
thinkingConfig: model.supportsReasoning ? { maxBudget: ANTHROPIC_MAX_THINKING_BUDGET } : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { buildApiHandler } from "@core/api"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { formatContentBlockToMarkdown } from "@/integrations/misc/export-markdown"
|
||||
import { ApiConfiguration } from "@/shared/api"
|
||||
@@ -444,29 +443,9 @@ const BINARY_EXTENSIONS = new Set([
|
||||
])
|
||||
|
||||
/**
|
||||
* Check if a file is binary based on its extension or content.
|
||||
* @param filePath - Absolute path to the file to check
|
||||
* @returns Promise<boolean> - true if the file is binary, false if text or if detection fails
|
||||
* Check if a file is binary based on its extension
|
||||
*/
|
||||
export async function detectBinaryFile(filePath: string): Promise<boolean> {
|
||||
const lastDotIndex = filePath.lastIndexOf(".")
|
||||
const lastSlashIndex = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"))
|
||||
const ext = lastDotIndex > lastSlashIndex ? filePath.substring(lastDotIndex).toLowerCase() : ""
|
||||
const isDotfile = lastDotIndex !== -1 && lastDotIndex === lastSlashIndex + 1
|
||||
|
||||
// Legacy/fast method: Check known binary extensions
|
||||
if (ext && BINARY_EXTENSIONS.has(ext)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Use actual binary check for dotfiles or files without extensions. Returns true if file is binary.
|
||||
if (!ext || isDotfile) {
|
||||
try {
|
||||
const result = await isBinaryFile(filePath)
|
||||
return result
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
export function isBinaryFile(filePath: string): boolean {
|
||||
const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase()
|
||||
return BINARY_EXTENSIONS.has(ext)
|
||||
}
|
||||
|
||||
+2
-3
@@ -2,7 +2,7 @@ You are Cline, a highly skilled software engineer with extensive knowledge in ma
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
====
|
||||
|
||||
@@ -68,8 +68,7 @@ When user is providing you with feedback on how you could improve, you can let t
|
||||
|
||||
RULES
|
||||
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from. 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.
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from.
|
||||
|
||||
====
|
||||
|
||||
|
||||
+1
-2
@@ -2,7 +2,7 @@ You are Cline, a highly skilled software engineer with extensive knowledge in ma
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
====
|
||||
|
||||
@@ -67,7 +67,6 @@ When user is providing you with feedback on how you could improve, you can let t
|
||||
RULES
|
||||
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from.
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.
|
||||
|
||||
====
|
||||
|
||||
|
||||
+2
-3
@@ -2,7 +2,7 @@ You are Cline, a highly skilled software engineer with extensive knowledge in ma
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
====
|
||||
|
||||
@@ -42,8 +42,7 @@ CAPABILITIES
|
||||
|
||||
RULES
|
||||
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from. 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.
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from.
|
||||
|
||||
====
|
||||
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ You are Cline, a highly skilled software engineer with extensive knowledge in ma
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
====
|
||||
|
||||
@@ -68,7 +68,7 @@ When user is providing you with feedback on how you could improve, you can let t
|
||||
|
||||
RULES
|
||||
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from. 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.
|
||||
- The current working directory is `/test/project` - this is the directory where all the tools will be executed from.
|
||||
|
||||
====
|
||||
|
||||
|
||||
@@ -608,6 +608,7 @@ 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.
|
||||
- You may use multiple tools in a single response when the operations are independent (e.g., reading several files, creating independent files). For dependent operations where one result informs the next, use tools sequentially and wait for the user's response. 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.
|
||||
|
||||
====
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ You are Cline, a software engineering AI. Your mission is to execute precisely w
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You should use a single tool at a time and wait for the result before proceeding. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
When using tools, proceed directly with tool calls. Save explanations for the attempt_completion summary. Both attempt_completion and plan_mode_respond display to the user as assistant messages, so include your message content within the tool call itself rather than duplicating it outside.
|
||||
|
||||
@@ -240,7 +240,7 @@ 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 as necessary. Use a single tool at a time and wait for the result before proceeding. 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.
|
||||
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. 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. If one of the values for a required parameter is missing, ask the user to provide the missing parameters using the ask_followup_question tool (use your tools to gather information when possible to avoid unnecessary questions). Focus on required parameters only - proceed with defaults for optional parameters.
|
||||
4. Once you've completed the user's task, use the attempt_completion tool to present the result. Provide a CLI command to showcase your work when applicable (e.g., `open index.html` for web development). Before calling attempt_completion, verify with the user that the feature works as expected.
|
||||
5. For non-actionable tasks, use attempt_completion to provide a clear explanation or direct answer.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ You are Cline, a software engineering AI. Your mission is to execute precisely w
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You should use a single tool at a time and wait for the result before proceeding. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
When using tools, proceed directly with tool calls. Save explanations for the attempt_completion summary. Both attempt_completion and plan_mode_respond display to the user as assistant messages, so include your message content within the tool call itself rather than duplicating it outside.
|
||||
|
||||
@@ -238,7 +238,7 @@ 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 as necessary. Use a single tool at a time and wait for the result before proceeding. 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.
|
||||
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. 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. If one of the values for a required parameter is missing, ask the user to provide the missing parameters using the ask_followup_question tool (use your tools to gather information when possible to avoid unnecessary questions). Focus on required parameters only - proceed with defaults for optional parameters.
|
||||
4. Once you've completed the user's task, use the attempt_completion tool to present the result. Provide a CLI command to showcase your work when applicable (e.g., `open index.html` for web development). Before calling attempt_completion, verify with the user that the feature works as expected.
|
||||
5. For non-actionable tasks, use attempt_completion to provide a clear explanation or direct answer.
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ You are Cline, a software engineering AI. Your mission is to execute precisely w
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You should use a single tool at a time and wait for the result before proceeding. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
When using tools, proceed directly with tool calls. Save explanations for the attempt_completion summary. Both attempt_completion and plan_mode_respond display to the user as assistant messages, so include your message content within the tool call itself rather than duplicating it outside.
|
||||
|
||||
@@ -218,7 +218,7 @@ 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 as necessary. Use a single tool at a time and wait for the result before proceeding. 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.
|
||||
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. 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. If one of the values for a required parameter is missing, ask the user to provide the missing parameters using the ask_followup_question tool (use your tools to gather information when possible to avoid unnecessary questions). Focus on required parameters only - proceed with defaults for optional parameters.
|
||||
4. Once you've completed the user's task, use the attempt_completion tool to present the result. Provide a CLI command to showcase your work when applicable (e.g., `open index.html` for web development). Before calling attempt_completion, verify with the user that the feature works as expected.
|
||||
5. For non-actionable tasks, use attempt_completion to provide a clear explanation or direct answer.
|
||||
|
||||
@@ -2,7 +2,7 @@ You are Cline, a software engineering AI. Your mission is to execute precisely w
|
||||
|
||||
TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You should use a single tool at a time and wait for the result before proceeding. You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
When using tools, proceed directly with tool calls. Save explanations for the attempt_completion summary. Both attempt_completion and plan_mode_respond display to the user as assistant messages, so include your message content within the tool call itself rather than duplicating it outside.
|
||||
|
||||
@@ -240,7 +240,7 @@ 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 as necessary. Use a single tool at a time and wait for the result before proceeding. 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.
|
||||
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. 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. If one of the values for a required parameter is missing, ask the user to provide the missing parameters using the ask_followup_question tool (use your tools to gather information when possible to avoid unnecessary questions). Focus on required parameters only - proceed with defaults for optional parameters.
|
||||
4. Once you've completed the user's task, use the attempt_completion tool to present the result. Provide a CLI command to showcase your work when applicable (e.g., `open index.html` for web development). Before calling attempt_completion, verify with the user that the feature works as expected.
|
||||
5. For non-actionable tasks, use attempt_completion to provide a clear explanation or direct answer.
|
||||
|
||||
@@ -3,23 +3,6 @@ import { SystemPromptSection } from "../templates/placeholders"
|
||||
import { TemplateEngine } from "../templates/TemplateEngine"
|
||||
import type { PromptVariant, SystemPromptContext } from "../types"
|
||||
|
||||
/**
|
||||
* Checks if there are any enabled MCP servers in the context.
|
||||
* This is a utility function to standardize MCP server detection across all prompt variants.
|
||||
*
|
||||
* @param context - The system prompt context
|
||||
* @returns true if there are enabled MCP servers, false otherwise
|
||||
*
|
||||
* @example
|
||||
* const hasMcp = hasEnabledMcpServers(context)
|
||||
* if (hasMcp) {
|
||||
* // Include MCP-specific instructions
|
||||
* }
|
||||
*/
|
||||
export function hasEnabledMcpServers(context: SystemPromptContext): boolean {
|
||||
return (context.mcpHub?.getServers() || []).length > 0
|
||||
}
|
||||
|
||||
const MCP_TEMPLATE_TEXT = `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.
|
||||
|
||||
@@ -14,24 +14,6 @@ Default Shell: {{shell}}
|
||||
Home Directory: {{homeDir}}
|
||||
{{WORKSPACE_TITLE}}: {{workingDir}}`
|
||||
|
||||
/**
|
||||
* Get the shell that will actually be used for command execution.
|
||||
* When using background exec mode, commands run in the system default shell
|
||||
* (cmd.exe on Windows, /bin/bash on Unix), not the VS Code configured shell.
|
||||
*/
|
||||
function getEffectiveShell(context: SystemPromptContext): string {
|
||||
if (context.terminalExecutionMode === "backgroundExec") {
|
||||
// Background exec uses the system default shell, not VS Code config
|
||||
if (process.platform === "win32") {
|
||||
return process.env.COMSPEC || "cmd.exe"
|
||||
} else {
|
||||
return process.env.SHELL || "/bin/bash"
|
||||
}
|
||||
}
|
||||
// VS Code terminal mode (or undefined) uses the VS Code configured shell
|
||||
return getShell()
|
||||
}
|
||||
|
||||
export async function getSystemEnv(context: SystemPromptContext, isTesting = false) {
|
||||
const currentWorkDir = context.cwd || process.cwd()
|
||||
const workspaces = (await getWorkspacePaths({}))?.paths || [currentWorkDir]
|
||||
@@ -48,7 +30,7 @@ export async function getSystemEnv(context: SystemPromptContext, isTesting = fal
|
||||
: {
|
||||
os: osName(),
|
||||
ide: context.ide,
|
||||
shell: getEffectiveShell(context),
|
||||
shell: getShell(),
|
||||
homeDir: osModule.homedir(),
|
||||
workingDir: currentWorkDir,
|
||||
workspaces: workspaces,
|
||||
|
||||
@@ -115,8 +115,6 @@ export interface SystemPromptContext {
|
||||
readonly isSubagentsEnabledAndCliInstalled?: boolean
|
||||
readonly isCliSubagent?: boolean
|
||||
readonly enableNativeToolCalls?: boolean
|
||||
readonly enableParallelToolCalling?: boolean
|
||||
readonly terminalExecutionMode?: "vscodeTerminal" | "backgroundExec"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { PromptVariant, SystemPromptContext } from "../../types"
|
||||
const GEMINI_3_AGENT_ROLE_TEMPLATE = (_context: SystemPromptContext) =>
|
||||
`You are Cline, a software engineering AI. Your mission is to execute precisely what is requested - implement exactly what was asked for, with the simplest solution that fulfills all requirements. Ask clarifying questions to ensure you understand the user's requirements and that they understand your approach before proceeding.`
|
||||
|
||||
const GEMINI_3_TOOL_USE_TEMPLATE = (context: SystemPromptContext) => `TOOL USE
|
||||
const GEMINI_3_TOOL_USE_TEMPLATE = (_context: SystemPromptContext) => `TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval.${context.enableParallelToolCalling ? " You may use multiple tools in a single response when the operations are independent (e.g., reading several files, searching in parallel). For dependent operations where one result informs the next, use tools sequentially." : " You should use a single tool at a time and wait for the result before proceeding."} You will receive the results of all tool uses in the user's response.
|
||||
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.
|
||||
|
||||
When using tools, proceed directly with tool calls. Save explanations for the attempt_completion summary. Both attempt_completion and plan_mode_respond display to the user as assistant messages, so include your message content within the tool call itself rather than duplicating it outside.`
|
||||
|
||||
@@ -15,7 +15,7 @@ const GEMINI_3_OBJECTIVE_TEMPLATE = (context: SystemPromptContext) => `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 as necessary. ${context.enableParallelToolCalling ? "You may call multiple independent tools in a single response to work efficiently." : "Use a single tool at a time and wait for the result before proceeding."} 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.
|
||||
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. 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.${context.yoloModeToggled !== true ? " If one of the values for a required parameter is missing, ask the user to provide the missing parameters using the ask_followup_question tool (use your tools to gather information when possible to avoid unnecessary questions)." : ""} Focus on required parameters only - proceed with defaults for optional parameters.
|
||||
4. Once you've completed the user's task, use the attempt_completion tool to present the result. Provide a CLI command to showcase your work when applicable (e.g., \`open index.html\` for web development).${context.yoloModeToggled !== true ? " Before calling attempt_completion, verify with the user that the feature works as expected." : ""}
|
||||
5. For non-actionable tasks, use attempt_completion to provide a clear explanation or direct answer.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { hasEnabledMcpServers } from "../../components/mcp"
|
||||
import { SystemPromptSection } from "../../templates/placeholders"
|
||||
import type { SystemPromptContext } from "../../types"
|
||||
|
||||
const GLM_TOOL_USE_TEMPLATE = (context: SystemPromptContext) => {
|
||||
const hasMcpServers = hasEnabledMcpServers(context)
|
||||
const hasMcpServers = (context.mcpHub?.getServers() || []).length > 0
|
||||
|
||||
return `Begin every task by exploring the codebase (e.g., list_files, search_files, read_file) and outlining the required changes. Do not implement until exploration yields enough context to state objectives, approach, affected files, and risks. Briefly summarize the plan, then proceed with implementation.
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { hasEnabledMcpServers } from "../../components/mcp"
|
||||
import { SystemPromptSection } from "../../templates/placeholders"
|
||||
import type { SystemPromptContext } from "../../types"
|
||||
|
||||
@@ -78,7 +77,8 @@ const RULES = (context: SystemPromptContext) => `RULES
|
||||
- When using the replace_in_file tool, you must include complete lines in your SEARCH blocks, not partial lines. The system requires exact line matches and cannot match partial lines. For example, if you want to match a line containing "const x = 5;", your SEARCH block must include the entire line, not just "x = 5" or other fragments.
|
||||
- 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.
|
||||
- You may use multiple tools in a single response when the operations are independent (e.g., reading several files, creating independent files). For dependent operations where one result informs the next, use tools sequentially and wait for the user's response.{{BROWSER_WAIT_RULES}}${hasEnabledMcpServers(context) ? "\n- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations." : ""}`
|
||||
- You may use multiple tools in a single response when the operations are independent (e.g., reading several files, creating independent files). For dependent operations where one result informs the next, use tools sequentially and wait for the user's response.{{BROWSER_WAIT_RULES}}
|
||||
- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations.`
|
||||
|
||||
export const GPT_5_TEMPLATE_OVERRIDES = {
|
||||
BASE,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { hasEnabledMcpServers } from "../../components/mcp"
|
||||
import { SystemPromptSection } from "../../templates/placeholders"
|
||||
import type { SystemPromptContext } from "../../types"
|
||||
|
||||
@@ -49,22 +48,13 @@ export const BASE = `{{${SystemPromptSection.AGENT_ROLE}}}
|
||||
|
||||
{{${SystemPromptSection.USER_INSTRUCTIONS}}}`
|
||||
|
||||
const RULES = (context: SystemPromptContext) => {
|
||||
const hasMcpServers = hasEnabledMcpServers(context)
|
||||
const RULES = (_context: SystemPromptContext) => `RULES
|
||||
|
||||
return `RULES
|
||||
- The current working directory is \`{{CWD}}\` - this is the directory where all the tools will be executed from.`
|
||||
|
||||
- The current working directory is \`{{CWD}}\` - this is the directory where all the tools will be executed from.${
|
||||
context.enableParallelToolCalling
|
||||
? `
|
||||
- You may use multiple tools in a single response when the operations are independent (e.g., reading several files, creating independent files). For dependent operations where one result informs the next, use tools sequentially and wait for the user's response.`
|
||||
: ""
|
||||
}{{BROWSER_WAIT_RULES}}${hasMcpServers ? "\n- MCP operations should be used one at a time, similar to other tool usage. Wait for confirmation of success before proceeding with additional operations." : ""}`
|
||||
}
|
||||
const TOOL_USE = (_context: SystemPromptContext) => `TOOL USE
|
||||
|
||||
const TOOL_USE = (context: SystemPromptContext) => `TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval.${context.enableParallelToolCalling ? " You may use multiple tools in a single response when the operations are independent (e.g., reading several files, searching in parallel). For dependent operations where one result informs the next, use tools sequentially." : ""} You will receive the results of all tool uses in the user's response.`
|
||||
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.`
|
||||
|
||||
const ACT_VS_PLAN = (context: SystemPromptContext) => `ACT MODE V.S. PLAN MODE
|
||||
|
||||
@@ -89,7 +79,7 @@ const OBJECTIVE = (context: SystemPromptContext) => `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 ${context.enableParallelToolCalling ? "as necessary. You may call multiple independent tools in a single response to work efficiently." : "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.
|
||||
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. 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)${context.yoloModeToggled !== true ? " 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. If the task is not actionable, you may use the attempt_completion tool to explain to the user why the task cannot be completed, or provide a simple answer if that is what the user is looking for.`
|
||||
|
||||
@@ -73,12 +73,8 @@ import type { SystemPromptContext } from "@/core/prompts/system-prompt"
|
||||
import { getSystemPrompt } from "@/core/prompts/system-prompt"
|
||||
import { HostProvider } from "@/hosts/host-provider"
|
||||
import { FileEditProvider } from "@/integrations/editor/FileEditProvider"
|
||||
import {
|
||||
CommandExecutor,
|
||||
CommandExecutorCallbacks,
|
||||
FullCommandExecutorConfig,
|
||||
StandaloneTerminalManager,
|
||||
} from "@/integrations/terminal"
|
||||
import { CommandExecutorCallbacks, StandaloneTerminalManager } from "@/integrations/terminal"
|
||||
import { CommandExecutor, FullCommandExecutorConfig } from "@/integrations/terminal/CommandExecutor"
|
||||
import { ClineError, ClineErrorType, ErrorService } from "@/services/error"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import {
|
||||
@@ -276,6 +272,7 @@ export class Task {
|
||||
this.cancelTask = cancelTask
|
||||
this.clineIgnoreController = new ClineIgnoreController(cwd)
|
||||
this.taskLockAcquired = taskLockAcquired
|
||||
|
||||
// Determine terminal execution mode and create appropriate terminal manager
|
||||
this.terminalExecutionMode = vscodeTerminalExecutionMode || "vscodeTerminal"
|
||||
|
||||
@@ -513,7 +510,7 @@ export class Task {
|
||||
},
|
||||
updateBackgroundCommandState: (isRunning: boolean) =>
|
||||
this.controller.updateBackgroundCommandState(isRunning, this.taskId),
|
||||
updateClineMessage: async (index: number, updates: { commandCompleted?: boolean; text?: string }) => {
|
||||
updateClineMessage: async (index: number, updates: { commandCompleted?: boolean }) => {
|
||||
await this.messageStateHandler.updateClineMessage(index, updates)
|
||||
},
|
||||
getClineMessages: () => this.messageStateHandler.getClineMessages() as Array<{ ask?: string; say?: string }>,
|
||||
@@ -1783,8 +1780,6 @@ export class Task {
|
||||
isSubagentsEnabledAndCliInstalled,
|
||||
isCliSubagent,
|
||||
enableNativeToolCalls: this.stateManager.getGlobalStateKey("nativeToolCallEnabled"),
|
||||
enableParallelToolCalling: this.stateManager.getGlobalSettingsKey("enableParallelToolCalling"),
|
||||
terminalExecutionMode: this.terminalExecutionMode,
|
||||
}
|
||||
|
||||
const { systemPrompt, tools } = await getSystemPrompt(promptContext)
|
||||
|
||||
@@ -468,7 +468,7 @@ export class ApplyPatchHandler implements IFullyManagedTool {
|
||||
changes[path] = {
|
||||
type: PatchActionType.UPDATE,
|
||||
oldContent: originalFiles[path],
|
||||
newContent: this.applyChunks(originalFiles[path]!, action.chunks, path),
|
||||
newContent: this.applyChunks(originalFiles[path]!, action.chunks, path).trimEnd(),
|
||||
movePath: action.movePath,
|
||||
}
|
||||
break
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ToolUse } from "@core/assistant-message"
|
||||
import {
|
||||
buildDiffContent,
|
||||
type ChangedFile,
|
||||
detectBinaryFile,
|
||||
isBinaryFile,
|
||||
openDiffView,
|
||||
setupCommentController,
|
||||
streamAIExplanationComments,
|
||||
@@ -160,7 +160,7 @@ export class GenerateExplanationToolHandler implements IToolHandler, IPartialBlo
|
||||
const absolutePath = path.join(cwd, filePath)
|
||||
|
||||
// Skip binary files - they can't be displayed properly in diff view
|
||||
if (await detectBinaryFile(absolutePath)) {
|
||||
if (isBinaryFile(filePath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +480,8 @@ export class WriteToFileToolHandler implements IFullyManagedTool {
|
||||
return
|
||||
}
|
||||
|
||||
newContent = newContent.trimEnd() // remove any trailing newlines, since it's automatically inserted by the editor
|
||||
|
||||
return { relPath, absolutePath, fileExists, diff, content, newContent, workspaceContext }
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -228,7 +228,7 @@ function createAuthSucceededHtml(redirectUri?: string): string {
|
||||
<title>Cline - Authentication Success</title>
|
||||
${redirect}
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Azeret:wght@300;400;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Azeret+Mono:wght@300;400;700&display=swap');
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
@@ -237,7 +237,7 @@ function createAuthSucceededHtml(redirectUri?: string): string {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Azeret', sans-serif;
|
||||
font-family: 'Azeret Mono', monospace;
|
||||
background-color: #ffffff;
|
||||
color: #333333;
|
||||
height: 100vh;
|
||||
|
||||
@@ -96,34 +96,17 @@ export class VscodeDiffViewProvider extends DiffViewProvider {
|
||||
if (!this.activeDiffEditor || !this.activeDiffEditor.document) {
|
||||
throw new Error("User closed text editor, unable to edit file...")
|
||||
}
|
||||
|
||||
// Place cursor at the beginning of the diff editor to keep it out of the way of the stream animation
|
||||
const beginningOfDocument = new vscode.Position(0, 0)
|
||||
this.activeDiffEditor.selection = new vscode.Selection(beginningOfDocument, beginningOfDocument)
|
||||
|
||||
// Replace the text in the diff editor document.
|
||||
const document = this.activeDiffEditor.document
|
||||
const document = this.activeDiffEditor?.document
|
||||
const edit = new vscode.WorkspaceEdit()
|
||||
|
||||
// IMPORTANT: VS Code may treat an out-of-bounds end position as an insertion instead of a
|
||||
// replacement. Always validate the range against the current document to keep edits
|
||||
// strictly within the real end-of-file.
|
||||
const startLine = Math.max(0, Math.min(rangeToReplace.startLine, document.lineCount - 1))
|
||||
const desiredEndLine = Math.max(rangeToReplace.startLine, rangeToReplace.endLine)
|
||||
const validatedRange = document.validateRange(
|
||||
new vscode.Range(new vscode.Position(startLine, 0), new vscode.Position(desiredEndLine, 0)),
|
||||
)
|
||||
|
||||
edit.replace(document.uri, validatedRange, content)
|
||||
const range = new vscode.Range(rangeToReplace.startLine, 0, rangeToReplace.endLine, 0)
|
||||
edit.replace(document.uri, range, content)
|
||||
await vscode.workspace.applyEdit(edit)
|
||||
|
||||
// Preserve trailing newline: if content ends with newline, ensure document does too
|
||||
if (content.endsWith("\n") && !document.getText().endsWith("\n")) {
|
||||
const fixEdit = new vscode.WorkspaceEdit()
|
||||
fixEdit.insert(document.uri, document.lineAt(Math.max(0, document.lineCount - 1)).range.end, "\n")
|
||||
await vscode.workspace.applyEdit(fixEdit)
|
||||
}
|
||||
|
||||
if (currentLine !== undefined) {
|
||||
// Update decorations for the entire changed section
|
||||
this.activeLineController?.setActiveLine(currentLine)
|
||||
|
||||
@@ -3,16 +3,12 @@ import { EventEmitter } from "events"
|
||||
import * as vscode from "vscode"
|
||||
import { stripAnsi } from "@/hosts/vscode/terminal/ansiUtils"
|
||||
import { getLatestTerminalOutput } from "@/hosts/vscode/terminal/get-latest-output"
|
||||
import {
|
||||
isCompilingOutput,
|
||||
MAX_FULL_OUTPUT_SIZE,
|
||||
MAX_UNRETRIEVED_LINES,
|
||||
PROCESS_HOT_TIMEOUT_COMPILING,
|
||||
PROCESS_HOT_TIMEOUT_NORMAL,
|
||||
TRUNCATE_KEEP_LINES,
|
||||
} from "@/integrations/terminal/constants"
|
||||
import type { ITerminalProcess, TerminalProcessEvents } from "@/integrations/terminal/types"
|
||||
|
||||
// how long to wait after a process outputs anything before we consider it "cool" again
|
||||
const PROCESS_HOT_TIMEOUT_NORMAL = 2_000
|
||||
const PROCESS_HOT_TIMEOUT_COMPILING = 15_000
|
||||
|
||||
/**
|
||||
* VscodeTerminalProcess - Manages command execution in VSCode's integrated terminal.
|
||||
*
|
||||
@@ -160,7 +156,24 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
clearTimeout(this.hotTimer)
|
||||
}
|
||||
// these markers indicate the command is some kind of local dev server recompiling the app, which we want to wait for output of before sending request to cline
|
||||
const isCompiling = isCompilingOutput(data)
|
||||
const compilingMarkers = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
|
||||
const markerNullifiers = [
|
||||
"compiled",
|
||||
"success",
|
||||
"finish",
|
||||
"complete",
|
||||
"succeed",
|
||||
"done",
|
||||
"end",
|
||||
"stop",
|
||||
"exit",
|
||||
"terminate",
|
||||
"error",
|
||||
"fail",
|
||||
]
|
||||
const isCompiling =
|
||||
compilingMarkers.some((marker) => data.toLowerCase().includes(marker.toLowerCase())) &&
|
||||
!markerNullifiers.some((nullifier) => data.toLowerCase().includes(nullifier.toLowerCase()))
|
||||
this.hotTimer = setTimeout(
|
||||
() => {
|
||||
this.isHot = false
|
||||
@@ -176,15 +189,6 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
}
|
||||
|
||||
this.fullOutput += data
|
||||
|
||||
// Cap fullOutput at MAX_FULL_OUTPUT_SIZE to prevent memory exhaustion
|
||||
if (this.fullOutput.length > MAX_FULL_OUTPUT_SIZE) {
|
||||
// Keep last half of max size
|
||||
this.fullOutput = this.fullOutput.slice(-MAX_FULL_OUTPUT_SIZE / 2)
|
||||
// Reset lastRetrievedIndex since we truncated the beginning
|
||||
this.lastRetrievedIndex = 0
|
||||
}
|
||||
|
||||
if (this.isListening) {
|
||||
this.emitIfEol(data)
|
||||
this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length
|
||||
@@ -196,18 +200,18 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
// the command process is finished, let's check the output to see if we need to use the terminal capture fallback
|
||||
if (!this.fullOutput.trim()) {
|
||||
// No output captured via shell integration, trying fallback
|
||||
telemetryService.captureTerminalOutputFailure(TerminalOutputFailureReason.TIMEOUT, "vscode")
|
||||
telemetryService.captureTerminalOutputFailure(TerminalOutputFailureReason.TIMEOUT)
|
||||
await returnCurrentTerminalContents()
|
||||
// Check if fallback worked
|
||||
const terminalSnapshot = await getLatestTerminalOutput()
|
||||
if (terminalSnapshot && terminalSnapshot.trim()) {
|
||||
telemetryService.captureTerminalExecution(true, "vscode", "clipboard")
|
||||
telemetryService.captureTerminalExecution(true, "clipboard")
|
||||
} else {
|
||||
telemetryService.captureTerminalExecution(false, "vscode", "none")
|
||||
telemetryService.captureTerminalExecution(false, "none")
|
||||
}
|
||||
} else {
|
||||
// Shell integration worked
|
||||
telemetryService.captureTerminalExecution(true, "vscode", "shell_integration")
|
||||
telemetryService.captureTerminalExecution(true, "shell_integration")
|
||||
}
|
||||
|
||||
// for now we don't want this delaying requests since we don't send diagnostics automatically anymore (previous: "even though the command is finished, we still want to consider it 'hot' in case so that api request stalls to let diagnostics catch up")
|
||||
@@ -221,7 +225,7 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
this.emit("continue")
|
||||
} else {
|
||||
// no shell integration detected, we'll fallback to running the command and capturing the terminal's output after some time
|
||||
telemetryService.captureTerminalOutputFailure(TerminalOutputFailureReason.NO_SHELL_INTEGRATION, "vscode")
|
||||
telemetryService.captureTerminalOutputFailure(TerminalOutputFailureReason.NO_SHELL_INTEGRATION)
|
||||
terminal.sendText(command, true)
|
||||
|
||||
// wait 3 seconds for the command to run
|
||||
@@ -232,9 +236,9 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
// Check if clipboard fallback worked
|
||||
const terminalSnapshot = await getLatestTerminalOutput()
|
||||
if (terminalSnapshot && terminalSnapshot.trim()) {
|
||||
telemetryService.captureTerminalExecution(true, "vscode", "clipboard")
|
||||
telemetryService.captureTerminalExecution(true, "clipboard")
|
||||
} else {
|
||||
telemetryService.captureTerminalExecution(false, "vscode", "none")
|
||||
telemetryService.captureTerminalExecution(false, "none")
|
||||
}
|
||||
// For terminals without shell integration, we can't know when the command completes
|
||||
// So we'll just emit the continue event after a delay
|
||||
@@ -281,24 +285,9 @@ export class VscodeTerminalProcess extends EventEmitter<TerminalProcessEvents> i
|
||||
this.emit("continue")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output that hasn't been retrieved yet.
|
||||
* Truncates if output is too large to prevent context window overflow.
|
||||
* @returns The unretrieved output (truncated if necessary)
|
||||
*/
|
||||
getUnretrievedOutput(): string {
|
||||
const unretrieved = this.fullOutput.slice(this.lastRetrievedIndex)
|
||||
this.lastRetrievedIndex = this.fullOutput.length
|
||||
|
||||
// Truncate if too many lines to prevent context overflow
|
||||
const lines = unretrieved.split("\n")
|
||||
if (lines.length > MAX_UNRETRIEVED_LINES) {
|
||||
const first = lines.slice(0, TRUNCATE_KEEP_LINES)
|
||||
const last = lines.slice(-TRUNCATE_KEEP_LINES)
|
||||
const skipped = lines.length - first.length - last.length
|
||||
return this.removeLastLineArtifacts([...first, `\n... (${skipped} lines truncated) ...\n`, ...last].join("\n"))
|
||||
}
|
||||
|
||||
return this.removeLastLineArtifacts(unretrieved)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { sendCheckpointEvent } from "@core/controller/checkpoints/subscribeToCheckpoints"
|
||||
import fs from "fs/promises"
|
||||
import { isBinaryFile } from "isbinaryfile"
|
||||
import * as path from "path"
|
||||
import simpleGit from "simple-git"
|
||||
import type { FolderLockWithRetryResult } from "@/core/locks/types"
|
||||
@@ -424,23 +423,6 @@ class CheckpointTracker {
|
||||
const filePath = file.file
|
||||
const absolutePath = path.join(this.cwd, filePath)
|
||||
|
||||
// For extensionless files or dotfiles: exclude from diff result if binary
|
||||
const lastDotIndex = filePath.lastIndexOf(".")
|
||||
const lastSlashIndex = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"))
|
||||
const ext = lastDotIndex > lastSlashIndex ? filePath.substring(lastDotIndex).toLowerCase() : ""
|
||||
const isDotfile = lastDotIndex !== -1 && lastDotIndex === lastSlashIndex + 1
|
||||
|
||||
if (!ext || isDotfile) {
|
||||
try {
|
||||
const isBinary = await isBinaryFile(absolutePath).catch(() => false)
|
||||
if (isBinary) {
|
||||
continue
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
let beforeContent = ""
|
||||
try {
|
||||
beforeContent = await git.show([`${this.cleanCommitHash(lhsHash)}:${filePath}`])
|
||||
|
||||
@@ -182,12 +182,7 @@ export abstract class DiffViewProvider {
|
||||
// Replace all content up to the current line with accumulated lines
|
||||
// This is necessary (as compared to inserting one line at a time) to handle cases where html tags
|
||||
// on previous lines are auto closed for example
|
||||
let contentToReplace = accumulatedLines.slice(0, currentLine + 1).join("\n")
|
||||
if (!isFinal) {
|
||||
// During streaming, add trailing newline for cursor positioning
|
||||
contentToReplace += "\n"
|
||||
}
|
||||
|
||||
const contentToReplace = accumulatedLines.slice(0, currentLine + 1).join("\n") + "\n"
|
||||
const rangeToReplace = { startLine: 0, endLine: currentLine + 1 }
|
||||
await this.replaceText(contentToReplace, rangeToReplace, currentLine)
|
||||
|
||||
@@ -217,6 +212,15 @@ export abstract class DiffViewProvider {
|
||||
if (isFinal) {
|
||||
// Handle any remaining lines if the new content is shorter than the original
|
||||
await this.truncateDocument(this.streamedLines.length)
|
||||
|
||||
// Add empty last line if original content had one
|
||||
const hasEmptyLastLine = this.originalContent?.endsWith("\n")
|
||||
if (hasEmptyLastLine) {
|
||||
const accumulatedLines = accumulatedContent.split("\n")
|
||||
if (accumulatedLines[accumulatedLines.length - 1] !== "") {
|
||||
accumulatedContent += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +277,10 @@ export abstract class DiffViewProvider {
|
||||
|
||||
// If the edited content has different EOL characters, we don't want to show a diff with all the EOL differences.
|
||||
const newContentEOL = this.newContent.includes("\r\n") ? "\r\n" : "\n"
|
||||
const normalizedPreSaveContent = preSaveContent.replace(/\r\n|\n/g, newContentEOL)
|
||||
const normalizedPostSaveContent = postSaveContent.replace(/\r\n|\n/g, newContentEOL) // this is the final content we return to the model to use as the new baseline for future edits
|
||||
const normalizedPreSaveContent = preSaveContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL // trimEnd to fix issue where editor adds in extra new line automatically
|
||||
const normalizedPostSaveContent = postSaveContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL // this is the final content we return to the model to use as the new baseline for future edits
|
||||
// just in case the new content has a mix of varying EOL characters
|
||||
const normalizedNewContent = this.newContent.replace(/\r\n|\n/g, newContentEOL)
|
||||
const normalizedNewContent = this.newContent.replace(/\r\n|\n/g, newContentEOL).trimEnd() + newContentEOL
|
||||
|
||||
let userEdits: string | undefined
|
||||
if (normalizedPreSaveContent !== normalizedNewContent) {
|
||||
|
||||
@@ -43,48 +43,19 @@ export class FileEditProvider extends DiffViewProvider {
|
||||
|
||||
// Split the document into lines
|
||||
const lines = this.documentContent.split("\n")
|
||||
const originalEndsWithNewline = this.documentContent.endsWith("\n")
|
||||
|
||||
// If original ends with newline, split creates a trailing empty string that isn't a real line.
|
||||
// Remove it for line-based operations, we'll add it back at the end if needed.
|
||||
const realLines = originalEndsWithNewline && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines
|
||||
|
||||
// Replace the specified range with the new content
|
||||
const newContentLines = content.split("\n")
|
||||
const contentEndsWithNewline = content.endsWith("\n")
|
||||
|
||||
// Determine if we're replacing to the end of the document
|
||||
const replacingToEnd = rangeToReplace.endLine >= realLines.length
|
||||
|
||||
// Handle trailing empty string from split:
|
||||
// - If content ends with \n, split creates an empty string at the end
|
||||
// - When replacing to end: this empty string becomes the document's trailing newline - keep it
|
||||
// - When replacing middle: this empty string would create an extra newline - remove it
|
||||
// (the join operation will naturally add newlines between lines)
|
||||
// - If content doesn't end with \n but split created empty string, remove it
|
||||
if (!contentEndsWithNewline && newContentLines[newContentLines.length - 1] === "") {
|
||||
newContentLines.pop()
|
||||
} else if (contentEndsWithNewline && !replacingToEnd && newContentLines[newContentLines.length - 1] === "") {
|
||||
// Content ends with newline but we're replacing middle section - remove trailing empty string
|
||||
// Remove trailing empty line if present in newContentLines for proper splicing
|
||||
if (newContentLines[newContentLines.length - 1] === "") {
|
||||
newContentLines.pop()
|
||||
}
|
||||
|
||||
// Splice the real lines array to replace the range
|
||||
realLines.splice(rangeToReplace.startLine, rangeToReplace.endLine - rangeToReplace.startLine, ...newContentLines)
|
||||
// Splice the lines array to replace the range
|
||||
lines.splice(rangeToReplace.startLine, rangeToReplace.endLine - rangeToReplace.startLine, ...newContentLines)
|
||||
|
||||
// Join the lines back together
|
||||
let result = realLines.join("\n")
|
||||
|
||||
// Preserve trailing newline: add it back if original had one OR if we replaced to end with content that ends with newline
|
||||
const shouldHaveTrailingNewline = originalEndsWithNewline || (replacingToEnd && contentEndsWithNewline)
|
||||
if (shouldHaveTrailingNewline && !result.endsWith("\n")) {
|
||||
result += "\n"
|
||||
} else if (!shouldHaveTrailingNewline && result.endsWith("\n")) {
|
||||
// Shouldn't have trailing newline but result has one - remove it
|
||||
result = result.slice(0, -1)
|
||||
}
|
||||
|
||||
this.documentContent = result
|
||||
this.documentContent = lines.join("\n")
|
||||
}
|
||||
|
||||
protected async scrollEditorToLine(_line: number): Promise<void> {
|
||||
|
||||
@@ -18,18 +18,28 @@
|
||||
import { isSubagentCommand, transformClineCommand } from "@integrations/cli-subagents/subagent_command"
|
||||
import { Logger } from "@services/logging/Logger"
|
||||
import { telemetryService } from "@services/telemetry"
|
||||
import { findLastIndex } from "@shared/array"
|
||||
import { ClineToolResponseContent } from "@shared/messages"
|
||||
import { orchestrateCommandExecution } from "./CommandOrchestrator"
|
||||
import { StandaloneTerminalManager } from "./standalone/StandaloneTerminalManager"
|
||||
import type {
|
||||
import {
|
||||
ActiveBackgroundCommand,
|
||||
CommandExecutorCallbacks,
|
||||
CommandExecutorConfig,
|
||||
ITerminalManager,
|
||||
ShellIntegrationWarningTracker,
|
||||
TerminalProcessResultPromise,
|
||||
} from "./types"
|
||||
|
||||
// Re-export types for convenience
|
||||
export type { CommandExecutorCallbacks, CommandExecutorConfig, FullCommandExecutorConfig } from "./types"
|
||||
|
||||
/**
|
||||
* Tracker for shell integration warnings to determine when to show background terminal suggestion
|
||||
*/
|
||||
interface ShellIntegrationWarningTracker {
|
||||
timestamps: number[]
|
||||
lastSuggestionShown?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* CommandExecutor - Unified command executor for all terminal modes.
|
||||
*
|
||||
@@ -45,18 +55,19 @@ export class CommandExecutor {
|
||||
private standaloneManager: StandaloneTerminalManager
|
||||
private callbacks: CommandExecutorCallbacks
|
||||
|
||||
// Track the currently executing foreground process for cancellation
|
||||
private currentProcess: TerminalProcessResultPromise | null = null
|
||||
|
||||
// Flag to track if the current command was cancelled externally
|
||||
private wasCancelledExternally = false
|
||||
|
||||
// Track shell integration warnings to determine when to show background terminal suggestion
|
||||
private shellIntegrationWarningTracker: ShellIntegrationWarningTracker = {
|
||||
timestamps: [],
|
||||
lastSuggestionShown: undefined,
|
||||
}
|
||||
|
||||
// Track active background command for cancellation (standalone mode only)
|
||||
private activeBackgroundCommand?: {
|
||||
process: TerminalProcessResultPromise & { terminate?: () => void }
|
||||
command: string
|
||||
outputLines: string[]
|
||||
}
|
||||
|
||||
constructor(config: CommandExecutorConfig, callbacks: CommandExecutorCallbacks) {
|
||||
this.cwd = config.cwd
|
||||
this.taskId = config.taskId
|
||||
@@ -65,27 +76,16 @@ export class CommandExecutor {
|
||||
this.terminalManager = config.terminalManager
|
||||
this.callbacks = callbacks
|
||||
|
||||
// When in backgroundExec mode, the terminalManager is already a StandaloneTerminalManager
|
||||
// created by Task. We should reuse it so that Task.getEnvironmentDetails() can see
|
||||
// the terminals and processes we create (for isHot logic, busy terminals, etc.)
|
||||
if (config.terminalExecutionMode === "backgroundExec" && config.terminalManager instanceof StandaloneTerminalManager) {
|
||||
// Reuse the same instance that Task is using
|
||||
this.standaloneManager = config.terminalManager
|
||||
Logger.info(`[CommandExecutor] Reusing Task's StandaloneTerminalManager for backgroundExec mode`)
|
||||
} else {
|
||||
// Create new StandaloneTerminalManager for subagents (even in VSCode mode)
|
||||
// This ensures subagents run in hidden terminals, not cluttering the user's VSCode terminal
|
||||
this.standaloneManager = new StandaloneTerminalManager()
|
||||
Logger.info(`[CommandExecutor] Created new StandaloneTerminalManager for subagents`)
|
||||
// Always create StandaloneTerminalManager for subagents (even in VSCode mode)
|
||||
this.standaloneManager = new StandaloneTerminalManager()
|
||||
|
||||
// Copy settings from the provided terminalManager to ensure consistency
|
||||
if ("shellIntegrationTimeout" in config.terminalManager) {
|
||||
const tm = config.terminalManager as any
|
||||
this.standaloneManager.setShellIntegrationTimeout(tm.shellIntegrationTimeout || 4000)
|
||||
this.standaloneManager.setTerminalReuseEnabled(tm.terminalReuseEnabled ?? true)
|
||||
this.standaloneManager.setTerminalOutputLineLimit(tm.terminalOutputLineLimit || 500)
|
||||
this.standaloneManager.setSubagentTerminalOutputLineLimit(tm.subagentTerminalOutputLineLimit || 2000)
|
||||
}
|
||||
// Copy settings from the provided terminalManager to ensure consistency
|
||||
if ("shellIntegrationTimeout" in config.terminalManager) {
|
||||
const tm = config.terminalManager as any
|
||||
this.standaloneManager.setShellIntegrationTimeout(tm.shellIntegrationTimeout || 4000)
|
||||
this.standaloneManager.setTerminalReuseEnabled(tm.terminalReuseEnabled ?? true)
|
||||
this.standaloneManager.setTerminalOutputLineLimit(tm.terminalOutputLineLimit || 500)
|
||||
this.standaloneManager.setSubagentTerminalOutputLineLimit(tm.subagentTerminalOutputLineLimit || 2000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ export class CommandExecutor {
|
||||
// Subagents always use standalone manager (hidden terminal)
|
||||
const useStandalone = isSubagent || this.terminalExecutionMode === "backgroundExec"
|
||||
const manager = useStandalone ? this.standaloneManager : this.terminalManager
|
||||
|
||||
Logger.info(`Executing command in ${useStandalone ? "standalone" : "VSCode"} terminal: ${command}`)
|
||||
|
||||
// Get terminal and run command
|
||||
@@ -127,124 +128,146 @@ export class CommandExecutor {
|
||||
terminalInfo.terminal.show()
|
||||
const process = manager.runCommand(terminalInfo, command)
|
||||
|
||||
// Reset cancellation flag and track the current process
|
||||
this.wasCancelledExternally = false
|
||||
this.currentProcess = process
|
||||
const clearCurrentProcess = () => {
|
||||
this.currentProcess = null
|
||||
// Track background command for standalone mode (enables cancellation)
|
||||
if (useStandalone) {
|
||||
this.activeBackgroundCommand = {
|
||||
process: process as any,
|
||||
command,
|
||||
outputLines: [],
|
||||
}
|
||||
}
|
||||
process.once("completed", clearCurrentProcess)
|
||||
process.once("error", clearCurrentProcess)
|
||||
|
||||
// Use shared orchestration logic
|
||||
// The StandaloneTerminalManager handles background command tracking internally
|
||||
const result = await orchestrateCommandExecution(process, manager, this.callbacks, {
|
||||
command,
|
||||
timeoutSeconds,
|
||||
// When "Proceed While Running" is triggered, track the command in the manager
|
||||
// Returns the log file path so the orchestrator can send it to the UI
|
||||
// existingOutput contains all output lines captured so far
|
||||
onProceedWhileRunning: useStandalone
|
||||
? (existingOutput: string[]) => {
|
||||
const backgroundCmd = this.standaloneManager.trackBackgroundCommand(process, command, existingOutput)
|
||||
return { logFilePath: backgroundCmd.logFilePath }
|
||||
onOutputLine: useStandalone
|
||||
? (line) => {
|
||||
if (this.activeBackgroundCommand) {
|
||||
this.activeBackgroundCommand.outputLines.push(line)
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
showShellIntegrationSuggestion: this.shouldShowBackgroundTerminalSuggestion(),
|
||||
terminalType: useStandalone ? "standalone" : "vscode",
|
||||
})
|
||||
|
||||
// Clear background command tracking if completed
|
||||
if (result.completed && useStandalone) {
|
||||
this.activeBackgroundCommand = undefined
|
||||
}
|
||||
|
||||
// Capture subagent telemetry
|
||||
if (isSubagent && subAgentStartTime > 0) {
|
||||
const durationMs = Math.round(performance.now() - subAgentStartTime)
|
||||
telemetryService.captureSubagentExecution(this.ulid, durationMs, result.outputLines.length, result.completed)
|
||||
}
|
||||
|
||||
// If the command was cancelled externally (via cancel button), return a clear cancellation message
|
||||
// This ensures the AI agent knows the command was cancelled by the user
|
||||
if (this.wasCancelledExternally) {
|
||||
const outputSoFar =
|
||||
result.outputLines.length > 0
|
||||
? `\nOutput captured before cancellation:\n${manager.processOutput(result.outputLines)}`
|
||||
: ""
|
||||
return [true, `Command was cancelled by the user.${outputSoFar}`]
|
||||
}
|
||||
|
||||
return [result.userRejected, result.result]
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all running commands (both foreground and background).
|
||||
* Cancel the currently running background command.
|
||||
* Only works in standalone/backgroundExec mode.
|
||||
*
|
||||
* This method cancels:
|
||||
* 1. All detached background commands (those that were "proceeded while running")
|
||||
* 2. The current foreground process (if one is actively running)
|
||||
*
|
||||
* @returns true if any commands were cancelled, false otherwise
|
||||
* @returns true if a command was cancelled, false otherwise
|
||||
*/
|
||||
async cancelBackgroundCommand(): Promise<boolean> {
|
||||
let cancelled = false
|
||||
if (!this.activeBackgroundCommand) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 1. Cancel all detached background commands
|
||||
const runningCommands = this.standaloneManager.getRunningBackgroundCommands()
|
||||
for (const cmd of runningCommands) {
|
||||
if (this.standaloneManager.cancelBackgroundCommand(cmd.id)) {
|
||||
cancelled = true
|
||||
Logger.info(`Cancelled background command: ${cmd.command}`)
|
||||
const { process, command, outputLines } = this.activeBackgroundCommand
|
||||
this.activeBackgroundCommand = undefined
|
||||
this.callbacks.updateBackgroundCommandState(false)
|
||||
|
||||
try {
|
||||
// Try to terminate the process if the method exists
|
||||
if (typeof process.terminate === "function") {
|
||||
try {
|
||||
await process.terminate()
|
||||
Logger.info(`Terminated background command: ${command}`)
|
||||
} catch (error) {
|
||||
Logger.error(`Error terminating background command: ${command}`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Cancel the current foreground process (if any)
|
||||
if (this.currentProcess && typeof (this.currentProcess as any).terminate === "function") {
|
||||
// Set flag so execute() knows the command was cancelled externally
|
||||
this.wasCancelledExternally = true
|
||||
;(this.currentProcess as any).terminate()
|
||||
this.currentProcess = null
|
||||
cancelled = true
|
||||
Logger.info("Cancelled foreground command")
|
||||
}
|
||||
// Ensure any pending operations complete
|
||||
if (typeof process.continue === "function") {
|
||||
try {
|
||||
process.continue()
|
||||
} catch (error) {
|
||||
Logger.error(`Error continuing background command: ${command}`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update UI state and notify user by modifying existing message
|
||||
// We modify the previous command_output message instead of sending a new say()
|
||||
// to avoid interfering with any pending ask() dialogs (which would cause
|
||||
// "Current ask promise was ignored" errors)
|
||||
if (cancelled) {
|
||||
this.callbacks.updateBackgroundCommandState(false)
|
||||
|
||||
// Wait for terminal buffers to flush before updating the message
|
||||
// This prevents the cancellation notice from appearing in the middle of output
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
|
||||
// Find the last command_output message and update it
|
||||
const messages = this.callbacks.getClineMessages()
|
||||
const lastCommandOutputIndex = findLastIndex(messages, (m) => m.ask === "command_output")
|
||||
if (lastCommandOutputIndex !== -1) {
|
||||
const existingText = messages[lastCommandOutputIndex].text || ""
|
||||
const cancellationNotice = "\n\nCommand(s) cancelled by user."
|
||||
await this.callbacks.updateClineMessage(lastCommandOutputIndex, {
|
||||
text: existingText + cancellationNotice,
|
||||
// Mark the command message as completed in the UI
|
||||
const clineMessages = this.callbacks.getClineMessages()
|
||||
const lastCommandIndex = this.findLastIndex(clineMessages, (m) => m.ask === "command" || m.say === "command")
|
||||
if (lastCommandIndex !== -1) {
|
||||
await this.callbacks.updateClineMessage(lastCommandIndex, {
|
||||
commandCompleted: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return cancelled
|
||||
// Process the captured output to include in the cancellation message
|
||||
const processedOutput = this.standaloneManager.processOutput(outputLines, undefined, false)
|
||||
|
||||
// Add cancellation information to the API conversation history
|
||||
let cancellationMessage = `Command "${command}" was cancelled by the user.`
|
||||
if (processedOutput.length > 0) {
|
||||
cancellationMessage += `\n\nOutput captured before cancellation:\n${processedOutput}`
|
||||
}
|
||||
|
||||
this.callbacks.addToUserMessageContent({
|
||||
type: "text",
|
||||
text: cancellationMessage,
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
Logger.error("Error in cancelBackgroundCommand", error)
|
||||
return false
|
||||
} finally {
|
||||
try {
|
||||
await this.callbacks.say("command_output", "Command execution has been cancelled.")
|
||||
} catch (error) {
|
||||
Logger.error("Failed to send cancellation notification", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any active background commands.
|
||||
* Delegates to StandaloneTerminalManager.
|
||||
* Check if there's an active background command
|
||||
*/
|
||||
hasActiveBackgroundCommand(): boolean {
|
||||
return this.standaloneManager.hasActiveBackgroundCommands()
|
||||
return !!this.activeBackgroundCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of background commands for environment details.
|
||||
* Delegates to StandaloneTerminalManager which tracks multiple commands.
|
||||
* Get the active background command info (for external access)
|
||||
*/
|
||||
getActiveBackgroundCommand(): ActiveBackgroundCommand | undefined {
|
||||
return this.activeBackgroundCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of background commands for environment details
|
||||
*/
|
||||
getBackgroundCommandSummary(): string | undefined {
|
||||
const summary = this.standaloneManager.getBackgroundCommandsSummary()
|
||||
return summary || undefined
|
||||
if (!this.activeBackgroundCommand) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const { command, outputLines } = this.activeBackgroundCommand
|
||||
const recentOutput = outputLines.slice(-10).join("\n")
|
||||
|
||||
let summary = "# Background Commands\n"
|
||||
summary += `## Running: \`${command}\`\n`
|
||||
if (recentOutput) {
|
||||
summary += `### Recent Output\n${recentOutput}`
|
||||
}
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,4 +304,16 @@ export class CommandExecutor {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to find last index matching a predicate
|
||||
*/
|
||||
private findLastIndex<T>(array: T[], predicate: (item: T) => boolean): number {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
if (predicate(array[i])) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,6 @@ import { processFilesIntoText } from "@integrations/misc/extract-text"
|
||||
import { Logger } from "@services/logging/Logger"
|
||||
import { TerminalHangStage, TerminalUserInterventionAction, telemetryService } from "@services/telemetry"
|
||||
import { COMMAND_CANCEL_TOKEN } from "@shared/ExtensionMessage"
|
||||
import * as fs from "fs"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
BUFFER_STUCK_TIMEOUT_MS,
|
||||
CHUNK_BYTE_SIZE,
|
||||
CHUNK_DEBOUNCE_MS,
|
||||
CHUNK_LINE_COUNT,
|
||||
COMPLETION_TIMEOUT_MS,
|
||||
MAX_BYTES_BEFORE_FILE,
|
||||
MAX_LINES_BEFORE_FILE,
|
||||
SUMMARY_LINES_TO_KEEP,
|
||||
} from "./constants"
|
||||
import type {
|
||||
CommandExecutorCallbacks,
|
||||
ITerminalManager,
|
||||
@@ -40,6 +27,16 @@ import type {
|
||||
TerminalProcessResultPromise,
|
||||
} from "./types"
|
||||
|
||||
// Chunked terminal output buffering constants
|
||||
export const CHUNK_LINE_COUNT = 20
|
||||
export const CHUNK_BYTE_SIZE = 2048 // 2KB
|
||||
export const CHUNK_DEBOUNCE_MS = 100
|
||||
export const BUFFER_STUCK_TIMEOUT_MS = 6000 // 6 seconds
|
||||
export const COMPLETION_TIMEOUT_MS = 6000 // 6 seconds
|
||||
|
||||
// Re-export types for convenience
|
||||
export type { OrchestrationOptions, OrchestrationResult } from "./types"
|
||||
|
||||
/**
|
||||
* Orchestrate command execution with shared logic for buffering, user interaction, and result formatting.
|
||||
*
|
||||
@@ -55,13 +52,7 @@ export async function orchestrateCommandExecution(
|
||||
callbacks: CommandExecutorCallbacks,
|
||||
options: OrchestrationOptions,
|
||||
): Promise<OrchestrationResult> {
|
||||
const {
|
||||
timeoutSeconds,
|
||||
onOutputLine,
|
||||
showShellIntegrationSuggestion,
|
||||
onProceedWhileRunning,
|
||||
terminalType = "vscode",
|
||||
} = options
|
||||
const { command, timeoutSeconds, onOutputLine, showShellIntegrationSuggestion } = options
|
||||
|
||||
// Track command execution state
|
||||
callbacks.updateBackgroundCommandState(true)
|
||||
@@ -88,7 +79,6 @@ export async function orchestrateCommandExecution(
|
||||
let userFeedback: { text?: string; images?: string[]; files?: string[] } | undefined
|
||||
let didContinue = false
|
||||
let didCancelViaUi = false
|
||||
let backgroundTrackingResult: OrchestrationResult | null = null // Set when background tracking returns early
|
||||
|
||||
// Chunked terminal output buffering
|
||||
let outputBuffer: string[] = []
|
||||
@@ -114,7 +104,7 @@ export async function orchestrateCommandExecution(
|
||||
if (!didContinue) {
|
||||
// Start timer to detect if buffer gets stuck
|
||||
bufferStuckTimer = setTimeout(() => {
|
||||
telemetryService.captureTerminalHang(TerminalHangStage.BUFFER_STUCK, terminalType)
|
||||
telemetryService.captureTerminalHang(TerminalHangStage.BUFFER_STUCK)
|
||||
bufferStuckTimer = null
|
||||
}, BUFFER_STUCK_TIMEOUT_MS)
|
||||
|
||||
@@ -125,70 +115,22 @@ export async function orchestrateCommandExecution(
|
||||
|
||||
if (response === "yesButtonClicked") {
|
||||
// Track when user clicks "Proceed While Running"
|
||||
telemetryService.captureTerminalUserIntervention(
|
||||
TerminalUserInterventionAction.PROCESS_WHILE_RUNNING,
|
||||
terminalType,
|
||||
)
|
||||
telemetryService.captureTerminalUserIntervention(TerminalUserInterventionAction.PROCESS_WHILE_RUNNING)
|
||||
// Proceed while running - but still capture user feedback if provided
|
||||
if (text || (images && images.length > 0) || (files && files.length > 0)) {
|
||||
userFeedback = { text, images, files }
|
||||
}
|
||||
didContinue = true
|
||||
|
||||
// Notify caller to start background command tracking
|
||||
// Pass existing output lines so they can be written to the log file
|
||||
// and send log file path to UI if tracking was started
|
||||
if (onProceedWhileRunning) {
|
||||
const trackingResult = onProceedWhileRunning(outputLines)
|
||||
|
||||
// Clear timers first
|
||||
if (chunkTimer) {
|
||||
clearTimeout(chunkTimer)
|
||||
chunkTimer = null
|
||||
}
|
||||
if (completionTimer) {
|
||||
clearTimeout(completionTimer)
|
||||
completionTimer = null
|
||||
}
|
||||
|
||||
// Set early return result BEFORE resuming the process
|
||||
// This prevents the orchestrator's listener from processing new lines
|
||||
const result = terminalManager.processOutput(outputLines)
|
||||
const logMsg = trackingResult?.logFilePath ? `Log file: ${trackingResult.logFilePath}\n` : ""
|
||||
const outputMsg = result.length > 0 ? `Output so far:\n${result}` : ""
|
||||
|
||||
backgroundTrackingResult = {
|
||||
userRejected: false,
|
||||
result: `Command is running in the background. You can proceed with other tasks.\n${logMsg}${outputMsg}`,
|
||||
completed: false,
|
||||
outputLines,
|
||||
}
|
||||
|
||||
// Send log file message to UI BEFORE resuming the process
|
||||
// This ensures the message appears before any new output lines
|
||||
if (trackingResult?.logFilePath) {
|
||||
await callbacks.say("command_output", `\n📋 Output is being logged to: ${trackingResult.logFilePath}`)
|
||||
}
|
||||
|
||||
// Now resume the process - any new lines will be handled by the background tracker
|
||||
process.continue()
|
||||
return
|
||||
}
|
||||
|
||||
process.continue()
|
||||
} else if (response === "noButtonClicked" && text === COMMAND_CANCEL_TOKEN) {
|
||||
telemetryService.captureTerminalUserIntervention(TerminalUserInterventionAction.CANCELLED, terminalType)
|
||||
// Set flags BEFORE resuming the process to prevent new lines from being processed
|
||||
telemetryService.captureTerminalUserIntervention(TerminalUserInterventionAction.CANCELLED)
|
||||
didCancelViaUi = true
|
||||
userFeedback = undefined
|
||||
didContinue = true
|
||||
process.continue()
|
||||
outputBuffer = []
|
||||
outputBufferSize = 0
|
||||
// Send cancellation message BEFORE resuming the process
|
||||
// This ensures the message appears before any new output lines
|
||||
await callbacks.say("command_output", "Command cancelled")
|
||||
// Now resume the process
|
||||
process.continue()
|
||||
} else {
|
||||
userFeedback = { text, images, files }
|
||||
didContinue = true
|
||||
@@ -220,134 +162,31 @@ export async function orchestrateCommandExecution(
|
||||
chunkTimer = setTimeout(async () => await flushBuffer(), CHUNK_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
// Large output file-based logging state
|
||||
let isWritingToFile = false
|
||||
let largeOutputLogPath: string | null = null
|
||||
let largeOutputLogStream: fs.WriteStream | null = null
|
||||
let totalOutputBytes = 0
|
||||
let totalLineCount = 0
|
||||
let firstLines: string[] = [] // Keep first N lines for summary
|
||||
let lastLines: string[] = [] // Keep last N lines for summary (circular buffer)
|
||||
|
||||
/**
|
||||
* Switch to file-based logging when output is too large.
|
||||
* This protects against memory exhaustion from commands with huge output.
|
||||
*/
|
||||
const switchToFileBased = async () => {
|
||||
if (isWritingToFile) return
|
||||
|
||||
isWritingToFile = true
|
||||
|
||||
// FIRST: Flush any pending buffer to UI so the "writing to file" message appears at the end
|
||||
if (outputBuffer.length > 0) {
|
||||
const chunk = outputBuffer.join("\n")
|
||||
outputBuffer = []
|
||||
outputBufferSize = 0
|
||||
if (!didContinue) {
|
||||
// Use say() instead of ask() since we're transitioning to file mode
|
||||
await callbacks.say("command_output", chunk)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any pending flush timer
|
||||
if (chunkTimer) {
|
||||
clearTimeout(chunkTimer)
|
||||
chunkTimer = null
|
||||
}
|
||||
|
||||
// Set up file logging
|
||||
largeOutputLogPath = path.join(os.tmpdir(), `cline-large-output-${Date.now()}.log`)
|
||||
largeOutputLogStream = fs.createWriteStream(largeOutputLogPath, { flags: "a" })
|
||||
|
||||
// Write all existing lines to file in a single batch to reduce I/O overhead
|
||||
if (outputLines.length > 0) {
|
||||
largeOutputLogStream.write(outputLines.join("\n") + "\n")
|
||||
}
|
||||
|
||||
// Keep first N lines for summary
|
||||
firstLines = outputLines.slice(0, SUMMARY_LINES_TO_KEEP)
|
||||
|
||||
// Keep last N lines for summary (will be updated as more lines come in)
|
||||
lastLines = outputLines.slice(-SUMMARY_LINES_TO_KEEP)
|
||||
|
||||
// FINALLY: Notify user (now this will appear at the end after all buffered output)
|
||||
await callbacks.say(
|
||||
"command_output",
|
||||
`\n📋 Output is large (${outputLines.length} lines, ${Math.round(totalOutputBytes / 1024)}KB). Writing to: ${largeOutputLogPath}`,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up file-based logging resources.
|
||||
*/
|
||||
const cleanupFileBased = () => {
|
||||
if (largeOutputLogStream) {
|
||||
largeOutputLogStream.end()
|
||||
largeOutputLogStream = null
|
||||
}
|
||||
}
|
||||
|
||||
const outputLines: string[] = []
|
||||
process.on("line", async (line: string) => {
|
||||
if (didCancelViaUi) {
|
||||
return
|
||||
}
|
||||
|
||||
// If background tracking is active, don't process lines here
|
||||
// The background tracker's listener will handle them
|
||||
if (backgroundTrackingResult) {
|
||||
return
|
||||
}
|
||||
|
||||
const lineBytes = Buffer.byteLength(line, "utf8")
|
||||
totalOutputBytes += lineBytes
|
||||
totalLineCount++
|
||||
|
||||
// Check if we should switch to file-based logging
|
||||
if (!isWritingToFile && (outputLines.length >= MAX_LINES_BEFORE_FILE || totalOutputBytes >= MAX_BYTES_BEFORE_FILE)) {
|
||||
await switchToFileBased()
|
||||
}
|
||||
|
||||
if (isWritingToFile) {
|
||||
// Write to file instead of keeping in memory
|
||||
if (largeOutputLogStream) {
|
||||
largeOutputLogStream.write(line + "\n")
|
||||
}
|
||||
|
||||
// Update last lines circular buffer for summary
|
||||
lastLines.push(line)
|
||||
if (lastLines.length > SUMMARY_LINES_TO_KEEP) {
|
||||
lastLines.shift()
|
||||
}
|
||||
} else {
|
||||
// Normal behavior - keep in memory
|
||||
outputLines.push(line)
|
||||
}
|
||||
outputLines.push(line)
|
||||
|
||||
// Notify caller about output line (for background command tracking)
|
||||
if (onOutputLine) {
|
||||
onOutputLine(line)
|
||||
}
|
||||
|
||||
// Apply buffered streaming (only if not in file mode or still showing initial output)
|
||||
// Apply buffered streaming
|
||||
if (!didContinue) {
|
||||
if (!isWritingToFile) {
|
||||
outputBuffer.push(line)
|
||||
outputBufferSize += lineBytes
|
||||
// Flush if buffer is large enough
|
||||
if (outputBuffer.length >= CHUNK_LINE_COUNT || outputBufferSize >= CHUNK_BYTE_SIZE) {
|
||||
await flushBuffer()
|
||||
} else {
|
||||
scheduleFlush()
|
||||
}
|
||||
outputBuffer.push(line)
|
||||
outputBufferSize += Buffer.byteLength(line, "utf8")
|
||||
// Flush if buffer is large enough
|
||||
if (outputBuffer.length >= CHUNK_LINE_COUNT || outputBufferSize >= CHUNK_BYTE_SIZE) {
|
||||
await flushBuffer()
|
||||
} else {
|
||||
scheduleFlush()
|
||||
}
|
||||
// When in file mode, we've already notified the user, so don't keep buffering
|
||||
} else {
|
||||
// After "Proceed While Running" (without background tracking): stream output directly to UI
|
||||
// But throttle if we're in file mode to avoid flooding UI
|
||||
if (!isWritingToFile) {
|
||||
await callbacks.say("command_output", line)
|
||||
}
|
||||
// After "Proceed While Running": stream output directly to UI
|
||||
await callbacks.say("command_output", line)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -357,7 +196,7 @@ export async function orchestrateCommandExecution(
|
||||
// Start timer to detect if waiting for completion takes too long
|
||||
completionTimer = setTimeout(() => {
|
||||
if (!completed) {
|
||||
telemetryService.captureTerminalHang(TerminalHangStage.WAITING_FOR_COMPLETION, terminalType)
|
||||
telemetryService.captureTerminalHang(TerminalHangStage.WAITING_FOR_COMPLETION)
|
||||
completionTimer = null
|
||||
}
|
||||
}, COMPLETION_TIMEOUT_MS)
|
||||
@@ -402,8 +241,9 @@ export async function orchestrateCommandExecution(
|
||||
if (error.message === "COMMAND_TIMEOUT") {
|
||||
// Timeout triggers "Proceed While Running" behavior
|
||||
didContinue = true
|
||||
process.continue()
|
||||
|
||||
// Clear all our timers first
|
||||
// Clear all our timers
|
||||
if (chunkTimer) {
|
||||
clearTimeout(chunkTimer)
|
||||
chunkTimer = null
|
||||
@@ -413,43 +253,6 @@ export async function orchestrateCommandExecution(
|
||||
completionTimer = null
|
||||
}
|
||||
|
||||
// If background tracking is available (standalone mode only), use it
|
||||
// This writes output to a log file and detaches the command
|
||||
if (onProceedWhileRunning) {
|
||||
const trackingResult = onProceedWhileRunning(outputLines)
|
||||
|
||||
// Set early return result BEFORE resuming the process
|
||||
// This prevents the orchestrator's listener from processing new lines
|
||||
const result = terminalManager.processOutput(outputLines)
|
||||
const logMsg = trackingResult?.logFilePath ? `Log file: ${trackingResult.logFilePath}\n` : ""
|
||||
const outputMsg = result.length > 0 ? `Output so far:\n${result}` : ""
|
||||
|
||||
backgroundTrackingResult = {
|
||||
userRejected: false,
|
||||
result: `Command timed out after ${timeoutSeconds} seconds. Running in background.\n${logMsg}${outputMsg}`,
|
||||
completed: false,
|
||||
outputLines,
|
||||
}
|
||||
|
||||
// Send log file message to UI BEFORE resuming the process
|
||||
if (trackingResult?.logFilePath) {
|
||||
await callbacks.say(
|
||||
"command_output",
|
||||
`\n⏱️ Command timed out. Output is being logged to: ${trackingResult.logFilePath}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Now resume the process - any new lines will be handled by the background tracker
|
||||
process.continue()
|
||||
// Clean up file-based logging if active before returning
|
||||
cleanupFileBased()
|
||||
return backgroundTrackingResult
|
||||
}
|
||||
|
||||
// VSCode terminal mode: no background tracking available
|
||||
// Just continue the process and return timeout result
|
||||
process.continue()
|
||||
|
||||
// Process any output we captured before timeout
|
||||
await setTimeoutPromise(50)
|
||||
const result = terminalManager.processOutput(outputLines)
|
||||
@@ -471,14 +274,6 @@ export async function orchestrateCommandExecution(
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we returned early due to background tracking
|
||||
// This happens when user clicks "Proceed While Running" with background tracking enabled
|
||||
if (backgroundTrackingResult) {
|
||||
// Clean up file-based logging if active before returning
|
||||
cleanupFileBased()
|
||||
return backgroundTrackingResult
|
||||
}
|
||||
|
||||
// Clear timer if process completes normally
|
||||
if (completionTimer) {
|
||||
clearTimeout(completionTimer)
|
||||
@@ -488,23 +283,7 @@ export async function orchestrateCommandExecution(
|
||||
// Wait for a short delay to ensure all messages are sent to the webview
|
||||
await setTimeoutPromise(50)
|
||||
|
||||
// Clean up file-based logging if active
|
||||
cleanupFileBased()
|
||||
|
||||
// Build result based on whether we used file-based logging
|
||||
let result: string
|
||||
let resultOutputLines: string[]
|
||||
|
||||
if (isWritingToFile) {
|
||||
// Build summary from first and last lines
|
||||
const skippedLines = totalLineCount - firstLines.length - lastLines.length
|
||||
const summaryLines = [...firstLines, `\n... (${skippedLines} lines written to ${largeOutputLogPath}) ...\n`, ...lastLines]
|
||||
result = terminalManager.processOutput(summaryLines)
|
||||
resultOutputLines = summaryLines
|
||||
} else {
|
||||
result = terminalManager.processOutput(outputLines)
|
||||
resultOutputLines = outputLines
|
||||
}
|
||||
const result = terminalManager.processOutput(outputLines)
|
||||
|
||||
if (didCancelViaUi) {
|
||||
return {
|
||||
@@ -513,8 +292,7 @@ export async function orchestrateCommandExecution(
|
||||
`Command cancelled. ${result.length > 0 ? `\nOutput captured before cancellation:\n${result}` : ""}`,
|
||||
),
|
||||
completed: false,
|
||||
outputLines: resultOutputLines,
|
||||
logFilePath: largeOutputLogPath || undefined,
|
||||
outputLines,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,30 +314,25 @@ export async function orchestrateCommandExecution(
|
||||
fileContentString,
|
||||
),
|
||||
completed: false,
|
||||
outputLines: resultOutputLines,
|
||||
logFilePath: largeOutputLogPath || undefined,
|
||||
outputLines,
|
||||
}
|
||||
}
|
||||
|
||||
if (completed) {
|
||||
const logFileMsg = largeOutputLogPath ? `\nFull output saved to: ${largeOutputLogPath}` : ""
|
||||
return {
|
||||
userRejected: false,
|
||||
result: `Command executed.${result.length > 0 ? `\nOutput:\n${result}` : ""}${logFileMsg}`,
|
||||
result: `Command executed.${result.length > 0 ? `\nOutput:\n${result}` : ""}`,
|
||||
completed: true,
|
||||
outputLines: resultOutputLines,
|
||||
logFilePath: largeOutputLogPath || undefined,
|
||||
outputLines,
|
||||
}
|
||||
} else {
|
||||
const logFileMsg = largeOutputLogPath ? `\nFull output saved to: ${largeOutputLogPath}` : ""
|
||||
return {
|
||||
userRejected: false,
|
||||
result: `Command is still running in the user's terminal.${
|
||||
result.length > 0 ? `\nHere's the output so far:\n${result}` : ""
|
||||
}${logFileMsg}\n\nYou will be updated on the terminal status and new output in the future.`,
|
||||
}\n\nYou will be updated on the terminal status and new output in the future.`,
|
||||
completed: false,
|
||||
outputLines: resultOutputLines,
|
||||
logFilePath: largeOutputLogPath || undefined,
|
||||
outputLines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* Terminal Constants
|
||||
*
|
||||
* Central location for all terminal-related constants.
|
||||
* This makes it easy to understand and tune terminal behavior.
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// Process "Hot" State Timeouts
|
||||
// =============================================================================
|
||||
// How long to wait after output before considering the process "cool"
|
||||
// This stalls API requests to let terminal output settle
|
||||
|
||||
/** Normal timeout after last output (2 seconds) */
|
||||
export const PROCESS_HOT_TIMEOUT_NORMAL = 2_000
|
||||
|
||||
/** Extended timeout for compilation/build commands (15 seconds) */
|
||||
export const PROCESS_HOT_TIMEOUT_COMPILING = 15_000
|
||||
|
||||
// =============================================================================
|
||||
// Output Buffering (CommandOrchestrator)
|
||||
// =============================================================================
|
||||
// Controls how output is chunked and sent to the UI
|
||||
|
||||
/** Lines to buffer before flushing to UI */
|
||||
export const CHUNK_LINE_COUNT = 20
|
||||
|
||||
/** Bytes to buffer before flushing to UI */
|
||||
export const CHUNK_BYTE_SIZE = 2048 // 2KB
|
||||
|
||||
/** Debounce time for buffer flush */
|
||||
export const CHUNK_DEBOUNCE_MS = 100
|
||||
|
||||
/** Timeout to detect stuck buffer */
|
||||
export const BUFFER_STUCK_TIMEOUT_MS = 6000 // 6 seconds
|
||||
|
||||
/** Timeout to detect stuck completion */
|
||||
export const COMPLETION_TIMEOUT_MS = 6000 // 6 seconds
|
||||
|
||||
// =============================================================================
|
||||
// Large Output Protection
|
||||
// =============================================================================
|
||||
// Prevents memory exhaustion and context window overflow
|
||||
|
||||
/** Switch to file-based logging after this many lines */
|
||||
export const MAX_LINES_BEFORE_FILE = 1000
|
||||
|
||||
/** Switch to file-based logging after this many bytes */
|
||||
export const MAX_BYTES_BEFORE_FILE = 512 * 1024 // 512KB
|
||||
|
||||
/** Lines to keep at start/end for summary when truncating */
|
||||
export const SUMMARY_LINES_TO_KEEP = 100
|
||||
|
||||
/** Maximum size for fullOutput storage (memory protection) */
|
||||
export const MAX_FULL_OUTPUT_SIZE = 1024 * 1024 // 1MB
|
||||
|
||||
/** Maximum lines to return from getUnretrievedOutput */
|
||||
export const MAX_UNRETRIEVED_LINES = 500
|
||||
|
||||
/** Lines to keep at start/end when truncating unretrieved output */
|
||||
export const TRUNCATE_KEEP_LINES = 100
|
||||
|
||||
// =============================================================================
|
||||
// Output Line Limits (processOutput)
|
||||
// =============================================================================
|
||||
// Controls truncation when returning output to AI
|
||||
|
||||
/** Default max lines for command output */
|
||||
export const DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT = 500
|
||||
|
||||
/** Max lines for subagent commands (more context needed) */
|
||||
export const DEFAULT_SUBAGENT_TERMINAL_OUTPUT_LINE_LIMIT = 2000
|
||||
|
||||
// =============================================================================
|
||||
// Background Command Tracking
|
||||
// =============================================================================
|
||||
// Controls background command behavior for "Proceed While Running"
|
||||
|
||||
/** Hard timeout for background commands to prevent zombie processes (10 minutes) */
|
||||
export const BACKGROUND_COMMAND_TIMEOUT_MS = 10 * 60 * 1000
|
||||
|
||||
// =============================================================================
|
||||
// Compilation Detection Markers
|
||||
// =============================================================================
|
||||
// Used to detect if a command is compiling/building
|
||||
|
||||
/** Markers that indicate compilation is starting */
|
||||
export const COMPILING_MARKERS = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
|
||||
|
||||
/** Markers that indicate compilation is done (nullify extended timeout) */
|
||||
export const COMPILING_NULLIFIERS = [
|
||||
"compiled",
|
||||
"success",
|
||||
"finish",
|
||||
"complete",
|
||||
"succeed",
|
||||
"done",
|
||||
"end",
|
||||
"stop",
|
||||
"exit",
|
||||
"terminate",
|
||||
"error",
|
||||
"fail",
|
||||
]
|
||||
|
||||
/**
|
||||
* Check if terminal output indicates compilation/building.
|
||||
* Matches markers anywhere in the output.
|
||||
*/
|
||||
export function isCompilingOutput(data: string): boolean {
|
||||
const lowerData = data.toLowerCase()
|
||||
const hasMarker = COMPILING_MARKERS.some((marker) => lowerData.includes(marker.toLowerCase()))
|
||||
const hasNullifier = COMPILING_NULLIFIERS.some((nullifier) => lowerData.includes(nullifier.toLowerCase()))
|
||||
return hasMarker && !hasNullifier
|
||||
}
|
||||
@@ -23,7 +23,17 @@
|
||||
export { CommandExecutor } from "./CommandExecutor"
|
||||
|
||||
// Export command orchestrator (shared logic)
|
||||
export { findLastIndex, orchestrateCommandExecution } from "./CommandOrchestrator"
|
||||
export {
|
||||
BUFFER_STUCK_TIMEOUT_MS,
|
||||
CHUNK_BYTE_SIZE,
|
||||
CHUNK_DEBOUNCE_MS,
|
||||
CHUNK_LINE_COUNT,
|
||||
COMPLETION_TIMEOUT_MS,
|
||||
findLastIndex,
|
||||
orchestrateCommandExecution,
|
||||
} from "./CommandOrchestrator"
|
||||
|
||||
// Export terminal process interface
|
||||
|
||||
// Export standalone terminal implementations
|
||||
export { StandaloneTerminal } from "./standalone/StandaloneTerminal"
|
||||
|
||||
@@ -4,29 +4,12 @@
|
||||
* This class provides the same interface as VSCode's TerminalManager but works
|
||||
* in CLI and JetBrains environments by using subprocess management instead of
|
||||
* VSCode's terminal API.
|
||||
*
|
||||
* Also handles background command tracking for "Proceed While Running" functionality:
|
||||
* - Logs output to temp files for later retrieval
|
||||
* - Tracks command status (running, completed, error, timed_out)
|
||||
* - Implements 10-minute hard timeout to prevent zombie processes
|
||||
* - Provides summary for environment details
|
||||
*/
|
||||
|
||||
import * as fs from "fs"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {
|
||||
BACKGROUND_COMMAND_TIMEOUT_MS,
|
||||
DEFAULT_SUBAGENT_TERMINAL_OUTPUT_LINE_LIMIT,
|
||||
DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT,
|
||||
} from "../constants"
|
||||
import type { BackgroundCommand, ITerminalManager, TerminalInfo, TerminalProcessResultPromise } from "../types"
|
||||
import type { ITerminalManager, TerminalInfo, TerminalProcessResultPromise } from "../types"
|
||||
import { StandaloneTerminalProcess } from "./StandaloneTerminalProcess"
|
||||
import { StandaloneTerminalRegistry } from "./StandaloneTerminalRegistry"
|
||||
|
||||
// Re-export BackgroundCommand for backwards compatibility
|
||||
export type { BackgroundCommand }
|
||||
|
||||
/**
|
||||
* Helper function to merge a process with a promise for the TerminalProcessResultPromise type.
|
||||
* This allows the returned object to be both awaitable and have event methods.
|
||||
@@ -80,27 +63,14 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
private terminalReuseEnabled: boolean = true
|
||||
|
||||
/** Maximum output lines to keep */
|
||||
private terminalOutputLineLimit: number = DEFAULT_TERMINAL_OUTPUT_LINE_LIMIT
|
||||
private terminalOutputLineLimit: number = 500
|
||||
|
||||
/** Maximum output lines for subagent commands */
|
||||
private subagentTerminalOutputLineLimit: number = DEFAULT_SUBAGENT_TERMINAL_OUTPUT_LINE_LIMIT
|
||||
private subagentTerminalOutputLineLimit: number = 2000
|
||||
|
||||
/** Default terminal profile */
|
||||
private defaultTerminalProfile: string = "default"
|
||||
|
||||
// =========================================================================
|
||||
// Background Command Tracking
|
||||
// =========================================================================
|
||||
|
||||
/** Map of background command ID to command info */
|
||||
private backgroundCommands: Map<string, BackgroundCommand> = new Map()
|
||||
|
||||
/** Map of background command ID to log file write stream */
|
||||
private logStreams: Map<string, fs.WriteStream> = new Map()
|
||||
|
||||
/** Map of background command ID to timeout handle */
|
||||
private backgroundTimeouts: Map<string, NodeJS.Timeout> = new Map()
|
||||
|
||||
/**
|
||||
* Run a command in the specified terminal.
|
||||
* @param terminalInfo The terminal to run the command in
|
||||
@@ -118,8 +88,9 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
terminalInfo.busy = false
|
||||
})
|
||||
|
||||
process.once("error", (_error: Error) => {
|
||||
process.once("error", (error: Error) => {
|
||||
terminalInfo.busy = false
|
||||
console.error(`[StandaloneTerminalManager] Command error on terminal ${terminalInfo.id}:`, error)
|
||||
})
|
||||
|
||||
// Create promise for the process
|
||||
@@ -167,6 +138,7 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
availableTerminal.terminal.shellIntegration.cwd.fsPath = cwd
|
||||
}
|
||||
this.terminalIds.add(availableTerminal.id)
|
||||
console.log(`[StandaloneTerminalManager] Reused terminal ${availableTerminal.id} with cd`)
|
||||
return availableTerminal
|
||||
}
|
||||
}
|
||||
@@ -186,19 +158,10 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
* @returns Array of terminal info with id and last command
|
||||
*/
|
||||
getTerminals(busy: boolean): { id: number; lastCommand: string }[] {
|
||||
const allTerminalIds = Array.from(this.terminalIds)
|
||||
|
||||
const terminals = allTerminalIds
|
||||
return Array.from(this.terminalIds)
|
||||
.map((id) => this.registry.getTerminal(id))
|
||||
.filter((t): t is TerminalInfo => {
|
||||
if (t === undefined) {
|
||||
return false
|
||||
}
|
||||
return t.busy === busy
|
||||
})
|
||||
.filter((t): t is TerminalInfo => t !== undefined && t.busy === busy)
|
||||
.map((t) => ({ id: t.id, lastCommand: t.lastCommand }))
|
||||
|
||||
return terminals
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,9 +213,6 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
* Dispose of all terminals and clean up resources.
|
||||
*/
|
||||
disposeAll(): void {
|
||||
// Dispose background commands first
|
||||
this.disposeBackgroundCommands()
|
||||
|
||||
// Terminate all processes
|
||||
for (const [_terminalId, process] of this.processes) {
|
||||
if (process && process.terminate) {
|
||||
@@ -409,212 +369,4 @@ export class StandaloneTerminalManager implements ITerminalManager {
|
||||
closeAllTerminals(): number {
|
||||
return this.closeTerminals(() => true, true)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Background Command Tracking Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Track a command that will continue running in the background.
|
||||
* Called when user clicks "Proceed While Running".
|
||||
* Creates a log file and pipes output to it.
|
||||
* Sets up a 10-minute hard timeout to prevent zombie processes.
|
||||
*
|
||||
* @param process The terminal process to track
|
||||
* @param command The command string being executed
|
||||
* @param existingOutput Output lines already captured before tracking started
|
||||
* @returns The background command info with log file path
|
||||
*/
|
||||
trackBackgroundCommand(
|
||||
process: TerminalProcessResultPromise,
|
||||
command: string,
|
||||
existingOutput: string[] = [],
|
||||
): BackgroundCommand {
|
||||
const id = `background-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
const logFilePath = path.join(os.tmpdir(), `cline-${id}.log`)
|
||||
|
||||
const backgroundCommand: BackgroundCommand = {
|
||||
id,
|
||||
command,
|
||||
startTime: Date.now(),
|
||||
status: "running",
|
||||
logFilePath,
|
||||
lineCount: existingOutput.length,
|
||||
process,
|
||||
}
|
||||
|
||||
// Create write stream for log file
|
||||
const logStream = fs.createWriteStream(logFilePath, { flags: "a" })
|
||||
this.logStreams.set(id, logStream)
|
||||
|
||||
// Write existing output that was captured before tracking started
|
||||
if (existingOutput.length > 0) {
|
||||
logStream.write(existingOutput.join("\n") + "\n")
|
||||
}
|
||||
|
||||
// Pipe future process output to log file
|
||||
process.on("line", (line: string) => {
|
||||
backgroundCommand.lineCount++
|
||||
logStream.write(line + "\n")
|
||||
})
|
||||
|
||||
// Set up 10-minute hard timeout to prevent zombie processes
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (backgroundCommand.status === "running") {
|
||||
backgroundCommand.status = "timed_out"
|
||||
logStream.write("\n[TIMEOUT] Process killed after 10 minutes\n")
|
||||
logStream.end()
|
||||
|
||||
// Terminate the process if it has a terminate method
|
||||
if (process && typeof (process as any).terminate === "function") {
|
||||
;(process as any).terminate()
|
||||
}
|
||||
}
|
||||
}, BACKGROUND_COMMAND_TIMEOUT_MS)
|
||||
this.backgroundTimeouts.set(id, timeoutId)
|
||||
|
||||
// Listen for completion - clear timeout
|
||||
process.on("completed", () => {
|
||||
// Guard: Skip if already handled by timeout
|
||||
if (backgroundCommand.status !== "running") {
|
||||
return
|
||||
}
|
||||
const timeout = this.backgroundTimeouts.get(id)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
this.backgroundTimeouts.delete(id)
|
||||
}
|
||||
backgroundCommand.status = "completed"
|
||||
logStream.end()
|
||||
})
|
||||
|
||||
// Listen for errors - clear timeout
|
||||
process.on("error", (error: Error) => {
|
||||
// Guard: Skip if already handled by timeout
|
||||
if (backgroundCommand.status !== "running") {
|
||||
return
|
||||
}
|
||||
const timeout = this.backgroundTimeouts.get(id)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
this.backgroundTimeouts.delete(id)
|
||||
}
|
||||
backgroundCommand.status = "error"
|
||||
// Try to extract exit code from error message if available
|
||||
const exitCodeMatch = error.message.match(/exit code (\d+)/)
|
||||
if (exitCodeMatch) {
|
||||
backgroundCommand.exitCode = parseInt(exitCodeMatch[1], 10)
|
||||
}
|
||||
logStream.end()
|
||||
})
|
||||
|
||||
this.backgroundCommands.set(id, backgroundCommand)
|
||||
return backgroundCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific background command by ID.
|
||||
*/
|
||||
getBackgroundCommand(id: string): BackgroundCommand | undefined {
|
||||
return this.backgroundCommands.get(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tracked background commands.
|
||||
*/
|
||||
getAllBackgroundCommands(): BackgroundCommand[] {
|
||||
return Array.from(this.backgroundCommands.values())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get only running background commands.
|
||||
*/
|
||||
getRunningBackgroundCommands(): BackgroundCommand[] {
|
||||
return this.getAllBackgroundCommands().filter((c) => c.status === "running")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any active background commands.
|
||||
*/
|
||||
hasActiveBackgroundCommands(): boolean {
|
||||
return this.getRunningBackgroundCommands().length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel/terminate a specific background command.
|
||||
* @param id The background command ID to cancel
|
||||
* @returns true if cancelled, false if not found or already completed
|
||||
*/
|
||||
cancelBackgroundCommand(id: string): boolean {
|
||||
const command = this.backgroundCommands.get(id)
|
||||
if (!command || command.status !== "running") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Clear timeout
|
||||
const timeout = this.backgroundTimeouts.get(id)
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
this.backgroundTimeouts.delete(id)
|
||||
}
|
||||
|
||||
// Close log stream
|
||||
const logStream = this.logStreams.get(id)
|
||||
if (logStream) {
|
||||
logStream.write("\n[CANCELLED] Command cancelled by user\n")
|
||||
logStream.end()
|
||||
this.logStreams.delete(id)
|
||||
}
|
||||
|
||||
// Terminate process
|
||||
if (command.process && typeof (command.process as any).terminate === "function") {
|
||||
;(command.process as any).terminate()
|
||||
}
|
||||
|
||||
command.status = "error"
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary string for environment details.
|
||||
* Shows running background commands with duration, line count, and log paths.
|
||||
*/
|
||||
getBackgroundCommandsSummary(): string {
|
||||
const running = this.getRunningBackgroundCommands()
|
||||
if (running.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const lines = [`# Background Commands (${running.length} running)`]
|
||||
for (const c of running) {
|
||||
const duration = Math.round((Date.now() - c.startTime) / 1000 / 60)
|
||||
lines.push(`- ${c.command} (running ${duration}m, ${c.lineCount} lines, log: ${c.logFilePath})`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all background command resources.
|
||||
* Called when disposing the manager.
|
||||
*/
|
||||
disposeBackgroundCommands(): void {
|
||||
// Clear all timeouts
|
||||
for (const [_id, timeout] of this.backgroundTimeouts) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
this.backgroundTimeouts.clear()
|
||||
|
||||
// Close all log streams
|
||||
for (const [_id, logStream] of this.logStreams) {
|
||||
try {
|
||||
logStream.end()
|
||||
} catch (_error) {
|
||||
// Ignore errors when closing log streams
|
||||
}
|
||||
}
|
||||
this.logStreams.clear()
|
||||
|
||||
// Clear command tracking
|
||||
this.backgroundCommands.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,9 @@
|
||||
* Implements ITerminalProcess interface for polymorphic usage with CommandExecutor.
|
||||
*/
|
||||
|
||||
import { telemetryService } from "@services/telemetry"
|
||||
import { ChildProcess, spawn } from "child_process"
|
||||
import { EventEmitter } from "events"
|
||||
import { terminateProcessTree } from "@/utils/process-termination"
|
||||
|
||||
import {
|
||||
isCompilingOutput,
|
||||
MAX_FULL_OUTPUT_SIZE,
|
||||
MAX_UNRETRIEVED_LINES,
|
||||
PROCESS_HOT_TIMEOUT_COMPILING,
|
||||
PROCESS_HOT_TIMEOUT_NORMAL,
|
||||
TRUNCATE_KEEP_LINES,
|
||||
} from "../constants"
|
||||
import type { ITerminal, ITerminalProcess, TerminalProcessEvents } from "../types"
|
||||
|
||||
/**
|
||||
@@ -77,6 +67,8 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
* @param command The command to execute
|
||||
*/
|
||||
async run(terminal: ITerminal, command: string): Promise<void> {
|
||||
console.log(`[StandaloneTerminal] Running command: ${command}`)
|
||||
|
||||
// Get shell and working directory from terminal
|
||||
const shell = (terminal as any)._shellPath || this.getDefaultShell()
|
||||
const cwd = (terminal as any)._cwd || process.cwd()
|
||||
@@ -112,12 +104,8 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
// Spawn the process with special handling for "cmd.exe"
|
||||
this.childProcess = spawn("cmd.exe", shellArgs, shellOptions)
|
||||
} else {
|
||||
// Spawn the process with detached: true to create a process group
|
||||
// This allows us to kill the entire process tree when terminating
|
||||
this.childProcess = spawn(shell, shellArgs, {
|
||||
...shellOptions,
|
||||
detached: true,
|
||||
})
|
||||
// Spawn the process
|
||||
this.childProcess = spawn(shell, shellArgs, shellOptions)
|
||||
}
|
||||
|
||||
// Track process state
|
||||
@@ -144,7 +132,8 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
})
|
||||
|
||||
// Handle process completion
|
||||
this.childProcess.on("close", (code: number | null, _signal: NodeJS.Signals | null) => {
|
||||
this.childProcess.on("close", (code: number | null, signal: NodeJS.Signals | null) => {
|
||||
console.log(`[StandaloneTerminal] Process closed with code ${code}, signal ${signal}`)
|
||||
this.exitCode = code
|
||||
this.isCompleted = true
|
||||
this.emitRemainingBuffer()
|
||||
@@ -155,18 +144,13 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
this.isHot = false
|
||||
}
|
||||
|
||||
// Track terminal execution telemetry
|
||||
const success = code === 0 || code === null
|
||||
telemetryService.captureTerminalExecution(success, "standalone", "child_process")
|
||||
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
})
|
||||
|
||||
// Handle process errors
|
||||
this.childProcess.on("error", (error: Error) => {
|
||||
// Track terminal execution error telemetry
|
||||
telemetryService.captureTerminalExecution(false, "standalone", "child_process_error")
|
||||
console.error(`[StandaloneTerminal] Process error:`, error)
|
||||
this.emit("error", error)
|
||||
})
|
||||
|
||||
@@ -174,6 +158,7 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
;(terminal as any)._process = this.childProcess
|
||||
;(terminal as any)._processId = this.childProcess.pid
|
||||
} catch (error) {
|
||||
console.error(`[StandaloneTerminal] Failed to spawn process:`, error)
|
||||
this.emit("error", error)
|
||||
}
|
||||
}
|
||||
@@ -191,25 +176,37 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
}
|
||||
|
||||
// Check for compilation markers to adjust hot timeout
|
||||
const isCompiling = isCompilingOutput(data)
|
||||
const hotTimeout = isCompiling ? PROCESS_HOT_TIMEOUT_COMPILING : PROCESS_HOT_TIMEOUT_NORMAL
|
||||
const compilingMarkers = ["compiling", "building", "bundling", "transpiling", "generating", "starting"]
|
||||
const markerNullifiers = [
|
||||
"compiled",
|
||||
"success",
|
||||
"finish",
|
||||
"complete",
|
||||
"succeed",
|
||||
"done",
|
||||
"end",
|
||||
"stop",
|
||||
"exit",
|
||||
"terminate",
|
||||
"error",
|
||||
"fail",
|
||||
]
|
||||
|
||||
const isCompiling =
|
||||
compilingMarkers.some((marker) => data.toLowerCase().includes(marker.toLowerCase())) &&
|
||||
!markerNullifiers.some((nullifier) => data.toLowerCase().includes(nullifier.toLowerCase()))
|
||||
|
||||
const hotTimeout = isCompiling ? 15000 : 2000
|
||||
this.hotTimer = setTimeout(() => {
|
||||
this.isHot = false
|
||||
}, hotTimeout)
|
||||
|
||||
// Store full output with size cap to prevent memory exhaustion
|
||||
// Store full output
|
||||
this.fullOutput += data
|
||||
|
||||
// Cap fullOutput at MAX_FULL_OUTPUT_SIZE to prevent memory exhaustion
|
||||
if (this.fullOutput.length > MAX_FULL_OUTPUT_SIZE) {
|
||||
// Keep last half of max size
|
||||
this.fullOutput = this.fullOutput.slice(-MAX_FULL_OUTPUT_SIZE / 2)
|
||||
// Reset lastRetrievedIndex since we truncated the beginning
|
||||
this.lastRetrievedIndex = 0
|
||||
}
|
||||
|
||||
if (this.isListening) {
|
||||
this.emitLines(data)
|
||||
this.lastRetrievedIndex = this.fullOutput.length - this.buffer.length
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,37 +240,22 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
|
||||
/**
|
||||
* Continue execution without waiting for completion.
|
||||
* Emits "continue" event but keeps emitting "line" events for background tracking.
|
||||
*
|
||||
* Note: We intentionally do NOT call removeAllListeners("line") or set isListening=false
|
||||
* because background command tracking needs to continue receiving output lines
|
||||
* after the user clicks "Proceed While Running".
|
||||
* Stops event emission and resolves the promise.
|
||||
*/
|
||||
continue(): void {
|
||||
this.emitRemainingBuffer()
|
||||
// Keep isListening = true so we continue emitting "line" events
|
||||
// This is needed for background command tracking to log output to file
|
||||
this.isListening = false
|
||||
this.removeAllListeners("line")
|
||||
this.emit("continue")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get output that hasn't been retrieved yet.
|
||||
* Truncates if output is too large to prevent context window overflow.
|
||||
* @returns The unretrieved output (truncated if necessary)
|
||||
* @returns The unretrieved output
|
||||
*/
|
||||
getUnretrievedOutput(): string {
|
||||
const unretrieved = this.fullOutput.slice(this.lastRetrievedIndex)
|
||||
this.lastRetrievedIndex = this.fullOutput.length
|
||||
|
||||
// Truncate if too many lines to prevent context overflow
|
||||
const lines = unretrieved.split("\n")
|
||||
if (lines.length > MAX_UNRETRIEVED_LINES) {
|
||||
const first = lines.slice(0, TRUNCATE_KEEP_LINES)
|
||||
const last = lines.slice(-TRUNCATE_KEEP_LINES)
|
||||
const skipped = lines.length - first.length - last.length
|
||||
return this.removeLastLineArtifacts([...first, `\n... (${skipped} lines truncated) ...\n`, ...last].join("\n"))
|
||||
}
|
||||
|
||||
return this.removeLastLineArtifacts(unretrieved)
|
||||
}
|
||||
|
||||
@@ -323,29 +305,41 @@ export class StandaloneTerminalProcess extends EventEmitter<TerminalProcessEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate the process and all its children.
|
||||
*
|
||||
* Uses terminateProcessTree utility which handles:
|
||||
* - Cross-platform process tree termination via tree-kill
|
||||
* - Graceful shutdown with SIGTERM
|
||||
* - SIGKILL fallback after 2 second timeout
|
||||
* Terminate the process if it's still running.
|
||||
*/
|
||||
async terminate(): Promise<void> {
|
||||
terminate(): void {
|
||||
if (!this.childProcess || this.isCompleted) {
|
||||
console.log(`[StandaloneTerminal] Process already completed or doesn't exist, skipping termination`)
|
||||
return
|
||||
}
|
||||
|
||||
const pid = this.childProcess.pid
|
||||
if (!pid) {
|
||||
// Fallback: try to kill the process directly if PID is unavailable
|
||||
this.childProcess.kill("SIGTERM")
|
||||
return
|
||||
}
|
||||
console.log(`[StandaloneTerminal] Terminating process ${pid} with SIGTERM`)
|
||||
|
||||
await terminateProcessTree({
|
||||
pid,
|
||||
childProcess: this.childProcess,
|
||||
isCompleted: () => this.isCompleted,
|
||||
})
|
||||
try {
|
||||
this.childProcess.kill("SIGTERM")
|
||||
|
||||
// Force kill after timeout if process doesn't exit gracefully
|
||||
setTimeout(() => {
|
||||
if (!this.isCompleted && this.childProcess) {
|
||||
console.log(`[StandaloneTerminal] Process ${pid} did not exit gracefully, force killing with SIGKILL`)
|
||||
try {
|
||||
this.childProcess.kill("SIGKILL")
|
||||
} catch (killError) {
|
||||
console.error(`[StandaloneTerminal] Failed to force kill process ${pid}:`, killError)
|
||||
}
|
||||
} else {
|
||||
console.log(`[StandaloneTerminal] Process ${pid} exited gracefully`)
|
||||
}
|
||||
}, 5000)
|
||||
} catch (error) {
|
||||
console.error(`[StandaloneTerminal] Failed to send SIGTERM to process ${pid}:`, error)
|
||||
// Try SIGKILL immediately if SIGTERM fails
|
||||
try {
|
||||
this.childProcess.kill("SIGKILL")
|
||||
} catch (killError) {
|
||||
console.error(`[StandaloneTerminal] Failed to send SIGKILL to process ${pid}:`, killError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,8 @@ export interface ITerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
* Terminate the process if it's still running.
|
||||
* Only available for standalone processes (child_process).
|
||||
* VSCode terminal processes cannot be terminated via this interface.
|
||||
*
|
||||
* May be async to allow for graceful shutdown with SIGKILL fallback.
|
||||
*/
|
||||
terminate?(): void | Promise<void>
|
||||
terminate?(): void
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -245,51 +243,12 @@ export interface StandaloneTerminalOptions {
|
||||
shellPath?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Background Command Types
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Represents a command running in the background after user clicked "Proceed While Running".
|
||||
* Used by StandaloneTerminalManager to track background commands.
|
||||
*/
|
||||
export interface BackgroundCommand {
|
||||
/** Unique identifier for the background command */
|
||||
id: string
|
||||
/** The command string being executed */
|
||||
command: string
|
||||
/** Timestamp when the command started */
|
||||
startTime: number
|
||||
/** Current status of the command */
|
||||
status: "running" | "completed" | "error" | "timed_out"
|
||||
/** Path to the log file where output is being written */
|
||||
logFilePath: string
|
||||
/** Number of lines written to the log file */
|
||||
lineCount: number
|
||||
/** Exit code if the command completed or errored */
|
||||
exitCode?: number
|
||||
/** The terminal process running the command */
|
||||
process: TerminalProcessResultPromise
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Command Executor Types
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Tracker for shell integration warnings to determine when to show background terminal suggestion.
|
||||
* Used internally by CommandExecutor to track warning frequency.
|
||||
*/
|
||||
export interface ShellIntegrationWarningTracker {
|
||||
/** Timestamps of recent shell integration warnings */
|
||||
timestamps: number[]
|
||||
/** Timestamp when the suggestion was last shown */
|
||||
lastSuggestionShown?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an active background command that can be cancelled
|
||||
* @deprecated Use BackgroundCommand instead
|
||||
*/
|
||||
export interface ActiveBackgroundCommand {
|
||||
process: {
|
||||
@@ -325,13 +284,10 @@ export interface CommandExecutorCallbacks {
|
||||
ask: (type: string, text?: string, partial?: boolean) => Promise<AskResponse>
|
||||
/** Update the background command running state in the controller */
|
||||
updateBackgroundCommandState: (running: boolean) => void
|
||||
/**
|
||||
* Update a cline message by index
|
||||
* Supports updating commandCompleted status and/or text content
|
||||
*/
|
||||
updateClineMessage: (index: number, updates: { commandCompleted?: boolean; text?: string }) => Promise<void>
|
||||
/** Update a cline message by index */
|
||||
updateClineMessage: (index: number, updates: { commandCompleted?: boolean }) => Promise<void>
|
||||
/** Get cline messages array */
|
||||
getClineMessages: () => Array<{ ask?: string; say?: string; text?: string }>
|
||||
getClineMessages: () => Array<{ ask?: string; say?: string }>
|
||||
/** Add content to user message for next API request */
|
||||
addToUserMessageContent: (content: { type: string; text: string }) => void
|
||||
}
|
||||
@@ -371,18 +327,6 @@ export interface OrchestrationOptions {
|
||||
onOutputLine?: (line: string) => void
|
||||
/** Whether to show shell integration warning with suggestion */
|
||||
showShellIntegrationSuggestion?: boolean
|
||||
/**
|
||||
* Callback invoked when user clicks "Proceed While Running".
|
||||
* Used to start background command tracking in the terminal manager.
|
||||
* @param existingOutput The output lines captured so far (to write to log file)
|
||||
* @returns The log file path if tracking was started, undefined otherwise
|
||||
*/
|
||||
onProceedWhileRunning?: (existingOutput: string[]) => { logFilePath: string } | undefined
|
||||
/**
|
||||
* The type of terminal being used for telemetry tracking.
|
||||
* Defaults to "vscode" for backward compatibility.
|
||||
*/
|
||||
terminalType?: "vscode" | "standalone"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,6 +341,4 @@ export interface OrchestrationResult {
|
||||
completed: boolean
|
||||
/** All output lines captured */
|
||||
outputLines: string[]
|
||||
/** Path to log file if output was too large and written to file */
|
||||
logFilePath?: string
|
||||
}
|
||||
|
||||
@@ -38,10 +38,6 @@ export interface ClineAuthInfo {
|
||||
startedAt?: number
|
||||
}
|
||||
|
||||
export interface InternalAuthState extends Omit<AuthState, "user"> {
|
||||
authInfo?: ClineAuthInfo
|
||||
}
|
||||
|
||||
export interface ClineAccountUserInfo {
|
||||
createdAt: string
|
||||
displayName: string
|
||||
@@ -68,11 +64,9 @@ export interface ClineAccountOrganization {
|
||||
|
||||
export class AuthService {
|
||||
protected static instance: AuthService | null = null
|
||||
protected provider: IAuthProvider | null = null
|
||||
protected authState: InternalAuthState = {
|
||||
hasSessionData: false,
|
||||
pending: false,
|
||||
}
|
||||
protected _authenticated: boolean = false
|
||||
protected _clineAuthInfo: ClineAuthInfo | null = null
|
||||
protected _provider: IAuthProvider | null = null
|
||||
protected _activeAuthStatusUpdateHandlers = new Set<StreamingResponseHandler<AuthState>>()
|
||||
protected _handlerToController = new Map<StreamingResponseHandler<AuthState>, Controller>()
|
||||
protected _controller: Controller
|
||||
@@ -122,11 +116,11 @@ export class AuthService {
|
||||
* Refreshing it if necessary.
|
||||
*/
|
||||
async getAuthToken(): Promise<string | null> {
|
||||
if (!this.provider) {
|
||||
if (!this._provider) {
|
||||
throw new Error("Auth provider is not set")
|
||||
}
|
||||
|
||||
return this.internalGetAuthToken(this.provider)
|
||||
return this.internalGetAuthToken(this._provider)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,10 +128,10 @@ export class AuthService {
|
||||
* @returns The active organization ID, or null if no active organization exists
|
||||
*/
|
||||
getActiveOrganizationId(): string | null {
|
||||
if (!this.authState?.authInfo?.userInfo?.organizations) {
|
||||
if (!this._clineAuthInfo?.userInfo?.organizations) {
|
||||
return null
|
||||
}
|
||||
const activeOrg = this.authState.authInfo?.userInfo.organizations.find((org) => org.active)
|
||||
const activeOrg = this._clineAuthInfo.userInfo.organizations.find((org) => org.active)
|
||||
return activeOrg?.organizationId ?? null
|
||||
}
|
||||
|
||||
@@ -146,26 +140,26 @@ export class AuthService {
|
||||
* @returns Array of organizations, or undefined if not available
|
||||
*/
|
||||
getUserOrganizations(): ClineAccountOrganization[] | undefined {
|
||||
return this.authState?.authInfo?.userInfo?.organizations
|
||||
return this._clineAuthInfo?.userInfo?.organizations
|
||||
}
|
||||
|
||||
private async internalGetAuthToken(provider: IAuthProvider): Promise<string | null> {
|
||||
try {
|
||||
let clineAccountAuthToken = this.authState?.authInfo?.idToken
|
||||
if (!this.authState || !clineAccountAuthToken || this.authState?.authInfo?.provider !== provider.name) {
|
||||
let clineAccountAuthToken = this._clineAuthInfo?.idToken
|
||||
if (!this._clineAuthInfo || !clineAccountAuthToken || this._clineAuthInfo.provider !== provider.name) {
|
||||
// Not authenticated
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if token has expired
|
||||
|
||||
if (await provider.shouldRefreshIdToken(clineAccountAuthToken, this.authState.authInfo.expiresAt)) {
|
||||
if (await provider.shouldRefreshIdToken(clineAccountAuthToken, this._clineAuthInfo.expiresAt)) {
|
||||
// If a refresh is already in progress, wait for it to complete
|
||||
if (this._refreshPromise) {
|
||||
Logger.info("Token refresh already in progress, waiting for completion")
|
||||
await this._refreshPromise
|
||||
// After waiting, return the updated token
|
||||
clineAccountAuthToken = this.authState?.authInfo?.idToken
|
||||
clineAccountAuthToken = this._clineAuthInfo?.idToken
|
||||
return clineAccountAuthToken ? `workos:${clineAccountAuthToken}` : null
|
||||
}
|
||||
|
||||
@@ -175,18 +169,19 @@ export class AuthService {
|
||||
|
||||
try {
|
||||
const updatedAuthInfo = await provider.retrieveClineAuthInfo(this._controller)
|
||||
this.authState = updatedAuthInfo
|
||||
clineAccountAuthToken = updatedAuthInfo.authInfo?.idToken
|
||||
authStatusChanged = true
|
||||
if (updatedAuthInfo) {
|
||||
this._clineAuthInfo = updatedAuthInfo
|
||||
this._authenticated = true
|
||||
clineAccountAuthToken = updatedAuthInfo.idToken
|
||||
authStatusChanged = true
|
||||
}
|
||||
} catch (error) {
|
||||
// Only log out for permanent auth failures, not network issues
|
||||
if (error instanceof AuthInvalidTokenError) {
|
||||
Logger.error("Token is invalid or expired:", error)
|
||||
this.authState = {
|
||||
hasSessionData: false,
|
||||
pending: false,
|
||||
}
|
||||
telemetryService.captureAuthLoggedOut(this.provider?.name, LogoutReason.ERROR_RECOVERY)
|
||||
this._clineAuthInfo = null
|
||||
this._authenticated = false
|
||||
telemetryService.captureAuthLoggedOut(this._provider?.name, LogoutReason.ERROR_RECOVERY)
|
||||
authStatusChanged = true
|
||||
} else if (error instanceof AuthNetworkError) {
|
||||
Logger.error("Network error refreshing token", error)
|
||||
@@ -220,7 +215,7 @@ export class AuthService {
|
||||
|
||||
protected _initProvider(): void {
|
||||
// Only ClineAuthProvider is supported going forward
|
||||
this.provider = new ClineAuthProvider()
|
||||
this._provider = new ClineAuthProvider()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,15 +223,15 @@ export class AuthService {
|
||||
* @returns The provider name (e.g., "cline", "firebase"), or null if not authenticated
|
||||
*/
|
||||
getProviderName(): string | null {
|
||||
return this.authState?.authInfo?.provider ?? null
|
||||
return this._clineAuthInfo?.provider ?? null
|
||||
}
|
||||
|
||||
getInfo(): AuthState {
|
||||
// TODO: this logic should be cleaner, but this will determine the authentication state for the webview -- if a user object is returned then the webview assumes authenticated, otherwise it assumes logged out (we previously returned a UserInfo object with empty fields, and this represented a broken logged in state)
|
||||
let user: any = null
|
||||
if (this.authState.authInfo) {
|
||||
const userInfo = this.authState.authInfo?.userInfo
|
||||
this.authState.authInfo.userInfo.appBaseUrl = ClineEnv.config()?.appBaseUrl
|
||||
if (this._clineAuthInfo && this._authenticated) {
|
||||
const userInfo = this._clineAuthInfo.userInfo
|
||||
this._clineAuthInfo.userInfo.appBaseUrl = ClineEnv.config()?.appBaseUrl
|
||||
|
||||
user = UserInfo.create({
|
||||
// TODO: create proto for new user info type
|
||||
@@ -250,21 +245,17 @@ export class AuthService {
|
||||
|
||||
return AuthState.create({
|
||||
user,
|
||||
pending: this.authState.pending,
|
||||
hasSessionData: this.authState.hasSessionData,
|
||||
error: this.authState.error,
|
||||
nextRetryAt: this.authState.nextRetryAt,
|
||||
})
|
||||
}
|
||||
|
||||
async createAuthRequest(strict = false): Promise<String> {
|
||||
// In strict mode, we do not open a new auth window if already authenticated
|
||||
if (strict) {
|
||||
if (strict && this._authenticated) {
|
||||
this.sendAuthStatusUpdate()
|
||||
return String.create({ value: "Already authenticated" })
|
||||
}
|
||||
|
||||
if (!this.provider) {
|
||||
if (!this._provider) {
|
||||
return String.create({
|
||||
value: "Authentication provider is not configured",
|
||||
})
|
||||
@@ -273,25 +264,23 @@ export class AuthService {
|
||||
const callbackHost = await HostProvider.get().getCallbackUrl()
|
||||
const callbackUrl = `${callbackHost}/auth`
|
||||
|
||||
const authUrl = await this.provider.getAuthRequest(callbackUrl)
|
||||
const authUrl = await this._provider.getAuthRequest(callbackUrl)
|
||||
const authUrlString = authUrl.toString()
|
||||
|
||||
await openExternal(authUrlString)
|
||||
telemetryService.captureAuthStarted(this.provider.name)
|
||||
telemetryService.captureAuthStarted(this._provider.name)
|
||||
return String.create({ value: authUrlString })
|
||||
}
|
||||
|
||||
async handleDeauth(reason: LogoutReason = LogoutReason.UNKNOWN): Promise<void> {
|
||||
if (!this.provider) {
|
||||
if (!this._provider) {
|
||||
throw new Error("Auth provider is not set")
|
||||
}
|
||||
|
||||
try {
|
||||
telemetryService.captureAuthLoggedOut(this.provider.name, reason)
|
||||
this.authState = {
|
||||
hasSessionData: false,
|
||||
pending: false,
|
||||
}
|
||||
telemetryService.captureAuthLoggedOut(this._provider.name, reason)
|
||||
this._clineAuthInfo = null
|
||||
this._authenticated = false
|
||||
this.destroyTokens()
|
||||
this.sendAuthStatusUpdate()
|
||||
} catch (error) {
|
||||
@@ -301,17 +290,19 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async handleAuthCallback(authorizationCode: string, provider: string): Promise<void> {
|
||||
if (!this.provider) {
|
||||
if (!this._provider) {
|
||||
throw new Error("Auth provider is not set")
|
||||
}
|
||||
|
||||
try {
|
||||
this.authState = await this.provider.signIn(this._controller, authorizationCode, provider)
|
||||
telemetryService.captureAuthSucceeded(this.provider.name)
|
||||
this._clineAuthInfo = await this._provider.signIn(this._controller, authorizationCode, provider)
|
||||
this._authenticated = this._clineAuthInfo?.idToken !== undefined
|
||||
|
||||
telemetryService.captureAuthSucceeded(this._provider.name)
|
||||
await setWelcomeViewCompleted(this._controller, { value: true })
|
||||
} catch (error) {
|
||||
console.error("Error signing in with custom token:", error)
|
||||
telemetryService.captureAuthFailed(this.provider.name)
|
||||
telemetryService.captureAuthFailed(this._provider.name)
|
||||
throw error
|
||||
} finally {
|
||||
await this.sendAuthStatusUpdate()
|
||||
@@ -332,26 +323,32 @@ export class AuthService {
|
||||
* This is typically called when the extension is activated.
|
||||
*/
|
||||
async restoreRefreshTokenAndRetrieveAuthInfo(): Promise<void> {
|
||||
if (!this.provider) {
|
||||
if (!this._provider) {
|
||||
throw new Error("Auth provider is not set")
|
||||
}
|
||||
|
||||
try {
|
||||
this.authState = await this.retrieveAuthInfo()
|
||||
this._clineAuthInfo = await this.retrieveAuthInfo()
|
||||
if (this._clineAuthInfo) {
|
||||
this._authenticated = true
|
||||
await this.sendAuthStatusUpdate()
|
||||
} else {
|
||||
console.warn("No user found after restoring auth token")
|
||||
this._authenticated = false
|
||||
this._clineAuthInfo = null
|
||||
telemetryService.captureAuthLoggedOut(this._provider?.name, LogoutReason.ERROR_RECOVERY)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error restoring auth token:", error)
|
||||
this.authState = {
|
||||
pending: false,
|
||||
hasSessionData: false,
|
||||
error: "Unknown error.",
|
||||
}
|
||||
} finally {
|
||||
this.sendAuthStatusUpdate().catch(console.error)
|
||||
this._authenticated = false
|
||||
this._clineAuthInfo = null
|
||||
telemetryService.captureAuthLoggedOut(this._provider?.name, LogoutReason.ERROR_RECOVERY)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private async retrieveAuthInfo(): Promise<InternalAuthState> {
|
||||
if (!this.provider) {
|
||||
private async retrieveAuthInfo(): Promise<ClineAuthInfo | null> {
|
||||
if (!this._provider) {
|
||||
throw new Error("Auth provider is not set")
|
||||
}
|
||||
|
||||
@@ -361,7 +358,7 @@ export class AuthService {
|
||||
await this._refreshPromise
|
||||
}
|
||||
|
||||
return this.provider.retrieveClineAuthInfo(this._controller)
|
||||
return this._provider.retrieveClineAuthInfo(this._controller)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,11 +427,10 @@ export class AuthService {
|
||||
|
||||
await Promise.all(streamSends)
|
||||
// Identify the user in telemetry if available
|
||||
const userInfo = this.authState.authInfo?.userInfo
|
||||
if (userInfo?.id) {
|
||||
telemetryService.identifyAccount(userInfo)
|
||||
if (this._clineAuthInfo?.userInfo?.id) {
|
||||
telemetryService.identifyAccount(this._clineAuthInfo.userInfo)
|
||||
// Poll feature flags immediately for authenticated users to ensure cache is populated
|
||||
await featureFlagsService.poll(userInfo.id)
|
||||
await featureFlagsService.poll(this._clineAuthInfo?.userInfo?.id)
|
||||
} else {
|
||||
// Poll feature flags for unauthenticated state
|
||||
await featureFlagsService.poll(undefined)
|
||||
|
||||
@@ -38,10 +38,10 @@ export class AuthServiceMock extends AuthService {
|
||||
}
|
||||
|
||||
override async getAuthToken(): Promise<string | null> {
|
||||
if (!this.authState.authInfo) {
|
||||
if (!this._clineAuthInfo) {
|
||||
return null
|
||||
}
|
||||
return this.authState.authInfo.idToken
|
||||
return this._clineAuthInfo.idToken
|
||||
}
|
||||
|
||||
override async createAuthRequest(): Promise<String> {
|
||||
@@ -49,13 +49,11 @@ export class AuthServiceMock extends AuthService {
|
||||
const authUrl = new URL(ClineEnv.config().apiBaseUrl)
|
||||
const authUrlString = authUrl.toString()
|
||||
// Call the parent implementation
|
||||
if (this.authState.hasSessionData && this.authState.authInfo) {
|
||||
if (this._authenticated && this._clineAuthInfo) {
|
||||
console.log("Already authenticated with mock server")
|
||||
return String.create({ value: authUrlString })
|
||||
}
|
||||
|
||||
this.authState.pending = false
|
||||
|
||||
try {
|
||||
// Use token exchange endpoint like ClineAuthProvider
|
||||
const tokenExchangeUri = new URL(CLINE_API_ENDPOINT.TOKEN_EXCHANGE, ClineEnv.config().apiBaseUrl)
|
||||
@@ -86,7 +84,7 @@ export class AuthServiceMock extends AuthService {
|
||||
const authData = responseData.data
|
||||
|
||||
// Convert to ClineAuthInfo format matching ClineAuthProvider
|
||||
this.authState.authInfo = {
|
||||
this._clineAuthInfo = {
|
||||
idToken: authData.accessToken,
|
||||
refreshToken: authData.refreshToken,
|
||||
expiresAt: new Date(authData.expiresAt).getTime() / 1000,
|
||||
@@ -99,22 +97,21 @@ export class AuthServiceMock extends AuthService {
|
||||
appBaseUrl: ClineEnv.config().appBaseUrl,
|
||||
subject: authData.userInfo.subject,
|
||||
},
|
||||
provider: this.provider?.name || "mock",
|
||||
provider: this._provider?.name || "mock",
|
||||
}
|
||||
this.authState.pending = false
|
||||
|
||||
console.log(`Successfully authenticated with mock server as ${authData.userInfo.name} (${authData.userInfo.email})`)
|
||||
|
||||
const visibleWebview = WebviewProvider.getVisibleInstance()
|
||||
|
||||
// Use appropriate provider name for callback
|
||||
const providerName = this.provider?.name || "mock"
|
||||
const providerName = this._provider?.name || "mock"
|
||||
// Simulate handling the auth callback as if from a real provider
|
||||
await visibleWebview?.controller.handleAuthCallback(authData.accessToken, providerName)
|
||||
} catch (error) {
|
||||
console.error("Error signing in with mock server:", error)
|
||||
this.authState.hasSessionData = false
|
||||
this.authState.authInfo = undefined
|
||||
this._authenticated = false
|
||||
this._clineAuthInfo = null
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -123,8 +120,7 @@ export class AuthServiceMock extends AuthService {
|
||||
|
||||
override async handleAuthCallback(_token: string, _provider: string): Promise<void> {
|
||||
try {
|
||||
this.authState.hasSessionData = true
|
||||
this.authState.pending = false
|
||||
this._authenticated = true
|
||||
await setWelcomeViewCompleted(this._controller, { value: true })
|
||||
await this.sendAuthStatusUpdate()
|
||||
} catch (error) {
|
||||
@@ -135,20 +131,18 @@ export class AuthServiceMock extends AuthService {
|
||||
|
||||
override async restoreRefreshTokenAndRetrieveAuthInfo(): Promise<void> {
|
||||
try {
|
||||
this.authState.pending = false
|
||||
|
||||
if (this.authState.authInfo) {
|
||||
this.authState.hasSessionData = true
|
||||
if (this._clineAuthInfo) {
|
||||
this._authenticated = true
|
||||
await this.sendAuthStatusUpdate()
|
||||
} else {
|
||||
console.warn("No user found after restoring auth token")
|
||||
this.authState.hasSessionData = false
|
||||
this.authState.authInfo = undefined
|
||||
this._authenticated = false
|
||||
this._clineAuthInfo = null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error restoring auth token:", error)
|
||||
this.authState.hasSessionData = false
|
||||
this.authState.authInfo = undefined
|
||||
this._authenticated = false
|
||||
this._clineAuthInfo = null
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Logger } from "@/services/logging/Logger"
|
||||
import { telemetryService } from "@/services/telemetry"
|
||||
import { CLINE_API_ENDPOINT } from "@/shared/cline/api"
|
||||
import { fetch, getAxiosSettings } from "@/shared/net"
|
||||
import { type ClineAccountUserInfo, type ClineAuthInfo, InternalAuthState } from "../AuthService"
|
||||
import { type ClineAccountUserInfo, type ClineAuthInfo } from "../AuthService"
|
||||
import { parseJwtPayload } from "../oca/utils/utils"
|
||||
import { IAuthProvider } from "./IAuthProvider"
|
||||
|
||||
@@ -123,10 +123,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
controller.stateManager.setSecret("cline:clineAccountId", undefined)
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
return {
|
||||
hasSessionData: false,
|
||||
pending: false,
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private logFailedRefreshAttempt(response: Response, storedAuthData?: ClineAuthInfo) {
|
||||
@@ -161,7 +158,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
* @param controller - The controller instance to access stored secrets.
|
||||
* @returns {Promise<ClineAuthInfo | null>} A promise that resolves with the auth info or null.
|
||||
*/
|
||||
async retrieveClineAuthInfo(controller: Controller): Promise<InternalAuthState> {
|
||||
async retrieveClineAuthInfo(controller: Controller): Promise<ClineAuthInfo | null> {
|
||||
try {
|
||||
// Get the stored auth data from secure storage
|
||||
const storedAuthDataString = controller.stateManager.getSecretKey("cline:clineAccountId")
|
||||
@@ -171,10 +168,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
// Reset retry count when there's no stored auth
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
return {
|
||||
hasSessionData: false,
|
||||
pending: false,
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Parse the stored auth data
|
||||
@@ -201,11 +195,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
) {
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
return {
|
||||
authInfo: storedAuthData,
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
}
|
||||
return storedAuthData
|
||||
}
|
||||
|
||||
// Check if we need to wait before retrying
|
||||
@@ -215,28 +205,14 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
Logger.debug(
|
||||
`Waiting ${Math.ceil((this.RETRY_DELAY_MS - timeSinceLastAttempt) / 1000)}s before retry attempt ${this.refreshRetryCount + 1}/${this.MAX_REFRESH_RETRIES}`,
|
||||
)
|
||||
|
||||
return {
|
||||
hasSessionData: true,
|
||||
pending: true,
|
||||
nextRetryAt: now + this.RETRY_DELAY_MS - timeSinceLastAttempt,
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if we've exceeded max retries
|
||||
if (this.refreshRetryCount >= this.MAX_REFRESH_RETRIES) {
|
||||
const waitTime = this.RETRY_DELAY_MS * 3
|
||||
setTimeout(() => {
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
}, waitTime)
|
||||
|
||||
return {
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
error: "Failed to fetch user information. Waiting before retrying again.",
|
||||
nextRetryAt: now + waitTime,
|
||||
}
|
||||
Logger.error(`Max refresh retries (${this.MAX_REFRESH_RETRIES}) exceeded.`)
|
||||
// Don't clear session - return stored data and let API request fail later
|
||||
return storedAuthData
|
||||
}
|
||||
|
||||
// Try to refresh the token using the refresh token
|
||||
@@ -257,12 +233,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
Logger.debug("Token refresh successful")
|
||||
|
||||
return {
|
||||
authInfo: authInfo,
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
}
|
||||
return authInfo || null
|
||||
} catch (refreshError) {
|
||||
Logger.error(
|
||||
`Token refresh failed (attempt ${this.refreshRetryCount}/${this.MAX_REFRESH_RETRIES}):`,
|
||||
@@ -278,12 +249,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
|
||||
// For network errors, return stored data - let the API request fail later
|
||||
// when the user actually tries to use Cline, not at startup
|
||||
return {
|
||||
hasSessionData: true,
|
||||
error: "Unknown network error.",
|
||||
pending: true,
|
||||
nextRetryAt: now + this.RETRY_DELAY_MS,
|
||||
}
|
||||
return storedAuthData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,11 +259,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
|
||||
// Is the token valid?
|
||||
if (storedAuthData.idToken && storedAuthData.refreshToken && storedAuthData.userInfo.id) {
|
||||
return {
|
||||
authInfo: storedAuthData,
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
}
|
||||
return storedAuthData
|
||||
}
|
||||
|
||||
// Verify the token structure
|
||||
@@ -311,12 +273,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
if (payload.external_id) {
|
||||
storedAuthData.userInfo.id = payload.external_id
|
||||
}
|
||||
|
||||
return {
|
||||
authInfo: storedAuthData,
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
}
|
||||
return storedAuthData
|
||||
} catch (error) {
|
||||
Logger.error("Authentication failed with stored credential:", error)
|
||||
// Reset retry count on unexpected errors
|
||||
@@ -324,12 +281,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
this.refreshRetryCount = 0
|
||||
this.lastRefreshAttempt = 0
|
||||
}
|
||||
return {
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
error: "Unexpected error.",
|
||||
nextRetryAt: Date.now() + this.RETRY_DELAY_MS,
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +387,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
}
|
||||
}
|
||||
|
||||
async signIn(controller: Controller, authorizationCode: string, provider: string): Promise<InternalAuthState> {
|
||||
async signIn(controller: Controller, authorizationCode: string, provider: string): Promise<ClineAuthInfo | null> {
|
||||
try {
|
||||
// Get the callback URL that was used during the initial auth request
|
||||
const callbackHost = await HostProvider.get().getCallbackUrl()
|
||||
@@ -486,11 +438,7 @@ export class ClineAuthProvider implements IAuthProvider {
|
||||
|
||||
controller.stateManager.setSecret("cline:clineAccountId", JSON.stringify(clineAuthInfo))
|
||||
|
||||
return {
|
||||
authInfo: clineAuthInfo,
|
||||
hasSessionData: true,
|
||||
pending: false,
|
||||
}
|
||||
return clineAuthInfo
|
||||
} catch (error) {
|
||||
Logger.error("Error handling auth callback:", error)
|
||||
throw error
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { EnvironmentConfig } from "@/config"
|
||||
import { Controller } from "@/core/controller"
|
||||
import { InternalAuthState } from "../AuthService"
|
||||
import { ClineAuthInfo } from "../AuthService"
|
||||
|
||||
export interface IAuthProvider {
|
||||
readonly name: string
|
||||
config: EnvironmentConfig
|
||||
retrieveClineAuthInfo(controller: Controller): Promise<InternalAuthState>
|
||||
shouldRefreshIdToken(token: string, expiresAt?: number): Promise<boolean>
|
||||
retrieveClineAuthInfo(controller: Controller): Promise<ClineAuthInfo | null>
|
||||
refreshToken(refreshToken: string, storedData: ClineAuthInfo): Promise<Partial<ClineAuthInfo>>
|
||||
getAuthRequest(callbackUrl: string): Promise<string>
|
||||
signIn(controller: Controller, authorizationCode: string, provider: string): Promise<InternalAuthState>
|
||||
signIn(controller: Controller, authorizationCode: string, provider: string): Promise<ClineAuthInfo | null>
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user