Browse Source

Add shell integration warning; reuse terminals when possible

Saoud Rizwan 1 year ago
parent
commit
4edffb0d6d

+ 85 - 108
package-lock.json

@@ -28,6 +28,7 @@
         "p-wait-for": "^5.0.2",
         "p-wait-for": "^5.0.2",
         "pdf-parse": "^1.1.1",
         "pdf-parse": "^1.1.1",
         "serialize-error": "^11.0.3",
         "serialize-error": "^11.0.3",
+        "strip-ansi": "^7.1.0",
         "tree-sitter-wasms": "^0.1.11",
         "tree-sitter-wasms": "^0.1.11",
         "web-tree-sitter": "^0.22.6"
         "web-tree-sitter": "^0.22.6"
       },
       },
@@ -2714,35 +2715,6 @@
         "node": ">=12"
         "node": ">=12"
       }
       }
     },
     },
-    "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
-      }
-    },
-    "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
-      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
-      }
-    },
     "node_modules/@istanbuljs/schema": {
     "node_modules/@istanbuljs/schema": {
       "version": "0.1.3",
       "version": "0.1.3",
       "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
       "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -5367,6 +5339,19 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
+    "node_modules/cliui/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/cliui/node_modules/wrap-ansi": {
     "node_modules/cliui/node_modules/wrap-ansi": {
       "version": "7.0.0",
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -6051,6 +6036,19 @@
         "node": "*"
         "node": "*"
       }
       }
     },
     },
+    "node_modules/eslint/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/espree": {
     "node_modules/espree": {
       "version": "9.6.1",
       "version": "9.6.1",
       "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
       "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -7828,6 +7826,19 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
+    "node_modules/mocha/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/mocha/node_modules/supports-color": {
     "node_modules/mocha/node_modules/supports-color": {
       "version": "8.1.1",
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -8381,19 +8392,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
         "url": "https://github.com/sponsors/sindresorhus"
       }
       }
     },
     },
-    "node_modules/ora/node_modules/ansi-regex": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
-      }
-    },
     "node_modules/ora/node_modules/chalk": {
     "node_modules/ora/node_modules/chalk": {
       "version": "5.3.0",
       "version": "5.3.0",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
@@ -8462,22 +8460,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
         "url": "https://github.com/sponsors/sindresorhus"
       }
       }
     },
     },
-    "node_modules/ora/node_modules/strip-ansi": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
-      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
-      }
-    },
     "node_modules/os-name": {
     "node_modules/os-name": {
       "version": "6.0.0",
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/os-name/-/os-name-6.0.0.tgz",
       "resolved": "https://registry.npmjs.org/os-name/-/os-name-6.0.0.tgz",
@@ -9349,33 +9331,17 @@
       "dev": true,
       "dev": true,
       "license": "MIT"
       "license": "MIT"
     },
     },
-    "node_modules/string-width/node_modules/ansi-regex": {
+    "node_modules/string-width-cjs/node_modules/strip-ansi": {
       "version": "6.0.1",
       "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
-      }
-    },
-    "node_modules/string-width/node_modules/strip-ansi": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
-      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
       "dev": true,
       "dev": true,
       "license": "MIT",
       "license": "MIT",
       "dependencies": {
       "dependencies": {
-        "ansi-regex": "^6.0.1"
+        "ansi-regex": "^5.0.1"
       },
       },
       "engines": {
       "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+        "node": ">=8"
       }
       }
     },
     },
     "node_modules/string.prototype.padend": {
     "node_modules/string.prototype.padend": {
@@ -9450,16 +9416,18 @@
       }
       }
     },
     },
     "node_modules/strip-ansi": {
     "node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
       "license": "MIT",
       "license": "MIT",
       "dependencies": {
       "dependencies": {
-        "ansi-regex": "^5.0.1"
+        "ansi-regex": "^6.0.1"
       },
       },
       "engines": {
       "engines": {
-        "node": ">=8"
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
       }
       }
     },
     },
     "node_modules/strip-ansi-cjs": {
     "node_modules/strip-ansi-cjs": {
@@ -9476,6 +9444,18 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
+    "node_modules/strip-ansi/node_modules/ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
     "node_modules/strip-bom": {
     "node_modules/strip-bom": {
       "version": "3.0.0",
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -10127,17 +10107,17 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
-    "node_modules/wrap-ansi/node_modules/ansi-regex": {
+    "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
       "version": "6.0.1",
       "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
-      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
       "dev": true,
       "dev": true,
       "license": "MIT",
       "license": "MIT",
-      "engines": {
-        "node": ">=12"
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
       },
       },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      "engines": {
+        "node": ">=8"
       }
       }
     },
     },
     "node_modules/wrap-ansi/node_modules/ansi-styles": {
     "node_modules/wrap-ansi/node_modules/ansi-styles": {
@@ -10153,22 +10133,6 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
       }
     },
     },
