mirror of
https://github.com/cline/cline.git
synced 2026-09-07 12:58:33 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ceebb8a9f3 | |||
| d28a9b960c | |||
| badbd50f76 | |||
| 32cffa90a3 | |||
| 7abfcafb53 | |||
| 2e62b03073 | |||
| bcf063d497 | |||
| a1ea9799ab | |||
| 7d5fce3e37 | |||
| 03d44105cc | |||
| 79b76fd783 | |||
| 19cc8bc9f8 | |||
| 08c04a3c67 | |||
| 41ae7326c0 | |||
| b0961f4538 | |||
| 26242f6378 | |||
| 13228ed46f | |||
| d162a4b420 | |||
| 1704684af8 | |||
| c63d9a13a5 | |||
| 65243adb24 | |||
| e35f7b4e21 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
updated drag and drop text to say "drop" instead of "drag"
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Remove linear pull request action
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
UI Refresh: Enhanced welcome screen with dynamic time-based greetings that change according to the user's local time ("Good Morning", "Good Afternoon", or "Good Evening"). Added contextual random sub-messages that differ between normal working hours and late night sessions. This UI refresh transforms Cline's interface from a technical, feature-focused experience to a personal, welcoming environment that greets users by name. Visual improvements including rounded corners and distinct color-coding for Plan/Act modes create a more modern aesthetic while improving usability through better visual distinction between different states. By shifting from a capabilities-focused introduction to a personalized greeting with the Cline logo, the experience feels more human and approachable, potentially increasing user engagement and reducing the intimidation factor for those new to AI coding assistants.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix for git commit mentions in repos with no git commits
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Added copy button to code blocks.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Introduce UI library for future UI development
|
||||
@@ -13,6 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
if: false
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
name: Create Linear Issue on Pull Request
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
create-linear-issue-on-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for existing Linear link
|
||||
id: check-linear
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
// 1) PR body
|
||||
if (/https?:\/\/linear\.app/.test(pr.body||"")) {
|
||||
return "true";
|
||||
}
|
||||
// 2) Any linked GitHub issues?
|
||||
const res = await github.graphql(
|
||||
`query($owner:String!,$repo:String!,$prNumber:Int!){
|
||||
repository(owner:$owner,name:$repo){
|
||||
pullRequest(number:$prNumber){
|
||||
closingIssuesReferences(first:10){
|
||||
nodes{number}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
prNumber: pr.number
|
||||
}
|
||||
);
|
||||
for (const {number} of res.repository.pullRequest.closingIssuesReferences.nodes) {
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: number
|
||||
});
|
||||
if (comments.data.some(c=>/https?:\/\/linear\.app/.test(c.body))) {
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
return "false";
|
||||
|
||||
- name: Find or create Linear issue via GraphQL
|
||||
if: steps.check-linear.outputs.result == 'false'
|
||||
id: linear
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
const API = 'https://api.linear.app/graphql';
|
||||
const apiKey = process.env.LINEAR_API_KEY;
|
||||
|
||||
// Check if API key exists
|
||||
if (!apiKey) {
|
||||
core.setFailed('LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
||||
core.setOutput('error', 'true');
|
||||
core.setOutput('error-message', 'LINEAR_API_KEY is not set. Please add it to your repository secrets.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper to call Linear with error handling
|
||||
async function gql(q, v) {
|
||||
try {
|
||||
const r = await fetch(API, {
|
||||
method:'POST',
|
||||
headers:{
|
||||
'Content-Type':'application/json',
|
||||
'Authorization': apiKey
|
||||
},
|
||||
body: JSON.stringify({ query: q, variables: v })
|
||||
});
|
||||
|
||||
if (!r.ok) {
|
||||
throw new Error(`Linear API responded with status ${r.status}: ${await r.text()}`);
|
||||
}
|
||||
|
||||
const json = await r.json();
|
||||
|
||||
// Check for GraphQL errors
|
||||
if (json.errors && json.errors.length > 0) {
|
||||
const errorMessages = json.errors.map(e => e.message).join(', ');
|
||||
throw new Error(`Linear GraphQL errors: ${errorMessages}`);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
} catch (error) {
|
||||
core.error(`Error calling Linear API: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 1) Set team ID
|
||||
const teamId = "19b9c1b2-5f58-498c-b1bf-23ee8f52a677"
|
||||
|
||||
// 2) Look for existing issue by PR URL
|
||||
const pr = context.payload.pull_request;
|
||||
const searchData = await gql(
|
||||
`query($team:ID!,$q:String!){
|
||||
issues(filter: { team: { id: { eq: $team } } attachments: { some: { url: { eq: $q } } } }){nodes{id,url}}
|
||||
}`,
|
||||
{ team: teamId, q: pr.html_url }
|
||||
);
|
||||
let issue = searchData.issues.nodes[0];
|
||||
|
||||
// 3) Create if missing
|
||||
if (!issue) {
|
||||
const createData = await gql(
|
||||
`mutation($input:IssueCreateInput!){
|
||||
issueCreate(input:$input){issue{id,url}}
|
||||
}`,
|
||||
{
|
||||
input: {
|
||||
teamId,
|
||||
title: `[GITHUB] ${pr.title}`,
|
||||
description: `${pr.body||''}\n\n${pr.html_url}`,
|
||||
stateId: "4d9bcba2-6712-47e3-b577-6ec1ee023dc2",
|
||||
labelIds: ["504e7d60-5037-483f-a9b8-7e298bdf116f"]
|
||||
}
|
||||
}
|
||||
);
|
||||
issue = createData.issueCreate.issue;
|
||||
}
|
||||
|
||||
// Set output for next steps
|
||||
core.setOutput('linear-issue-url', issue.url);
|
||||
core.setOutput('error', 'false');
|
||||
} catch (error) {
|
||||
core.setOutput('error', 'true');
|
||||
core.setOutput('error-message', error.message);
|
||||
core.setFailed(`Failed to create or find Linear issue: ${error.message}`);
|
||||
}
|
||||
|
||||
- name: Comment PR with Linear link
|
||||
if: steps.check-linear.outputs.result == 'false'
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const url = `${{ steps.linear.outputs.linear-issue-url }}`;
|
||||
const body = `🔗 Linear issue created: ${url}`;
|
||||
// Fetch existing comments
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
...context.repo,
|
||||
issue_number: pr.number
|
||||
});
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === "Bot" && c.body.startsWith("🔗 Linear issue created:")
|
||||
);
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
...context.repo,
|
||||
comment_id: botComment.id,
|
||||
body
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: pr.number,
|
||||
body
|
||||
});
|
||||
}
|
||||
@@ -70,8 +70,8 @@ jobs:
|
||||
run: npm run format
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Extension
|
||||
run: npm run compile
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
- name: Unit Tests
|
||||
run: npm run test:unit
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
xvfb-run -a npm run test:coverage > extension_coverage.txt 2>&1 || true
|
||||
xvfb-run -a npm run test:coverage > extension_coverage.txt 2>&1
|
||||
PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
|
||||
# Run webview tests with coverage
|
||||
@@ -106,6 +106,18 @@ jobs:
|
||||
webview-ui/webview_coverage.txt
|
||||
retention-period: workflow # Artifacts are automatically deleted when the workflow completes
|
||||
|
||||
# Set the check as failed if any of the tests failed
|
||||
- name: Check for test failures
|
||||
run: |
|
||||
# Check if any of the test steps failed
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Tests failed."
|
||||
cat extension_coverage.txt
|
||||
cat webview-ui/webview_coverage.txt
|
||||
exit 1
|
||||
fi
|
||||
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -6,6 +6,10 @@ export default defineConfig({
|
||||
mocha: {
|
||||
ui: "bdd",
|
||||
timeout: 20000, // Maximum time (in ms) that a test can run before failing
|
||||
/** Set up alias path resolution during tests
|
||||
* @See {@link file://./test-setup.js}
|
||||
*/
|
||||
require: ["./test-setup.js"],
|
||||
},
|
||||
workspaceFolder: "test-workspace",
|
||||
version: "stable",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="115" height="121" viewBox="0 0 115 121" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M114.317 68.0068L107.099 53.5136V45.1668C107.099 31.3312 95.9942 20.1262 82.2992 20.1262H69.9623C70.8552 18.2924 71.3456 16.231 71.3456 14.0557C71.3456 6.36649 65.1583 0.144287 57.5123 0.144287C49.8662 0.144287 43.6789 6.36649 43.6789 14.0557C43.6789 16.231 44.1693 18.2924 45.0622 20.1262H32.7254C19.0303 20.1262 7.9259 31.3312 7.9259 45.1668V53.5136L0.556479 67.9689C-0.185493 69.4232 -0.185493 71.1558 0.556479 72.6102L7.9259 86.9011V95.2479C7.9259 109.083 19.0303 120.289 32.7254 120.289H82.2992C95.9942 120.289 107.099 109.083 107.099 95.2479V86.9011L114.305 72.5596C115.021 71.1306 115.021 69.4485 114.317 68.0068ZM49.9668 79.8189C49.9668 86.1043 44.8987 91.201 38.6486 91.201C32.3984 91.201 27.3303 86.1043 27.3303 79.8189V59.5841C27.3303 53.2986 32.3984 48.202 38.6486 48.202C44.8987 48.202 49.9668 53.2986 49.9668 59.5841V79.8189ZM86.4366 79.8189C86.4366 86.1043 81.3685 91.201 75.1184 91.201C68.8682 91.201 63.8002 86.1043 63.8002 79.8189V59.5841C63.8002 53.2986 68.8682 48.202 75.1184 48.202C81.3685 48.202 86.4366 53.2986 86.4366 59.5841V79.8189Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -19,6 +19,7 @@ const aliasResolverPlugin = {
|
||||
"@services": path.resolve(__dirname, "src/services"),
|
||||
"@shared": path.resolve(__dirname, "src/shared"),
|
||||
"@utils": path.resolve(__dirname, "src/utils"),
|
||||
"@packages": path.resolve(__dirname, "src/packages"),
|
||||
}
|
||||
|
||||
// For each alias entry, create a resolver
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
|
||||
"protos": "node proto/build-proto.js && prettier src/shared/proto --write && prettier src/core/controller --write",
|
||||
"compile-tests": "tsc -p ./tsconfig.test.json --outDir out",
|
||||
"compile-tests": "node ./scripts/build-tests.js",
|
||||
"watch-tests": "tsc -p . -w --outDir out",
|
||||
"pretest": "npm run compile-tests && npm run compile && npm run lint",
|
||||
"check-types": "tsc --noEmit",
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const { execSync } = require("child_process")
|
||||
const esbuild = require("esbuild")
|
||||
|
||||
const watch = process.argv.includes("--watch")
|
||||
|
||||
/**
|
||||
* @type {import('esbuild').Plugin}
|
||||
*/
|
||||
const esbuildProblemMatcherPlugin = {
|
||||
name: "esbuild-problem-matcher",
|
||||
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log("[watch] build started")
|
||||
})
|
||||
build.onEnd((result) => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`)
|
||||
console.error(` ${location.file}:${location.line}:${location.column}:`)
|
||||
})
|
||||
console.log("[watch] build finished")
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const srcConfig = {
|
||||
bundle: true,
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
sourcesContent: true,
|
||||
logLevel: "silent",
|
||||
entryPoints: ["src/packages/**/*.ts"],
|
||||
outdir: "out/packages",
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
define: {
|
||||
"process.env.IS_DEV": "true",
|
||||
"process.env.IS_TEST": "true",
|
||||
},
|
||||
external: ["vscode"],
|
||||
plugins: [esbuildProblemMatcherPlugin],
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const srcCtx = await esbuild.context(srcConfig)
|
||||
|
||||
if (watch) {
|
||||
await srcCtx.watch()
|
||||
} else {
|
||||
await srcCtx.rebuild()
|
||||
|
||||
await srcCtx.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
execSync("tsc -p ./tsconfig.test.json --outDir out", { encoding: "utf-8" })
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -28,20 +28,26 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
const modelId = await this.getModelId()
|
||||
const model = this.getModel()
|
||||
|
||||
// This baseModelId is used to indicate the capabilities of the model.
|
||||
// If the user selects a custom model, baseModelId will be set to the base model ID of the custom model.
|
||||
// Otherwise, baseModelId will be the same as modelId.
|
||||
const baseModelId =
|
||||
(this.options.awsBedrockCustomSelected ? this.options.awsBedrockCustomModelBaseId : modelId) || modelId
|
||||
|
||||
// Check if this is an Amazon Nova model
|
||||
if (modelId.includes("amazon.nova")) {
|
||||
if (baseModelId.includes("amazon.nova")) {
|
||||
yield* this.createNovaMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this is a Deepseek model
|
||||
if (modelId.includes("deepseek")) {
|
||||
if (baseModelId.includes("deepseek")) {
|
||||
yield* this.createDeepseekMessage(systemPrompt, messages, modelId, model)
|
||||
return
|
||||
}
|
||||
|
||||
const budget_tokens = this.options.thinkingBudgetTokens || 0
|
||||
const reasoningOn = modelId.includes("3-7") && budget_tokens !== 0 ? true : false
|
||||
const reasoningOn = baseModelId.includes("3-7") && budget_tokens !== 0 ? true : false
|
||||
|
||||
// Get model info and message indices for caching
|
||||
const userMsgIndices = messages.reduce((acc, msg, index) => (msg.role === "user" ? [...acc, index] : acc), [] as number[])
|
||||
@@ -167,12 +173,23 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): { id: BedrockModelId; info: ModelInfo } {
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId
|
||||
if (modelId && modelId in bedrockModels) {
|
||||
const id = modelId as BedrockModelId
|
||||
return { id, info: bedrockModels[id] }
|
||||
}
|
||||
|
||||
const customSelected = this.options.awsBedrockCustomSelected
|
||||
const baseModel = this.options.awsBedrockCustomModelBaseId
|
||||
if (customSelected && modelId && baseModel && baseModel in bedrockModels) {
|
||||
// Use the user-input model ID but inherit capabilities from the base model
|
||||
return {
|
||||
id: modelId,
|
||||
info: bedrockModels[baseModel],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: bedrockDefaultModelId,
|
||||
info: bedrockModels[bedrockDefaultModelId],
|
||||
@@ -290,7 +307,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: BedrockModelId; info: ModelInfo },
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
@@ -482,7 +499,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
modelId: string,
|
||||
model: { id: BedrockModelId; info: ModelInfo },
|
||||
model: { id: string; info: ModelInfo },
|
||||
): ApiStream {
|
||||
// Get Bedrock client with proper credentials
|
||||
const client = await this.getBedrockClient()
|
||||
|
||||
@@ -76,26 +76,6 @@ describe("ModelContextTracker", () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error when controller is dereferenced", async () => {
|
||||
// Create a new tracker with a controller that will be garbage collected
|
||||
const weakTracker = new ModelContextTracker(mockContext, taskId)
|
||||
|
||||
// Force the WeakRef to return null by overriding the deref method
|
||||
const weakRef = { deref: sandbox.stub().returns(null) }
|
||||
sandbox.stub(WeakRef.prototype, "deref").callsFake(() => weakRef.deref())
|
||||
|
||||
try {
|
||||
// Try to call the method - this should throw
|
||||
await weakTracker.recordModelUsage("any-provider", "any-model", "any-mode")
|
||||
|
||||
// If we get here, the test should fail
|
||||
expect.fail("Expected an error to be thrown")
|
||||
} catch (error) {
|
||||
// Verify the error message
|
||||
expect(error.message).to.equal("Unable to access extension context")
|
||||
}
|
||||
})
|
||||
|
||||
it("should append model usage to existing entries", async () => {
|
||||
// Add an existing model usage entry
|
||||
const existingTimestamp = 1617200000000
|
||||
|
||||
@@ -136,8 +136,14 @@ export class Controller {
|
||||
|
||||
async initTask(task?: string, images?: string[], historyItem?: HistoryItem) {
|
||||
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
|
||||
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
|
||||
await getAllExtensionState(this.context)
|
||||
const {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
if (autoApprovalSettings) {
|
||||
const updatedAutoApprovalSettings = {
|
||||
@@ -159,6 +165,7 @@ export class Controller {
|
||||
autoApprovalSettings,
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
shellIntegrationTimeout,
|
||||
customInstructions,
|
||||
task,
|
||||
images,
|
||||
@@ -784,6 +791,21 @@ export class Controller {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "updateTerminalConnectionTimeout": {
|
||||
if (message.shellIntegrationTimeout !== undefined) {
|
||||
const timeout = message.shellIntegrationTimeout
|
||||
|
||||
if (typeof timeout === "number" && !isNaN(timeout) && timeout > 0) {
|
||||
await updateGlobalState(this.context, "shellIntegrationTimeout", timeout)
|
||||
await this.postStateToWebview()
|
||||
} else {
|
||||
console.warn(
|
||||
`Invalid shell integration timeout value received: ${timeout}. ` + `Expected a positive number.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -810,6 +832,8 @@ export class Controller {
|
||||
previousModeVsCodeLmModelSelector: newVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens: newThinkingBudgetTokens,
|
||||
previousModeReasoningEffort: newReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected: newAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId: newAwsBedrockCustomModelBaseId,
|
||||
planActSeparateModelsSetting,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
@@ -822,7 +846,6 @@ export class Controller {
|
||||
await updateGlobalState(this.context, "previousModeReasoningEffort", apiConfiguration.reasoningEffort)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
@@ -832,6 +855,19 @@ export class Controller {
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.apiModelId)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomSelected",
|
||||
apiConfiguration.awsBedrockCustomSelected,
|
||||
)
|
||||
await updateGlobalState(
|
||||
this.context,
|
||||
"previousModeAwsBedrockCustomModelBaseId",
|
||||
apiConfiguration.awsBedrockCustomModelBaseId,
|
||||
)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
@@ -877,7 +913,6 @@ export class Controller {
|
||||
await updateGlobalState(this.context, "reasoningEffort", newReasoningEffort)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
case "asksage":
|
||||
@@ -887,6 +922,11 @@ export class Controller {
|
||||
case "xai":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
break
|
||||
case "bedrock":
|
||||
await updateGlobalState(this.context, "apiModelId", newModelId)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomSelected", newAwsBedrockCustomSelected)
|
||||
await updateGlobalState(this.context, "awsBedrockCustomModelBaseId", newAwsBedrockCustomModelBaseId)
|
||||
break
|
||||
case "openrouter":
|
||||
case "cline":
|
||||
await updateGlobalState(this.context, "openRouterModelId", newModelId)
|
||||
@@ -1769,6 +1809,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
telemetrySetting,
|
||||
planActSeparateModelsSetting,
|
||||
globalClineRulesToggles,
|
||||
shellIntegrationTimeout,
|
||||
} = await getAllExtensionState(this.context)
|
||||
|
||||
const localClineRulesToggles =
|
||||
@@ -1798,6 +1839,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
vscMachineId: vscode.env.machineId,
|
||||
globalClineRulesToggles: globalClineRulesToggles || {},
|
||||
localClineRulesToggles: localClineRulesToggles || {},
|
||||
shellIntegrationTimeout,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fileExistsAtPath } from "@utils/fs"
|
||||
import { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { TaskMetadata } from "@core/context/context-tracking/ContextTrackerTypes"
|
||||
import os from "os"
|
||||
import { execa } from "execa"
|
||||
import { execa } from "@packages/execa"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
|
||||
@@ -29,6 +29,8 @@ export type GlobalStateKey =
|
||||
| "awsBedrockEndpoint"
|
||||
| "awsProfile"
|
||||
| "awsUseProfile"
|
||||
| "awsBedrockCustomSelected"
|
||||
| "awsBedrockCustomModelBaseId"
|
||||
| "vertexProjectId"
|
||||
| "vertexRegion"
|
||||
| "lastShownAnnouncementId"
|
||||
@@ -60,6 +62,8 @@ export type GlobalStateKey =
|
||||
| "previousModeThinkingBudgetTokens"
|
||||
| "previousModeReasoningEffort"
|
||||
| "previousModeVsCodeLmModelSelector"
|
||||
| "previousModeAwsBedrockCustomSelected"
|
||||
| "previousModeAwsBedrockCustomModelBaseId"
|
||||
| "previousModeModelInfo"
|
||||
| "liteLlmBaseUrl"
|
||||
| "liteLlmModelId"
|
||||
@@ -76,5 +80,6 @@ export type GlobalStateKey =
|
||||
| "planActSeparateModelsSetting"
|
||||
| "favoritedModelIds"
|
||||
| "requestTimeoutMs"
|
||||
| "shellIntegrationTimeout"
|
||||
|
||||
export type LocalStateKey = "localClineRulesToggles"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
|
||||
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
|
||||
import { GlobalStateKey, SecretKey } from "./state-keys"
|
||||
import { ApiConfiguration, ApiProvider, ModelInfo } from "@shared/api"
|
||||
import { ApiConfiguration, ApiProvider, BedrockModelId, ModelInfo } from "@shared/api"
|
||||
import { HistoryItem } from "@shared/HistoryItem"
|
||||
import { AutoApprovalSettings } from "@shared/AutoApprovalSettings"
|
||||
import { BrowserSettings } from "@shared/BrowserSettings"
|
||||
@@ -67,6 +67,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -113,6 +115,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
qwenApiLine,
|
||||
liteLlmApiKey,
|
||||
telemetrySetting,
|
||||
@@ -126,6 +130,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
favoritedModelIds,
|
||||
globalClineRulesToggles,
|
||||
requestTimeoutMs,
|
||||
shellIntegrationTimeout,
|
||||
] = await Promise.all([
|
||||
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
|
||||
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
|
||||
@@ -141,6 +146,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "awsBedrockEndpoint") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsProfile") as Promise<string | undefined>,
|
||||
getGlobalState(context, "awsUseProfile") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "awsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "vertexProjectId") as Promise<string | undefined>,
|
||||
getGlobalState(context, "vertexRegion") as Promise<string | undefined>,
|
||||
getGlobalState(context, "openAiBaseUrl") as Promise<string | undefined>,
|
||||
@@ -187,6 +194,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "previousModeVsCodeLmModelSelector") as Promise<vscode.LanguageModelChatSelector | undefined>,
|
||||
getGlobalState(context, "previousModeThinkingBudgetTokens") as Promise<number | undefined>,
|
||||
getGlobalState(context, "previousModeReasoningEffort") as Promise<string | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomSelected") as Promise<boolean | undefined>,
|
||||
getGlobalState(context, "previousModeAwsBedrockCustomModelBaseId") as Promise<BedrockModelId | undefined>,
|
||||
getGlobalState(context, "qwenApiLine") as Promise<string | undefined>,
|
||||
getSecret(context, "liteLlmApiKey") as Promise<string | undefined>,
|
||||
getGlobalState(context, "telemetrySetting") as Promise<TelemetrySetting | undefined>,
|
||||
@@ -200,6 +209,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
|
||||
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
|
||||
getGlobalState(context, "requestTimeoutMs") as Promise<number | undefined>,
|
||||
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -256,6 +266,8 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -316,9 +328,12 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
|
||||
previousModeVsCodeLmModelSelector,
|
||||
previousModeThinkingBudgetTokens,
|
||||
previousModeReasoningEffort,
|
||||
previousModeAwsBedrockCustomSelected,
|
||||
previousModeAwsBedrockCustomModelBaseId,
|
||||
mcpMarketplaceEnabled,
|
||||
telemetrySetting: telemetrySetting || "unset",
|
||||
planActSeparateModelsSetting,
|
||||
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +352,8 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
awsBedrockEndpoint,
|
||||
awsProfile,
|
||||
awsUseProfile,
|
||||
awsBedrockCustomSelected,
|
||||
awsBedrockCustomModelBaseId,
|
||||
vertexProjectId,
|
||||
vertexRegion,
|
||||
openAiBaseUrl,
|
||||
@@ -394,6 +411,8 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
|
||||
await updateGlobalState(context, "awsBedrockEndpoint", awsBedrockEndpoint)
|
||||
await updateGlobalState(context, "awsProfile", awsProfile)
|
||||
await updateGlobalState(context, "awsUseProfile", awsUseProfile)
|
||||
await updateGlobalState(context, "awsBedrockCustomSelected", awsBedrockCustomSelected)
|
||||
await updateGlobalState(context, "awsBedrockCustomModelBaseId", awsBedrockCustomModelBaseId)
|
||||
await updateGlobalState(context, "vertexProjectId", vertexProjectId)
|
||||
await updateGlobalState(context, "vertexRegion", vertexRegion)
|
||||
await updateGlobalState(context, "openAiBaseUrl", openAiBaseUrl)
|
||||
|
||||
@@ -173,6 +173,7 @@ export class Task {
|
||||
autoApprovalSettings: AutoApprovalSettings,
|
||||
browserSettings: BrowserSettings,
|
||||
chatSettings: ChatSettings,
|
||||
shellIntegrationTimeout: number,
|
||||
customInstructions?: string,
|
||||
task?: string,
|
||||
images?: string[],
|
||||
@@ -191,6 +192,7 @@ export class Task {
|
||||
console.error("Failed to initialize ClineIgnoreController:", error)
|
||||
})
|
||||
this.terminalManager = new TerminalManager()
|
||||
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
|
||||
this.urlContentFetcher = new UrlContentFetcher(context)
|
||||
this.browserSession = new BrowserSession(context, browserSettings)
|
||||
this.contextManager = new ContextManager()
|
||||
|
||||
@@ -39,7 +39,7 @@ const terminalManager = new TerminalManager(context);
|
||||
const process = terminalManager.runCommand('npm install', '/path/to/project');
|
||||
|
||||
process.on('line', (line) => {
|
||||
console.log(line);
|
||||
console.log(line);
|
||||
});
|
||||
|
||||
// To wait for the process to complete naturally:
|
||||
@@ -93,6 +93,7 @@ export class TerminalManager {
|
||||
private terminalIds: Set<number> = new Set()
|
||||
private processes: Map<number, TerminalProcess> = new Map()
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private shellIntegrationTimeout: number = 4000
|
||||
|
||||
constructor() {
|
||||
let disposable: vscode.Disposable | undefined
|
||||
@@ -144,13 +145,30 @@ export class TerminalManager {
|
||||
process.run(terminalInfo.terminal, command)
|
||||
} else {
|
||||
// docs recommend waiting 3s for shell integration to activate
|
||||
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, { timeout: 4000 }).finally(() => {
|
||||
const existingProcess = this.processes.get(terminalInfo.id)
|
||||
if (existingProcess && existingProcess.waitForShellIntegration) {
|
||||
existingProcess.waitForShellIntegration = false
|
||||
existingProcess.run(terminalInfo.terminal, command)
|
||||
}
|
||||
console.log(
|
||||
`[TerminalManager Test] Waiting for shell integration for terminal ${terminalInfo.id} with timeout ${this.shellIntegrationTimeout}ms`,
|
||||
)
|
||||
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, {
|
||||
timeout: this.shellIntegrationTimeout,
|
||||
})
|
||||
.then(() => {
|
||||
console.log(
|
||||
`[TerminalManager Test] Shell integration activated for terminal ${terminalInfo.id} within timeout.`,
|
||||
)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(
|
||||
`[TerminalManager Test] Shell integration timed out or failed for terminal ${terminalInfo.id}: ${err.message}`,
|
||||
)
|
||||
})
|
||||
.finally(() => {
|
||||
console.log(`[TerminalManager Test] Proceeding with command execution for terminal ${terminalInfo.id}.`)
|
||||
const existingProcess = this.processes.get(terminalInfo.id)
|
||||
if (existingProcess && existingProcess.waitForShellIntegration) {
|
||||
existingProcess.waitForShellIntegration = false
|
||||
existingProcess.run(terminalInfo.terminal, command)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return mergePromise(process, promise)
|
||||
@@ -219,4 +237,8 @@ export class TerminalManager {
|
||||
this.disposables.forEach((disposable) => disposable.dispose())
|
||||
this.disposables = []
|
||||
}
|
||||
|
||||
setShellIntegrationTimeout(timeout: number): void {
|
||||
this.shellIntegrationTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { execa } from "execa"
|
||||
@@ -132,6 +132,7 @@ export interface ExtensionState {
|
||||
shouldShowAnnouncement: boolean
|
||||
taskHistory: HistoryItem[]
|
||||
telemetrySetting: TelemetrySetting
|
||||
shellIntegrationTimeout: number
|
||||
uriScheme?: string
|
||||
userInfo?: {
|
||||
displayName: string | null
|
||||
|
||||
@@ -71,6 +71,7 @@ export interface WebviewMessage {
|
||||
| "toggleClineRule"
|
||||
| "deleteClineRule"
|
||||
| "copyToClipboard"
|
||||
| "updateTerminalConnectionTimeout"
|
||||
|
||||
// | "relaunchChromeDebugMode"
|
||||
text?: string
|
||||
@@ -121,6 +122,7 @@ export interface WebviewMessage {
|
||||
filename?: string
|
||||
|
||||
offset?: number
|
||||
shellIntegrationTimeout?: number
|
||||
}
|
||||
|
||||
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface ApiHandlerOptions {
|
||||
awsUseProfile?: boolean
|
||||
awsProfile?: string
|
||||
awsBedrockEndpoint?: string
|
||||
awsBedrockCustomSelected?: boolean
|
||||
awsBedrockCustomModelBaseId?: BedrockModelId
|
||||
vertexProjectId?: string
|
||||
vertexRegion?: string
|
||||
openAiBaseUrl?: string
|
||||
|
||||
+30
-2
@@ -30,6 +30,15 @@ async function checkGitInstalled(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
async function checkGitRepoHasCommits(cwd: string): Promise<boolean> {
|
||||
try {
|
||||
await execAsync("git rev-parse HEAD", { cwd })
|
||||
return true
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchCommits(query: string, cwd: string): Promise<GitCommit[]> {
|
||||
try {
|
||||
const isInstalled = await checkGitInstalled()
|
||||
@@ -44,6 +53,12 @@ export async function searchCommits(query: string, cwd: string): Promise<GitComm
|
||||
return []
|
||||
}
|
||||
|
||||
// Check if repo has any commits
|
||||
if (!(await checkGitRepoHasCommits(cwd))) {
|
||||
// No commits yet in the repository
|
||||
return []
|
||||
}
|
||||
|
||||
// Search commits by hash or message, limiting to 10 results
|
||||
const { stdout } = await execAsync(
|
||||
`git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short ` + `--grep="${query}" --regexp-ignore-case`,
|
||||
@@ -100,6 +115,11 @@ export async function getCommitInfo(hash: string, cwd: string): Promise<string>
|
||||
return "Not a git repository"
|
||||
}
|
||||
|
||||
// Check if repo has any commits
|
||||
if (!(await checkGitRepoHasCommits(cwd))) {
|
||||
return "Repository has no commits yet"
|
||||
}
|
||||
|
||||
// Get commit info, stats, and diff separately
|
||||
const { stdout: info } = await execAsync(`git show --format="%H%n%h%n%s%n%an%n%ad%n%b" --no-patch ${hash}`, {
|
||||
cwd,
|
||||
@@ -147,8 +167,16 @@ export async function getWorkingState(cwd: string): Promise<string> {
|
||||
return "No changes in working directory"
|
||||
}
|
||||
|
||||
// Get all changes (both staged and unstaged) compared to HEAD
|
||||
const { stdout: diff } = await execAsync("git diff HEAD", { cwd })
|
||||
// Check if repo has any commits before trying to diff against HEAD
|
||||
let diff = ""
|
||||
if (await checkGitRepoHasCommits(cwd)) {
|
||||
// Only run git diff if there are commits
|
||||
const { stdout: diffOutput } = await execAsync("git diff HEAD", { cwd })
|
||||
diff = diffOutput
|
||||
} else {
|
||||
// No commits yet, use status output only
|
||||
return `Working directory changes (new repository):\n\n${status}`
|
||||
}
|
||||
const output = `Working directory changes:\n\n${status}\n\n${diff}`.trim()
|
||||
return truncateOutput(output)
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const tsConfigPaths = require("tsconfig-paths")
|
||||
const fs = require("fs")
|
||||
|
||||
const tsConfig = JSON.parse(fs.readFileSync("./tsconfig.json", "utf-8"))
|
||||
|
||||
/**
|
||||
* The aliases point towards the `src` directory.
|
||||
* However, `tsc` doesn't compile paths by itself
|
||||
* (https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-does-not-affect-emit)
|
||||
* So we need to use tsconfig-paths to resolve the aliases when running tests,
|
||||
* but pointing to `out` instead.
|
||||
*/
|
||||
const outPaths = {}
|
||||
Object.keys(tsConfig.compilerOptions.paths).forEach((key) => {
|
||||
const value = tsConfig.compilerOptions.paths[key]
|
||||
outPaths[key] = value.map((path) => path.replace("src", "out"))
|
||||
})
|
||||
|
||||
tsConfigPaths.register({
|
||||
baseUrl: ".",
|
||||
paths: outPaths,
|
||||
})
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
"@integrations/*": ["src/integrations/*"],
|
||||
"@services/*": ["src/services/*"],
|
||||
"@shared/*": ["src/shared/*"],
|
||||
"@utils/*": ["src/utils/*"]
|
||||
"@utils/*": ["src/utils/*"],
|
||||
"@packages/*": ["src/packages/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "scripts/**/*"],
|
||||
|
||||
Generated
+4295
-162
File diff suppressed because it is too large
Load Diff
@@ -15,11 +15,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.27.4",
|
||||
"@heroui/react": "^2.8.0-beta.2",
|
||||
"@vscode/webview-ui-toolkit": "^1.4.0",
|
||||
"debounce": "^2.1.1",
|
||||
"dompurify": "^3.2.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"firebase": "^11.3.0",
|
||||
"framer-motion": "^12.7.4",
|
||||
"fuse.js": "^7.0.0",
|
||||
"fzf": "^0.5.2",
|
||||
"mermaid": "^11.4.1",
|
||||
@@ -42,7 +44,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@tailwindcss/vite": "^4.0.12",
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
@@ -60,10 +62,10 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"tailwindcss": "^4.0.12",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.2.6",
|
||||
"vite": "^6.3.4",
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import HistoryView from "./components/history/HistoryView"
|
||||
import SettingsView from "./components/settings/SettingsView"
|
||||
import WelcomeView from "./components/welcome/WelcomeView"
|
||||
import AccountView from "./components/account/AccountView"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
|
||||
import { FirebaseAuthProvider } from "./context/FirebaseAuthContext"
|
||||
import { useExtensionState } from "./context/ExtensionStateContext"
|
||||
import { vscode } from "./utils/vscode"
|
||||
import McpView from "./components/mcp/configuration/McpConfigurationView"
|
||||
import { Providers } from "./Providers"
|
||||
|
||||
const AppContent = () => {
|
||||
const { didHydrateState, showWelcome, shouldShowAnnouncement, showMcp, mcpTab } = useExtensionState()
|
||||
@@ -126,11 +126,9 @@ const AppContent = () => {
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ExtensionStateContextProvider>
|
||||
<FirebaseAuthProvider>
|
||||
<AppContent />
|
||||
</FirebaseAuthProvider>
|
||||
</ExtensionStateContextProvider>
|
||||
<Providers>
|
||||
<AppContent />
|
||||
</Providers>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
import { ExtensionStateContextProvider } from "./context/ExtensionStateContext"
|
||||
import { FirebaseAuthProvider } from "./context/FirebaseAuthContext"
|
||||
import { HeroUIProvider } from "@heroui/react"
|
||||
|
||||
export function Providers({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ExtensionStateContextProvider>
|
||||
<FirebaseAuthProvider>
|
||||
<HeroUIProvider>{children}</HeroUIProvider>
|
||||
</FirebaseAuthProvider>
|
||||
</ExtensionStateContextProvider>
|
||||
)
|
||||
}
|
||||
@@ -58,7 +58,8 @@ interface GitCommit {
|
||||
description: string
|
||||
}
|
||||
|
||||
const PLAN_MODE_COLOR = "var(--vscode-inputValidation-warningBorder)"
|
||||
const PLAN_MODE_COLOR = "#955CF1" // Purple color for plan mode
|
||||
const ACT_MODE_COLOR = "var(--vscode-terminal-ansiGreen)"
|
||||
|
||||
const SwitchOption = styled.div<{ isActive: boolean }>`
|
||||
padding: 2px 8px;
|
||||
@@ -93,7 +94,7 @@ const Slider = styled.div<{ isAct: boolean; isPlan?: boolean }>`
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
background-color: ${(props) => (props.isPlan ? PLAN_MODE_COLOR : "var(--vscode-focusBorder)")};
|
||||
background-color: ${(props) => (props.isPlan ? PLAN_MODE_COLOR : props.isAct ? ACT_MODE_COLOR : "var(--vscode-focusBorder)")};
|
||||
transition: transform 0.2s ease;
|
||||
transform: translateX(${(props) => (props.isAct ? "100%" : "0%")});
|
||||
`
|
||||
@@ -1287,7 +1288,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
inset: "10px 15px",
|
||||
backgroundColor: "rgba(var(--vscode-errorForeground-rgb), 0.1)",
|
||||
border: "2px solid var(--vscode-errorForeground)",
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@@ -1337,7 +1338,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
position: "absolute",
|
||||
inset: "10px 15px",
|
||||
border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
pointerEvents: "none",
|
||||
zIndex: 5,
|
||||
}}
|
||||
@@ -1360,7 +1361,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
fontFamily: "var(--vscode-font-family)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
lineHeight: "var(--vscode-editor-line-height)",
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
borderTop: 0,
|
||||
@@ -1400,14 +1401,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
}}
|
||||
placeholder={showUnsupportedFileError ? "" : placeholderText}
|
||||
maxRows={10}
|
||||
minRows={3}
|
||||
autoFocus={true}
|
||||
style={{
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
backgroundColor: "transparent",
|
||||
color: "var(--vscode-input-foreground)",
|
||||
//border: "1px solid var(--vscode-input-border)",
|
||||
borderRadius: 2,
|
||||
borderRadius: 4,
|
||||
fontFamily: "var(--vscode-font-family)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
lineHeight: "var(--vscode-editor-line-height)",
|
||||
@@ -1415,17 +1416,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
overflowX: "hidden",
|
||||
overflowY: "scroll",
|
||||
scrollbarWidth: "none",
|
||||
// Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410)
|
||||
// borderTop: "9px solid transparent",
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
borderTop: 0,
|
||||
borderBottom: `${thumbnailsHeight + 6}px solid transparent`,
|
||||
borderColor: "transparent",
|
||||
// borderRight: "54px solid transparent",
|
||||
// borderLeft: "9px solid transparent", // NOTE: react-textarea-autosize doesn't calculate correct height when using borderLeft/borderRight so we need to use horizontal padding instead
|
||||
// Instead of using boxShadow, we use a div with a border to better replicate the behavior when the textarea is focused
|
||||
// boxShadow: "0px 0px 0px 1px var(--vscode-input-border)",
|
||||
padding: "9px 28px 3px 9px",
|
||||
cursor: textAreaDisabled ? "not-allowed" : undefined,
|
||||
flex: 1,
|
||||
@@ -1434,7 +1429,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
isDraggingOver && !showUnsupportedFileError // Only show drag outline if not showing error
|
||||
? "2px dashed var(--vscode-focusBorder)"
|
||||
: isTextAreaFocused
|
||||
? `1px solid ${chatSettings.mode === "plan" ? PLAN_MODE_COLOR : "var(--vscode-focusBorder)"}`
|
||||
? `1px solid ${chatSettings.mode === "plan" ? PLAN_MODE_COLOR : ACT_MODE_COLOR}`
|
||||
: "none",
|
||||
outlineOffset: isDraggingOver && !showUnsupportedFileError ? "1px" : "0px", // Add offset for drag-over outline
|
||||
}}
|
||||
@@ -1461,7 +1456,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
right: 23,
|
||||
display: "flex",
|
||||
alignItems: "flex-center",
|
||||
height: textAreaBaseHeight || 31,
|
||||
height: 31,
|
||||
bottom: 9.5, // should be 10 but doesn't look good on mac
|
||||
zIndex: 2,
|
||||
}}>
|
||||
@@ -1616,7 +1611,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
Hold Shift to Drag Files
|
||||
Hold Shift to Drop Files
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useDeepCompareEffect, useEvent, useMount } from "react-use"
|
||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
|
||||
import styled from "styled-components"
|
||||
import HomeHeader from "@/components/welcome/HomeHeader"
|
||||
import {
|
||||
ClineApiReqInfo,
|
||||
ClineAsk,
|
||||
@@ -914,21 +915,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
}}>
|
||||
{telemetrySetting === "unset" && <TelemetryBanner />}
|
||||
|
||||
<HomeHeader />
|
||||
|
||||
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
||||
|
||||
<div style={{ padding: "0 20px", flexShrink: 0 }}>
|
||||
<h2>What can I do for you?</h2>
|
||||
<p>
|
||||
Thanks to{" "}
|
||||
<VSCodeLink href="https://www.anthropic.com/claude/sonnet" style={{ display: "inline" }}>
|
||||
Claude 3.7 Sonnet's
|
||||
</VSCodeLink>
|
||||
agentic coding capabilities, I can handle complex software development tasks step-by-step. With tools
|
||||
that let me create & edit files, explore complex projects, use a browser, and execute terminal
|
||||
commands (after you grant permission), I can assist you in ways that go beyond code completion or tech
|
||||
support. I can even use MCP to create new tools and extend my own capabilities.
|
||||
</p>
|
||||
</div>
|
||||
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { memo, useEffect } from "react"
|
||||
import React, { memo, useEffect, useRef, useState } from "react"
|
||||
import type { ComponentProps } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import rehypeHighlight, { Options } from "rehype-highlight"
|
||||
@@ -91,15 +91,34 @@ const remarkPreventBoldFilenames = () => {
|
||||
}
|
||||
}
|
||||
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
|
||||
const CopyButton = styled(VSCodeButton)`
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
`
|
||||
|
||||
const CodeBlockContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
&:hover ${CopyButton} {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMarkdown = styled.div`
|
||||
pre {
|
||||
background-color: ${CODE_BLOCK_BG_COLOR};
|
||||
border-radius: 3px;
|
||||
margin: 13x 0;
|
||||
margin: 13px 0;
|
||||
padding: 10px 10px;
|
||||
max-width: calc(100vw - 20px);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding-right: 70px;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
@@ -197,8 +216,42 @@ const StyledPre = styled.pre<{ theme: any }>`
|
||||
.join("")}
|
||||
`
|
||||
|
||||
const PreWithCopyButton = ({
|
||||
children,
|
||||
theme,
|
||||
...preProps
|
||||
}: { theme: Record<string, string> } & React.HTMLAttributes<HTMLPreElement>) => {
|
||||
const preRef = useRef<HTMLPreElement>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = () => {
|
||||
if (preRef.current) {
|
||||
const codeElement = preRef.current.querySelector("code")
|
||||
const textToCopy = codeElement ? codeElement.textContent : preRef.current.textContent
|
||||
|
||||
if (!textToCopy) return
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeBlockContainer>
|
||||
<CopyButton appearance="icon" onClick={handleCopy} aria-label={copied ? "Copied" : "Copy"}>
|
||||
<span className={`codicon codicon-${copied ? "check" : "copy"}`}></span>
|
||||
</CopyButton>
|
||||
<StyledPre {...preProps} theme={theme} ref={preRef}>
|
||||
{children}
|
||||
</StyledPre>
|
||||
</CodeBlockContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
const { theme } = useExtensionState()
|
||||
|
||||
const [reactContent, setMarkdown] = useRemark({
|
||||
remarkPlugins: [
|
||||
remarkPreventBoldFilenames,
|
||||
@@ -223,7 +276,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
],
|
||||
rehypeReactOptions: {
|
||||
components: {
|
||||
pre: ({ node, children, ...preProps }: any) => {
|
||||
pre: ({ children, ...preProps }: React.HTMLAttributes<HTMLPreElement>) => {
|
||||
if (Array.isArray(children) && children.length === 1 && React.isValidElement(children[0])) {
|
||||
const child = children[0] as React.ReactElement<{ className?: string }>
|
||||
if (child.props?.className?.includes("language-mermaid")) {
|
||||
@@ -231,9 +284,9 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<StyledPre {...preProps} theme={theme}>
|
||||
<PreWithCopyButton {...preProps} theme={theme || {}}>
|
||||
{children}
|
||||
</StyledPre>
|
||||
</PreWithCopyButton>
|
||||
)
|
||||
},
|
||||
code: (props: ComponentProps<"code">) => {
|
||||
@@ -253,7 +306,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
}, [markdown, setMarkdown, theme])
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<div>
|
||||
<StyledMarkdown>{reactContent}</StyledMarkdown>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -769,6 +769,101 @@ const ApiOptions = ({
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<label htmlFor="bedrock-model-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
|
||||
<VSCodeDropdown
|
||||
id="bedrock-model-dropdown"
|
||||
value={apiConfiguration?.awsBedrockCustomSelected ? "custom" : selectedModelId}
|
||||
onChange={(e: any) => {
|
||||
const isCustom = e.target.value === "custom"
|
||||
setApiConfiguration({
|
||||
...apiConfiguration,
|
||||
apiModelId: isCustom ? "" : e.target.value,
|
||||
awsBedrockCustomSelected: isCustom,
|
||||
awsBedrockCustomModelBaseId: bedrockDefaultModelId,
|
||||
})
|
||||
}}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{Object.keys(bedrockModels).map((modelId) => (
|
||||
<VSCodeOption
|
||||
key={modelId}
|
||||
value={modelId}
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
maxWidth: "100%",
|
||||
}}>
|
||||
{modelId}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
<VSCodeOption value="custom">Custom</VSCodeOption>
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
{apiConfiguration?.awsBedrockCustomSelected && (
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
Select "Custom" when using the Application Inference Profile in Bedrock. Enter the Application
|
||||
Inference Profile ID in the Model ID field. However, be sure to encode the / in the ARN as %2F.
|
||||
<br />
|
||||
Example: arn:aws:bedrock:us-west-2:<AWS Account
|
||||
ID>:application-inference-profile%2Fxxxxxxxxxxxx
|
||||
</p>
|
||||
<label htmlFor="bedrock-model-input">
|
||||
<span style={{ fontWeight: 500 }}>Model ID</span>
|
||||
</label>
|
||||
<VSCodeTextField
|
||||
id="bedrock-model-input"
|
||||
value={apiConfiguration?.apiModelId || ""}
|
||||
style={{ width: "100%", marginTop: 3 }}
|
||||
onInput={handleInputChange("apiModelId")}
|
||||
placeholder="Enter custom model ID..."
|
||||
/>
|
||||
<label htmlFor="bedrock-base-model-dropdown">
|
||||
<span style={{ fontWeight: 500 }}>Base Inference Model</span>
|
||||
</label>
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 3} className="dropdown-container">
|
||||
<VSCodeDropdown
|
||||
id="bedrock-base-model-dropdown"
|
||||
value={apiConfiguration?.awsBedrockCustomModelBaseId || bedrockDefaultModelId}
|
||||
onChange={handleInputChange("awsBedrockCustomModelBaseId")}
|
||||
style={{ width: "100%" }}>
|
||||
<VSCodeOption value="">Select a model...</VSCodeOption>
|
||||
{Object.keys(bedrockModels).map((modelId) => (
|
||||
<VSCodeOption
|
||||
key={modelId}
|
||||
value={modelId}
|
||||
style={{
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
maxWidth: "100%",
|
||||
}}>
|
||||
{modelId}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</DropdownContainer>
|
||||
</div>
|
||||
)}
|
||||
{(selectedModelId === "anthropic.claude-3-7-sonnet-20250219-v1:0" ||
|
||||
(apiConfiguration?.awsBedrockCustomSelected &&
|
||||
apiConfiguration?.awsBedrockCustomModelBaseId === "anthropic.claude-3-7-sonnet-20250219-v1:0")) && (
|
||||
<ThinkingBudgetSlider apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
)}
|
||||
<ModelInfoView
|
||||
selectedModelId={selectedModelId}
|
||||
modelInfo={selectedModelInfo}
|
||||
isDescriptionExpanded={isDescriptionExpanded}
|
||||
setIsDescriptionExpanded={setIsDescriptionExpanded}
|
||||
isPopup={isPopup}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1702,6 +1797,7 @@ const ApiOptions = ({
|
||||
selectedProvider !== "vscode-lm" &&
|
||||
selectedProvider !== "litellm" &&
|
||||
selectedProvider !== "requesty" &&
|
||||
selectedProvider !== "bedrock" &&
|
||||
showModelOptions && (
|
||||
<>
|
||||
<DropdownContainer zIndex={DROPDOWN_Z_INDEX - 2} className="dropdown-container">
|
||||
@@ -1709,7 +1805,6 @@ const ApiOptions = ({
|
||||
<span style={{ fontWeight: 500 }}>Model</span>
|
||||
</label>
|
||||
{selectedProvider === "anthropic" && createDropdown(anthropicModels)}
|
||||
{selectedProvider === "bedrock" && createDropdown(bedrockModels)}
|
||||
{selectedProvider === "vertex" && createDropdown(vertexModels)}
|
||||
{selectedProvider === "gemini" && createDropdown(geminiModels)}
|
||||
{selectedProvider === "openai-native" && createDropdown(openAiNativeModels)}
|
||||
@@ -1726,7 +1821,6 @@ const ApiOptions = ({
|
||||
</DropdownContainer>
|
||||
|
||||
{((selectedProvider === "anthropic" && selectedModelId === "claude-3-7-sonnet-20250219") ||
|
||||
(selectedProvider === "bedrock" && selectedModelId === "anthropic.claude-3-7-sonnet-20250219-v1:0") ||
|
||||
(selectedProvider === "vertex" && selectedModelId === "claude-3-7-sonnet@20250219")) && (
|
||||
<ThinkingBudgetSlider apiConfiguration={apiConfiguration} setApiConfiguration={setApiConfiguration} />
|
||||
)}
|
||||
@@ -2048,6 +2142,14 @@ export function normalizeApiConfiguration(apiConfiguration?: ApiConfiguration):
|
||||
case "anthropic":
|
||||
return getProviderData(anthropicModels, anthropicDefaultModelId)
|
||||
case "bedrock":
|
||||
if (apiConfiguration?.awsBedrockCustomSelected) {
|
||||
const baseModelId = apiConfiguration.awsBedrockCustomModelBaseId
|
||||
return {
|
||||
selectedProvider: provider,
|
||||
selectedModelId: modelId || bedrockDefaultModelId,
|
||||
selectedModelInfo: (baseModelId && bedrockModels[baseModelId]) || bedrockModels[bedrockDefaultModelId],
|
||||
}
|
||||
}
|
||||
return getProviderData(bedrockModels, bedrockDefaultModelId)
|
||||
case "vertex":
|
||||
return getProviderData(vertexModels, vertexDefaultModelId)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { TabButton } from "../mcp/configuration/McpConfigurationView"
|
||||
import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "@shared/ExtensionMessage"
|
||||
import BrowserSettingsSection from "./BrowserSettingsSection"
|
||||
import TerminalSettingsSection from "./TerminalSettingsSection"
|
||||
|
||||
const { IS_DEV } = process.env
|
||||
|
||||
@@ -240,6 +241,9 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
{/* Browser Settings Section */}
|
||||
<BrowserSettingsSection />
|
||||
|
||||
{/* Terminal Settings Section */}
|
||||
<TerminalSettingsSection />
|
||||
|
||||
<div className="mt-auto pr-2 flex justify-center">
|
||||
<SettingsButton
|
||||
onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import React, { useState } from "react"
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
|
||||
export const TerminalSettingsSection: React.FC = () => {
|
||||
const { shellIntegrationTimeout, setShellIntegrationTimeout } = useExtensionState()
|
||||
const [inputValue, setInputValue] = useState((shellIntegrationTimeout / 1000).toString())
|
||||
const [inputError, setInputError] = useState<string | null>(null)
|
||||
|
||||
const handleTimeoutChange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const value = target.value
|
||||
|
||||
setInputValue(value)
|
||||
|
||||
const seconds = parseFloat(value)
|
||||
if (isNaN(seconds) || seconds <= 0) {
|
||||
setInputError("Please enter a positive number")
|
||||
return
|
||||
}
|
||||
|
||||
setInputError(null)
|
||||
const timeout = Math.round(seconds * 1000) // Convert to milliseconds
|
||||
|
||||
// Update local state
|
||||
setShellIntegrationTimeout(timeout)
|
||||
|
||||
// Send to extension
|
||||
vscode.postMessage({
|
||||
type: "updateTerminalConnectionTimeout",
|
||||
shellIntegrationTimeout: timeout,
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputBlur = () => {
|
||||
// If there was an error, reset the input to the current valid value
|
||||
if (inputError) {
|
||||
setInputValue((shellIntegrationTimeout / 1000).toString())
|
||||
setInputError(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id="terminal-settings-section"
|
||||
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Terminal Settings</h3>
|
||||
<div style={{ marginBottom: 15 }}>
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
|
||||
Shell integration timeout (seconds)
|
||||
</label>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<VSCodeTextField
|
||||
style={{ width: "100%" }}
|
||||
value={inputValue}
|
||||
placeholder="Enter timeout in seconds"
|
||||
onChange={(event) => handleTimeoutChange(event as Event)}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
</div>
|
||||
{inputError && (
|
||||
<div style={{ color: "var(--vscode-errorForeground)", fontSize: "12px", marginTop: 5 }}>{inputError}</div>
|
||||
)}
|
||||
</div>
|
||||
<p style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)", margin: 0 }}>
|
||||
Set how long Cline waits for shell integration to activate before executing commands. Increase this value if
|
||||
you experience terminal connection timeouts.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TerminalSettingsSection
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useState, useEffect } from "react"
|
||||
import { useFirebaseAuth } from "@/context/FirebaseAuthContext"
|
||||
import ClineLogoWhite from "@/assets/ClineLogoWhite"
|
||||
|
||||
type TimeOfDay = "morning" | "afternoon" | "evening" | "night"
|
||||
|
||||
const getTimeOfDay = (): TimeOfDay => {
|
||||
const hour = new Date().getHours()
|
||||
|
||||
if (hour >= 5 && hour < 12) return "morning"
|
||||
if (hour >= 12 && hour < 18) return "afternoon"
|
||||
if (hour >= 18 && hour < 24) return "evening"
|
||||
return "night"
|
||||
}
|
||||
|
||||
const secondaryMessages: Record<TimeOfDay, string[]> = {
|
||||
morning: ["Grab coffee and let's get to work.", "Ready for a productive day?", "What are we building today?"],
|
||||
afternoon: ["Let's keep the momentum going.", "Time for the next task."],
|
||||
evening: ["Still going strong!", "Let's get those final touches in."],
|
||||
night: [
|
||||
"Ah, the silence of deep focus… or deep chaos.",
|
||||
"Running on caffeine and pure determination?",
|
||||
"The world sleeps. You debug.",
|
||||
"Back again? The bugs didn't stand a chance.",
|
||||
"Let's code like no one's watching — because no one is.",
|
||||
"Quiet hours, loud ideas.",
|
||||
"Running low on sleep, high on inspiration?",
|
||||
"The best commits are made under moonlight.",
|
||||
"If this isn't dedication, I don't know what is.",
|
||||
"Brainstorming or bug-hunting — I'm with you either way.",
|
||||
"Burning the midnight oil?",
|
||||
],
|
||||
}
|
||||
|
||||
// Should never hit default, just built in for redundancy
|
||||
const defaultSecondaryMessages = ["Let's get to work.", "Ready when you are.", "How can I assist?"]
|
||||
|
||||
const primaryGreetings: Record<TimeOfDay, string[]> = {
|
||||
morning: ["Good Morning", "Morning", "Top o' the mornin'"],
|
||||
afternoon: ["Good Afternoon", "Afternoon", "Howdy"],
|
||||
evening: ["Good Evening", "Evening"],
|
||||
night: ["Good Evening", "Evening"],
|
||||
}
|
||||
|
||||
// Should never hit default, just built in for redundancy
|
||||
const defaultPrimaryGreetings = ["Hello", "Hi", "Hey!", "Yo!"]
|
||||
|
||||
const getSecondaryMessage = (timeOfDay: TimeOfDay): string => {
|
||||
const messages = secondaryMessages[timeOfDay] || defaultSecondaryMessages
|
||||
const randomIndex = Math.floor(Math.random() * messages.length)
|
||||
return messages[randomIndex]
|
||||
}
|
||||
|
||||
const getPrimaryGreeting = (timeOfDay: TimeOfDay): string => {
|
||||
const greetings = primaryGreetings[timeOfDay] || defaultPrimaryGreetings
|
||||
const randomIndex = Math.floor(Math.random() * greetings.length)
|
||||
return greetings[randomIndex]
|
||||
}
|
||||
|
||||
const getFirstName = (displayName: string | null | undefined) => {
|
||||
if (!displayName) return ""
|
||||
return displayName.split(" ")[0]
|
||||
}
|
||||
|
||||
const HomeHeader = () => {
|
||||
const { user } = useFirebaseAuth()
|
||||
const [timeOfDay] = useState<TimeOfDay>(getTimeOfDay())
|
||||
const [greeting] = useState<string>(getPrimaryGreeting(timeOfDay))
|
||||
const [secondaryMessage] = useState<string>(getSecondaryMessage(timeOfDay))
|
||||
const [firstName, setFirstName] = useState<string>("")
|
||||
|
||||
// Calculate firstName only once when the component mounts or when user changes
|
||||
useEffect(() => {
|
||||
setFirstName(getFirstName(user?.displayName))
|
||||
}, [user?.displayName])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center mb-5">
|
||||
<div className="my-5">
|
||||
<ClineLogoWhite className="size-16" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h2 className="m-0 text-[var(--vscode-font-size)]">
|
||||
{greeting}
|
||||
{firstName ? `, ${firstName}` : ""}!
|
||||
</h2>
|
||||
<div className="text-[var(--vscode-descriptionForeground)] text-sm font-normal mt-1">{secondaryMessage}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeHeader
|
||||
@@ -39,6 +39,7 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
setTelemetrySetting: (value: TelemetrySetting) => void
|
||||
setShowAnnouncement: (value: boolean) => void
|
||||
setPlanActSeparateModelsSetting: (value: boolean) => void
|
||||
setShellIntegrationTimeout: (value: number) => void
|
||||
setMcpServers: (value: McpServer[]) => void
|
||||
|
||||
// Navigation
|
||||
@@ -69,6 +70,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
planActSeparateModelsSetting: true,
|
||||
globalClineRulesToggles: {},
|
||||
localClineRulesToggles: {},
|
||||
shellIntegrationTimeout: 4000, // default timeout for shell integration
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -242,6 +244,11 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
...prevState,
|
||||
shouldShowAnnouncement: value,
|
||||
})),
|
||||
setShellIntegrationTimeout: (value) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
shellIntegrationTimeout: value,
|
||||
})),
|
||||
setMcpServers: (mcpServers: McpServer[]) => setMcpServers(mcpServers),
|
||||
setShowMcp,
|
||||
setMcpTab,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
/* @import "tailwindcss/preflight.css" layer(base); */
|
||||
@import "tailwindcss/utilities.css" layer(utilities);
|
||||
|
||||
@config "../tailwind.config.js";
|
||||
|
||||
textarea:focus {
|
||||
outline: 1.5px solid var(--vscode-focusBorder, #007fd4);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
const { heroui } = require("@heroui/react")
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}", "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
darkMode: "class",
|
||||
plugins: [
|
||||
heroui({
|
||||
defaultTheme: "vscode",
|
||||
themes: {
|
||||
vscode: {
|
||||
colors: {
|
||||
background: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user