Compare commits

...
Author SHA1 Message Date
Cline Evaluation faac4941c2 Adding Gemini 2.5 pro preview 06-05 2025-06-06 00:11:51 +05:30
Cline Evaluation 2a634317f7 New files 2025-06-05 21:46:31 +05:30
EvanandElephant Lumps c68e427d13 migrate mcpMarketplaceCatalog (#4023)
Co-authored-by: Elephant Lumps <celestial_vault@Elephants-MacBook-Pro.local>
2025-06-04 23:17:13 -07:00
EvanandElephant Lumps 0dca4dedbd migrate partialMessage (#4033)
Co-authored-by: Elephant Lumps <celestial_vault@Elephants-MacBook-Pro.local>
2025-06-04 23:02:37 -07:00
EvanandElephant Lumps c6e7b5249e Migrate settingsButtonClicked protobus (#3976)
* migrate settingsButtonClicked

* changeset

* add provider type filtering

---------

Co-authored-by: Elephant Lumps <celestial_vault@Elephants-MacBook-Pro.local>
2025-06-04 20:49:07 -07:00
111 changed files with 1706 additions and 823 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Migrate settingsButtonClicked to protobus
+1 -1
View File
@@ -742,7 +742,7 @@
## [1.9.0]
- Claude can now use a browser! This update adds a new `inspect_site` tool that captures screenshots and console logs from websites (including localhost), making it easier for Claude to troubleshoot issues on his own.
- Claude can now use a browser! This update adds a new `inspect_site` tool that captures screenshots and , making it easier for Claude to troubleshoot issues on his own.
- Improved automatic linter/compiler debugging by only sending Claude new errors that result from his edits, rather than reporting all workspace problems.
## [1.8.0]
+1 -3
View File
@@ -113,9 +113,7 @@ const server = new Server({ name: "remote-server", version: "1.0.0" })
// Use SSE transport
const transport = new SSEServerTransport(server)
app.use("/mcp", transport.requestHandler())
app.listen(3000, () => {
console.log("MCP server listening on port 3000")
})
app.listen(3000, () => {})
```
## Local vs. Hosted: Deployment Aspects
+1 -4
View File
@@ -68,15 +68,12 @@ const esbuildProblemMatcherPlugin = {
name: "esbuild-problem-matcher",
setup(build) {
build.onStart(() => {
console.log("[watch] build started")
})
build.onStart(() => {})
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")
})
},
}
@@ -52,7 +52,7 @@ grpcRuleTester.run("no-grpc-client-object-literals", grpcRule, {
{
code: `
function processData(data) {
console.log(data);
;
}
processData({
+9 -9
View File
@@ -91,7 +91,7 @@ module.exports = createRule({
if (typeName) {
// Check if it's a direct protobuf import
if (protobufImports.has(typeName)) {
//console.log('🚨 VIOLATION: Using object literal for protobuf type:', typeName);
//;
const sourceCode = context.getSourceCode()
const declaratorText = sourceCode.getText(declarator)
const objectText = sourceCode.getText(node)
@@ -114,7 +114,7 @@ module.exports = createRule({
// Check if it's a namespaced protobuf type (e.g., proto.MyRequest)
if (isNamespacedProtobufType(protobufNamespaceImports, typeName)) {
//console.log('🚨 VIOLATION: Using object literal for namespaced protobuf type:', typeName);
//;
const sourceCode = context.getSourceCode()
const declaratorText = sourceCode.getText(declarator)
context.report({
@@ -164,7 +164,7 @@ module.exports = createRule({
}
if (typeName && protobufImports.has(typeName)) {
//console.log('🚨 VIOLATION: Using object literal in assignment for protobuf type:', typeName);
//;
const sourceCode = context.getSourceCode()
const assignmentText = sourceCode.getText(assignment.left) + " = "
const objectText = sourceCode.getText(node)
@@ -211,7 +211,7 @@ module.exports = createRule({
// Check if the return type is a protobuf type
if (returnTypeName) {
if (protobufImports.has(returnTypeName)) {
//console.log('🚨 VIOLATION: Return type is a protobuf type:', returnTypeName);
//;
const sourceCode = context.getSourceCode()
const returnText = sourceCode.getText(node.parent)
context.report({
@@ -234,7 +234,7 @@ module.exports = createRule({
if (isNamespacedProtobufType(protobufNamespaceImports, returnTypeName)) {
const sourceCode = context.getSourceCode()
const returnText = sourceCode.getText(node.parent)
//console.log('🚨 VIOLATION: Return type is a namespaced protobuf type:', returnTypeName);
//;
context.report({
node,
messageId: "useProtobufMethodGeneric",
@@ -270,7 +270,7 @@ module.exports = createRule({
if (returnTypeRegex.test(functionText)) {
const returnText = sourceCode.getText(node.parent)
//console.log('🚨 VIOLATION: regex matched protobuf type:', functionText);
//;
context.report({
node,
messageId: "useProtobufMethod",
@@ -303,7 +303,7 @@ module.exports = createRule({
if (namespaceReturnTypeRegex.test(functionText)) {
const returnText = sourceCode.getText(node.parent)
//console.log('🚨 VIOLATION: regex matched namespaced protobuf type:', functionText, "namespace:", namespace);
//;
context.report({
node,
messageId: "useProtobufMethodGeneric",
@@ -356,7 +356,7 @@ module.exports = createRule({
if (protobufNamespaceImports.has(namespace)) {
const sourceCode = context.getSourceCode()
const callText = sourceCode.getText(node.parent)
//console.log('🚨 VIOLATION: Check function call arguments object literal:', callText);
//;
context.report({
node,
messageId: "useProtobufMethodGeneric",
@@ -414,7 +414,7 @@ module.exports = createRule({
isNamespacedProtobufType(protobufNamespaceImports, typeName))
) {
const callText = sourceCode.getText(node.parent)
//console.log('🚨 VIOLATION: Function call arguments object literal:', callText);
//;
context.report({
node,
messageId: "useProtobufMethodGeneric",
+1 -6
View File
@@ -19,16 +19,11 @@ export class ExercismAdapter implements BenchmarkAdapter {
const exercismDir = path.join(EVALS_DIR, "repositories", "exercism")
if (!fs.existsSync(exercismDir)) {
console.log(`Cloning Exercism repository to ${exercismDir}...`)
await execa("git", ["clone", "https://github.com/pashpashpash/evals.git", exercismDir])
console.log("Exercism repository cloned successfully")
} else {
console.log(`Exercism repository already exists at ${exercismDir}`)
// Pull latest changes
console.log("Pulling latest changes...")
await execa("git", ["pull"], { cwd: exercismDir })
console.log("Repository updated successfully")
}
}
+2 -11
View File
@@ -15,13 +15,10 @@ export class MultiSWEAdapter implements BenchmarkAdapter {
* Set up the Multi-SWE-Bench benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("Multi-SWE-Bench dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "multi-swe")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy Multi-SWE-Bench directory at ${repoDir}`)
}
}
@@ -107,10 +104,7 @@ export class MultiSWEAdapter implements BenchmarkAdapter {
// TypeScript frontend
fs.mkdirSync(path.join(taskDir, "frontend"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "frontend", "app.ts"),
`// TODO: Implement TypeScript frontend\nconsole.log('Frontend starting...');\n`,
)
fs.writeFileSync(path.join(taskDir, "frontend", "app.ts"), `// TODO: Implement TypeScript frontend\n;\n`)
// Rust processing service
fs.mkdirSync(path.join(taskDir, "processor"), { recursive: true })
@@ -149,10 +143,7 @@ export class MultiSWEAdapter implements BenchmarkAdapter {
// Node.js service
fs.mkdirSync(path.join(taskDir, "service-node"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "service-node", "server.js"),
`// TODO: Implement Node.js service\nconsole.log('Node.js service starting...');\n`,
)
fs.writeFileSync(path.join(taskDir, "service-node", "server.js"), `// TODO: Implement Node.js service\n;\n`)
// Java service
fs.mkdirSync(path.join(taskDir, "service-java"), { recursive: true })
-3
View File
@@ -15,13 +15,10 @@ export class SWEBenchAdapter implements BenchmarkAdapter {
* Set up the SWE-Bench benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("SWE-Bench dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "swe-bench")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy SWE-Bench directory at ${repoDir}`)
}
}
+1 -7
View File
@@ -15,13 +15,10 @@ export class SWELancerAdapter implements BenchmarkAdapter {
* Set up the SWELancer benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("SWELancer dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "swelancer")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy SWELancer directory at ${repoDir}`)
}
}
@@ -102,10 +99,7 @@ export class SWELancerAdapter implements BenchmarkAdapter {
`<!DOCTYPE html>\n<html>\n<head>\n <title>Landing Page</title>\n</head>\n<body>\n <!-- TODO: Implement landing page -->\n</body>\n</html>`,
)
} else if (task.id === "swelancer-task-2") {
fs.writeFileSync(
path.join(taskDir, "server.js"),
`// TODO: Implement REST API\nconsole.log('Server starting...');`,
)
fs.writeFileSync(path.join(taskDir, "server.js"), `// TODO: Implement REST API\n;`)
} else if (task.id === "swelancer-task-3") {
fs.writeFileSync(
path.join(taskDir, "styles.css"),
+3 -12
View File
@@ -17,37 +17,28 @@ export async function evalsEnvHandler(options: EvalsEnvOptions): Promise<void> {
const repoRoot = path.resolve(currentDir, "..", "..") // Navigate up from evals/cli to root
const directory = options.directory || repoRoot
console.log(chalk.blue(`Working with directory: ${directory}`))
// Perform the requested action
switch (options.action) {
case "create":
console.log(chalk.blue("Creating evals.env file..."))
createEvalsEnvFile(directory)
console.log(chalk.green("The Cline extension should now detect this file and enter test mode."))
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
break
case "remove":
console.log(chalk.blue("Removing evals.env file..."))
removeEvalsEnvFile(directory)
console.log(chalk.green("The Cline extension should now exit test mode."))
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
break
case "check":
console.log(chalk.blue("Checking for evals.env file..."))
const exists = checkEvalsEnvFile(directory)
if (exists) {
console.log(chalk.green("The Cline extension should be in test mode."))
} else {
console.log(chalk.yellow("The Cline extension should not be in test mode."))
}
break
default:
console.error(chalk.red(`Unknown action: ${options.action}`))
console.log(chalk.yellow("Valid actions are: create, remove, check"))
break
}
}
-2
View File
@@ -24,8 +24,6 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
// Get all runs
const runs = db.getRuns()
console.log(chalk.blue(`Found ${runs.length} evaluation runs`))
if (runs.length === 0) {
spinner.fail("No evaluation runs found")
return
+1 -16
View File
@@ -25,16 +25,11 @@ export async function runHandler(options: RunOptions): Promise<void> {
const model = options.model
const count = options.count || Infinity
console.log(chalk.blue(`Running evaluations for model: ${model}`))
console.log(chalk.blue(`Benchmarks: ${benchmarks.join(", ")}`))
// Create a run for each benchmark
for (const benchmark of benchmarks) {
const runId = uuidv4()
const db = new ResultsDatabase()
console.log(chalk.green(`\nStarting run for benchmark: ${benchmark}`))
// Create run in database
db.createRun(runId, model, benchmark)
@@ -50,21 +45,17 @@ export async function runHandler(options: RunOptions): Promise<void> {
// Limit number of tasks if specified
const tasksToRun = tasks.slice(0, count)
console.log(chalk.blue(`Running ${tasksToRun.length} tasks...`))
// Run each task
for (let i = 0; i < tasksToRun.length; i++) {
const task = tasksToRun[i]
console.log(chalk.cyan(`\nTask ${i + 1}/${tasksToRun.length}: ${task.name}`))
// Prepare task
const prepareSpinner = ora("Preparing task...").start()
const preparedTask = await adapter.prepareTask(task.id)
prepareSpinner.succeed("Task prepared")
// Spawn VSCode
console.log("Spawning VSCode...")
await spawnVSCode(preparedTask.workspacePath)
// Send task to server
@@ -92,8 +83,6 @@ export async function runHandler(options: RunOptions): Promise<void> {
await storeTaskResult(runId, preparedTask, result, verification)
storeSpinner.succeed("Result stored")
console.log(chalk.green(`Task completed. Success: ${verification.success}`))
// Clean up VS Code and temporary files
const cleanupSpinner = ora("Cleaning up...").start()
try {
@@ -121,13 +110,9 @@ export async function runHandler(options: RunOptions): Promise<void> {
// Mark run as complete
db.completeRun(runId)
console.log(chalk.green(`\nRun complete for benchmark: ${benchmark}`))
} catch (error: any) {
console.error(chalk.red(`Error running benchmark ${benchmark}: ${error.message}`))
console.error(error.stack)
}
}
console.log(chalk.green("\nAll evaluations complete"))
}
-4
View File
@@ -17,8 +17,6 @@ interface SetupOptions {
export async function setupHandler(options: SetupOptions): Promise<void> {
const benchmarks = options.benchmarks.split(",")
console.log(chalk.blue(`Setting up benchmarks: ${benchmarks.join(", ")}`))
// Create directories
const evalsDir = path.resolve(__dirname, "../../../")
const reposDir = path.join(evalsDir, "repositories")
@@ -63,8 +61,6 @@ export async function setupHandler(options: SetupOptions): Promise<void> {
throw error
}
}
console.log(chalk.green("Setup complete"))
} catch (error) {
console.error(chalk.red(`Setup failed: ${(error as Error).message}`))
throw error
+2 -6
View File
@@ -12,7 +12,6 @@ export function createEvalsEnvFile(directory: string): boolean {
// Check if the file already exists
if (fs.existsSync(evalsEnvPath)) {
console.log(chalk.yellow(`evals.env file already exists at ${evalsEnvPath}`))
return false
}
@@ -27,7 +26,7 @@ export function createEvalsEnvFile(directory: string): boolean {
# Delete this file to deactivate test mode.
`
fs.writeFileSync(evalsEnvPath, content)
console.log(chalk.green(`Created evals.env file at ${evalsEnvPath}`))
return true
} catch (error) {
console.error(chalk.red(`Error creating evals.env file: ${error}`))
@@ -45,14 +44,13 @@ export function removeEvalsEnvFile(directory: string): boolean {
// Check if the file exists
if (!fs.existsSync(evalsEnvPath)) {
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
return false
}
// Remove the file
try {
fs.unlinkSync(evalsEnvPath)
console.log(chalk.green(`Removed evals.env file from ${evalsEnvPath}`))
return true
} catch (error) {
console.error(chalk.red(`Error removing evals.env file: ${error}`))
@@ -70,9 +68,7 @@ export function checkEvalsEnvFile(directory: string): boolean {
const exists = fs.existsSync(evalsEnvPath)
if (exists) {
console.log(chalk.green(`evals.env file found at ${evalsEnvPath}`))
} else {
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
}
return exists
-7
View File
@@ -22,8 +22,6 @@ export const REQUIRED_EXTENSIONS = [
* @returns Promise that resolves when all extensions are installed
*/
export async function installRequiredExtensions(extensionsDir: string): Promise<void> {
console.log("Installing required VSCode extensions...")
// Create the extensions directory if it doesn't exist
if (!fs.existsSync(extensionsDir)) {
fs.mkdirSync(extensionsDir, { recursive: true })
@@ -32,16 +30,12 @@ export async function installRequiredExtensions(extensionsDir: string): Promise<
// Install each extension
for (const extension of REQUIRED_EXTENSIONS) {
try {
console.log(`Installing extension: ${extension}...`)
await execa("code", ["--extensions-dir", extensionsDir, "--install-extension", extension, "--force"])
console.log(`✅ Extension ${extension} installed successfully`)
} catch (error: any) {
console.warn(`⚠️ Failed to install extension ${extension}: ${error.message}`)
// Continue with other extensions even if one fails
}
}
console.log("✅ All required extensions installed")
}
/**
@@ -127,5 +121,4 @@ export function configureExtensionSettings(userDataDir: string): void {
// Write updated settings
fs.writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2))
console.log("✅ Extension settings configured")
}
-2
View File
@@ -11,8 +11,6 @@ export async function sendTaskToServer(task: string, apiKey?: string): Promise<a
const SERVER_URL = "http://localhost:9876/task"
try {
console.log(chalk.blue(`Sending task to server: ${task.substring(0, 100)}${task.length > 100 ? "..." : ""}`))
const response = await fetch(SERVER_URL, {
method: "POST",
headers: {
+14 -43
View File
@@ -31,7 +31,7 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
if (!vsixPath) {
try {
// Build the VSIX (no longer need to set IS_TEST=true as we'll use evals.env file)
console.log("Building VSIX...")
const clineRoot = path.resolve(process.cwd(), "..", "..")
await execa("npx", ["vsce", "package"], {
cwd: clineRoot,
@@ -58,14 +58,10 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
// Use the most recent VSIX
vsixPath = vsixFilesWithStats[0].path
console.log(`Using most recent VSIX: ${vsixPath} (modified ${vsixFilesWithStats[0].mtime.toISOString()})`)
// Log all found VSIX files for debugging
if (vsixFiles.length > 1) {
console.log(`Found ${vsixFiles.length} VSIX files:`)
vsixFilesWithStats.forEach((f) => {
console.log(` - ${f.file} (modified ${f.mtime.toISOString()})`)
})
vsixFilesWithStats.forEach((f) => {})
}
} else {
console.warn("Could not find generated VSIX file")
@@ -78,15 +74,13 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
// Create a temporary user data directory for this VS Code instance
const tempUserDataDir = path.join(os.tmpdir(), `vscode-cline-eval-${Date.now()}`)
fs.mkdirSync(tempUserDataDir, { recursive: true })
console.log(`Created temporary user data directory: ${tempUserDataDir}`)
// Create a temporary extensions directory to ensure no other extensions are loaded
const tempExtensionsDir = path.join(os.tmpdir(), `vscode-cline-eval-ext-${Date.now()}`)
fs.mkdirSync(tempExtensionsDir, { recursive: true })
console.log(`Created temporary extensions directory: ${tempExtensionsDir}`)
// Create evals.env file in the workspace to trigger test mode
console.log(`Creating evals.env file in workspace: ${workspacePath}`)
const evalsEnvPath = path.join(workspacePath, "evals.env")
fs.writeFileSync(
evalsEnvPath,
@@ -138,7 +132,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
"extensions.autoUpdate": false,
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
console.log(`Created settings.json to disable workspace trust and auto-open Cline`)
// Create keybindings.json to automatically open Cline on startup
const keybindingsPath = path.join(settingsDir, "keybindings.json")
@@ -155,7 +148,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
},
]
fs.writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2))
console.log(`Created keybindings.json to help with Cline activation`)
// Build the command arguments with custom user data directory
const args = [
@@ -195,7 +187,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
}, 5000);
`
fs.writeFileSync(startupScriptPath, startupScript)
console.log(`Created startup script to activate Cline`)
// If a VSIX is provided, install it
if (vsixPath) {
@@ -206,11 +197,11 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
}
// Install required extensions
console.log("Installing required VSCode extensions...")
await installRequiredExtensions(tempExtensionsDir)
// Configure extension settings
console.log("Configuring extension settings...")
configureExtensionSettings(tempUserDataDir)
// Execute the command
@@ -219,13 +210,13 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
// The VSIX will be installed in the isolated environment if provided in the args
// Launch VS Code
console.log("Launching VS Code...")
await execa("code", args, {
stdio: "inherit",
})
// Wait longer for VSCode to initialize and extension to load
console.log("Waiting for VS Code to initialize...")
await new Promise((resolve) => setTimeout(resolve, 30000))
// Create a JavaScript file that will be loaded as a VS Code extension
@@ -264,7 +255,7 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('Cline Activator is now active!');
;
// Register the command to activate Cline
let disposable = vscode.commands.registerCommand('cline-activator.activate', async function () {
@@ -277,33 +268,33 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
}
if (!extension.isActive) {
console.log('Activating Cline extension...');
;
await extension.activate();
}
// Show the Cline sidebar
console.log('Opening Cline sidebar...');
;
await vscode.commands.executeCommand('workbench.view.extension.saoudrizwan.claude-dev-ActivityBar');
// Wait a moment for the sidebar to initialize
await new Promise(resolve => setTimeout(resolve, 2000));
// Also open Cline in a tab as a fallback
console.log('Opening Cline in a tab...');
;
await vscode.commands.executeCommand('cline.openInNewTab');
// Wait a moment for the tab to initialize
await new Promise(resolve => setTimeout(resolve, 2000));
// Create the test server if it doesn't exist
console.log('Creating test server...');
;
// Get the visible webview instance
const clineRootPath = '${path.resolve(process.cwd(), "..", "..")}';
const visibleWebview = require(path.join(clineRootPath, 'src', 'core', 'webview')).WebviewProvider.getVisibleInstance();
if (visibleWebview) {
require(path.join(clineRootPath, 'src', 'services', 'test', 'TestServer')).createTestServer(visibleWebview);
console.log('Test server created successfully');
;
} else {
console.error('No visible webview instance found');
}
@@ -328,7 +319,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
}
`
fs.writeFileSync(extensionJsPath, extensionJs)
console.log(`Created Cline Activator extension`)
// Try multiple approaches to activate the extension
let serverStarted = false
@@ -343,11 +333,9 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
vscode.commands.executeCommand('cline-activator.activate');
`
fs.writeFileSync(activationScriptPath, activationScript)
console.log(`Created activation script to run in VS Code`)
// Execute the activation script
try {
console.log("Executing activation script to start Cline and test server...")
await execa(
"code",
[
@@ -366,7 +354,7 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
)
// Wait for the test server to start
console.log("Waiting for test server to start...")
for (let i = 0; i < 30; i++) {
try {
// Try to connect to the test server
@@ -378,7 +366,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
})
if (response.status === 204) {
console.log("Test server is running!")
serverStarted = true
break
}
@@ -393,7 +380,6 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
if (!serverStarted) {
console.warn("Test server did not start after multiple attempts")
console.log("You may need to manually open the Cline extension in VS Code")
}
// Store the resources for this workspace
@@ -417,18 +403,14 @@ export async function spawnVSCode(workspacePath: string, vsixPath?: string): Pro
* @param workspacePath The workspace path to clean up resources for
*/
export async function cleanupVSCode(workspacePath: string): Promise<void> {
console.log(`Cleaning up VS Code resources for workspace: ${workspacePath}`)
// Get the resources for this workspace
const resources = workspaceResources.get(workspacePath)
if (!resources) {
console.log(`No resources found for workspace: ${workspacePath}`)
return
}
// Try to shut down the test server
try {
console.log("Shutting down test server...")
await fetch("http://localhost:9876/shutdown", {
method: "POST",
headers: {
@@ -443,8 +425,6 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
// Try to gracefully close VS Code instead of killing it
try {
console.log("Attempting to gracefully close VS Code...")
// Create a settings file that will disable the crash reporter and the exit confirmation dialog
const settingsDir = path.join(resources.tempUserDataDir, "User")
const settingsPath = path.join(settingsDir, "settings.json")
@@ -505,7 +485,6 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
const pid = parseInt(parts[1])
if (pid && !isNaN(pid)) {
console.log(`Sending SIGTERM to VS Code process with PID: ${pid}`)
try {
// Use SIGTERM instead of SIGKILL for a graceful shutdown
process.kill(pid, "SIGTERM")
@@ -545,8 +524,6 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
// If VS Code is still running, use forceful termination as a last resort
if (vsCodeStillRunning) {
console.log("Graceful shutdown failed, falling back to forceful termination...")
if (process.platform === "win32") {
try {
await execa("taskkill", ["/IM", "code.exe", "/F"])
@@ -564,7 +541,6 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
const pid = parseInt(parts[1])
if (pid && !isNaN(pid)) {
console.log(`Forcefully killing VS Code process with PID: ${pid}`)
try {
process.kill(pid, "SIGKILL")
} catch (killError) {
@@ -584,14 +560,12 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
// Clean up temporary directories and evals.env file
try {
console.log(`Removing temporary user data directory: ${resources.tempUserDataDir}`)
fs.rmSync(resources.tempUserDataDir, { recursive: true, force: true })
} catch (error) {
console.warn(`Error removing temporary user data directory: ${error}`)
}
try {
console.log(`Removing temporary extensions directory: ${resources.tempExtensionsDir}`)
fs.rmSync(resources.tempExtensionsDir, { recursive: true, force: true })
} catch (error) {
console.warn(`Error removing temporary extensions directory: ${error}`)
@@ -601,7 +575,6 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
try {
const evalsEnvPath = path.join(workspacePath, "evals.env")
if (fs.existsSync(evalsEnvPath)) {
console.log(`Removing evals.env file: ${evalsEnvPath}`)
fs.unlinkSync(evalsEnvPath)
}
} catch (error) {
@@ -610,6 +583,4 @@ export async function cleanupVSCode(workspacePath: string): Promise<void> {
// Remove from the global map
workspaceResources.delete(workspacePath)
console.log("Cleanup completed")
}
+1 -4
View File
@@ -75,7 +75,6 @@ export class GrpcHandler {
requestRegistry.registerRequest(
requestId,
() => {
console.log(`[DEBUG] Cleaning up streaming request: ${requestId}`)
if (streamingCallbacks.onComplete && !completionCalled) {
completionCalled = true
streamingCallbacks.onComplete()
@@ -86,7 +85,7 @@ export class GrpcHandler {
)
// Call the streaming handler directly
console.log(`[DEBUG] Streaming gRPC host call to ${service}.${method} req:${requestId}`)
try {
await this.handleStreamingRequest(service, method, message, requestId)
} catch (error) {
@@ -97,7 +96,6 @@ export class GrpcHandler {
// Return a function to cancel the stream
return () => {
console.log(`[DEBUG] Cancelling streaming request: ${requestId}`)
this.cancelRequest(requestId)
}
}
@@ -146,7 +144,6 @@ export class GrpcHandler {
}
}
} else {
console.log(`[DEBUG] Request not found for cancellation: ${requestId}`)
}
return cancelled
+28
View File
@@ -0,0 +1,28 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { StreamingResponseHandler } from "./host-grpc-handler"
import { handleUriServiceRequest, handleUriServiceStreamingRequest } from "./uri/index"
import { handleWatchServiceRequest, handleWatchServiceStreamingRequest } from "./watch/index"
/**
* Configuration for a host service handler
*/
export interface HostServiceHandlerConfig {
requestHandler: (method: string, message: any) => Promise<any>;
streamingHandler: (method: string, message: any, responseStream: StreamingResponseHandler, requestId?: string) => Promise<void>;
}
/**
* Map of host service names to their handler configurations
*/
export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {
"host.UriService": {
requestHandler: handleUriServiceRequest,
streamingHandler: handleUriServiceStreamingRequest
},
"host.WatchService": {
requestHandler: handleWatchServiceRequest,
streamingHandler: handleWatchServiceStreamingRequest
}
};
-1
View File
@@ -50,7 +50,6 @@ export class ServiceRegistry {
}
this.methodMetadata[methodName] = { isStreaming, ...metadata }
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
}
/**
+22
View File
@@ -0,0 +1,22 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../host-grpc-service"
import { StreamingResponseHandler } from "../host-grpc-handler"
import { registerAllMethods } from "./methods"
// Create uri service registry
const uriService = createServiceRegistry("uri")
// Export the method handler types and registration function
export type UriMethodHandler = ServiceMethodHandler
export type UriStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = uriService.registerMethod
// Export the request handlers
export const handleUriServiceRequest = uriService.handleRequest
export const handleUriServiceStreamingRequest = uriService.handleStreamingRequest
export const isStreamingMethod = uriService.isStreamingMethod
// Register all uri methods
registerAllMethods()
+16
View File
@@ -0,0 +1,16 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
// Import all method implementations
import { registerMethod } from "./index"
import { file } from "./file"
import { joinPath } from "./joinPath"
import { parse } from "./parse"
// Register all uri service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("file", file)
registerMethod("joinPath", joinPath)
registerMethod("parse", parse)
}
+22
View File
@@ -0,0 +1,22 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createServiceRegistry, ServiceMethodHandler, StreamingMethodHandler } from "../host-grpc-service"
import { StreamingResponseHandler } from "../host-grpc-handler"
import { registerAllMethods } from "./methods"
// Create watch service registry
const watchService = createServiceRegistry("watch")
// Export the method handler types and registration function
export type WatchMethodHandler = ServiceMethodHandler
export type WatchStreamingMethodHandler = StreamingMethodHandler
export const registerMethod = watchService.registerMethod
// Export the request handlers
export const handleWatchServiceRequest = watchService.handleRequest
export const handleWatchServiceStreamingRequest = watchService.handleStreamingRequest
export const isStreamingMethod = watchService.isStreamingMethod
// Register all watch methods
registerAllMethods()
+17
View File
@@ -0,0 +1,17 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
// Import all method implementations
import { registerMethod } from "./index"
import { subscribeToFile } from "./subscribeToFile"
// Streaming methods for this service
export const streamingMethods = [
"subscribeToFile"
]
// Register all watch service methods
export function registerAllMethods(): void {
// Register each method with the registry
registerMethod("subscribeToFile", subscribeToFile, { isStreaming: true })
}
-7
View File
@@ -28,11 +28,9 @@ export async function subscribeToFile(
requestId?: string,
): Promise<void> {
const filePath = request.path
console.log(`[DEBUG] Setting up file subscription for ${filePath}`)
try {
// We don't send an initial event to avoid triggering handlers immediately
console.log(`[DEBUG] Now watching file: ${filePath}`)
// Set up or reuse file watcher
if (!fileWatchers.has(filePath)) {
@@ -42,7 +40,6 @@ export async function subscribeToFile(
if (eventType === "change") {
try {
const content = await fs.readFile(filePath, "utf8")
console.log(`[DEBUG] File changed: ${filePath}`)
// Get the watcher info
const watcherInfo = fileWatchers.get(filePath)
@@ -86,7 +83,6 @@ export async function subscribeToFile(
await fs.access(filePath)
// File exists, so it was created or renamed
const content = await fs.readFile(filePath, "utf8")
console.log(`[DEBUG] File created/renamed: ${filePath}`)
// Get the watcher info
const watcherInfo = fileWatchers.get(filePath)
@@ -122,7 +118,6 @@ export async function subscribeToFile(
}
} catch (error) {
// File doesn't exist, so it was deleted
console.log(`[DEBUG] File deleted: ${filePath}`)
// Get the watcher info
const watcherInfo = fileWatchers.get(filePath)
@@ -179,7 +174,6 @@ export async function subscribeToFile(
// Register cleanup when the connection is closed
const cleanup = () => {
console.log(`[DEBUG] Cleaning up file subscription for ${filePath}`)
const watcherInfo = fileWatchers.get(filePath)
if (watcherInfo) {
watcherInfo.subscribers.delete(responseStream)
@@ -220,6 +214,5 @@ function cleanupWatcher(filePath: string): void {
if (watcherInfo) {
watcherInfo.watcher.close()
fileWatchers.delete(filePath)
console.log(`[DEBUG] Removed file watcher for ${filePath}`)
}
}
+26 -56
View File
@@ -50,8 +50,6 @@ const hostServiceNameMap = {
const hostServiceDirs = Object.keys(hostServiceNameMap).map((serviceKey) => path.join(ROOT_DIR, "hosts", "vscode", serviceKey))
async function main() {
console.log(chalk.bold.blue("Starting Protocol Buffer code generation..."))
// Check for Apple Silicon compatibility before proceeding
checkAppleSiliconCompatibility()
@@ -62,7 +60,7 @@ async function main() {
await fs.mkdir(TS_OUT_DIR, { recursive: true })
// Clean up existing generated files
console.log(chalk.cyan("Cleaning up existing generated TypeScript files..."))
const existingFiles = await globby("**/*.ts", { cwd: TS_OUT_DIR })
for (const file of existingFiles) {
await fs.unlink(path.join(TS_OUT_DIR, file))
@@ -72,7 +70,7 @@ async function main() {
await ensureProtoFilesExist()
// Process all proto files
console.log(chalk.cyan("Processing proto files from"), SCRIPT_DIR)
const protoFiles = await globby("**/*.proto", { cwd: SCRIPT_DIR, realpath: true })
// Build the protoc command with proper path handling for cross-platform
@@ -87,7 +85,6 @@ async function main() {
...protoFiles,
].join(" ")
try {
console.log(chalk.cyan(`Generating TypeScript code for:\n${protoFiles.join("\n")}...`))
execSync(tsProtocCommand, { stdio: "inherit" })
} catch (error) {
console.error(chalk.red("Error generating TypeScript for proto files:"), error)
@@ -105,16 +102,12 @@ async function main() {
...protoFiles,
].join(" ")
try {
console.log(chalk.cyan("Generating descriptor set..."))
execSync(descriptorProtocCommand, { stdio: "inherit" })
} catch (error) {
console.error(chalk.red("Error generating descriptor set for proto file:"), error)
process.exit(1)
}
console.log(chalk.green("Protocol Buffer code generation completed successfully."))
console.log(chalk.green(`TypeScript files generated in: ${TS_OUT_DIR}`))
await generateMethodRegistrations()
await generateHostMethodRegistrations()
await generateServiceConfig()
@@ -128,8 +121,6 @@ async function main() {
* This eliminates the need for manual imports and client creation in grpc-client.ts
*/
async function generateGrpcClientConfig() {
console.log(chalk.cyan("Generating gRPC client configuration..."))
const serviceImports = []
const serviceClientCreations = []
const serviceExports = []
@@ -165,7 +156,6 @@ export {
const configPath = path.join(ROOT_DIR, "webview-ui", "src", "services", "grpc-client.ts")
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated gRPC client at ${configPath}`))
}
/**
@@ -175,8 +165,6 @@ export {
* @returns Map of service names to their streaming methods
*/
async function parseProtoForStreamingMethods(protoFiles, scriptDir) {
console.log(chalk.cyan("Parsing proto files for streaming methods..."))
// Map of service name to array of streaming method names
const streamingMethodsMap = new Map()
@@ -227,8 +215,6 @@ async function parseProtoForStreamingMethods(protoFiles, scriptDir) {
}
async function generateMethodRegistrations() {
console.log(chalk.cyan("Generating method registration files..."))
// Parse proto files for streaming methods
const protoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
const streamingMethodsMap = await parseProtoForStreamingMethods(protoFiles, SCRIPT_DIR)
@@ -237,7 +223,6 @@ async function generateMethodRegistrations() {
try {
await fs.access(serviceDir)
} catch (error) {
console.log(chalk.cyan(`Creating directory ${serviceDir} for new service`))
await fs.mkdir(serviceDir, { recursive: true })
}
@@ -248,8 +233,6 @@ async function generateMethodRegistrations() {
const fullServiceName = serviceNameMap[serviceName]
const streamingMethods = streamingMethodsMap.get(fullServiceName) || []
console.log(chalk.cyan(`Generating method registrations for ${serviceName}...`))
// Get all TypeScript files in the service directory
const files = await globby("*.ts", { cwd: serviceDir })
@@ -266,7 +249,13 @@ import { registerMethod } from "./index"\n`
// Import implementations directly
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
methodsContent += `import { ${baseName} } from "./${baseName}"\n`
let importedName = baseName
if (baseName === "incrementalStateUpdate") {
importedName = "sendIncrementalStateUpdate"
} else if (baseName === "messageWindowManager") {
importedName = "MessageWindowManager"
}
methodsContent += `import { ${importedName} } from "./${baseName}"\n`
}
// Add streaming methods information
@@ -287,12 +276,25 @@ export function registerAllMethods(): void {
// Add registration statements
for (const file of implementationFiles) {
const baseName = path.basename(file, ".ts")
let handlerName = baseName
if (baseName === "incrementalStateUpdate") {
handlerName = "sendIncrementalStateUpdate"
} else if (baseName === "messageWindowManager") {
handlerName = "MessageWindowManager"
}
// Skip registration for sendIncrementalStateUpdate and MessageWindowManager as they are not direct gRPC methods
if (baseName === "incrementalStateUpdate" || baseName === "messageWindowManager") {
methodsContent += `\t// Skipping registration for ${handlerName} as it's not a direct gRPC method or is a class.\n`
continue
}
const isStreaming = streamingMethods.some((m) => m.name === baseName)
if (isStreaming) {
methodsContent += `\tregisterMethod("${baseName}", ${baseName}, { isStreaming: true })\n`
methodsContent += `\tregisterMethod("${baseName}", ${handlerName}, { isStreaming: true })\n`
} else {
methodsContent += `\tregisterMethod("${baseName}", ${baseName})\n`
methodsContent += `\tregisterMethod("${baseName}", ${handlerName})\n`
}
}
@@ -301,7 +303,6 @@ export function registerAllMethods(): void {
// Write the methods.ts file
await fs.writeFile(registryFile, methodsContent)
console.log(chalk.green(`Generated ${registryFile}`))
// Generate index.ts file
const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
@@ -330,10 +331,7 @@ registerAllMethods()`
// Write the index.ts file
await fs.writeFile(indexFile, indexContent)
console.log(chalk.green(`Generated ${indexFile}`))
}
console.log(chalk.green("Method registration files generated successfully."))
}
/**
@@ -341,8 +339,6 @@ registerAllMethods()`
* This eliminates the need for manual switch/case statements in grpc-handler.ts
*/
async function generateServiceConfig() {
console.log(chalk.cyan("Generating service configuration file..."))
const serviceImports = []
const serviceConfigs = []
@@ -382,7 +378,6 @@ export const serviceHandlers: Record<string, ServiceHandlerConfig> = {${serviceC
const configPath = path.join(ROOT_DIR, "src", "core", "controller", "grpc-service-config.ts")
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated service configuration at ${configPath}`))
}
/**
@@ -390,8 +385,6 @@ export const serviceHandlers: Record<string, ServiceHandlerConfig> = {${serviceC
* If a .proto file doesn't exist, create a template file
*/
async function ensureProtoFilesExist() {
console.log(chalk.cyan("Checking for missing proto files..."))
// Get existing proto files
const existingProtoFiles = await globby("*.proto", { cwd: SCRIPT_DIR })
const existingProtoServices = existingProtoFiles.map((file) => path.basename(file, ".proto"))
@@ -399,8 +392,6 @@ async function ensureProtoFilesExist() {
// Check each service in serviceNameMap
for (const [serviceName, fullServiceName] of Object.entries(serviceNameMap)) {
if (!existingProtoServices.includes(serviceName)) {
console.log(chalk.yellow(`Creating template proto file for ${serviceName}...`))
// Extract service class name from full name (e.g., "cline.ModelsService" -> "ModelsService")
const serviceClassName = fullServiceName.split(".").pop()
@@ -432,7 +423,6 @@ service ${serviceClassName} {
// Write the template proto file
const protoFilePath = path.join(SCRIPT_DIR, `${serviceName}.proto`)
await fs.writeFile(protoFilePath, protoContent)
console.log(chalk.green(`Created template proto file at ${protoFilePath}`))
}
}
}
@@ -441,8 +431,6 @@ service ${serviceClassName} {
* Generate method registration files for host services
*/
async function generateHostMethodRegistrations() {
console.log(chalk.cyan("Generating host method registration files..."))
// Parse proto files for streaming methods
const hostProtoFiles = await globby("*.proto", { cwd: path.join(SCRIPT_DIR, "host") })
const streamingMethodsMap = await parseProtoForStreamingMethods(hostProtoFiles, path.join(SCRIPT_DIR, "host"))
@@ -451,7 +439,6 @@ async function generateHostMethodRegistrations() {
try {
await fs.access(serviceDir)
} catch (error) {
console.log(chalk.cyan(`Creating directory ${serviceDir} for new host service`))
await fs.mkdir(serviceDir, { recursive: true })
}
@@ -462,8 +449,6 @@ async function generateHostMethodRegistrations() {
const fullServiceName = hostServiceNameMap[serviceName]
const streamingMethods = streamingMethodsMap.get(fullServiceName) || []
console.log(chalk.cyan(`Generating method registrations for host ${serviceName}...`))
// Get all TypeScript files in the service directory
const files = await globby("*.ts", { cwd: serviceDir })
@@ -515,7 +500,6 @@ export function registerAllMethods(): void {
// Write the methods.ts file
await fs.writeFile(registryFile, methodsContent)
console.log(chalk.green(`Generated ${registryFile}`))
// Generate index.ts file
const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1)
@@ -544,18 +528,13 @@ registerAllMethods()`
// Write the index.ts file
await fs.writeFile(indexFile, indexContent)
console.log(chalk.green(`Generated ${indexFile}`))
}
console.log(chalk.green("Host method registration files generated successfully."))
}
/**
* Generate a service configuration file for host services
*/
async function generateHostServiceConfig() {
console.log(chalk.cyan("Generating host service configuration file..."))
const serviceImports = []
const serviceConfigs = []
@@ -595,15 +574,12 @@ export const hostServiceHandlers: Record<string, HostServiceHandlerConfig> = {${
const configPath = path.join(ROOT_DIR, "hosts", "vscode", "host-grpc-service-config.ts")
await fs.mkdir(path.dirname(configPath), { recursive: true })
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated host service configuration at ${configPath}`))
}
/**
* Generate a gRPC client configuration file for host services
*/
async function generateHostGrpcClientConfig() {
console.log(chalk.cyan("Generating host gRPC client configuration..."))
const serviceImports = []
const serviceClientCreations = []
const serviceExports = []
@@ -640,7 +616,6 @@ export {
const configPath = path.join(ROOT_DIR, "src", "standalone", "services", "host-grpc-client.ts")
await fs.mkdir(path.dirname(configPath), { recursive: true })
await fs.writeFile(configPath, content)
console.log(chalk.green(`Generated host gRPC client at ${configPath}`))
}
// Check for Apple Silicon compatibility
@@ -658,18 +633,13 @@ function checkAppleSiliconCompatibility() {
const rosettaCheck = execSync('/usr/bin/pgrep oahd || echo "NOT_INSTALLED"').toString().trim()
if (rosettaCheck === "NOT_INSTALLED") {
console.log(chalk.yellow("Detected Apple Silicon (ARM64) architecture."))
console.log(
chalk.red("Rosetta 2 is NOT installed. The npm version of protoc is not compatible with Apple Silicon."),
)
console.log(chalk.cyan("Please install Rosetta 2 using the following command:"))
console.log(chalk.cyan(" softwareupdate --install-rosetta --agree-to-license"))
console.log(chalk.red("Aborting build process."))
process.exit(1)
}
} catch (error) {
console.log(chalk.yellow("Could not determine Rosetta installation status. Proceeding anyway."))
}
} catch (error) {}
}
}
+3
View File
@@ -16,6 +16,9 @@ service McpService {
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
rpc openMcpSettings(EmptyRequest) returns (Empty);
// Subscribe to MCP marketplace catalog updates
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
}
message ToggleMcpServerRequest {
+206
View File
@@ -18,6 +18,206 @@ message WebviewProviderTypeRequest {
WebviewProviderType providerType = 2;
}
// Enum for ClineMessage type
enum ClineMessageType {
ASK = 0;
SAY = 1;
}
// Enum for ClineAsk types
enum ClineAsk {
FOLLOWUP = 0;
PLAN_MODE_RESPOND = 1;
COMMAND = 2;
COMMAND_OUTPUT = 3;
COMPLETION_RESULT = 4;
TOOL = 5;
API_REQ_FAILED = 6;
RESUME_TASK = 7;
RESUME_COMPLETED_TASK = 8;
MISTAKE_LIMIT_REACHED = 9;
AUTO_APPROVAL_MAX_REQ_REACHED = 10;
BROWSER_ACTION_LAUNCH = 11;
USE_MCP_SERVER = 12;
NEW_TASK = 13;
CONDENSE = 14;
REPORT_BUG = 15;
}
// Enum for ClineSay types
enum ClineSay {
TASK = 0;
ERROR = 1;
API_REQ_STARTED = 2;
API_REQ_FINISHED = 3;
TEXT = 4;
REASONING = 5;
COMPLETION_RESULT_SAY = 6;
USER_FEEDBACK = 7;
USER_FEEDBACK_DIFF = 8;
API_REQ_RETRIED = 9;
COMMAND_SAY = 10;
COMMAND_OUTPUT_SAY = 11;
TOOL_SAY = 12;
SHELL_INTEGRATION_WARNING = 13;
BROWSER_ACTION_LAUNCH_SAY = 14;
BROWSER_ACTION = 15;
BROWSER_ACTION_RESULT = 16;
MCP_SERVER_REQUEST_STARTED = 17;
MCP_SERVER_RESPONSE = 18;
USE_MCP_SERVER_SAY = 19;
DIFF_ERROR = 20;
DELETED_API_REQS = 21;
CLINEIGNORE_ERROR = 22;
CHECKPOINT_CREATED = 23;
LOAD_MCP_DOCUMENTATION = 24;
INFO = 25;
}
// Enum for ClineSayTool tool types
enum ClineSayToolType {
EDITED_EXISTING_FILE = 0;
NEW_FILE_CREATED = 1;
READ_FILE = 2;
LIST_FILES_TOP_LEVEL = 3;
LIST_FILES_RECURSIVE = 4;
LIST_CODE_DEFINITION_NAMES = 5;
SEARCH_FILES = 6;
WEB_FETCH = 7;
}
// Enum for browser actions
enum BrowserAction {
LAUNCH = 0;
CLICK = 1;
TYPE = 2;
SCROLL_DOWN = 3;
SCROLL_UP = 4;
CLOSE = 5;
}
// Enum for MCP server request types
enum McpServerRequestType {
USE_MCP_TOOL = 0;
ACCESS_MCP_RESOURCE = 1;
}
// Enum for API request cancel reasons
enum ClineApiReqCancelReason {
STREAMING_FAILED = 0;
USER_CANCELLED = 1;
RETRIES_EXHAUSTED = 2;
}
// Message for conversation history deleted range
message ConversationHistoryDeletedRange {
int32 start_index = 1;
int32 end_index = 2;
}
// Message for ClineSayTool
message ClineSayTool {
ClineSayToolType tool = 1;
string path = 2;
string diff = 3;
string content = 4;
string regex = 5;
string file_pattern = 6;
bool operation_is_located_in_workspace = 7;
}
// Message for ClineSayBrowserAction
message ClineSayBrowserAction {
BrowserAction action = 1;
string coordinate = 2;
string text = 3;
}
// Message for BrowserActionResult
message BrowserActionResult {
string screenshot = 1;
string logs = 2;
string current_url = 3;
string current_mouse_position = 4;
}
// Message for ClineAskUseMcpServer
message ClineAskUseMcpServer {
string server_name = 1;
McpServerRequestType type = 2;
string tool_name = 3;
string arguments = 4;
string uri = 5;
}
// Message for ClinePlanModeResponse
message ClinePlanModeResponse {
string response = 1;
repeated string options = 2;
string selected = 3;
}
// Message for ClineAskQuestion
message ClineAskQuestion {
string question = 1;
repeated string options = 2;
string selected = 3;
}
// Message for ClineAskNewTask
message ClineAskNewTask {
string context = 1;
}
// Message for API request retry status
message ApiReqRetryStatus {
int32 attempt = 1;
int32 max_attempts = 2;
int32 delay_sec = 3;
string error_snippet = 4;
}
// Message for ClineApiReqInfo
message ClineApiReqInfo {
string request = 1;
int32 tokens_in = 2;
int32 tokens_out = 3;
int32 cache_writes = 4;
int32 cache_reads = 5;
double cost = 6;
ClineApiReqCancelReason cancel_reason = 7;
string streaming_failed_message = 8;
ApiReqRetryStatus retry_status = 9;
}
// Main ClineMessage type
message ClineMessage {
int64 ts = 1;
ClineMessageType type = 2;
ClineAsk ask = 3;
ClineSay say = 4;
string text = 5;
string reasoning = 6;
repeated string images = 7;
repeated string files = 8;
bool partial = 9;
string last_checkpoint_hash = 10;
bool is_checkpoint_checked_out = 11;
bool is_operation_outside_workspace = 12;
int32 conversation_history_index = 13;
ConversationHistoryDeletedRange conversation_history_deleted_range = 14;
// Additional fields for specific ask/say types
ClineSayTool say_tool = 15;
ClineSayBrowserAction say_browser_action = 16;
BrowserActionResult browser_action_result = 17;
ClineAskUseMcpServer ask_use_mcp_server = 18;
ClinePlanModeResponse plan_mode_response = 19;
ClineAskQuestion ask_question = 20;
ClineAskNewTask ask_new_task = 21;
ClineApiReqInfo api_req_info = 22;
}
// UiService provides methods for managing UI interactions
service UiService {
// Scrolls to a specific settings section in the settings view
@@ -40,4 +240,10 @@ service UiService {
// Subscribe to account button click events
rpc subscribeToAccountButtonClicked(EmptyRequest) returns (stream Empty);
// Subscribe to settings button clicked events
rpc subscribeToSettingsButtonClicked(WebviewProviderTypeRequest) returns (stream Empty);
// Subscribe to partial message updates (streaming Cline messages as they're built)
rpc subscribeToPartialMessage(EmptyRequest) returns (stream ClineMessage);
}
+1 -4
View File
@@ -10,15 +10,12 @@ const esbuildProblemMatcherPlugin = {
name: "esbuild-problem-matcher",
setup(build) {
build.onStart(() => {
console.log("[watch] build started")
})
build.onStart(() => {})
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")
})
},
}
-2
View File
@@ -77,5 +77,3 @@ ${handlerSetup}
`
// Write output file
fs.writeFileSync(OUT_FILE, output)
console.log(`Generated service handlers in ${OUT_FILE}.`)
+2 -4
View File
@@ -11,7 +11,7 @@ const SOURCE_DIR = "standalone/runtime-files"
await cp(SOURCE_DIR, BUILD_DIR, { recursive: true })
// Run npm install in the distribution directory
console.log("Running npm install in distribution directory...")
const cwd = process.cwd()
process.chdir(BUILD_DIR)
try {
@@ -38,9 +38,7 @@ const zipPath = path.join(BUILD_DIR, "standalone.zip")
const output = fs.createWriteStream(zipPath)
const archive = archiver("zip", { zlib: { level: 9 } })
output.on("close", () => {
console.log(`Created ${zipPath} (${(archive.pointer() / 1024 / 1024).toFixed(1)} MB)`)
})
output.on("close", () => {})
archive.on("error", (err) => {
throw err
+2 -13
View File
@@ -53,10 +53,6 @@ const checkGitHubAuth = async () => {
execSync("gh auth status", { stdio: "ignore" })
return true
} catch (err) {
console.log("\nGitHub authentication required.")
console.log("\nPlease run the following command in your terminal to authenticate:")
console.log("\n gh auth login\n")
console.log("After authenticating, run this script again.")
return false
}
}
@@ -89,19 +85,14 @@ const openUrl = (url) => {
execSync(`xdg-open "${url}"`)
break
default:
console.log("\nPlease open this URL in your browser:")
console.log(url)
}
} catch (err) {
console.log("\nFailed to open URL automatically. Please open this URL in your browser:")
console.log(url)
}
} catch (err) {}
}
const submitIssue = async (issueTitle, systemInfo) => {
try {
const issueUrl = createIssueUrl(systemInfo, issueTitle)
console.log("\nOpening GitHub issue creation page in your browser...")
openUrl(issueUrl)
} catch (err) {
console.error("\nFailed to create issue URL:", err.message)
@@ -111,12 +102,10 @@ const submitIssue = async (issueTitle, systemInfo) => {
async function main() {
const consent = await ask("Do you consent to collect system data and submit a GitHub issue? (y/n): ")
if (consent.trim().toLowerCase() !== "y") {
console.log("\nAborted.")
rl.close()
return
}
console.log("Collecting system data...")
const systemInfo = collectSystemInfo()
const isAuthenticated = await checkGitHubAuth()
-4
View File
@@ -4,14 +4,10 @@ const process = require("process")
try {
if (process.platform === "linux") {
console.log("Detected Linux environment.")
execSync("which xvfb-run", { stdio: "ignore" })
console.log("xvfb-run is installed. Running tests with xvfb-run...")
execSync("xvfb-run -a npm run test:coverage", { stdio: "inherit" })
} else {
console.log("Non-Linux environment detected. Running tests normally.")
execSync("npm run test:integration", { stdio: "inherit" })
}
} catch (error) {
@@ -16,7 +16,6 @@ describe("OllamaHandler", () => {
await axios.get("http://localhost:11434/api/version", { timeout: 2000 })
ollamaAvailable = true
} catch (error) {
console.log("Ollama server not available, skipping tests")
ollamaAvailable = false
}
})
-1
View File
@@ -36,7 +36,6 @@ export class AskSageHandler implements ApiHandler {
private apiKey: string
constructor(options: ApiHandlerOptions) {
console.log("init api url", options.asksageApiUrl, askSageDefaultURL)
this.options = options
this.apiKey = options.asksageApiKey || ""
this.apiUrl = options.asksageApiUrl || askSageDefaultURL
+1 -1
View File
@@ -294,7 +294,7 @@ export class GeminiHandler implements ApiHandler {
trace.cacheRead = { price: cacheReadsPrice, tokens: cacheReadTokens ?? 0, cost: cacheReadCost }
}
// console.log(`[GeminiHandler] calculateCost -> ${totalCost}`, trace)
//
return totalCost
}
+2 -2
View File
@@ -102,7 +102,7 @@ export class OpenRouterHandler implements ApiHandler {
try {
const generationIterator = this.fetchGenerationDetails(this.lastGenerationId)
const generation = (await generationIterator.next()).value
// console.log("OpenRouter generation details:", generation)
//
return {
type: "usage",
cacheWriteTokens: 0,
@@ -122,7 +122,7 @@ export class OpenRouterHandler implements ApiHandler {
@withRetry({ maxRetries: 4, baseDelay: 250, maxDelay: 1000, retryAllErrors: true })
async *fetchGenerationDetails(genId: string) {
// console.log("Fetching generation details for:", genId)
//
try {
const response = await axios.get(`https://openrouter.ai/api/v1/generation?id=${genId}`, {
headers: {
+1 -1
View File
@@ -119,7 +119,7 @@ declare module "vscode" {
* const systemPrompt = "You are a helpful assistant";
* const messages = [{ role: "user", content: "Hello!" }];
* for await (const chunk of handler.createMessage(systemPrompt, messages)) {
* console.log(chunk);
* ;
* }
* ```
*/
+3 -3
View File
@@ -358,8 +358,8 @@ function validateToolInput(toolName: string, tool_input: Record<string, string>)
// </write_to_file>`;
//
// const { normalText, toolCalls } = parseAIResponse(aiResponse);
// console.log(normalText);
// console.log(toolCalls);
// ;
// ;
// Convert OpenAI response to Anthropic format
export function convertO1ResponseToAnthropicMessage(
@@ -432,4 +432,4 @@ export function convertO1ResponseToAnthropicMessage(
// usage: { prompt_tokens: 50, completion_tokens: 100 }
// };
// const anthropicMessage = convertO1ResponseToAnthropicMessage(openAICompletion);
// console.log(anthropicMessage);
// ;
@@ -96,7 +96,7 @@ class ContextManager {
const [start, end] = deletedRange
// the range is inclusive - both start and end indices and everything in between will be removed from the final result.
// NOTE: if you try to console log these, don't forget that logging a reference to an array may not provide the same result as logging a slice() snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
// NOTE: if you try to snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
return [...messages.slice(0, start), ...messages.slice(end + 1)]
}
}
@@ -255,7 +255,7 @@ export class ContextManager {
const updatedMessages = this.applyContextHistoryUpdates(messages, deletedRange ? deletedRange[1] + 1 : 2)
// OLD NOTE: if you try to console log these, don't forget that logging a reference to an array may not provide the same result as logging a slice() snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
// OLD NOTE: if you try to snapshot of that array at that exact moment. The following DOES in fact include the latest assistant message.
return updatedMessages
}
@@ -18,8 +18,6 @@ export async function accountLoginClicked(controller: Controller, _: EmptyReques
await storeSecret(controller.context, "authNonce", nonce)
// Open browser for authentication with state param
console.log("Login button clicked in account page")
console.log("Opening auth page with state param")
const uriScheme = vscode.env.uriScheme
+1 -1
View File
@@ -27,7 +27,7 @@ export const getRelativePaths: FileMethodHandler = async (
}),
)
const fileUri = vscode.Uri.parse(`${parseResponse.scheme}://${parseResponse.authority}${parseResponse.path}`)
console.log("[DEBUG] UriServiceClient.parse:", fileUri)
const relativePathToGet = vscode.workspace.asRelativePath(fileUri, false)
// If the path is still absolute, it's outside the workspace
-1
View File
@@ -201,7 +201,6 @@ export async function handleGrpcRequestCancel(
},
})
} else {
console.log(`[DEBUG] Request not found for cancellation: ${request.request_id}`)
}
}
@@ -54,7 +54,6 @@ export class GrpcRequestRegistry {
timestamp: new Date(),
responseStream,
})
console.log(`[DEBUG] Registered request: ${requestId}`)
}
/**
@@ -67,7 +66,6 @@ export class GrpcRequestRegistry {
if (requestInfo) {
try {
requestInfo.cleanup()
console.log(`[DEBUG] Cleaned up request: ${requestId}`)
} catch (error) {
console.error(`Error cleaning up request ${requestId}:`, error)
}
-1
View File
@@ -56,7 +56,6 @@ export class ServiceRegistry {
}
this.methodMetadata[methodName] = { isStreaming, ...metadata }
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
}
/**
+5 -30
View File
@@ -56,6 +56,7 @@ import { sendStateUpdate } from "./state/subscribeToState"
import { sendAddToInputEvent } from "./ui/subscribeToAddToInput"
import { sendAuthCallbackEvent } from "./account/subscribeToAuthCallback"
import { sendChatButtonClickedEvent } from "./ui/subscribeToChatButtonClicked"
import { sendMcpMarketplaceCatalogEvent } from "./mcp/subscribeToMcpMarketplaceCatalog"
import { refreshClineRulesToggles } from "@core/context/instructions/user-instructions/cline-rules"
import { refreshExternalRulesToggles } from "@core/context/instructions/user-instructions/external-rules"
import { refreshWorkflowToggles } from "@core/context/instructions/user-instructions/workflows"
@@ -245,10 +246,7 @@ export class Controller {
getGlobalState(this.context, "mcpMarketplaceCatalog").then((mcpMarketplaceCatalog) => {
if (mcpMarketplaceCatalog) {
this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: mcpMarketplaceCatalog as McpMarketplaceCatalog,
})
sendMcpMarketplaceCatalogEvent(mcpMarketplaceCatalog as McpMarketplaceCatalog)
}
})
this.silentlyRefreshMcpMarketplace()
@@ -759,10 +757,6 @@ export class Controller {
console.error("Failed to fetch MCP marketplace:", error)
if (!silent) {
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
error: errorMessage,
})
vscode.window.showErrorMessage(errorMessage)
}
return undefined
@@ -808,10 +802,7 @@ export class Controller {
try {
const catalog = await this.fetchMcpMarketplaceFromApi(true)
if (catalog) {
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: catalog,
})
await sendMcpMarketplaceCatalogEvent(catalog)
}
} catch (error) {
console.error("Failed to silently refresh MCP marketplace:", error)
@@ -839,27 +830,17 @@ export class Controller {
| McpMarketplaceCatalog
| undefined
if (!forceRefresh && cachedCatalog?.items) {
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: cachedCatalog,
})
await sendMcpMarketplaceCatalogEvent(cachedCatalog)
return
}
const catalog = await this.fetchMcpMarketplaceFromApi(false)
if (catalog) {
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
mcpMarketplaceCatalog: catalog,
})
await sendMcpMarketplaceCatalogEvent(catalog)
}
} catch (error) {
console.error("Failed to handle cached MCP marketplace:", error)
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
await this.postMessageToWebview({
type: "mcpMarketplaceCatalog",
error: errorMessage,
})
vscode.window.showErrorMessage(errorMessage)
}
}
@@ -937,8 +918,6 @@ export class Controller {
}
await sendAddToInputEvent(input)
console.log("addSelectedCodeToChat", code, filePath, languageId)
}
// 'Add to Cline' context menu in Terminal
@@ -955,8 +934,6 @@ export class Controller {
// })
await sendAddToInputEvent(`Terminal output:\n\`\`\`\n${output}\n\`\`\``)
console.log("addSelectedTerminalOutputToChat", output, terminalName)
}
// 'Fix with Cline' in code actions
@@ -968,8 +945,6 @@ export class Controller {
const fileMention = this.getFileMentionFromPath(filePath)
const problemsString = this.convertDiagnosticsToProblemsString(diagnostics)
await this.initTask(`Fix the following code in ${fileMention}\n\`\`\`\n${code}\n\`\`\`\n\nProblems:\n${problemsString}`)
console.log("fixWithCline", code, filePath, languageId, diagnostics, problemsString)
}
convertDiagnosticsToProblemsString(diagnostics: vscode.Diagnostic[]) {
-2
View File
@@ -42,8 +42,6 @@ export async function downloadMcp(controller: Controller, request: StringRequest
throw new Error("Invalid response from MCP marketplace API")
}
console.log("[downloadMcp] Response from download API", { response })
const mcpDetails = response.data
// Validate required fields
@@ -0,0 +1,55 @@
import { Controller } from "../index"
import { EmptyRequest } from "@shared/proto/common"
import { McpMarketplaceCatalog } from "@shared/proto/mcp"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
// Keep track of active subscriptions
const activeMcpMarketplaceSubscriptions = new Set<StreamingResponseHandler>()
/**
* Subscribe to MCP marketplace catalog updates
* @param controller The controller instance
* @param request The empty request
* @param responseStream The streaming response handler
* @param requestId The ID of the request (passed by the gRPC handler)
*/
export async function subscribeToMcpMarketplaceCatalog(
controller: Controller,
request: EmptyRequest,
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
// Add this subscription to the active subscriptions
activeMcpMarketplaceSubscriptions.add(responseStream)
// Register cleanup when the connection is closed
const cleanup = () => {
activeMcpMarketplaceSubscriptions.delete(responseStream)
}
// Register the cleanup function with the request registry if we have a requestId
if (requestId) {
getRequestRegistry().registerRequest(requestId, cleanup, { type: "mcp_marketplace_subscription" }, responseStream)
}
}
/**
* Send an MCP marketplace catalog event to all active subscribers
*/
export async function sendMcpMarketplaceCatalogEvent(catalog: McpMarketplaceCatalog): Promise<void> {
// Send the event to all active subscribers
const promises = Array.from(activeMcpMarketplaceSubscriptions).map(async (responseStream) => {
try {
await responseStream(
catalog,
false, // Not the last message
)
} catch (error) {
console.error("Error sending MCP marketplace catalog event:", error)
// Remove the subscription if there was an error
activeMcpMarketplaceSubscriptions.delete(responseStream)
}
})
await Promise.all(promises)
}
+2 -2
View File
@@ -12,9 +12,9 @@ export async function updateMcpTimeout(controller: Controller, request: UpdateMc
try {
if (request.serverName && typeof request.serverName === "string" && typeof request.timeout === "number") {
const mcpServers = await controller.mcpHub?.updateServerTimeoutRPC(request.serverName, request.timeout)
console.log("mcpServers", mcpServers)
const convertedMcpServers = convertMcpServersToProtoMcpServers(mcpServers)
console.log("convertedMcpServers", convertedMcpServers)
return McpServers.create({ mcpServers: convertedMcpServers })
} else {
console.error("Server name and timeout are required")
@@ -0,0 +1,128 @@
# Memory Leak Analysis Summary for Cline
## Problem Description
The Cline extension experiences significant memory growth during long-running conversations, with the webview JavaScript heap memory reaching several hundred MB and not being released when switching tasks.
## Root Causes Identified
### 1. **Full State Transmission on Every Update**
- The entire `ExtensionState` object (including all messages) is sent via gRPC on every state update
- For long conversations, this can be several MB per update
- JSON.stringify creates additional copies in memory
### 2. **Unbounded Message Array Growth**
- The `clineMessages` array grows without limit
- All messages are kept in memory for the entire task duration
- No pagination or windowing mechanism
### 3. **React Component Re-renders**
- Every state update causes full re-renders of all components using ExtensionStateContext
- Large arrays are processed on every render
- No memoization or optimization
### 4. **Multiple State Copies**
- State is duplicated across:
- Core extension (Controller)
- gRPC streaming layer
- React context
- Component props
### 5. **Event Listener Accumulation**
- Multiple subscriptions in ExtensionStateContext
- Potential for listeners not being properly cleaned up
## Solutions Implemented
### 1. **Memory Optimization Documentation** (`memory-optimization.md`)
- Comprehensive guide for implementing memory optimizations
- Prioritized list of improvements
- Architecture recommendations
### 2. **Incremental State Updates** (`incrementalStateUpdate.ts`)
- Framework for sending only changed parts of state
- Tracks last sent state to calculate deltas
- Reduces data transmission overhead
### 3. **Message Window Manager** (`messageWindowManager.ts`)
- Limits messages kept in memory (default: 200 messages)
- Provides windowing mechanism for large conversations
- Includes memory statistics tracking
- Prepared for future disk archival
### 4. **React Optimization Hooks** (`useOptimizedState.ts`)
- `useOptimizedMessages`: Prevents unnecessary re-renders for unchanged messages
- `useDebouncedUpdate`: Reduces update frequency
- `useMessageRenderer`: Implements virtual windowing
- `useMemoryMonitor`: Tracks memory usage in real-time
## Immediate Actions to Take
### 1. **Limit Message Array Size**
In `src/core/controller/index.ts`, modify `getStateToPostToWebview`:
```typescript
clineMessages: this.task?.clineMessages.slice(-100) || [], // Only last 100 messages
```
### 2. **Implement Message Pagination**
Use the MessageWindowManager to paginate messages instead of sending all at once.
### 3. **Add React.memo to Message Components**
Wrap message list components with React.memo to prevent unnecessary re-renders.
### 4. **Implement Virtual Scrolling**
Use react-window or react-virtuoso for the message list to only render visible messages.
## Long-term Recommendations
### 1. **Implement Streaming Updates**
- Modify gRPC to support incremental updates
- Send only new/changed messages
- Implement client-side message assembly
### 2. **Add Message Archival**
- Store old messages to disk
- Load on-demand when scrolling
- Keep only recent messages in memory
### 3. **Optimize State Structure**
- Separate frequently changing data from static data
- Use normalized state structure
- Implement proper caching strategies
### 4. **Add Memory Monitoring**
- Track memory usage over time
- Alert when approaching limits
- Automatic cleanup when memory is high
## Testing Recommendations
1. **Create Long Conversation Test**
- Generate 1000+ messages
- Monitor memory usage
- Test task switching
2. **Memory Profiling**
- Use Chrome DevTools Memory Profiler
- Take heap snapshots at intervals
- Identify retained objects
3. **Performance Benchmarks**
- Measure render times
- Track state update frequency
- Monitor gRPC message sizes
## Expected Impact
With these optimizations:
- Memory usage should stabilize around 50-100MB for typical tasks
- Long conversations should not exceed 200MB
- Task switching should properly release memory
- UI responsiveness should improve significantly
## Next Steps
1. Implement the immediate actions listed above
2. Test with long-running conversations
3. Monitor memory usage patterns
4. Gradually implement long-term solutions
5. Add automated memory testing to CI/CD
+122
View File
@@ -0,0 +1,122 @@
# Memory Optimization Recommendations
## Critical Issues Found
### 1. State Streaming Memory Leak
The main issue is that the entire state object is being sent on every update, creating multiple copies in memory. For long conversations, this can be several megabytes per update.
### 2. Message Array Growth
The `clineMessages` array grows unbounded and is sent in full on every state update.
### 3. React Component Re-renders
Every state update causes a full re-render of all components that use the ExtensionStateContext.
## Recommended Solutions
### 1. Implement Incremental State Updates
Instead of sending the entire state, send only the changed parts:
```typescript
// Instead of:
await sendStateUpdate(fullState)
// Use:
await sendStateUpdateDelta({
type: 'partial',
changes: {
clineMessages: {
type: 'append',
items: newMessages
}
}
})
```
### 2. Implement Message Pagination
Don't send all messages at once:
```typescript
interface PaginatedMessages {
messages: ClineMessage[]
totalCount: number
offset: number
limit: number
}
```
### 3. Use React.memo and useMemo
Prevent unnecessary re-renders:
```typescript
const MemoizedMessageList = React.memo(MessageList, (prevProps, nextProps) => {
return prevProps.messages.length === nextProps.messages.length
})
```
### 4. Implement Message Virtualization
Only render visible messages:
```typescript
// Use react-window or react-virtuoso
<VirtualList
height={600}
itemCount={messages.length}
itemSize={100}
width="100%"
>
{Row}
</VirtualList>
```
### 5. Clean Up Old References
Implement a cleanup mechanism:
```typescript
// In Task class
cleanupOldMessages() {
if (this.clineMessages.length > 1000) {
// Archive old messages to disk
const toArchive = this.clineMessages.slice(0, -500)
await this.archiveMessages(toArchive)
this.clineMessages = this.clineMessages.slice(-500)
}
}
```
### 6. Use WeakMap for Caching
Prevent memory leaks from caching:
```typescript
const messageCache = new WeakMap<string, ProcessedMessage>()
```
### 7. Implement Debouncing for State Updates
Reduce the frequency of updates:
```typescript
const debouncedStateUpdate = debounce(async (state) => {
await sendStateUpdate(state)
}, 100)
```
## Implementation Priority
1. **High Priority**: Implement incremental state updates
2. **High Priority**: Add message pagination
3. **Medium Priority**: Add React optimizations
4. **Medium Priority**: Implement message virtualization
5. **Low Priority**: Add cleanup mechanisms
## Memory Profiling Results
Based on the heap snapshot, the main memory consumers are:
- State objects: ~145MB
- Detached DOM nodes: Multiple references
- String allocations from JSON.stringify: Significant overhead
## Next Steps
1. Implement a state diff mechanism
2. Add message streaming with pagination
3. Optimize React component rendering
4. Add memory monitoring and alerts
@@ -118,7 +118,6 @@ export async function refreshOpenRouterModels(
console.error("Invalid response from OpenRouter API")
}
await fs.writeFile(openRouterModelsFilePath, JSON.stringify(models))
console.log("OpenRouter models fetched and saved", models)
} catch (error) {
console.error("Error fetching OpenRouter models:", error)
@@ -40,7 +40,6 @@ export async function refreshRequestyModels(controller: Controller, _: EmptyRequ
})
models[model.id] = modelInfo
}
console.log("Requesty models fetched", models)
controller.postMessageToWebview({
type: "requestyModels",
@@ -0,0 +1,127 @@
import { ExtensionState } from "@shared/ExtensionMessage"
import { ClineMessage } from "@shared/ExtensionMessage"
import { sendStateUpdate } from "./subscribeToState"
interface StateUpdateDelta {
type: "full" | "partial"
fullState?: ExtensionState
changes?: {
clineMessages?: {
type: "append" | "update" | "replace"
items?: ClineMessage[]
updates?: Array<{ index: number; message: ClineMessage }>
startIndex?: number
}
taskHistory?: {
type: "update" | "replace"
items?: any[]
}
apiConfiguration?: any
customInstructions?: string
// Add other fields as needed
}
}
// Track the last sent state to calculate deltas
let lastSentState: Partial<ExtensionState> = {}
let lastSentMessageCount = 0
/**
* Send incremental state updates to reduce memory usage
* Only sends changed parts of the state instead of the entire state object
*/
export async function sendIncrementalStateUpdate(state: ExtensionState): Promise<void> {
// For initial state or when we need a full refresh
if (!lastSentState.version || lastSentState.version !== state.version) {
await sendStateUpdate(state)
lastSentState = { ...state }
lastSentMessageCount = state.clineMessages?.length || 0
return
}
// Check if we should send a full update (e.g., task changed)
if (lastSentState.currentTaskItem?.id !== state.currentTaskItem?.id) {
await sendStateUpdate(state)
lastSentState = { ...state }
lastSentMessageCount = state.clineMessages?.length || 0
return
}
// Build delta update
const delta: StateUpdateDelta = {
type: "partial",
changes: {},
}
// Handle message updates efficiently
if (state.clineMessages && state.clineMessages.length > lastSentMessageCount) {
// Only send new messages
const newMessages = state.clineMessages.slice(lastSentMessageCount)
delta.changes!.clineMessages = {
type: "append",
items: newMessages,
}
lastSentMessageCount = state.clineMessages.length
} else if (state.clineMessages && state.clineMessages.length > 0) {
// Check for updates to existing messages (e.g., partial message updates)
const lastMessage = state.clineMessages[state.clineMessages.length - 1]
const lastSentMessage = lastSentState.clineMessages?.[state.clineMessages.length - 1]
if (
lastMessage &&
lastSentMessage &&
lastMessage.ts === lastSentMessage.ts &&
lastMessage.text !== lastSentMessage.text
) {
delta.changes!.clineMessages = {
type: "update",
updates: [{ index: state.clineMessages.length - 1, message: lastMessage }],
}
}
}
// Check other fields for changes
if (state.apiConfiguration !== lastSentState.apiConfiguration) {
delta.changes!.apiConfiguration = state.apiConfiguration
}
if (state.customInstructions !== lastSentState.customInstructions) {
delta.changes!.customInstructions = state.customInstructions
}
// Only send if there are actual changes
if (Object.keys(delta.changes!).length > 0) {
// For now, we still send the full state, but we could modify the webview
// to handle incremental updates in the future
await sendStateUpdate(state)
lastSentState = { ...state }
}
}
/**
* Reset the incremental state tracker
* Call this when switching tasks or clearing state
*/
export function resetIncrementalStateTracker(): void {
lastSentState = {}
lastSentMessageCount = 0
}
/**
* Get memory usage statistics
*/
export function getStateMemoryStats(): {
messageCount: number
estimatedSize: number
lastUpdateSize: number
} {
const messageCount = lastSentMessageCount
const stateString = JSON.stringify(lastSentState)
const estimatedSize = new Blob([stateString]).size
return {
messageCount,
estimatedSize,
lastUpdateSize: 0, // Will be tracked in future implementation
}
}
@@ -0,0 +1,129 @@
import { ClineMessage } from "@shared/ExtensionMessage"
export interface MessageWindow {
messages: ClineMessage[]
totalCount: number
windowStart: number
windowSize: number
}
export class MessageWindowManager {
private static readonly DEFAULT_WINDOW_SIZE = 100
private static readonly MAX_MEMORY_MESSAGES = 200
private allMessages: ClineMessage[] = []
private windowStart: number = 0
private windowSize: number = MessageWindowManager.DEFAULT_WINDOW_SIZE
constructor(windowSize: number = MessageWindowManager.DEFAULT_WINDOW_SIZE) {
this.windowSize = Math.min(windowSize, MessageWindowManager.MAX_MEMORY_MESSAGES)
}
/**
* Add new messages and maintain window
*/
addMessages(messages: ClineMessage[]): void {
this.allMessages.push(...messages)
// If we exceed max memory limit, trim old messages
if (this.allMessages.length > MessageWindowManager.MAX_MEMORY_MESSAGES * 2) {
// Keep the most recent messages
const trimStart = this.allMessages.length - MessageWindowManager.MAX_MEMORY_MESSAGES
this.allMessages = this.allMessages.slice(trimStart)
// Adjust window start if needed
if (this.windowStart > 0) {
this.windowStart = Math.max(0, this.windowStart - trimStart)
}
}
}
/**
* Get current window of messages
*/
getCurrentWindow(): MessageWindow {
const start = Math.max(0, this.allMessages.length - this.windowSize)
const windowMessages = this.allMessages.slice(start)
return {
messages: windowMessages,
totalCount: this.allMessages.length,
windowStart: start,
windowSize: windowMessages.length,
}
}
/**
* Get messages for a specific range
*/
getMessageRange(start: number, count: number): ClineMessage[] {
const end = Math.min(start + count, this.allMessages.length)
return this.allMessages.slice(start, end)
}
/**
* Update a specific message (for partial updates)
*/
updateMessage(index: number, message: ClineMessage): void {
if (index >= 0 && index < this.allMessages.length) {
this.allMessages[index] = message
}
}
/**
* Clear all messages
*/
clear(): void {
this.allMessages = []
this.windowStart = 0
}
/**
* Get memory statistics
*/
getMemoryStats(): {
totalMessages: number
messagesInMemory: number
estimatedMemoryMB: number
} {
const avgMessageSize = 1024 // Assume 1KB average per message
const estimatedMemoryBytes = this.allMessages.length * avgMessageSize
return {
totalMessages: this.allMessages.length,
messagesInMemory: this.allMessages.length,
estimatedMemoryMB: estimatedMemoryBytes / (1024 * 1024),
}
}
/**
* Archive old messages (for future disk storage implementation)
*/
async archiveOldMessages(keepCount: number = 100): Promise<number> {
if (this.allMessages.length <= keepCount) {
return 0
}
const toArchive = this.allMessages.slice(0, -keepCount)
// TODO: Implement disk storage for archived messages
// For now, just remove them from memory
this.allMessages = this.allMessages.slice(-keepCount)
return toArchive.length
}
}
// Global instance for the current task
let currentMessageWindow: MessageWindowManager | null = null
export function getMessageWindowManager(): MessageWindowManager {
if (!currentMessageWindow) {
currentMessageWindow = new MessageWindowManager()
}
return currentMessageWindow
}
export function resetMessageWindowManager(): void {
currentMessageWindow = null
}
@@ -23,7 +23,7 @@ export async function subscribeToState(
const initialState = await controller.getStateToPostToWebview()
const initialStateJson = JSON.stringify(initialState)
console.log("[DEBUG] set up state subscription")
// Removed console.log that was logging large state objects
await responseStream({
stateJson: initialStateJson,
@@ -35,7 +35,7 @@ export async function subscribeToState(
// Register cleanup when the connection is closed
const cleanup = () => {
activeStateSubscriptions.delete(responseStream)
console.log("[DEBUG] Cleaned up state subscription")
// Removed console.log for cleanup
}
// Register the cleanup function with the request registry if we have a requestId
@@ -62,7 +62,7 @@ export async function sendStateUpdate(state: any): Promise<void> {
},
false, // Not the last message
)
console.log("[DEBUG] sending followup state", stateJson.length, "chars")
// Removed console.log that was logging state size
} catch (error) {
console.error("Error sending state update:", error)
// Remove the subscription if there was an error
@@ -27,8 +27,6 @@ export async function deleteNonFavoritedTasks(
const favoritedTasks = taskHistory.filter((task) => task.isFavorited === true)
const deletedCount = taskHistory.length - favoritedTasks.length
console.log(`[deleteNonFavoritedTasks] Found ${favoritedTasks.length} favorited tasks to preserve`)
// Update global state
if (favoritedTasks.length > 0) {
await updateGlobalState(controller.context, "taskHistory", favoritedTasks)
@@ -17,7 +17,6 @@ export async function toggleTaskFavorite(controller: Controller, request: TaskFa
const taskIndex = history.findIndex((item) => item.id === request.taskId)
if (taskIndex === -1) {
console.log(`[toggleTaskFavorite] Task not found in history array!`)
} else {
// Create a new array instead of modifying in place to ensure state change
const updatedHistory = [...history]
@@ -41,7 +41,6 @@ export async function sendAccountButtonClickedEvent(controllerId: string): Promi
const responseStream = activeSubscriptions.get(controllerId)
if (!responseStream) {
console.log(`No active subscription for controller ${controllerId}`)
return
}
@@ -20,15 +20,12 @@ export async function subscribeToAddToInput(
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
console.log("[DEBUG] set up addToInput subscription")
// Add this subscription to the active subscriptions
activeAddToInputSubscriptions.add(responseStream)
// Register cleanup when the connection is closed
const cleanup = () => {
activeAddToInputSubscriptions.delete(responseStream)
console.log("[DEBUG] Cleaned up addToInput subscription")
}
// Register the cleanup function with the request registry if we have a requestId
@@ -52,7 +49,6 @@ export async function sendAddToInputEvent(text: string): Promise<void> {
event,
false, // Not the last message
)
console.log("[DEBUG] sending addToInput event", text.length, "chars")
} catch (error) {
console.error("Error sending addToInput event:", error)
// Remove the subscription if there was an error
@@ -20,7 +20,6 @@ export async function subscribeToChatButtonClicked(
requestId?: string,
): Promise<void> {
const controllerId = controller.id
console.log(`[DEBUG] set up chatButtonClicked subscription for controller ${controllerId}`)
// Add this subscription to the active subscriptions with the controller ID
activeChatButtonClickedSubscriptions.set(controllerId, responseStream)
@@ -45,7 +44,6 @@ export async function sendChatButtonClickedEvent(controllerId: string): Promise<
const responseStream = activeChatButtonClickedSubscriptions.get(controllerId)
if (!responseStream) {
console.log(`[DEBUG] No active subscription for controller ${controllerId}`)
return
}
@@ -21,7 +21,6 @@ export async function subscribeToHistoryButtonClicked(
): Promise<void> {
// Extract the provider type from the request
const providerType = request.providerType
console.log(`[DEBUG] set up history button subscription for ${WebviewProviderType[providerType]} webview`)
// Add this subscription to the active subscriptions with its provider type
activeHistoryButtonClickedSubscriptions.set(responseStream, providerType)
@@ -20,7 +20,6 @@ export async function subscribeToMcpButtonClicked(
requestId?: string,
): Promise<void> {
const providerType = request.providerType
console.log(`[DEBUG] set up mcpButtonClicked subscription for ${WebviewProviderType[providerType]} webview`)
// Store the subscription with its provider type
mcpButtonClickedSubscriptions.set(responseStream, providerType)
@@ -0,0 +1,56 @@
import { Controller } from "../index"
import { EmptyRequest } from "@shared/proto/common"
import { ClineMessage } from "@shared/proto/ui"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
// Keep track of active partial message subscriptions
const activePartialMessageSubscriptions = new Set<StreamingResponseHandler>()
/**
* Subscribe to partial message events
* @param controller The controller instance
* @param request The empty request
* @param responseStream The streaming response handler
* @param requestId The ID of the request (passed by the gRPC handler)
*/
export async function subscribeToPartialMessage(
controller: Controller,
request: EmptyRequest,
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
// Add this subscription to the active subscriptions
activePartialMessageSubscriptions.add(responseStream)
// Register cleanup when the connection is closed
const cleanup = () => {
activePartialMessageSubscriptions.delete(responseStream)
}
// Register the cleanup function with the request registry if we have a requestId
if (requestId) {
getRequestRegistry().registerRequest(requestId, cleanup, { type: "partial_message_subscription" }, responseStream)
}
}
/**
* Send a partial message event to all active subscribers
* @param partialMessage The ClineMessage to send
*/
export async function sendPartialMessageEvent(partialMessage: ClineMessage): Promise<void> {
// Send the event to all active subscribers
const promises = Array.from(activePartialMessageSubscriptions).map(async (responseStream) => {
try {
await responseStream(
partialMessage,
false, // Not the last message
)
} catch (error) {
console.error("Error sending partial message event:", error)
// Remove the subscription if there was an error
activePartialMessageSubscriptions.delete(responseStream)
}
})
await Promise.all(promises)
}
@@ -0,0 +1,60 @@
import { Empty } from "@shared/proto/common"
import { WebviewProviderType, WebviewProviderTypeRequest } from "@shared/proto/ui"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
import type { Controller } from "../index"
// Track subscriptions with their provider type
const subscriptions = new Map<StreamingResponseHandler, WebviewProviderType>()
/**
* Subscribe to settings button clicked events
* @param controller The controller instance
* @param request The request with provider type
* @param responseStream The streaming response handler
* @param requestId The ID of the request (passed by the gRPC handler)
*/
export async function subscribeToSettingsButtonClicked(
controller: Controller,
request: WebviewProviderTypeRequest,
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
const providerType = request.providerType
// Store the subscription with its provider type
subscriptions.set(responseStream, providerType)
// Register cleanup when the connection is closed
const cleanup = () => {
subscriptions.delete(responseStream)
}
// Register the cleanup function with the request registry if we have a requestId
if (requestId) {
getRequestRegistry().registerRequest(requestId, cleanup, { type: "settings_button_clicked_subscription" }, responseStream)
}
}
/**
* Send a settings button clicked event to active subscribers of matching provider type
* @param webviewType The type of webview that triggered the event
*/
export async function sendSettingsButtonClickedEvent(webviewType?: WebviewProviderType): Promise<void> {
// Process all subscriptions, filtering based on the source
const promises = Array.from(subscriptions.entries()).map(async ([responseStream, providerType]) => {
// If webviewType is provided, only send to subscribers of the same type
if (webviewType !== undefined && webviewType !== providerType) {
return // Skip subscribers of different types
}
try {
const event = Empty.create({})
await responseStream(event, false) // Not the last message
} catch (error) {
console.error(`Error sending settings button clicked event to ${WebviewProviderType[providerType]}:`, error)
subscriptions.delete(responseStream)
}
})
await Promise.all(promises)
}
+14 -19
View File
@@ -72,6 +72,8 @@ import { ClineIgnoreController } from "@core/ignore/ClineIgnoreController"
import { parseMentions } from "@core/mentions"
import { formatResponse } from "@core/prompts/responses"
import { addUserInstructions, SYSTEM_PROMPT } from "@core/prompts/system"
import { sendPartialMessageEvent } from "@core/controller/ui/subscribeToPartialMessage"
import { convertClineMessageToProto } from "@shared/proto-conversions/cline-message"
import { getContextWindowInfo } from "@core/context/context-management/context-window-utils"
import { FileContextTracker } from "@core/context/context-tracking/FileContextTracker"
import { ModelContextTracker } from "@core/context/context-tracking/ModelContextTracker"
@@ -528,7 +530,6 @@ export class Task {
return
}
console.log("presentMultifileDiff", messageTs)
const messageIndex = this.clineMessages.findIndex((m) => m.ts === messageTs)
const message = this.clineMessages[messageIndex]
if (!message) {
@@ -743,10 +744,8 @@ export class Task {
// todo be more efficient about saving and posting only new data or one whole message at a time so ignore partial for saves, and only post parts of partial message instead of whole array in new listener
// await this.saveClineMessagesAndUpdateHistory()
// await this.postStateToWebview()
await this.postMessageToWebview({
type: "partialMessage",
partialMessage: lastMessage,
})
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage)
throw new Error("Current ask promise was ignored 1")
} else {
// this is a new partial message, so add it with partial state
@@ -787,10 +786,8 @@ export class Task {
lastMessage.partial = false
await this.saveClineMessagesAndUpdateHistory()
// await this.postStateToWebview()
await this.postMessageToWebview({
type: "partialMessage",
partialMessage: lastMessage,
})
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage)
} else {
// this is a new partial=false message, so add it like normal
this.askResponse = undefined
@@ -866,7 +863,8 @@ export class Task {
lastMessage.images = images
lastMessage.files = files
lastMessage.partial = partial
await this.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage })
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage)
} else {
// this is a new partial message, so add it with partial state
const sayTs = Date.now()
@@ -896,7 +894,8 @@ export class Task {
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
await this.saveClineMessagesAndUpdateHistory()
// await this.postStateToWebview()
await this.postMessageToWebview({ type: "partialMessage", partialMessage: lastMessage }) // more performant than an entire postStateToWebview
const protoMessage = convertClineMessageToProto(lastMessage)
await sendPartialMessageEvent(protoMessage) // more performant than an entire postStateToWebview
} else {
// this is a new partial=false message, so add it like normal
const sayTs = Date.now()
@@ -1748,7 +1747,6 @@ export class Task {
)
}
console.log("first chunk failed, waiting 1 second before retrying")
await setTimeoutPromise(1000)
this.didAutomaticallyRetryFailedApiRequest = true
} else {
@@ -1828,7 +1826,7 @@ export class Task {
const onError = (error: Error) => {
console.error("StreamingJsonReplacer error:", error)
console.log("Failed StreamingJsonReplacer update:")
// Handle error: push tool result, cleanup
this.userMessageContent.push({
type: "text",
@@ -1920,7 +1918,7 @@ export class Task {
// Get final list of replacements
const allReplacements = this.streamingJsonReplacer.getSuccessfullyParsedItems()
// console.log(`Total replacements applied: ${allReplacements.length}`)
//
// Cleanup
this.streamingJsonReplacer = undefined
@@ -1950,7 +1948,7 @@ export class Task {
if (this.didCompleteReadingStream) {
this.userMessageContentReady = true
}
// console.log("no more content blocks to stream! this shouldn't happen?")
//
this.presentAssistantMessageLocked = false
return
//throw new Error("No more content blocks to stream! This shouldn't happen...") // remove and just return after testing
@@ -2178,7 +2176,6 @@ export class Task {
const handleError = async (action: string, error: Error, isClaude4ModelFamily: boolean = false) => {
if (this.abandoned) {
console.log("Ignoring error since task was abandoned (i.e. from task cancellation after resetting)")
return
}
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
@@ -2268,7 +2265,6 @@ export class Task {
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
// Going through claude family of models
if (isClaude4ModelFamily && USE_EXPERIMENTAL_CLAUDE4_FEATURES && currentFullJson) {
console.log("[EDIT] Streaming JSON replacement")
const streamingResult = await this.handleStreamingJsonReplacement(
block,
relPath,
@@ -4417,7 +4413,7 @@ export class Task {
// lastMessage.ts = Date.now() DO NOT update ts since it is used as a key for virtuoso list
lastMessage.partial = false
// instead of streaming partialMessage events, we do a save and post like normal to persist to disk
console.log("updating partial message", lastMessage)
// await this.saveClineMessagesAndUpdateHistory()
}
@@ -4519,7 +4515,6 @@ export class Task {
}
if (this.abort) {
console.log("aborting stream...")
if (!this.abandoned) {
// only need to gracefully abort if this instance isn't abandoned (sometimes openrouter stream hangs, in which case this would affect future instances of cline)
await abortStream("user_cancelled")
+1 -1
View File
@@ -87,7 +87,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider {
this.setWebviewMessageListener(webviewView.webview)
// Logs show up in bottom panel > Debug Console
//console.log("registering listener")
//
// Listen for when the panel becomes visible
// https://github.com/microsoft/vscode-discussions/discussions/840
+1 -1
View File
@@ -23,7 +23,7 @@ The Cline extension exposes an API that can be used by other extensions. To use
// Get custom instructions
const instructions = await cline.getCustomInstructions()
console.log("Current custom instructions:", instructions)
// Start a new task with an initial message
await cline.startNewTask("Hello, Cline! Let's make a new project...")
+5 -19
View File
@@ -16,6 +16,7 @@ import { sendChatButtonClickedEvent } from "./core/controller/ui/subscribeToChat
import { ErrorService } from "./services/error/ErrorService"
import { initializeTestMode, cleanupTestMode } from "./services/test/TestMode"
import { telemetryService } from "./services/posthog/telemetry/TelemetryService"
import { sendSettingsButtonClickedEvent } from "./core/controller/ui/subscribeToSettingsButtonClicked"
import { v4 as uuidv4 } from "uuid"
import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui"
import { WebviewProviderType } from "./shared/webview/types"
@@ -94,7 +95,6 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cline.plusButtonClicked", async (webview: any) => {
console.log("[DEBUG] plusButtonClicked", webview)
// Pass the webview type to the event sender
const isSidebar = !webview
@@ -121,7 +121,6 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cline.mcpButtonClicked", (webview: any) => {
console.log("[DEBUG] mcpButtonClicked", webview)
// Pass the webview type to the event sender
const isSidebar = !webview
const webviewType = isSidebar ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB
@@ -169,26 +168,15 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cline.settingsButtonClicked", (webview: any) => {
WebviewProvider.getAllInstances().forEach((instance) => {
const openSettings = async (instance?: WebviewProvider) => {
instance?.controller.postMessageToWebview({
type: "action",
action: "settingsButtonClicked",
})
}
const isSidebar = !webview
if (isSidebar) {
openSettings(WebviewProvider.getSidebarInstance())
} else {
WebviewProvider.getTabInstances().forEach(openSettings)
}
})
const isSidebar = !webview
const webviewType = isSidebar ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB
sendSettingsButtonClickedEvent(webviewType)
}),
)
context.subscriptions.push(
vscode.commands.registerCommand("cline.historyButtonClicked", async (webview: any) => {
console.log("[DEBUG] historyButtonClicked", webview)
// Pass the webview type to the event sender
const isSidebar = !webview
const webviewType = isSidebar ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB
@@ -200,8 +188,6 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("cline.accountButtonClicked", (webview: any) => {
console.log("[DEBUG] accountButtonClicked", webview)
const isSidebar = !webview
if (isSidebar) {
const sidebarInstance = WebviewProvider.getSidebarInstance()
@@ -161,7 +161,6 @@ export class GitOperations {
try {
await fs.rename(fullPath, newPath)
console.log(`CheckpointTracker ${disable ? "disabled" : "enabled"} nested git repo ${gitPath}`)
} catch (error) {
console.error(`CheckpointTracker failed to ${disable ? "disable" : "enable"} nested git repo ${gitPath}:`, error)
}
@@ -404,7 +404,6 @@ class CheckpointTracker {
try {
await fs.rename(fullPath, newPath)
console.log(`CheckpointTracker ${disable ? "disabled" : "enabled"} nested git repo ${gitPath}`)
} catch (error) {
console.error(`CheckpointTracker failed to ${disable ? "disable" : "enable"} nested git repo ${gitPath}:`, error)
}
@@ -68,7 +68,7 @@ class DiagnosticsMonitor {
diagnostics.some((d) => d.severity === vscode.DiagnosticSeverity.Error)
)
if (hasErrors) {
console.log("Existing errors detected, extending timeout", currentDiagnostics)
timeout = 10_000
}
+3 -3
View File
@@ -54,11 +54,11 @@ export function getNewDiagnostics(
//
// const newProblems = getNewProblems(oldDiagnostics, newDiagnostics);
//
// console.log("New problems:");
// ;
// for (const [uri, diagnostics] of newProblems) {
// console.log(`File: ${uri.fsPath}`);
// ;
// for (const diagnostic of diagnostics) {
// console.log(`- ${diagnostic.message} (${diagnostic.range.start.line}:${diagnostic.range.start.character})`);
// ;
// }
// }
//
+1 -3
View File
@@ -321,9 +321,7 @@ export class DiffViewProvider {
// Remove only the directories we created, in reverse order
for (let i = this.createdDirs.length - 1; i >= 0; i--) {
await fs.rmdir(this.createdDirs[i])
console.log(`Directory ${this.createdDirs[i]} has been deleted.`)
}
console.log(`File ${absolutePath} has been deleted.`)
} else {
// revert document
const edit = new vscode.WorkspaceEdit()
@@ -335,7 +333,7 @@ export class DiffViewProvider {
// Apply the edit and save, since contents shouldn't have changed this won't show in local history unless of course the user made changes and saved during the edit
await vscode.workspace.applyEdit(edit)
await updatedDocument.save()
console.log(`File ${absolutePath} has been reverted to its original content.`)
if (this.documentWasOpen) {
await vscode.window.showTextDocument(vscode.Uri.file(absolutePath), {
preview: false,
+2 -4
View File
@@ -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);
;
});
// To wait for the process to complete naturally:
@@ -50,7 +50,7 @@ process.continue();
// Later, if you need to get the unretrieved output:
const unretrievedOutput = terminalManager.getUnretrievedOutput(terminalId);
console.log('Unretrieved output:', unretrievedOutput);
;
Resources:
- https://github.com/microsoft/vscode/issues/226655
@@ -164,7 +164,6 @@ export class TerminalManager {
// if shell integration is not available, remove terminal so it does not get reused as it may be running a long-running process
process.once("no_shell_integration", () => {
console.log(`no_shell_integration received for terminal ${terminalInfo.id}`)
// Remove the terminal so we can't reuse it (in case it's running a long-running process)
TerminalRegistry.removeTerminal(terminalInfo.id)
this.terminalIds.delete(terminalInfo.id)
@@ -204,7 +203,6 @@ export class TerminalManager {
)
})
.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
+1 -1
View File
@@ -192,7 +192,7 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
this.emit("continue")
this.emit("no_shell_integration")
// setTimeout(() => {
// console.log(`Emitting continue after delay for terminal`)
//
// // can't emit completed since we don't if the command actually completed, it could still be running server
// }, 500) // Adjust this delay as needed
}
+1 -3
View File
@@ -78,9 +78,7 @@ export async function getTheme() {
) as any
return converted
} catch (e) {
console.log("Error loading color theme: ", e)
}
} catch (e) {}
return undefined
}
+2 -12
View File
@@ -186,7 +186,6 @@ export class BrowserSession {
this.isConnectedToRemote = false
if (this.browserSettings.remoteBrowserEnabled) {
console.log(`launch browser called -- remote host mode (non-headless)`)
try {
await this.launchRemoteBrowser()
// Don't create a new page here, as we'll create it in launchRemoteBrowser
@@ -216,7 +215,6 @@ export class BrowserSession {
await this.launchLocalBrowser()
}
} else {
console.log(`launch browser called -- local mode (headless)`)
await this.launchLocalBrowser()
}
@@ -260,9 +258,7 @@ export class BrowserSession {
console.info(`Auto-discovered Chrome at ${discoveredHost}`)
remoteBrowserHost = discoveredHost
}
} catch (error) {
console.log(`Auto-discovery failed: ${error}`)
}
} catch (error) {}
}
// Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old)
@@ -277,8 +273,6 @@ export class BrowserSession {
this.isConnectedToRemote = true
return
} catch (error) {
console.log(`Failed to connect using cached endpoint: ${error}`)
// Capture error telemetry
if (this.taskId) {
telemetryService.captureBrowserError(
@@ -329,8 +323,6 @@ export class BrowserSession {
this.isConnectedToRemote = true
return
} catch (error) {
console.log(`Failed to connect to remote browser: ${error}`)
// Capture error telemetry
if (this.taskId) {
telemetryService.captureBrowserError(
@@ -359,9 +351,7 @@ export class BrowserSession {
// First try chrome-launcher's killAll to handle instances it launched
try {
await chromeLauncher.killAll()
} catch (err: unknown) {
console.log("Error in chrome-launcher killAll:", err)
}
} catch (err: unknown) {}
// Then kill other Chrome instances using platform-specific commands
try {
+2 -10
View File
@@ -123,7 +123,7 @@ export class McpHub {
const settingsPath = await this.getMcpSettingsFilePath()
// Subscribe to file changes using the gRPC WatchService
console.log("[DEBUG] subscribing to mcp file changes")
const cancelSubscription = WatchServiceClient.subscribeToFile(
SubscribeToFileRequest.create({
metadata: Metadata.create({}),
@@ -152,9 +152,7 @@ export class McpHub {
onError: (error) => {
console.error("Error watching MCP settings file:", error)
},
onComplete: () => {
console.log("[DEBUG] MCP settings file watch completed")
},
onComplete: () => {},
},
)
@@ -235,7 +233,6 @@ export class McpHub {
const isInfoLog = /INFO/i.test(output)
if (isInfoLog) {
console.log(`Server "${name}" info:`, output)
} else {
console.error(`Server "${name}" stderr:`, output)
const connection = this.findConnection(name, source)
@@ -420,7 +417,6 @@ export class McpHub {
for (const name of currentNames) {
if (!newNames.has(name)) {
await this.deleteConnection(name)
console.log(`Deleted MCP server: ${name}`)
}
}
@@ -446,7 +442,6 @@ export class McpHub {
}
await this.deleteConnection(name)
await this.connectToServer(name, config, "rpc")
console.log(`Reconnected MCP server with updated config: ${name}`)
} catch (error) {
console.error(`Failed to reconnect MCP server ${name}:`, error)
}
@@ -467,7 +462,6 @@ export class McpHub {
for (const name of currentNames) {
if (!newNames.has(name)) {
await this.deleteConnection(name)
console.log(`Deleted MCP server: ${name}`)
}
}
@@ -493,7 +487,6 @@ export class McpHub {
}
await this.deleteConnection(name)
await this.connectToServer(name, config, "internal")
console.log(`Reconnected MCP server with updated config: ${name}`)
} catch (error) {
console.error(`Failed to reconnect MCP server ${name}:`, error)
}
@@ -515,7 +508,6 @@ export class McpHub {
})
watcher.on("change", () => {
console.log(`Detected change in ${filePath}. Restarting server ${name}...`)
this.restartConnection(name)
})
+38 -32
View File
@@ -37,25 +37,28 @@ function createToolCallTracker(webviewProvider: WebviewProvider): {
// Intercept messages to track tool usage
const originalPostMessageToWebview = webviewProvider.controller.postMessageToWebview
webviewProvider.controller.postMessageToWebview = async (message: ExtensionMessage) => {
// Track tool calls
if (message.type === "partialMessage" && message.partialMessage?.say === "tool") {
const toolName = (message.partialMessage.text as any)?.tool
if (toolName) {
tracker.toolCalls[toolName] = (tracker.toolCalls[toolName] || 0) + 1
}
}
// NOTE: Tool tracking via partialMessage has been migrated to gRPC streaming
// This interceptor is kept for potential future use with other message types
// Track tool failures
if (message.type === "partialMessage" && message.partialMessage?.say === "error") {
const errorText = message.partialMessage.text
if (errorText && errorText.includes("Error executing tool")) {
const match = errorText.match(/Error executing tool: (\w+)/)
if (match && match[1]) {
const toolName = match[1]
tracker.toolFailures[toolName] = (tracker.toolFailures[toolName] || 0) + 1
}
}
}
// Track tool calls - commented out as partialMessage is now handled via gRPC
// if (message.type === "partialMessage" && message.partialMessage?.say === "tool") {
// const toolName = (message.partialMessage.text as any)?.tool
// if (toolName) {
// tracker.toolCalls[toolName] = (tracker.toolCalls[toolName] || 0) + 1
// }
// }
// Track tool failures - commented out as partialMessage is now handled via gRPC
// if (message.type === "partialMessage" && message.partialMessage?.say === "error") {
// const errorText = message.partialMessage.text
// if (errorText && errorText.includes("Error executing tool")) {
// const match = errorText.match(/Error executing tool: (\w+)/)
// if (match && match[1]) {
// const toolName = match[1]
// tracker.toolFailures[toolName] = (tracker.toolFailures[toolName] || 0) + 1
// }
// }
// }
return originalPostMessageToWebview.call(webviewProvider.controller, message)
}
@@ -504,22 +507,25 @@ export function createMessageCatcher(webviewProvider: WebviewProvider): vscode.D
// Intercept outgoing messages from extension to webview
webviewProvider.controller.postMessageToWebview = async (message: ExtensionMessage) => {
// Check for completion_result message
if (message.type === "partialMessage" && message.partialMessage?.say === "completion_result") {
// Complete the current task
completeTask()
}
// NOTE: Completion and ask message detection has been migrated to gRPC streaming
// This interceptor is kept for potential future use with other message types
// Check for ask messages that require user intervention
if (message.type === "partialMessage" && message.partialMessage?.type === "ask" && !message.partialMessage.partial) {
const askType = message.partialMessage.ask as ClineAsk
const askText = message.partialMessage.text
// Check for completion_result message - commented out as partialMessage is now handled via gRPC
// if (message.type === "partialMessage" && message.partialMessage?.say === "completion_result") {
// // Complete the current task
// completeTask()
// }
// Automatically respond to different types of asks
setTimeout(async () => {
await autoRespondToAsk(webviewProvider, askType, askText)
}, 100) // Small delay to ensure the message is processed first
}
// Check for ask messages that require user intervention - commented out as partialMessage is now handled via gRPC
// if (message.type === "partialMessage" && message.partialMessage?.type === "ask" && !message.partialMessage.partial) {
// const askType = message.partialMessage.ask as ClineAsk
// const askText = message.partialMessage.text
// // Automatically respond to different types of asks
// setTimeout(async () => {
// await autoRespondToAsk(webviewProvider, askType, askText)
// }, 100) // Small delay to ensure the message is processed first
// }
return originalPostMessageToWebview.call(webviewProvider.controller, message)
}
+1 -3
View File
@@ -169,9 +169,7 @@ async function parseFile(
lastLine = endLine
})
} catch (error) {
console.log(`Error parsing file: ${error}\n`)
}
} catch (error) {}
if (formattedOutput.length > 0) {
return `|----\n${formattedOutput}|----\n`
+1 -4
View File
@@ -21,13 +21,11 @@ export interface ExtensionMessage {
| "lmStudioModels"
| "theme"
| "workspaceUpdated"
| "partialMessage"
| "openRouterModels"
| "openAiModels"
| "requestyModels"
| "mcpServers"
| "relinquishControl"
| "mcpMarketplaceCatalog"
| "mcpDownloadDetails"
| "commitSearchResults"
| "openGraphData"
@@ -38,7 +36,7 @@ export interface ExtensionMessage {
| "fileSearchResults"
| "grpc_response" // New type for gRPC responses
text?: string
action?: "settingsButtonClicked" | "didBecomeVisible" | "accountLogoutClicked" | "focusChatInput"
action?: "didBecomeVisible" | "accountLogoutClicked" | "focusChatInput"
state?: ExtensionState
images?: string[]
files?: string[]
@@ -46,7 +44,6 @@ export interface ExtensionMessage {
lmStudioModels?: string[]
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
filePaths?: string[]
partialMessage?: ClineMessage
openRouterModels?: Record<string, ModelInfo>
openAiModels?: string[]
requestyModels?: Record<string, ModelInfo>
@@ -0,0 +1,253 @@
import { ClineMessage as AppClineMessage, ClineAsk as AppClineAsk, ClineSay as AppClineSay } from "@shared/ExtensionMessage"
import { ClineMessage as ProtoClineMessage, ClineMessageType, ClineAsk, ClineSay } from "@shared/proto/ui"
// Helper function to convert ClineAsk string to enum
function convertClineAskToProtoEnum(ask: AppClineAsk | undefined): ClineAsk | undefined {
if (!ask) {
return undefined
}
const mapping: Record<AppClineAsk, ClineAsk> = {
followup: ClineAsk.FOLLOWUP,
plan_mode_respond: ClineAsk.PLAN_MODE_RESPOND,
command: ClineAsk.COMMAND,
command_output: ClineAsk.COMMAND_OUTPUT,
completion_result: ClineAsk.COMPLETION_RESULT,
tool: ClineAsk.TOOL,
api_req_failed: ClineAsk.API_REQ_FAILED,
resume_task: ClineAsk.RESUME_TASK,
resume_completed_task: ClineAsk.RESUME_COMPLETED_TASK,
mistake_limit_reached: ClineAsk.MISTAKE_LIMIT_REACHED,
auto_approval_max_req_reached: ClineAsk.AUTO_APPROVAL_MAX_REQ_REACHED,
browser_action_launch: ClineAsk.BROWSER_ACTION_LAUNCH,
use_mcp_server: ClineAsk.USE_MCP_SERVER,
new_task: ClineAsk.NEW_TASK,
condense: ClineAsk.CONDENSE,
report_bug: ClineAsk.REPORT_BUG,
}
const result = mapping[ask]
if (result === undefined) {
console.warn(`Unknown ClineAsk value: ${ask}`)
}
return result
}
// Helper function to convert ClineAsk enum to string
function convertProtoEnumToClineAsk(ask: ClineAsk): AppClineAsk | undefined {
if (ask === ClineAsk.UNRECOGNIZED) {
console.warn("Received UNRECOGNIZED ClineAsk enum value")
return undefined
}
const mapping: Record<Exclude<ClineAsk, ClineAsk.UNRECOGNIZED>, AppClineAsk> = {
[ClineAsk.FOLLOWUP]: "followup",
[ClineAsk.PLAN_MODE_RESPOND]: "plan_mode_respond",
[ClineAsk.COMMAND]: "command",
[ClineAsk.COMMAND_OUTPUT]: "command_output",
[ClineAsk.COMPLETION_RESULT]: "completion_result",
[ClineAsk.TOOL]: "tool",
[ClineAsk.API_REQ_FAILED]: "api_req_failed",
[ClineAsk.RESUME_TASK]: "resume_task",
[ClineAsk.RESUME_COMPLETED_TASK]: "resume_completed_task",
[ClineAsk.MISTAKE_LIMIT_REACHED]: "mistake_limit_reached",
[ClineAsk.AUTO_APPROVAL_MAX_REQ_REACHED]: "auto_approval_max_req_reached",
[ClineAsk.BROWSER_ACTION_LAUNCH]: "browser_action_launch",
[ClineAsk.USE_MCP_SERVER]: "use_mcp_server",
[ClineAsk.NEW_TASK]: "new_task",
[ClineAsk.CONDENSE]: "condense",
[ClineAsk.REPORT_BUG]: "report_bug",
}
return mapping[ask]
}
// Helper function to convert ClineSay string to enum
function convertClineSayToProtoEnum(say: AppClineSay | undefined): ClineSay | undefined {
if (!say) {
return undefined
}
const mapping: Record<AppClineSay, ClineSay> = {
task: ClineSay.TASK,
error: ClineSay.ERROR,
api_req_started: ClineSay.API_REQ_STARTED,
api_req_finished: ClineSay.API_REQ_FINISHED,
text: ClineSay.TEXT,
reasoning: ClineSay.REASONING,
completion_result: ClineSay.COMPLETION_RESULT_SAY,
user_feedback: ClineSay.USER_FEEDBACK,
user_feedback_diff: ClineSay.USER_FEEDBACK_DIFF,
api_req_retried: ClineSay.API_REQ_RETRIED,
command: ClineSay.COMMAND_SAY,
command_output: ClineSay.COMMAND_OUTPUT_SAY,
tool: ClineSay.TOOL_SAY,
shell_integration_warning: ClineSay.SHELL_INTEGRATION_WARNING,
browser_action_launch: ClineSay.BROWSER_ACTION_LAUNCH_SAY,
browser_action: ClineSay.BROWSER_ACTION,
browser_action_result: ClineSay.BROWSER_ACTION_RESULT,
mcp_server_request_started: ClineSay.MCP_SERVER_REQUEST_STARTED,
mcp_server_response: ClineSay.MCP_SERVER_RESPONSE,
use_mcp_server: ClineSay.USE_MCP_SERVER_SAY,
diff_error: ClineSay.DIFF_ERROR,
deleted_api_reqs: ClineSay.DELETED_API_REQS,
clineignore_error: ClineSay.CLINEIGNORE_ERROR,
checkpoint_created: ClineSay.CHECKPOINT_CREATED,
load_mcp_documentation: ClineSay.LOAD_MCP_DOCUMENTATION,
info: ClineSay.INFO,
}
const result = mapping[say]
if (result === undefined) {
console.warn(`Unknown ClineSay value: ${say}`)
}
return result
}
// Helper function to convert ClineSay enum to string
function convertProtoEnumToClineSay(say: ClineSay): AppClineSay | undefined {
if (say === ClineSay.UNRECOGNIZED) {
console.warn("Received UNRECOGNIZED ClineSay enum value")
return undefined
}
const mapping: Record<Exclude<ClineSay, ClineSay.UNRECOGNIZED>, AppClineSay> = {
[ClineSay.TASK]: "task",
[ClineSay.ERROR]: "error",
[ClineSay.API_REQ_STARTED]: "api_req_started",
[ClineSay.API_REQ_FINISHED]: "api_req_finished",
[ClineSay.TEXT]: "text",
[ClineSay.REASONING]: "reasoning",
[ClineSay.COMPLETION_RESULT_SAY]: "completion_result",
[ClineSay.USER_FEEDBACK]: "user_feedback",
[ClineSay.USER_FEEDBACK_DIFF]: "user_feedback_diff",
[ClineSay.API_REQ_RETRIED]: "api_req_retried",
[ClineSay.COMMAND_SAY]: "command",
[ClineSay.COMMAND_OUTPUT_SAY]: "command_output",
[ClineSay.TOOL_SAY]: "tool",
[ClineSay.SHELL_INTEGRATION_WARNING]: "shell_integration_warning",
[ClineSay.BROWSER_ACTION_LAUNCH_SAY]: "browser_action_launch",
[ClineSay.BROWSER_ACTION]: "browser_action",
[ClineSay.BROWSER_ACTION_RESULT]: "browser_action_result",
[ClineSay.MCP_SERVER_REQUEST_STARTED]: "mcp_server_request_started",
[ClineSay.MCP_SERVER_RESPONSE]: "mcp_server_response",
[ClineSay.USE_MCP_SERVER_SAY]: "use_mcp_server",
[ClineSay.DIFF_ERROR]: "diff_error",
[ClineSay.DELETED_API_REQS]: "deleted_api_reqs",
[ClineSay.CLINEIGNORE_ERROR]: "clineignore_error",
[ClineSay.CHECKPOINT_CREATED]: "checkpoint_created",
[ClineSay.LOAD_MCP_DOCUMENTATION]: "load_mcp_documentation",
[ClineSay.INFO]: "info",
}
return mapping[say]
}
/**
* Convert application ClineMessage to proto ClineMessage
*/
export function convertClineMessageToProto(message: AppClineMessage): ProtoClineMessage {
// For sending messages, we need to provide values for required proto fields
const askEnum = message.ask ? convertClineAskToProtoEnum(message.ask) : undefined
const sayEnum = message.say ? convertClineSayToProtoEnum(message.say) : undefined
// Determine appropriate enum values based on message type
let finalAskEnum: ClineAsk = ClineAsk.FOLLOWUP // Proto default
let finalSayEnum: ClineSay = ClineSay.TEXT // Proto default
if (message.type === "ask") {
finalAskEnum = askEnum ?? ClineAsk.FOLLOWUP // Use FOLLOWUP as default for ask messages
} else if (message.type === "say") {
finalSayEnum = sayEnum ?? ClineSay.TEXT // Use TEXT as default for say messages
}
const protoMessage: ProtoClineMessage = {
ts: message.ts,
type: message.type === "ask" ? ClineMessageType.ASK : ClineMessageType.SAY,
ask: finalAskEnum,
say: finalSayEnum,
text: message.text ?? "",
reasoning: message.reasoning ?? "",
images: message.images ?? [],
files: message.files ?? [],
partial: message.partial ?? false,
lastCheckpointHash: message.lastCheckpointHash ?? "",
isCheckpointCheckedOut: message.isCheckpointCheckedOut ?? false,
isOperationOutsideWorkspace: message.isOperationOutsideWorkspace ?? false,
conversationHistoryIndex: message.conversationHistoryIndex ?? 0,
conversationHistoryDeletedRange: message.conversationHistoryDeletedRange
? {
startIndex: message.conversationHistoryDeletedRange[0],
endIndex: message.conversationHistoryDeletedRange[1],
}
: undefined,
}
return protoMessage
}
/**
* Convert proto ClineMessage to application ClineMessage
*/
export function convertProtoToClineMessage(protoMessage: ProtoClineMessage): AppClineMessage {
const message: AppClineMessage = {
ts: protoMessage.ts,
type: protoMessage.type === ClineMessageType.ASK ? "ask" : "say",
}
// Convert ask enum to string
if (protoMessage.type === ClineMessageType.ASK) {
const ask = convertProtoEnumToClineAsk(protoMessage.ask)
if (ask !== undefined) {
message.ask = ask
}
}
// Convert say enum to string
if (protoMessage.type === ClineMessageType.SAY) {
const say = convertProtoEnumToClineSay(protoMessage.say)
if (say !== undefined) {
message.say = say
}
}
// Convert other fields - preserve empty strings as they may be intentional
if (protoMessage.text !== "") {
message.text = protoMessage.text
}
if (protoMessage.reasoning !== "") {
message.reasoning = protoMessage.reasoning
}
if (protoMessage.images.length > 0) {
message.images = protoMessage.images
}
if (protoMessage.files.length > 0) {
message.files = protoMessage.files
}
if (protoMessage.partial) {
message.partial = protoMessage.partial
}
if (protoMessage.lastCheckpointHash !== "") {
message.lastCheckpointHash = protoMessage.lastCheckpointHash
}
if (protoMessage.isCheckpointCheckedOut) {
message.isCheckpointCheckedOut = protoMessage.isCheckpointCheckedOut
}
if (protoMessage.isOperationOutsideWorkspace) {
message.isOperationOutsideWorkspace = protoMessage.isOperationOutsideWorkspace
}
if (protoMessage.conversationHistoryIndex !== 0) {
message.conversationHistoryIndex = protoMessage.conversationHistoryIndex
}
// Convert conversationHistoryDeletedRange from object to tuple
if (protoMessage.conversationHistoryDeletedRange) {
message.conversationHistoryDeletedRange = [
protoMessage.conversationHistoryDeletedRange.startIndex,
protoMessage.conversationHistoryDeletedRange.endIndex,
]
}
return message
}
@@ -43,7 +43,6 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
) => {
// Use handleRequest with streaming callbacks
const requestId = uuidv4()
console.log(`[DEBUG] Streaming gRPC host call to ${service.fullName}.${methodKey} req:${requestId}`)
// We need to await the promise and then return the cancel function
return (async () => {
@@ -79,10 +78,9 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
client[methodKey as keyof GrpcClientType<T>] = ((request: any) => {
return new Promise(async (resolve, reject) => {
const requestId = uuidv4()
console.log(`[DEBUG] gRPC host call to ${service.fullName}.${methodKey} req:${requestId}`)
try {
const response = await grpcHandler.handleRequest(service.fullName, methodKey, request, requestId)
console.log(`[DEBUG] gRPC host resp to ${service.fullName}.${methodKey} req:${requestId}`)
// Check if the response is a function (streaming) or an object (unary)
if (typeof response === "function") {
@@ -94,7 +92,6 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
throw new Error("gRPC response didn't have a message")
}
} catch (e) {
console.log(`[DEBUG] gRPC host ERR to ${service.fullName}.${methodKey} req:${requestId} err:${e}`)
reject(e)
}
})
@@ -0,0 +1,14 @@
// AUTO-GENERATED FILE - DO NOT MODIFY DIRECTLY
// Generated by proto/build-proto.js
import { createGrpcClient } from "./host-grpc-client-base"
import { UriServiceDefinition } from "@shared/proto/host/uri"
import { WatchServiceDefinition } from "@shared/proto/host/watch"
const UriServiceClient = createGrpcClient(UriServiceDefinition)
const WatchServiceClient = createGrpcClient(WatchServiceDefinition)
export {
UriServiceClient,
WatchServiceClient
}
-1
View File
@@ -5,7 +5,6 @@ import * as health from "grpc-health-check"
const log = (...args: unknown[]) => {
const timestamp = new Date().toISOString()
console.log(`[${timestamp}]`, "#bot.cline.server.ts", ...args)
}
// Load service definitions.
-2
View File
@@ -50,6 +50,4 @@ const extensionContext: ExtensionContext = {
workspaceState: new MementoStore(path.join(DATA_DIR, "workspaceState.json")),
}
console.log("Finished loading vscode context...")
export { extensionContext, outputChannel, postMessage }
+5 -8
View File
@@ -77,12 +77,10 @@ export function createGitHubIssueUrl(baseUrl: string, params: Map<string, string
*/
export async function openUrlInBrowser(url: string): Promise<void> {
// For debugging
console.log(`Opening URL: ${url}`)
// Always copy to clipboard as a fallback
try {
await vscode.env.clipboard.writeText(url)
console.log("URL copied to clipboard as backup")
} catch (error) {
console.error(`Failed to copy URL to clipboard: ${error}`)
}
@@ -90,7 +88,6 @@ export async function openUrlInBrowser(url: string): Promise<void> {
// Try to open the URL using platform-specific commands
try {
const platform = os.platform()
console.log(`Detected platform: ${platform}`)
// Use promisify for better async error handling
const execPromise = util.promisify(cp.exec)
@@ -100,14 +97,14 @@ export async function openUrlInBrowser(url: string): Promise<void> {
// Windows - try multiple approaches
try {
await execPromise(`start "" "${url}"`)
console.log("Opened URL with Windows 'start' command")
return
} catch (winError) {
console.error(`Error with Windows 'start' command: ${winError}`)
try {
await execPromise(`powershell.exe -Command "Start-Process '${url}'"`)
console.log("Opened URL with PowerShell command")
return
} catch (psError) {
console.error(`Error with PowerShell command: ${psError}`)
@@ -117,7 +114,7 @@ export async function openUrlInBrowser(url: string): Promise<void> {
} else if (platform === "darwin") {
// macOS
await execPromise(`open "${url}"`)
console.log("Opened URL with macOS 'open' command")
return
} else {
// Linux and others - try multiple commands
@@ -126,7 +123,7 @@ export async function openUrlInBrowser(url: string): Promise<void> {
for (const cmd of linuxCommands) {
try {
await execPromise(`${cmd} "${url}"`)
console.log(`Opened URL with '${cmd}' command`)
return
} catch (cmdError) {
console.error(`Error with '${cmd}' command: ${cmdError}`)
@@ -146,7 +143,7 @@ export async function openUrlInBrowser(url: string): Promise<void> {
try {
// The 'true' parameter might help preserve some encodings, but this is not guaranteed
await vscode.env.openExternal(vscode.Uri.parse(url, true))
console.log("Opened URL with vscode.env.openExternal (note: URL encoding may be affected)")
return
} catch (vscodeError) {
console.error(`Error with vscode.env.openExternal: ${vscodeError}`)
@@ -2,15 +2,13 @@ function createStub(path) {
return new Proxy(function () {}, {
get: (target, prop) => {
const fullPath = `${path}.${String(prop)}`
console.log(`Accessed stub: ${fullPath}`)
return createStub(fullPath)
},
apply: (target, thisArg, args) => {
console.log(`Called stub: ${path} with args:`, args)
return createStub(path)
},
construct: (target, args) => {
console.log(`Constructed stub: ${path} with args:`, args)
return createStub(path)
},
})
@@ -1,39 +1,29 @@
console.log("Loading stub impls...")
const { createStub } = require("./stub-utils")
const open = require("open").default
vscode.window = {
showInformationMessage: (...args) => {
console.log("Stubbed showInformationMessage:", ...args)
return Promise.resolve(undefined)
},
showWarningMessage: (...args) => {
console.log("Stubbed showWarningMessage:", ...args)
return Promise.resolve(undefined)
},
showErrorMessage: (...args) => {
console.log("Stubbed showErrorMessage:", ...args)
return Promise.resolve(undefined)
},
showInputBox: async (options) => {
console.log("Stubbed showInputBox:", options)
return ""
},
showOpenDialog: async (options) => {
console.log("Stubbed showOpenDialog:", options)
return []
},
showSaveDialog: async (options) => {
console.log("Stubbed showSaveDialog:", options)
return undefined
},
showTextDocument: async (...args) => {
console.log("Stubbed showTextDocument:", ...args)
return {}
},
createOutputChannel: (name) => {
console.log("Stubbed createOutputChannel:", name)
return {
appendLine: console.log,
show: () => {},
@@ -41,7 +31,6 @@ vscode.window = {
}
},
createTerminal: (...args) => {
console.log("Stubbed createTerminal:", ...args)
return {
sendText: console.log,
show: () => {},
@@ -55,7 +44,6 @@ vscode.window = {
close: async () => {},
},
withProgress: async (_options, task) => {
console.log("Stubbed withProgress")
return task({ report: () => {} })
},
registerUriHandler: () => ({ dispose: () => {} }),
@@ -63,7 +51,6 @@ vscode.window = {
onDidChangeActiveTextEditor: () => ({ dispose: () => {} }),
createTextEditorDecorationType: () => ({ dispose: () => {} }),
createWebviewPanel: (...args) => {
console.log("Stubbed createWebviewPanel:", ...args)
return {
webview: {},
reveal: () => {},
@@ -141,9 +128,7 @@ vscode.Uri = {
vscode.env.openExternal = async (uri) => {
const url = typeof uri === "string" ? uri : (uri.toString?.() ?? "")
console.log("Opening browser:", url)
await open(url)
return true
}
console.log("Finished loading stub impls...")
File diff suppressed because it is too large Load Diff
@@ -27,9 +27,8 @@ export const BrowserSettingsMenu = () => {
// Function to fetch connection info
;(async () => {
try {
console.log("[DEBUG] SENDING BROWSER CONNECTION INFO REQUEST")
const info = await BrowserServiceClient.getBrowserConnectionInfo(EmptyRequest.create({}))
console.log("[DEBUG] GOT BROWSER REPLY:", info, typeof info)
setConnectionInfo({
isConnected: info.isConnected,
isRemote: info.isRemote,
@@ -982,9 +982,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
} else {
StateServiceClient.getLatestState(EmptyRequest.create())
.then(() => {
console.log("State refreshed")
})
.then(() => {})
.catch((error) => {
console.error("Error refreshing state:", error)
})
+1 -4
View File
@@ -461,7 +461,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
if (hasContent) {
console.log("[ChatView] handleSendMessage - Sending message:", messageToSend)
if (messages.length === 0) {
await TaskServiceClient.newTask(NewTaskRequest.create({ text: messageToSend, images, files }))
} else if (clineAsk) {
@@ -738,9 +737,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
onError: (error) => {
console.error("Error in addToInput subscription:", error)
},
onComplete: () => {
console.log("addToInput subscription completed")
},
onComplete: () => {},
})
return cleanup
@@ -151,8 +151,6 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
return (cacheReads !== undefined && cacheReads > 0) || (cacheWrites !== undefined && cacheWrites > 0)
}
console.log("IS_DEV", { IS_DEV, isItTrue: IS_DEV === '"true"' })
const ContextWindowComponent = (
<>
{isTaskExpanded && contextWindow && (
@@ -162,7 +162,6 @@ export default function MermaidBlock({ code }: MermaidBlockProps) {
}
async function svgToPng(svgEl: SVGElement): Promise<string> {
console.log("svgToPng function called")
// Clone the SVG to avoid modifying the original
const svgClone = svgEl.cloneNode(true) as SVGElement
-1
View File
@@ -46,7 +46,6 @@ export const TabList = forwardRef<
>(({ children, className, value, onValueChange, ...props }, ref) => {
const handleTabSelect = useCallback(
(tabValue: string) => {
console.log("Tab selected:", tabValue)
onValueChange(tabValue)
},
[onValueChange],
@@ -37,7 +37,6 @@ class ImagePreview extends React.Component<
componentDidMount() {
// Set up a timeout to handle cases where the image never loads or errors
this.timeoutId = setTimeout(() => {
console.log(`Image load timeout for ${this.props.url}`)
if (this.state.loading) {
this.setState({
loading: false,
@@ -63,15 +62,12 @@ class ImagePreview extends React.Component<
checkIfImageUrl(url)
.then((isImage) => {
if (isImage) {
console.log(`URL is confirmed as image: ${url}`)
this.loadImage(url)
} else {
console.log(`URL is not an image: ${url}`)
this.handleImageError()
}
})
.catch((error) => {
console.log(`Error checking if URL is an image: ${error}`)
// Don't fallback to direct image loading on error
// Instead, report the error so the URL can be handled as a non-image
this.handleImageError()
@@ -84,7 +80,6 @@ class ImagePreview extends React.Component<
// For SVG files, we don't need to calculate aspect ratio as they're vector-based
if (isSvg) {
console.log(`SVG image detected, skipping aspect ratio calculation: ${url}`)
// Default aspect ratio for SVGs
this.aspectRatio = 1
this.handleImageLoad()
@@ -95,8 +90,6 @@ class ImagePreview extends React.Component<
const testImg = new Image()
testImg.onload = () => {
console.log(`Test image loaded successfully: ${url}`)
// Calculate aspect ratio for proper display
if (testImg.width > 0 && testImg.height > 0) {
this.aspectRatio = testImg.width / testImg.height
@@ -106,7 +99,6 @@ class ImagePreview extends React.Component<
}
testImg.onerror = () => {
console.log(`Test image failed to load: ${url}`)
this.handleImageError()
}
@@ -132,14 +124,12 @@ class ImagePreview extends React.Component<
// Handle image load event
handleImageLoad = () => {
console.log(`Image loaded successfully: ${this.props.url}`)
this.setState({ loading: false })
this.cleanup()
}
// Handle image error event
handleImageError = () => {
console.log(`Image failed to load: ${this.props.url}`)
this.setState({
loading: false,
error: `Failed to load image: ${this.props.url}`,

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