-    "node_modules/wrap-ansi/node_modules/strip-ansi": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
-      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
-      }
-    },
     "node_modules/wrappy": {
     "node_modules/wrappy": {
       "version": "1.0.2",
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -10262,6 +10226,19 @@
         "node": ">=8"
         "node": ">=8"
       }
       }
     },
     },
+    "node_modules/yargs/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/yocto-queue": {
     "node_modules/yocto-queue": {
       "version": "0.1.0",
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
       "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

+ 1 - 0
package.json

@@ -156,6 +156,7 @@
     "p-wait-for": "^5.0.2",
     "p-wait-for": "^5.0.2",
     "pdf-parse": "^1.1.1",
     "pdf-parse": "^1.1.1",
     "serialize-error": "^11.0.3",
     "serialize-error": "^11.0.3",
+    "strip-ansi": "^7.1.0",
     "tree-sitter-wasms": "^0.1.11",
     "tree-sitter-wasms": "^0.1.11",
     "web-tree-sitter": "^0.22.6"
     "web-tree-sitter": "^0.22.6"
   }
   }

+ 18 - 21
src/ClaudeDev.ts

@@ -10,6 +10,7 @@ import * as path from "path"
 import { serializeError } from "serialize-error"
 import { serializeError } from "serialize-error"
 import * as vscode from "vscode"
 import * as vscode from "vscode"
 import { ApiHandler, buildApiHandler } from "./api"
 import { ApiHandler, buildApiHandler } from "./api"
