mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| faac4941c2 | |||
| 2a634317f7 |
+1
-1
@@ -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]
|
||||
|
||||
@@ -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
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,25 +9,20 @@ import { handleWatchServiceRequest, handleWatchServiceStreamingRequest } from ".
|
||||
* 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>
|
||||
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,
|
||||
},
|
||||
}
|
||||
"host.UriService": {
|
||||
requestHandler: handleUriServiceRequest,
|
||||
streamingHandler: handleUriServiceStreamingRequest
|
||||
},
|
||||
"host.WatchService": {
|
||||
requestHandler: handleWatchServiceRequest,
|
||||
streamingHandler: handleWatchServiceStreamingRequest
|
||||
}
|
||||
};
|
||||
@@ -50,7 +50,6 @@ export class ServiceRegistry {
|
||||
}
|
||||
|
||||
this.methodMetadata[methodName] = { isStreaming, ...metadata }
|
||||
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,4 +19,4 @@ export const handleUriServiceStreamingRequest = uriService.handleStreamingReques
|
||||
export const isStreamingMethod = uriService.isStreamingMethod
|
||||
|
||||
// Register all uri methods
|
||||
registerAllMethods()
|
||||
registerAllMethods()
|
||||
@@ -13,4 +13,4 @@ export function registerAllMethods(): void {
|
||||
registerMethod("file", file)
|
||||
registerMethod("joinPath", joinPath)
|
||||
registerMethod("parse", parse)
|
||||
}
|
||||
}
|
||||
@@ -19,4 +19,4 @@ export const handleWatchServiceStreamingRequest = watchService.handleStreamingRe
|
||||
export const isStreamingMethod = watchService.isStreamingMethod
|
||||
|
||||
// Register all watch methods
|
||||
registerAllMethods()
|
||||
registerAllMethods()
|
||||
@@ -6,10 +6,12 @@ import { registerMethod } from "./index"
|
||||
import { subscribeToFile } from "./subscribeToFile"
|
||||
|
||||
// Streaming methods for this service
|
||||
export const streamingMethods = ["subscribeToFile"]
|
||||
export const streamingMethods = [
|
||||
"subscribeToFile"
|
||||
]
|
||||
|
||||
// Register all watch service methods
|
||||
export function registerAllMethods(): void {
|
||||
// Register each method with the registry
|
||||
registerMethod("subscribeToFile", subscribeToFile, { isStreaming: true })
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -77,5 +77,3 @@ ${handlerSetup}
|
||||
`
|
||||
// Write output file
|
||||
fs.writeFileSync(OUT_FILE, output)
|
||||
|
||||
console.log(`Generated service handlers in ${OUT_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
@@ -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,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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
* ;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ export class ServiceRegistry {
|
||||
}
|
||||
|
||||
this.methodMetadata[methodName] = { isStreaming, ...metadata }
|
||||
console.log(`Registered ${this.serviceName} method: ${methodName}${isStreaming ? " (streaming)" : ""}`)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -918,8 +918,6 @@ export class Controller {
|
||||
}
|
||||
|
||||
await sendAddToInputEvent(input)
|
||||
|
||||
console.log("addSelectedCodeToChat", code, filePath, languageId)
|
||||
}
|
||||
|
||||
// 'Add to Cline' context menu in Terminal
|
||||
@@ -936,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
|
||||
@@ -949,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[]) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -20,7 +20,6 @@ export async function subscribeToSettingsButtonClicked(
|
||||
requestId?: string,
|
||||
): Promise<void> {
|
||||
const providerType = request.providerType
|
||||
console.log(`[DEBUG] set up settings button subscription for ${WebviewProviderType[providerType]} webview`)
|
||||
|
||||
// Store the subscription with its provider type
|
||||
subscriptions.set(responseStream, providerType)
|
||||
|
||||
@@ -530,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) {
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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...")
|
||||
|
||||
@@ -95,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
|
||||
|
||||
@@ -122,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
|
||||
@@ -179,7 +177,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
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
|
||||
@@ -191,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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})`);
|
||||
// ;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,4 +8,7 @@ import { WatchServiceDefinition } from "@shared/proto/host/watch"
|
||||
const UriServiceClient = createGrpcClient(UriServiceDefinition)
|
||||
const WatchServiceClient = createGrpcClient(WatchServiceDefinition)
|
||||
|
||||
export { UriServiceClient, WatchServiceClient }
|
||||
export {
|
||||
UriServiceClient,
|
||||
WatchServiceClient
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -319,7 +319,6 @@ class LinkPreview extends React.Component<LinkPreviewProps, LinkPreviewState> {
|
||||
}
|
||||
}}
|
||||
onError={(e) => {
|
||||
console.log(`Image could not be loaded: ${data.image}`)
|
||||
// Hide the broken image
|
||||
;(e.target as HTMLImageElement).style.display = "none"
|
||||
}}
|
||||
|
||||
@@ -134,11 +134,9 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
// If switching to plain mode, cancel any ongoing processing
|
||||
if (newMode === "plain") {
|
||||
console.log("Switching to plain mode - cancelling URL processing")
|
||||
setUrlMatches([]) // Clear any existing matches when switching to plain mode
|
||||
} else {
|
||||
// If switching to rich mode, the useEffect will re-run and fetch data
|
||||
console.log("Switching to rich mode - will start URL processing")
|
||||
}
|
||||
}, [displayMode])
|
||||
|
||||
@@ -155,7 +153,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
let processingCanceled = false
|
||||
|
||||
const processResponse = async () => {
|
||||
console.log("Processing MCP response for URL extraction")
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
@@ -173,13 +170,11 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
// Skip invalid URLs
|
||||
if (!isUrl(url)) {
|
||||
console.log("Skipping invalid URL:", url)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip localhost URLs to prevent security issues
|
||||
if (isLocalhostUrl(url)) {
|
||||
console.log("Skipping localhost URL:", url)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -194,8 +189,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
urlCount++
|
||||
}
|
||||
|
||||
console.log(`Found ${matches.length} URLs in text, will check if they are images`)
|
||||
|
||||
// Set matches immediately so UI can start rendering with loading states
|
||||
setUrlMatches(matches.sort((a, b) => a.index - b.index))
|
||||
|
||||
@@ -204,20 +197,16 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
|
||||
// Process image checks in the background - one at a time to avoid network flooding
|
||||
const processImageChecks = async () => {
|
||||
console.log(`Starting sequential URL processing for ${matches.length} URLs`)
|
||||
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
// Skip already processed URLs (from extension check)
|
||||
if (matches[i].isProcessed) continue
|
||||
|
||||
// Check if processing has been canceled (switched to plain mode)
|
||||
if (processingCanceled) {
|
||||
console.log("URL processing canceled - display mode changed to plain")
|
||||
return
|
||||
}
|
||||
|
||||
const match = matches[i]
|
||||
console.log(`Processing URL ${i + 1} of ${matches.length}: ${match.url}`)
|
||||
|
||||
try {
|
||||
// Process each URL individually
|
||||
@@ -234,7 +223,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
// Create a new array to ensure React detects the state change
|
||||
setUrlMatches([...matches])
|
||||
} catch (err) {
|
||||
console.log(`URL check error: ${match.url}`, err)
|
||||
match.isProcessed = true
|
||||
|
||||
// Update state even on error
|
||||
@@ -248,8 +236,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`URL processing complete. Found ${matches.filter((m) => m.isImage).length} image URLs`)
|
||||
}
|
||||
|
||||
// Start the background processing
|
||||
@@ -265,7 +251,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
// Cleanup function to cancel processing if component unmounts or dependencies change
|
||||
return () => {
|
||||
processingCanceled = true
|
||||
console.log("Cleaning up URL processing")
|
||||
}
|
||||
}, [responseText, displayMode, forceUpdateCounter])
|
||||
|
||||
@@ -328,7 +313,7 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
</div>,
|
||||
)
|
||||
embedCount++
|
||||
// console.log(`Added image embed for ${url}, embed count: ${embedCount}`);
|
||||
// ;
|
||||
} else if (match.isProcessed) {
|
||||
// For non-image URLs or URLs we haven't processed yet, show link preview
|
||||
try {
|
||||
@@ -343,10 +328,9 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
)
|
||||
|
||||
embedCount++
|
||||
// console.log(`Added link preview for ${url}, embed count: ${embedCount}`);
|
||||
// ;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Link preview could not be created")
|
||||
// Show error message for failed link preview
|
||||
segments.push(
|
||||
<div
|
||||
@@ -399,7 +383,6 @@ const McpResponseDisplay: React.FC<McpResponseDisplayProps> = ({ responseText })
|
||||
</ResponseContainer>
|
||||
)
|
||||
} catch (error) {
|
||||
console.log("Error rendering MCP response - falling back to plain text")
|
||||
return (
|
||||
<ResponseContainer>
|
||||
<ResponseHeader>
|
||||
|
||||
@@ -16,11 +16,10 @@ export const safeCreateUrl = (url: string): URL | null => {
|
||||
try {
|
||||
return new URL(`https://${url}`)
|
||||
} catch (e) {
|
||||
console.log(`Invalid URL: ${url}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
console.log(`Invalid URL: ${url}`)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -90,7 +89,6 @@ export const normalizeRelativeUrl = (relativeUrl: string, baseUrl: string): stri
|
||||
return `${baseUrlObj.protocol}//${baseUrlObj.host}${basePath}${relativeUrl}`
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error normalizing relative URL: ${error}`)
|
||||
return relativeUrl // Return original on error
|
||||
}
|
||||
}
|
||||
@@ -108,7 +106,6 @@ export const formatUrlForOpening = (url: string): string => {
|
||||
return urlObj.href
|
||||
}
|
||||
|
||||
console.log(`Invalid URL format: ${url}`)
|
||||
// Return a safe fallback that won't crash
|
||||
return "about:blank"
|
||||
}
|
||||
@@ -125,12 +122,10 @@ export const checkIfImageUrl = async (url: string): Promise<boolean> => {
|
||||
// Convert HTTP to HTTPS for security in the network request only
|
||||
if (secureUrl.startsWith("http://")) {
|
||||
secureUrl = secureUrl.replace("http://", "https://")
|
||||
console.log(`Using HTTPS version for image check: ${secureUrl}`)
|
||||
}
|
||||
|
||||
// Validate URL before proceeding
|
||||
if (!isUrl(url)) {
|
||||
console.log("Invalid URL format:", url)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -140,7 +135,6 @@ export const checkIfImageUrl = async (url: string): Promise<boolean> => {
|
||||
// Use the gRPC client with timeout
|
||||
const timeoutPromise = new Promise<boolean>((resolve) => {
|
||||
setTimeout(() => {
|
||||
console.log("Hit timeout waiting for checkIsImageUrl")
|
||||
resolve(false)
|
||||
}, 3000)
|
||||
})
|
||||
@@ -156,7 +150,6 @@ export const checkIfImageUrl = async (url: string): Promise<boolean> => {
|
||||
// Race between the service call and the timeout
|
||||
return Promise.race([servicePromise, timeoutPromise])
|
||||
} catch (error) {
|
||||
console.log("Error checking if URL is an image:", url)
|
||||
// Return false to indicate it's not an image
|
||||
return false
|
||||
}
|
||||
@@ -165,6 +158,6 @@ export const checkIfImageUrl = async (url: string): Promise<boolean> => {
|
||||
// Don't fall back to extension check for other URLs
|
||||
// Only data URLs (handled above) are guaranteed to be images
|
||||
// For all other URLs, we need proper content type verification
|
||||
console.log(`URL protocol not supported for image check: ${url}`)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
// type: "telemetrySetting",
|
||||
// text: telemetrySetting,
|
||||
// })
|
||||
// console.log("handleSubmit", withoutDone)
|
||||
//
|
||||
// vscode.postMessage({
|
||||
// type: "separateModeSetting",
|
||||
// text: separateModeSetting,
|
||||
@@ -329,7 +329,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
if (message.grpc_response?.message?.action === "scrollToSettings") {
|
||||
const tabId = message.grpc_response?.message?.value
|
||||
if (tabId) {
|
||||
console.log("Opening settings tab from GRPC response:", tabId)
|
||||
// Check if the value corresponds to a valid tab ID
|
||||
const isValidTabId = SETTINGS_TABS.some((tab) => tab.id === tabId)
|
||||
|
||||
@@ -391,16 +390,13 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
// Enhanced tab change handler with debugging
|
||||
const handleTabChange = useCallback(
|
||||
(tabId: string) => {
|
||||
console.log("Tab change requested:", tabId, "Current:", activeTab)
|
||||
setActiveTab(tabId)
|
||||
},
|
||||
[activeTab],
|
||||
)
|
||||
|
||||
// Debug tab changes
|
||||
useEffect(() => {
|
||||
console.log("Active tab changed to:", activeTab)
|
||||
}, [activeTab])
|
||||
useEffect(() => {}, [activeTab])
|
||||
|
||||
// Track whether we're in compact mode
|
||||
const [isCompactMode, setIsCompactMode] = useState(false)
|
||||
@@ -462,7 +458,6 @@ const SettingsView = ({ onDone, targetSection }: SettingsViewProps) => {
|
||||
data-testid={`tab-${tab.id}`}
|
||||
data-value={tab.id}
|
||||
onClick={() => {
|
||||
console.log("Compact tab clicked:", tab.id)
|
||||
handleTabChange(tab.id)
|
||||
}}>
|
||||
<div className={cn("flex items-center gap-2", isCompactMode && "justify-center")}>
|
||||
|
||||
@@ -259,7 +259,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
if (response.stateJson) {
|
||||
try {
|
||||
const stateData = JSON.parse(response.stateJson) as ExtensionState
|
||||
console.log("[DEBUG] parsed state JSON, updating state")
|
||||
|
||||
setState((prevState) => {
|
||||
// Versioning logic for autoApprovalSettings
|
||||
const incomingVersion = stateData.autoApprovalSettings?.version ?? 1
|
||||
@@ -304,23 +304,17 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
setShowWelcome(!hasKey)
|
||||
setDidHydrateState(true)
|
||||
|
||||
console.log("[DEBUG] returning new state in ESC")
|
||||
|
||||
return newState
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error parsing state JSON:", error)
|
||||
console.log("[DEBUG] ERR getting state", error)
|
||||
}
|
||||
}
|
||||
console.log('[DEBUG] ended "got subscribed state"')
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in state subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("State subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
})
|
||||
|
||||
// Subscribe to MCP button clicked events with webview type
|
||||
@@ -330,15 +324,12 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
}),
|
||||
{
|
||||
onResponse: () => {
|
||||
console.log("[DEBUG] Received mcpButtonClicked event from gRPC stream")
|
||||
navigateToMcp()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in mcpButtonClicked subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("mcpButtonClicked subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -350,15 +341,13 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
{
|
||||
onResponse: () => {
|
||||
// When history button is clicked, navigate to history view
|
||||
console.log("[DEBUG] Received history button clicked event from gRPC stream")
|
||||
|
||||
navigateToHistory()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in history button clicked subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("History button clicked subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -366,7 +355,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
chatButtonUnsubscribeRef.current = UiServiceClient.subscribeToChatButtonClicked(EmptyRequest.create({}), {
|
||||
onResponse: () => {
|
||||
// When chat button is clicked, navigate to chat
|
||||
console.log("[DEBUG] Received chat button clicked event from gRPC stream")
|
||||
|
||||
navigateToChat()
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -388,9 +377,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
onError: (error) => {
|
||||
console.error("Error in settings button clicked subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("Settings button clicked subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -398,8 +385,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
partialMessageUnsubscribeRef.current = UiServiceClient.subscribeToPartialMessage(EmptyRequest.create({}), {
|
||||
onResponse: (protoMessage) => {
|
||||
try {
|
||||
console.log("[PARTIAL] Received partialMessage event from gRPC stream")
|
||||
|
||||
// Validate critical fields
|
||||
if (!protoMessage.ts || protoMessage.ts <= 0) {
|
||||
console.error("Invalid timestamp in partial message:", protoMessage)
|
||||
@@ -407,8 +392,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
}
|
||||
|
||||
const partialMessage = convertProtoToClineMessage(protoMessage)
|
||||
console.log("[PARTIAL] Partial message:", partialMessage)
|
||||
console.log("\n")
|
||||
|
||||
setState((prevState) => {
|
||||
// worth noting it will never be possible for a more up-to-date message to be sent here or in normal messages post since the presentAssistantContent function uses lock
|
||||
const lastIndex = findLastIndex(prevState.clineMessages, (msg) => msg.ts === partialMessage.ts)
|
||||
@@ -426,23 +410,18 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
onError: (error) => {
|
||||
console.error("Error in partialMessage subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("[DEBUG] partialMessage subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
})
|
||||
|
||||
// Subscribe to MCP marketplace catalog updates
|
||||
mcpMarketplaceUnsubscribeRef.current = McpServiceClient.subscribeToMcpMarketplaceCatalog(EmptyRequest.create({}), {
|
||||
onResponse: (catalog) => {
|
||||
console.log("[DEBUG] Received MCP marketplace catalog update from gRPC stream")
|
||||
setMcpMarketplaceCatalog(catalog)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in MCP marketplace catalog subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("MCP marketplace catalog subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
})
|
||||
|
||||
// Still send the webviewDidLaunch message for other initialization
|
||||
@@ -452,15 +431,13 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
accountButtonClickedSubscriptionRef.current = UiServiceClient.subscribeToAccountButtonClicked(EmptyRequest.create(), {
|
||||
onResponse: () => {
|
||||
// When account button is clicked, navigate to account view
|
||||
console.log("[DEBUG] Received account button clicked event from gRPC stream")
|
||||
|
||||
navigateToAccount()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error in account button clicked subscription:", error)
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log("Account button clicked subscription completed")
|
||||
},
|
||||
onComplete: () => {},
|
||||
})
|
||||
|
||||
// Clean up subscriptions when component unmounts
|
||||
|
||||
@@ -64,7 +64,6 @@ export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
async (token: string) => {
|
||||
try {
|
||||
await signInWithCustomToken(auth, token)
|
||||
console.log("Successfully signed in with custom token")
|
||||
} catch (error) {
|
||||
console.error("Error signing in with custom token:", error)
|
||||
throw error
|
||||
@@ -93,7 +92,6 @@ export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
const handleSignOut = useCallback(async () => {
|
||||
try {
|
||||
await signOut(auth)
|
||||
console.log("Successfully signed out of Firebase")
|
||||
} catch (error) {
|
||||
console.error("Error signing out of Firebase:", error)
|
||||
throw error
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useRef, useMemo, useState, useEffect } from "react"
|
||||
import { ClineMessage } from "../../../src/shared/ExtensionMessage"
|
||||
|
||||
/**
|
||||
* Custom hook to optimize state updates and prevent unnecessary re-renders
|
||||
*/
|
||||
export function useOptimizedMessages(messages: ClineMessage[]) {
|
||||
const messagesRef = useRef<ClineMessage[]>(messages)
|
||||
const lastUpdateRef = useRef<number>(Date.now())
|
||||
|
||||
// Only update ref if messages actually changed
|
||||
const messagesChanged = useMemo(() => {
|
||||
if (messagesRef.current.length !== messages.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if last message changed (for partial updates)
|
||||
if (messages.length > 0) {
|
||||
const lastMessage = messages[messages.length - 1]
|
||||
const lastRefMessage = messagesRef.current[messages.length - 1]
|
||||
|
||||
if (!lastRefMessage || lastMessage.ts !== lastRefMessage.ts || lastMessage.text !== lastRefMessage.text) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, [messages])
|
||||
|
||||
if (messagesChanged) {
|
||||
messagesRef.current = messages
|
||||
lastUpdateRef.current = Date.now()
|
||||
}
|
||||
|
||||
// Return stable reference if messages haven't changed
|
||||
return messagesRef.current
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounced state update hook
|
||||
*/
|
||||
export function useDebouncedUpdate<T>(value: T, delay: number = 100): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
const timeoutRef = useRef<NodeJS.Timeout>()
|
||||
|
||||
useEffect(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setDebouncedValue(value)
|
||||
}, delay)
|
||||
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [value, delay])
|
||||
|
||||
return debouncedValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory-efficient message renderer
|
||||
*/
|
||||
export function useMessageRenderer(messages: ClineMessage[], windowSize: number = 50) {
|
||||
const visibleMessages = useMemo(() => {
|
||||
if (messages.length <= windowSize) {
|
||||
return messages
|
||||
}
|
||||
|
||||
// Only render the last N messages
|
||||
return messages.slice(-windowSize)
|
||||
}, [messages, windowSize])
|
||||
|
||||
const hasMoreMessages = messages.length > windowSize
|
||||
const hiddenCount = Math.max(0, messages.length - windowSize)
|
||||
|
||||
return {
|
||||
visibleMessages,
|
||||
hasMoreMessages,
|
||||
hiddenCount,
|
||||
totalCount: messages.length,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow compare hook for objects
|
||||
*/
|
||||
export function useShallowCompare<T extends Record<string, any>>(obj: T): T {
|
||||
const ref = useRef<T>(obj)
|
||||
|
||||
const hasChanged = useMemo(() => {
|
||||
const keys1 = Object.keys(ref.current)
|
||||
const keys2 = Object.keys(obj)
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
if (ref.current[key] !== obj[key]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}, [obj])
|
||||
|
||||
if (hasChanged) {
|
||||
ref.current = obj
|
||||
}
|
||||
|
||||
return ref.current
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory usage monitor hook
|
||||
*/
|
||||
export function useMemoryMonitor(threshold: number = 100) {
|
||||
const [memoryWarning, setMemoryWarning] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error - performance.memory is a Chrome-specific API
|
||||
if (!performance.memory) {
|
||||
return
|
||||
}
|
||||
|
||||
const checkMemory = () => {
|
||||
// @ts-expect-error - performance.memory is a Chrome-specific API
|
||||
const usedMemoryMB = performance.memory.usedJSHeapSize / (1024 * 1024)
|
||||
// @ts-expect-error - performance.memory is a Chrome-specific API
|
||||
const limitMB = performance.memory.jsHeapSizeLimit / (1024 * 1024)
|
||||
const percentUsed = (usedMemoryMB / limitMB) * 100
|
||||
|
||||
if (percentUsed > threshold) {
|
||||
setMemoryWarning(true)
|
||||
console.warn(
|
||||
`Memory usage high: ${usedMemoryMB.toFixed(2)}MB / ${limitMB.toFixed(2)}MB (${percentUsed.toFixed(1)}%)`,
|
||||
)
|
||||
} else {
|
||||
setMemoryWarning(false)
|
||||
}
|
||||
}
|
||||
|
||||
const interval = setInterval(checkMemory, 5000)
|
||||
checkMemory() // Check immediately
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [threshold])
|
||||
|
||||
return memoryWarning
|
||||
}
|
||||
@@ -126,7 +126,6 @@ export function createGrpcClient<T extends ProtoService>(service: T): GrpcClient
|
||||
request_id: requestId,
|
||||
},
|
||||
})
|
||||
console.log(`[DEBUG] Sent cancellation for request: ${requestId}`)
|
||||
}
|
||||
}) as any
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user