mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
Add shell integration warning; reuse terminals when possible
This commit is contained in:
193
package-lock.json
generated
193
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"serialize-error": "^11.0.3",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tree-sitter-wasms": "^0.1.11",
|
||||
"web-tree-sitter": "^0.22.6"
|
||||
},
|
||||
@@ -2714,35 +2715,6 @@
|
||||
"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": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
|
||||
@@ -5367,6 +5339,19 @@
|
||||
"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": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
@@ -6051,6 +6036,19 @@
|
||||
"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": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
|
||||
@@ -7828,6 +7826,19 @@
|
||||
"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": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
@@ -8381,19 +8392,6 @@
|
||||
"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": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||
@@ -8462,22 +8460,6 @@
|
||||
"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": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/os-name/-/os-name-6.0.0.tgz",
|
||||
@@ -9349,33 +9331,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width/node_modules/ansi-regex": {
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"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,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.padend": {
|
||||
@@ -9450,16 +9416,18 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
@@ -9476,6 +9444,18 @@
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
@@ -10127,17 +10107,17 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-regex": {
|
||||
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
|
||||
"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,
|
||||
"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": {
|
||||
@@ -10153,22 +10133,6 @@
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@@ -10262,6 +10226,19 @@
|
||||
"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": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"serialize-error": "^11.0.3",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tree-sitter-wasms": "^0.1.11",
|
||||
"web-tree-sitter": "^0.22.6"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as path from "path"
|
||||
import { serializeError } from "serialize-error"
|
||||
import * as vscode from "vscode"
|
||||
import { ApiHandler, buildApiHandler } from "./api"
|
||||
import { TerminalManager } from "./integrations/TerminalManager"
|
||||
import { LIST_FILES_LIMIT, listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
|
||||
import { ClaudeDevProvider } from "./providers/ClaudeDevProvider"
|
||||
import { ApiConfiguration } from "./shared/api"
|
||||
@@ -23,10 +24,8 @@ import { Tool, ToolName } from "./shared/Tool"
|
||||
import { ClaudeAskResponse } from "./shared/WebviewMessage"
|
||||
import { findLast, findLastIndex, formatContentBlockToMarkdown } from "./utils"
|
||||
import { truncateHalfConversation } from "./utils/context-management"
|
||||
import { regexSearchFiles } from "./utils/ripgrep"
|
||||
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 =
|
||||
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
|
||||
|
||||
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()}
|
||||
Current Working Directory: ${cwd}
|
||||
`
|
||||
@@ -1361,7 +1352,7 @@ export class ClaudeDev {
|
||||
try {
|
||||
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.
|
||||
const process = this.terminalManager.runCommand(terminalInfo, command, cwd)
|
||||
const process = this.terminalManager.runCommand(terminalInfo, command)
|
||||
|
||||
let userFeedback: { text?: string; images?: string[] } | undefined
|
||||
const sendCommandOutput = async (line: string): Promise<void> => {
|
||||
@@ -1386,10 +1377,14 @@ export class ClaudeDev {
|
||||
})
|
||||
|
||||
let completed = false
|
||||
process.on("completed", () => {
|
||||
process.once("completed", () => {
|
||||
completed = true
|
||||
})
|
||||
|
||||
process.once("no_shell_integration", async () => {
|
||||
await this.say("shell_integration_warning")
|
||||
})
|
||||
|
||||
await process
|
||||
|
||||
// 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 files = await listFiles(cwd, !isDesktop)
|
||||
const result = this.formatFilesList(cwd, files)
|
||||
details += `\n# Current Working Directory ('${cwd}') File Structure:${
|
||||
details += `\n# Current Working Directory ('${cwd}') Files${
|
||||
isDesktop
|
||||
? "\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() {
|
||||
let details = `<potentially_relevant_details>
|
||||
# VSCode Visible Files:
|
||||
# VSCode Visible Files
|
||||
${
|
||||
vscode.window.visibleTextEditors
|
||||
?.map((editor) => editor.document?.uri?.fsPath)
|
||||
@@ -1750,7 +1745,7 @@ ${
|
||||
.join("\n") || "(No files open)"
|
||||
}
|
||||
|
||||
# VSCode Opened Tabs:
|
||||
# VSCode Open Tabs
|
||||
${
|
||||
vscode.window.tabGroups.all
|
||||
.flatMap((group) => group.tabs)
|
||||
@@ -1770,7 +1765,7 @@ ${
|
||||
)
|
||||
|
||||
if (relevantDiagnostics.length > 0) {
|
||||
details += "\n\n# VSCode Workspace Diagnostics:"
|
||||
details += "\n\n# VSCode Workspace Diagnostics"
|
||||
for (const [uri, fileDiagnostics] of relevantDiagnostics) {
|
||||
const relativePath = path.relative(cwd, uri.fsPath)
|
||||
details += `\n## ${relativePath}:`
|
||||
@@ -1789,12 +1784,14 @@ ${
|
||||
|
||||
const busyTerminals = this.terminalManager.getBusyTerminals()
|
||||
if (busyTerminals.length > 0) {
|
||||
details += "\n\n# Active Terminals:"
|
||||
details += "\n\n# Active Terminals"
|
||||
for (const busyTerminal of busyTerminals) {
|
||||
details += `\n## Original command:\n${busyTerminal.lastCommand}`
|
||||
details += `\n## $ ${busyTerminal.lastCommand}`
|
||||
const newOutput = this.terminalManager.getUnretrievedOutput(busyTerminal.id)
|
||||
if (newOutput) {
|
||||
details += `\n## New output since last check:\n${newOutput}`
|
||||
details += `\n...\n${newOutput}`
|
||||
} else {
|
||||
details += `\n(Still running, no new output)`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode"
|
||||
import { EventEmitter } from "events"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import stripAnsi from "strip-ansi"
|
||||
|
||||
/*
|
||||
TerminalManager:
|
||||
@@ -59,12 +60,64 @@ Resources:
|
||||
- https://github.com/microsoft/vscode-extension-samples/blob/main/shell-integration-sample/src/extension.ts
|
||||
*/
|
||||
|
||||
export class TerminalManager {
|
||||
private terminals: TerminalInfo[] = []
|
||||
private processes: Map<number, TerminalProcess> = new Map()
|
||||
private nextTerminalId = 1
|
||||
// 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
|
||||
|
||||
runCommand(terminalInfo: TerminalInfo, command: string, cwd: string): TerminalProcessResultPromise {
|
||||
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 {
|
||||
private terminalIds: Set<number> = new Set()
|
||||
private processes: Map<number, TerminalProcess> = new Map()
|
||||
|
||||
runCommand(terminalInfo: TerminalInfo, command: string): TerminalProcessResultPromise {
|
||||
terminalInfo.busy = true
|
||||
terminalInfo.lastCommand = command
|
||||
const process = new TerminalProcess()
|
||||
@@ -75,6 +128,16 @@ export class TerminalManager {
|
||||
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) => {
|
||||
process.once("continue", () => {
|
||||
console.log(`continue received for terminal ${terminalInfo.id}`)
|
||||
@@ -113,11 +176,9 @@ export class TerminalManager {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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) {
|
||||
console.log("Reusing terminal", availableTerminal.id)
|
||||
this.terminalIds.add(availableTerminal.id)
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
const process = this.processes.get(terminalId)
|
||||
if (!process) {
|
||||
if (!this.terminalIds.has(terminalId)) {
|
||||
return ""
|
||||
}
|
||||
return process.getUnretrievedOutput()
|
||||
const process = this.processes.get(terminalId)
|
||||
return process ? process.getUnretrievedOutput() : ""
|
||||
}
|
||||
|
||||
disposeAll() {
|
||||
// for (const info of this.terminals) {
|
||||
// //info.terminal.dispose() // dont want to dispose terminals when task is aborted
|
||||
// }
|
||||
this.terminals = []
|
||||
this.terminalIds.clear()
|
||||
this.processes.clear()
|
||||
}
|
||||
}
|
||||
@@ -179,6 +235,7 @@ interface TerminalProcessEvents {
|
||||
continue: []
|
||||
completed: []
|
||||
error: [error: Error]
|
||||
no_shell_integration: []
|
||||
}
|
||||
|
||||
export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
@@ -192,12 +249,33 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
// super()
|
||||
|
||||
async run(terminal: vscode.Terminal, command: string) {
|
||||
if (terminal.shellIntegration) {
|
||||
if (terminal.shellIntegration && terminal.shellIntegration.executeCommand) {
|
||||
console.log(`Shell integration available for terminal`)
|
||||
const execution = terminal.shellIntegration.executeCommand(command)
|
||||
const stream = execution.read()
|
||||
// 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)
|
||||
this.fullOutput += data
|
||||
if (this.isListening) {
|
||||
@@ -209,25 +287,30 @@ export class TerminalProcess extends EventEmitter<TerminalProcessEvents> {
|
||||
|
||||
// Emit any remaining content in the buffer
|
||||
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.lastRetrievedIndex = this.fullOutput.length
|
||||
}
|
||||
|
||||
console.log(`Command execution completed for terminal`)
|
||||
this.emit("continue")
|
||||
this.emit("completed")
|
||||
this.emit("continue")
|
||||
} else {
|
||||
console.log(`Shell integration not available for terminal, falling back to sendText`)
|
||||
terminal.sendText(command, true)
|
||||
// For terminals without shell integration, we can't know when the command completes
|
||||
// So we'll just emit the continue event after a delay
|
||||
setTimeout(() => {
|
||||
console.log(`Emitting continue after delay for terminal`)
|
||||
this.emit("completed")
|
||||
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("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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export type ClaudeSay =
|
||||
| "api_req_retried"
|
||||
| "command_output"
|
||||
| "tool"
|
||||
| "shell_integration_warning"
|
||||
|
||||
export interface ClaudeSayTool {
|
||||
tool:
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
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> {
|
||||
const pythonExtension = vscode.extensions.getExtension("ms-python.python")
|
||||
|
||||
|
||||
@@ -438,6 +438,34 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
|
||||
</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:
|
||||
return (
|
||||
@@ -480,10 +508,6 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
|
||||
.split("")
|
||||
.map((char) => {
|
||||
switch (char) {
|
||||
case "\n":
|
||||
return "↵\n"
|
||||
case "\r":
|
||||
return "⏎"
|
||||
case "\t":
|
||||
return "→ "
|
||||
case "\b":
|
||||
|
||||
Reference in New Issue
Block a user