+import { TerminalManager } from "./integrations/TerminalManager"
 import { LIST_FILES_LIMIT, listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
 import { LIST_FILES_LIMIT, listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
 import { ClaudeDevProvider } from "./providers/ClaudeDevProvider"
 import { ClaudeDevProvider } from "./providers/ClaudeDevProvider"
 import { ApiConfiguration } from "./shared/api"
 import { ApiConfiguration } from "./shared/api"
@@ -23,10 +24,8 @@ import { Tool, ToolName } from "./shared/Tool"
 import { ClaudeAskResponse } from "./shared/WebviewMessage"
 import { ClaudeAskResponse } from "./shared/WebviewMessage"
 import { findLast, findLastIndex, formatContentBlockToMarkdown } from "./utils"
 import { findLast, findLastIndex, formatContentBlockToMarkdown } from "./utils"
 import { truncateHalfConversation } from "./utils/context-management"
 import { truncateHalfConversation } from "./utils/context-management"
-import { regexSearchFiles } from "./utils/ripgrep"
 import { extractTextFromFile } from "./utils/extract-text"
 import { extractTextFromFile } from "./utils/extract-text"
-import { getPythonEnvPath } from "./utils/get-python-env"
-import { TerminalManager } from "./integrations/TerminalManager"
+import { regexSearchFiles } from "./utils/ripgrep"
 
 
 const SYSTEM_PROMPT =
 const SYSTEM_PROMPT =
 	async () => `You are Claude Dev, a highly skilled software developer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
 	async () => `You are Claude Dev, a highly skilled software developer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
@@ -83,15 +82,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
 SYSTEM INFORMATION
 SYSTEM INFORMATION
 
 
 Operating System: ${osName()}
 Operating System: ${osName()}
-Default Shell: ${defaultShell}${await (async () => {
-		try {
-			const pythonEnvPath = await getPythonEnvPath()
-			if (pythonEnvPath) {
-				return `\nPython Environment: ${pythonEnvPath}`
-			}
-		} catch {}
-		return ""
-	})()}
+Default Shell: ${defaultShell}
 Home Directory: ${os.homedir()}
 Home Directory: ${os.homedir()}
 Current Working Directory: ${cwd}
 Current Working Directory: ${cwd}
 `
 `
@@ -1361,7 +1352,7 @@ export class ClaudeDev {
 		try {
 		try {
 			const terminalInfo = await this.terminalManager.getOrCreateTerminal(cwd)
 			const terminalInfo = await this.terminalManager.getOrCreateTerminal(cwd)
 			terminalInfo.terminal.show() // weird visual bug when creating new terminals (even manually) where there's an empty space at the top.
 			terminalInfo.terminal.show() // weird visual bug when creating new terminals (even manually) where there's an empty space at the top.
-			const process = this.terminalManager.runCommand(terminalInfo, command, cwd)
+			const process = this.terminalManager.runCommand(terminalInfo, command)
 
 
 			let userFeedback: { text?: string; images?: string[] } | undefined
 			let userFeedback: { text?: string; images?: string[] } | undefined
 			const sendCommandOutput = async (line: string): Promise<void> => {
 			const sendCommandOutput = async (line: string): Promise<void> => {
@@ -1386,10 +1377,14 @@ export class ClaudeDev {
 			})
 			})
 
 
 			let completed = false
 			let completed = false
-			process.on("completed", () => {
+			process.once("completed", () => {
 				completed = true
 				completed = true
 			})
 			})
 
 
+			process.once("no_shell_integration", async () => {
+				await this.say("shell_integration_warning")
+			})
+
 			await process
 			await process
 
 
 			// Wait for a short delay to ensure all messages are sent to the webview
 			// Wait for a short delay to ensure all messages are sent to the webview
@@ -1729,7 +1724,7 @@ ${this.customInstructions.trim()}
 		const isDesktop = cwd === path.join(os.homedir(), "Desktop")
 		const isDesktop = cwd === path.join(os.homedir(), "Desktop")
 		const files = await listFiles(cwd, !isDesktop)
 		const files = await listFiles(cwd, !isDesktop)
 		const result = this.formatFilesList(cwd, files)
 		const result = this.formatFilesList(cwd, files)
-		details += `\n# Current Working Directory ('${cwd}') File Structure:${
+		details += `\n# Current Working Directory ('${cwd}') Files${
 			isDesktop
 			isDesktop
 				? "\n(Desktop so only top-level contents shown for brevity, use list_files to explore further if necessary)"
 				? "\n(Desktop so only top-level contents shown for brevity, use list_files to explore further if necessary)"
 				: ""
 				: ""
@@ -1741,7 +1736,7 @@ ${this.customInstructions.trim()}
 
 
 	async getPotentiallyRelevantDetails() {
 	async getPotentiallyRelevantDetails() {
 		let details = `<potentially_relevant_details>
 		let details = `<potentially_relevant_details>
-# VSCode Visible Files:
+# VSCode Visible Files
 ${
 ${
 	vscode.window.visibleTextEditors
 	vscode.window.visibleTextEditors
 		?.map((editor) => editor.document?.uri?.fsPath)
 		?.map((editor) => editor.document?.uri?.fsPath)
@@ -1750,7 +1745,7 @@ ${
 		.join("\n") || "(No files open)"
 		.join("\n") || "(No files open)"
 }
 }
 
 
-# VSCode Opened Tabs:
+# VSCode Open Tabs
 ${
 ${
 	vscode.window.tabGroups.all
 	vscode.window.tabGroups.all
 		.flatMap((group) => group.tabs)
 		.flatMap((group) => group.tabs)
@@ -1770,7 +1765,7 @@ ${
 		)
 		)
 
 
 		if (relevantDiagnostics.length > 0) {
 		if (relevantDiagnostics.length > 0) {
-			details += "\n\n# VSCode Workspace Diagnostics:"
+			details += "\n\n# VSCode Workspace Diagnostics"
 			for (const [uri, fileDiagnostics] of relevantDiagnostics) {
 			for (const [uri, fileDiagnostics] of relevantDiagnostics) {
 				const relativePath = path.relative(cwd, uri.fsPath)
 				const relativePath = path.relative(cwd, uri.fsPath)
 				details += `\n## ${relativePath}:`
 				details += `\n## ${relativePath}:`
@@ -1789,12 +1784,14 @@ ${
 
 
 		const busyTerminals = this.terminalManager.getBusyTerminals()
 		const busyTerminals = this.terminalManager.getBusyTerminals()
 		if (busyTerminals.length > 0) {
 		if (busyTerminals.length > 0) {
-			details += "\n\n# Active Terminals:"
+			details += "\n\n# Active Terminals"
 			for (const busyTerminal of busyTerminals) {
 			for (const busyTerminal of busyTerminals) {
-				details += `\n## Original command:\n${busyTerminal.lastCommand}`
+				details += `\n## $ ${busyTerminal.lastCommand}`
 				const newOutput = this.terminalManager.getUnretrievedOutput(busyTerminal.id)
 				const newOutput = this.terminalManager.getUnretrievedOutput(busyTerminal.id)
 				if (newOutput) {
 				if (newOutput) {
-					details += `\n## New output since last check:\n${newOutput}`
+					details += `\n...\n${newOutput}`
+				} else {
+					details += `\n(Still running, no new output)`
 				}
 				}
 			}
 			}
 		}
 		}

+ 119 - 36
src/integrations/TerminalManager.ts

@@ -1,6 +1,7 @@
 import * as vscode from "vscode"
 import * as vscode from "vscode"
 import { EventEmitter } from "events"
 import { EventEmitter } from "events"
 import pWaitFor from "p-wait-for"
 import pWaitFor from "p-wait-for"
+import stripAnsi from "strip-ansi"
 
 
 /*
 /*
 TerminalManager:
 TerminalManager:
@@ -59,12 +60,64 @@ Resources:
 - https://github.com/microsoft/vscode-extension-samples/blob/main/shell-integration-sample/src/extension.ts
 - https://github.com/microsoft/vscode-extension-samples/blob/main/shell-integration-sample/src/extension.ts
 */
 */
 
 
+// Although vscode.window.terminals provides a list of all open terminals, there's no way to know whether they're busy or not (exitStatus does not provide useful information for most commands). In order to prevent creating too many terminals, we need to keep track of terminals through the life of the extension, as well as session specific terminals for the life of a task (to get latest unretrieved output).
+// Since we have promises keeping track of terminal processes, we get the added benefit of keep track of busy terminals even after a task is closed.
+class TerminalRegistry {
+	private static terminals: TerminalInfo[] = []
+	private static nextTerminalId = 1
+
+	static createTerminal(cwd?: string | vscode.Uri | undefined): TerminalInfo {
+		const terminal = vscode.window.createTerminal({
+			cwd,
+			name: "Claude Dev",
+			iconPath: new vscode.ThemeIcon("robot"),
+		})
+		const newInfo: TerminalInfo = {
+			terminal,
+			busy: false,
+			lastCommand: "",
+			id: this.nextTerminalId++,
+		}
+		this.terminals.push(newInfo)
+		return newInfo
+	}
+
+	static getTerminal(id: number): TerminalInfo | undefined {
+		const terminalInfo = this.terminals.find((t) => t.id === id)
+		if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) {
+			this.removeTerminal(id)
+			return undefined
+		}
+		return terminalInfo
+	}
+
+	static updateTerminal(id: number, updates: Partial<TerminalInfo>) {
+		const terminal = this.getTerminal(id)
+		if (terminal) {
+			Object.assign(terminal, updates)
+		}
+	}
+
+	static removeTerminal(id: number) {
+		this.terminals = this.terminals.filter((t) => t.id !== id)
+	}
+
+	static getAllTerminals(): TerminalInfo[] {
+		this.terminals = this.terminals.filter((t) => !this.isTerminalClosed(t.terminal))
+		return this.terminals
+	}
+
+	// The exit status of the terminal will be undefined while the terminal is active. (This value is set when onDidCloseTerminal is fired.)
+	private static isTerminalClosed(terminal: vscode.Terminal): boolean {
+		return terminal.exitStatus !== undefined
+	}
+}
+
 export class TerminalManager {
 export class TerminalManager {
-	private terminals: TerminalInfo[] = []
+	private terminalIds: Set<number> = new Set()
 	private processes: Map<number, TerminalProcess> = new Map()
 	private processes: Map<number, TerminalProcess> = new Map()
-	private nextTerminalId = 1
 
 
-	runCommand(terminalInfo: TerminalInfo, command: string, cwd: string): TerminalProcessResultPromise {
+	runCommand(terminalInfo: TerminalInfo, command: string): TerminalProcessResultPromise {
 		terminalInfo.busy = true
 		terminalInfo.busy = true
 		terminalInfo.lastCommand = command
 		terminalInfo.lastCommand = command
 		const process = new TerminalProcess()
 		const process = new TerminalProcess()
@@ -75,6 +128,16 @@ export class TerminalManager {
 			terminalInfo.busy = false
 			terminalInfo.busy = false
 		})
 		})
 
 
+		// 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)
+			this.processes.delete(terminalInfo.id)
+			console.log(`Removed terminal ${terminalInfo.id} from TerminalManager`)
+		})
+
 		const promise = new Promise<void>((resolve, reject) => {
 		const promise = new Promise<void>((resolve, reject) => {
 			process.once("continue", () => {
 			process.once("continue", () => {
 				console.log(`continue received for terminal ${terminalInfo.id}`)
 				console.log(`continue received for terminal ${terminalInfo.id}`)
@@ -113,11 +176,9 @@ export class TerminalManager {
 	}
 	}
 
 
 	async getOrCreateTerminal(cwd: string): Promise<TerminalInfo> {
 	async getOrCreateTerminal(cwd: string): Promise<TerminalInfo> {
-		const availableTerminal = this.terminals.find((t) => {
-			// it seems even if you close the terminal, it can still be reused
-			const isDisposed = !t.terminal || t.terminal.exitStatus // The exit status of the terminal will be undefined while the terminal is active.
-			console.log(`Terminal ${t.id} isDisposed:`, isDisposed)
-			if (t.busy || isDisposed) {
+		// Find available terminal from our pool first (created for this task)
+		const availableTerminal = TerminalRegistry.getAllTerminals().find((t) => {
+			if (t.busy) {
 				return false
 				return false
 			}
 			}
 			const terminalCwd = t.terminal.shellIntegration?.cwd // one of claude's commands could have changed the cwd of the terminal
 			const terminalCwd = t.terminal.shellIntegration?.cwd // one of claude's commands could have changed the cwd of the terminal
@@ -128,41 +189,36 @@ export class TerminalManager {
 		})
 		})
 		if (availableTerminal) {
 		if (availableTerminal) {
 			console.log("Reusing terminal", availableTerminal.id)
 			console.log("Reusing terminal", availableTerminal.id)
+			this.terminalIds.add(availableTerminal.id)
 			return availableTerminal
 			return availableTerminal
 		}
 		}
 
 
-		const newTerminal = vscode.window.createTerminal({
-			name: "Claude Dev",
-			cwd: cwd,
-			iconPath: new vscode.ThemeIcon("robot"),
-		})
-		const newTerminalInfo: TerminalInfo = {
-			terminal: newTerminal,
-			busy: false,
-			lastCommand: "",
-			id: this.nextTerminalId++,
-		}
-		this.terminals.push(newTerminalInfo)
+		const newTerminalInfo = TerminalRegistry.createTerminal(cwd)
+		this.terminalIds.add(newTerminalInfo.id)
+		console.log("Created new terminal", newTerminalInfo.id)
 		return newTerminalInfo
 		return newTerminalInfo
 	}
 	}
 
 
 	getBusyTerminals(): { id: number; lastCommand: string }[] {
 	getBusyTerminals(): { id: number; lastCommand: string }[] {
-		return this.terminals.filter((t) => t.busy).map((t) => ({ id: t.id, lastCommand: t.lastCommand }))
+		return Array.from(this.terminalIds)
+			.map((id) => TerminalRegistry.getTerminal(id))
+			.filter((t): t is TerminalInfo => t !== undefined && t.busy)
+			.map((t) => ({ id: t.id, lastCommand: t.lastCommand }))
 	}
 	}
 
 
 	getUnretrievedOutput(terminalId: number): string {
 	getUnretrievedOutput(terminalId: number): string {
-		const process = this.processes.get(terminalId)
-		if (!process) {
+		if (!this.terminalIds.has(terminalId)) {
 			return ""
 			return ""
 		}
 		}
-		return process.getUnretrievedOutput()
+		const process = this.processes.get(terminalId)
+		return process ? process.getUnretrievedOutput() : ""
 	}
 	}
 
 
 	disposeAll() {
 	disposeAll() {
 		// for (const info of this.terminals) {
 		// for (const info of this.terminals) {
 		// 	//info.terminal.dispose() // dont want to dispose terminals when task is aborted
 		// 	//info.terminal.dispose() // dont want to dispose terminals when task is aborted
 		// }
 		// }
-		this.terminals = []
+		this.terminalIds.clear()
 		this.processes.clear()
 		this.processes.clear()
 	}
 	}
 }
 }
@@ -179,6 +235,7 @@ interface TerminalProcessEvents {
 	continue: []
 	continue: []
 	completed: []
 	completed: []
 	error: [error: Error]
 	error: [error: Error]
+	no_shell_integration: []
 }
 }
 
 
 export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
 export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
@@ -192,12 +249,33 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
 	// 	super()
 	// 	super()
 
 
 	async run(terminal: vscode.Terminal, command: string) {
 	async run(terminal: vscode.Terminal, command: string) {
-		if (terminal.shellIntegration) {
+		if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
 			console.log(`Shell integration available for terminal`)
 			console.log(`Shell integration available for terminal`)
 			const execution = terminal.shellIntegration.executeCommand(command)
 			const execution = terminal.shellIntegration.executeCommand(command)
 			const stream = execution.read()
 			const stream = execution.read()
 			// todo: need to handle errors
 			// todo: need to handle errors
-			for await (const data of stream) {
+			let isFirstChunk = true
+			for await (let data of stream) {
+				// if (isFirstChunk) {
+				// 	// https://code.visualstudio.com/docs/terminal/shell-integration#_vs-code-custom-sequences-osc-633-st
+				// 	const vscodeSequenceRegex = /\x1b\]633;.[^\x07]*\x07/g
+				// 	data = stripAnsi(data.replace(vscodeSequenceRegex, ""))
+				// 	// Split data by newlines
+				// 	let lines = data.split("\n")
+				// 	// Remove the first line
+				// 	// if (lines.length > 0) {
+				// 	// 	lines.shift()
+				// 	// }
+				// 	// Process second line: remove everything up to the first alphanumeric character
+				// 	if (lines.length > 0) {
+				// 		lines[0] = lines[0].replace(/^[^a-zA-Z0-9]*/, "")
+				// 	}
+				// 	// Join lines back
+				// 	data = lines.join("\n")
+				// 	isFirstChunk = false
+				// } else {
+				// 	data = stripAnsi(data)
+				// }
 				console.log(`Received data chunk for terminal:`, data)
 				console.log(`Received data chunk for terminal:`, data)
 				this.fullOutput += data
 				this.fullOutput += data
 				if (this.isListening) {
 				if (this.isListening) {
@@ -209,25 +287,30 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
 
 
 			// Emit any remaining content in the buffer
 			// Emit any remaining content in the buffer
 			if (this.buffer && this.isListening) {
 			if (this.buffer && this.isListening) {
-				console.log(`Emitting remaining buffer for terminal:`, this.buffer.trim())
-				this.emit("line", this.buffer.trim())
+				const remainingBuffer = this.buffer.trim()
+				if (remainingBuffer !== "%") {
+					// for some reason vscode likes to end stream with %
+					console.log(`Emitting remaining buffer for terminal:`, remainingBuffer)
+					this.emit("line", remainingBuffer)
+				}
 				this.buffer = ""
 				this.buffer = ""
 				this.lastRetrievedIndex = this.fullOutput.length
 				this.lastRetrievedIndex = this.fullOutput.length
 			}
 			}
-
 			console.log(`Command execution completed for terminal`)
 			console.log(`Command execution completed for terminal`)
-			this.emit("continue")
 			this.emit("completed")
 			this.emit("completed")
+			this.emit("continue")
 		} else {
 		} else {
 			console.log(`Shell integration not available for terminal, falling back to sendText`)
 			console.log(`Shell integration not available for terminal, falling back to sendText`)
 			terminal.sendText(command, true)
 			terminal.sendText(command, true)
 			// For terminals without shell integration, we can't know when the command completes
 			// For terminals without shell integration, we can't know when the command completes
 			// So we'll just emit the continue event after a delay
 			// So we'll just emit the continue event after a delay
-			setTimeout(() => {
-				console.log(`Emitting continue after delay for terminal`)
-				this.emit("continue")
-				// can't emit completed since we don't if the command actually completed, it could still be running server
-			}, 2000) // Adjust this delay as needed
+			this.emit("completed")
+			this.emit("continue")
+			this.emit("no_shell_integration")
+			// setTimeout(() => {
+			// 	console.log(`Emitting continue after delay for terminal`)
+			// 	// can't emit completed since we don't if the command actually completed, it could still be running server
+			// }, 500) // Adjust this delay as needed
 		}
 		}
 	}
 	}
 
 

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -56,6 +56,7 @@ export type ClaudeSay =
 	| "api_req_retried"
 	| "api_req_retried"
 	| "command_output"
 	| "command_output"
 	| "tool"
 	| "tool"
+	| "shell_integration_warning"
 
 
 export interface ClaudeSayTool {
 export interface ClaudeSayTool {
 	tool:
 	tool:

+ 12 - 0
src/utils/get-python-env.ts

@@ -1,5 +1,17 @@
 import * as vscode from "vscode"
 import * as vscode from "vscode"
 
 
+/*
+Used to get user's current python environment (unnecessary now that we use the IDE's terminal)
+${await (async () => {
+		try {
+			const pythonEnvPath = await getPythonEnvPath()
+			if (pythonEnvPath) {
+				return `\nPython Environment: ${pythonEnvPath}`
+			}
+		} catch {}
+		return ""
+	})()}
+*/
 export async function getPythonEnvPath(): Promise<string | undefined> {
 export async function getPythonEnvPath(): Promise<string | undefined> {
 	const pythonExtension = vscode.extensions.getExtension("ms-python.python")
 	const pythonExtension = vscode.extensions.getExtension("ms-python.python")
 
 

+ 28 - 4
webview-ui/src/components/ChatRow.tsx

@@ -438,6 +438,34 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
 							</div>
 							</div>
 						</>
 						</>
 					)
 					)
+				case "shell_integration_warning":
+					return (
+						<>
+							<div
+								style={{
+									display: "flex",
+									alignItems: "center",
+									backgroundColor: "rgba(255, 191, 0, 0.1)",
+									padding: 8,
+									borderRadius: 3,
+									fontSize: 12,
+								}}>
+								<i
+									className="codicon codicon-warning"
+									style={{
+										marginRight: 8,
+										fontSize: 18,
+										color: "#FFA500",
+									}}></i>
+								<span>
+									Shell integration is not available! Claude will not be able to see the output of the
+									command. Please update to the latest version of VSCode (
+									{"CMD/CTRL + Shift + P → Update"}) and ensure you are using one of the following
+									shells: bash, zsh, fish, or PowerShell.
+								</span>
+							</div>
+						</>
+					)
 
 
 				default:
 				default:
 					return (
 					return (
@@ -480,10 +508,6 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
 								.split("")
 								.split("")
 								.map((char) => {
 								.map((char) => {
 									switch (char) {
 									switch (char) {
-										case "\n":
-											return "↵\n"
-										case "\r":
-											return "⏎"
 										case "\t":
 										case "\t":
 											return "→   "
 											return "→   "
 										case "\b":
 										case "\b":