diff --git a/HOW_TO_CONTRIBUTE.md b/HOW_TO_CONTRIBUTE.md index 15323527..693ddd5c 100644 --- a/HOW_TO_CONTRIBUTE.md +++ b/HOW_TO_CONTRIBUTE.md @@ -23,11 +23,11 @@ Most of Void's code lives in the folder `src/vs/workbench/contrib/void/`. ## Building Void -### a. Build Prerequisites - Mac +### a. Mac - Build Prerequisites If you're using a Mac, you need Python and XCode. You probably have these by default. -### b. Build Prerequisites - Windows +### b. Windows - Build Prerequisites If you're using a Windows computer, first get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) (recommended) or [VS Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) (not recommended). If you already have both, you might need to run the next few steps on both of them. @@ -42,7 +42,7 @@ Go to the "Individual Components" tab and select: Finally, click Install. -### c. Build Prerequisites - Linux +### c. Linux - Build Prerequisites First, run `npm install -g node-gyp`. Then: @@ -50,27 +50,28 @@ First, run `npm install -g node-gyp`. Then: - Red Hat (Fedora, etc): `sudo dnf install @development-tools gcc gcc-c++ make libsecret-devel krb5-devel libX11-devel libxkbfile-devel`. - Others: see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute). -### d. Building Void +### d. Building Void from inside VSCode To build Void, open `void/` inside VSCode. Then open your terminal and run: 1. `npm install` to install all dependencies. -2. `npm run watchreact` to build Void's browser dependencies like React. (If this doesn't work, try `npm run buildreact`). -3. Build Void. +2. Build Void. - Press Cmd+Shift+B (Mac). - Press Ctrl+Shift+B (Windows/Linux). - This step can take ~5 min. The build is done when you see two check marks (one of the items will continue spinning indefinitely - it compiles our React code). -4. Run Void. +3. Run Void. - Run `./scripts/code.sh` (Mac/Linux). - Run `./scripts/code.bat` (Windows). -6. Nice-to-knows. +4. Nice-to-knows. - You can always press Ctrl+R (Cmd+R) inside the new window to reload and see your new changes. It's faster than Ctrl+Shift+P and `Reload Window`. - You might want to add the flags `--user-data-dir ./.tmp/user-data --extensions-dir ./.tmp/extensions` to the above run command, which lets you delete the `.tmp` folder to reset any IDE changes you made when testing. - You can kill any of the build scripts by pressing `Ctrl+D` in VSCode terminal. If you press `Ctrl+C` the script will close but will keep running in the background (to open all background scripts, just re-build). +If you get any errors, scroll down for common fixes. + #### Building Void from Terminal -Alternatively, if you want to build Void from the terminal, instead of pressing Cmd+Shift+B you can run `npm run watch`. The build is done when you see something like this: +To build Void from the terminal instead of from inside VSCode, follow the steps above, but instead of pressing Cmd+Shift+B, run `npm run watch`. The build is done when you see something like this: ``` [watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms @@ -80,15 +81,17 @@ Alternatively, if you want to build Void from the terminal, instead of pressing ``` - #### Common Fixes -- Make sure you followed the prerequisite steps. +- Make sure you followed the prerequisite steps above. - Make sure you have Node version `20.18.2` (the version in `.nvmrc`)! - If you get `"TypeError: Failed to fetch dynamically imported module"`, make sure all imports end with `.js`. +- If you get an error with React, try running `NODE_OPTIONS="--max-old-space-size=8192" npm run buildreact`. - If you see missing styles, wait a few seconds and then reload. -- If you have any questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page. -- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos) +- If you get errors like `npm error libtool: error: unrecognised option: '-static'`, when running ./scripts/code.sh, make sure you have GNU libtool instead of BSD libtool (BSD is the default in macos) +- If you get erorrs like `The SUID sandbox helper binary was found, but is not configured correctly` when running ./scripts/code.sh, run +`sudo chown root:root .build/electron/chrome-sandbox && sudo chmod 4755 .build/electron/chrome-sandbox` and then run `./scripts/code.sh` again. +- If you have any other questions, feel free to [submit an issue](https://github.com/voideditor/void/issues/new). You can also refer to VSCode's complete [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page. ## Packaging diff --git a/package-lock.json b/package-lock.json index cc4edf47..330e6147 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "@xterm/addon-webgl": "^0.19.0-beta.98", "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", + "ajv": "^8.17.1", "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.4", @@ -1686,6 +1687,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "9.25.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz", @@ -5477,16 +5502,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -5511,30 +5535,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -9272,6 +9272,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -9285,6 +9302,13 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/esniff": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", @@ -9743,7 +9767,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -9788,7 +9811,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, "funding": [ { "type": "github", @@ -9898,6 +9920,23 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/file-loader/node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -9908,6 +9947,13 @@ "ajv": "^6.9.1" } }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/file-loader/node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -14925,10 +14971,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -20618,7 +20663,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -21109,30 +21153,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/scope-tailwind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/scope-tailwind/-/scope-tailwind-1.0.9.tgz", diff --git a/package.json b/package.json index de9b869e..23fd68a8 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "@xterm/addon-webgl": "^0.19.0-beta.98", "@xterm/headless": "^5.6.0-beta.98", "@xterm/xterm": "^5.6.0-beta.98", + "ajv": "^8.17.1", "cross-spawn": "^7.0.6", "diff": "^7.0.0", "eslint-plugin-react": "^7.37.4", diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx index 36ac3e53..e3fadf98 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ChatMarkdownRender.tsx @@ -5,9 +5,6 @@ import React, { JSX, useMemo, useState } from 'react' import { marked, MarkedToken, Token } from 'marked' -import katex from 'katex' -import 'katex/dist/katex.min.css' -import dompurify from '../../../../../../../base/browser/dompurify/dompurify.js' import { convertToVscodeLang, detectLanguage } from '../../../../common/helpers/languageHelpers.js' import { BlockCodeApplyWrapper } from './ApplyBlockHoverButtons.js' @@ -36,59 +33,59 @@ function isValidUri(s: string): boolean { // renders contiguous string of latex eg $e^{i\pi}$ const LatexRender = ({ latex }: { latex: string }) => { + return {latex} + // try { + // let formula = latex; + // let displayMode = false; - try { - let formula = latex; - let displayMode = false; + // // Extract the formula from delimiters + // if (latex.startsWith('$') && latex.endsWith('$')) { + // // Check if it's display math $$...$$ + // if (latex.startsWith('$$') && latex.endsWith('$$')) { + // formula = latex.slice(2, -2); + // displayMode = true; + // } else { + // formula = latex.slice(1, -1); + // } + // } else if (latex.startsWith('\\(') && latex.endsWith('\\)')) { + // formula = latex.slice(2, -2); + // } else if (latex.startsWith('\\[') && latex.endsWith('\\]')) { + // formula = latex.slice(2, -2); + // displayMode = true; + // } - // Extract the formula from delimiters - if (latex.startsWith('$') && latex.endsWith('$')) { - // Check if it's display math $$...$$ - if (latex.startsWith('$$') && latex.endsWith('$$')) { - formula = latex.slice(2, -2); - displayMode = true; - } else { - formula = latex.slice(1, -1); - } - } else if (latex.startsWith('\\(') && latex.endsWith('\\)')) { - formula = latex.slice(2, -2); - } else if (latex.startsWith('\\[') && latex.endsWith('\\]')) { - formula = latex.slice(2, -2); - displayMode = true; - } + // // Render LaTeX + // const html = katex.renderToString(formula, { + // displayMode: displayMode, + // throwOnError: false, + // output: 'html' + // }); - // Render LaTeX - const html = katex.renderToString(formula, { - displayMode: displayMode, - throwOnError: false, - output: 'html' - }); + // // Sanitize the HTML output with DOMPurify + // const sanitizedHtml = dompurify.sanitize(html, { + // RETURN_TRUSTED_TYPE: true, + // USE_PROFILES: { html: true, svg: true, mathMl: true } + // }); - // Sanitize the HTML output with DOMPurify - const sanitizedHtml = dompurify.sanitize(html, { - RETURN_TRUSTED_TYPE: true, - USE_PROFILES: { html: true, svg: true, mathMl: true } - }); + // // Add proper styling based on mode + // const className = displayMode + // ? 'katex-block my-2 text-center' + // : 'katex-inline'; - // Add proper styling based on mode - const className = displayMode - ? 'katex-block my-2 text-center' - : 'katex-inline'; + // // Use the ref approach to avoid dangerouslySetInnerHTML + // const mathRef = React.useRef(null); - // Use the ref approach to avoid dangerouslySetInnerHTML - const mathRef = React.useRef(null); + // React.useEffect(() => { + // if (mathRef.current) { + // mathRef.current.innerHTML = sanitizedHtml as unknown as string; + // } + // }, [sanitizedHtml]); - React.useEffect(() => { - if (mathRef.current) { - mathRef.current.innerHTML = sanitizedHtml as unknown as string; - } - }, [sanitizedHtml]); - - return ; - } catch (error) { - console.error('KaTeX rendering error:', error); - return {latex}; - } + // return ; + // } catch (error) { + // console.error('KaTeX rendering error:', error); + // return {latex}; + // } } const Codespan = ({ text, className, onClick }: { text: string, className?: string, onClick?: () => void }) => { diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index b85f8d76..e71ef004 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -30,6 +30,7 @@ import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames } import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js'; import ErrorBoundary from './ErrorBoundary.js'; import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js'; +import { terminalNameOfId } from '../../../terminalToolService.js'; @@ -696,6 +697,7 @@ type ToolHeaderParams = { children?: React.ReactNode; bottomChildren?: React.ReactNode; onClick?: () => void; + desc2OnClick?: () => void; isOpen?: boolean; className?: string; } @@ -713,6 +715,7 @@ const ToolHeaderWrapper = ({ bottomChildren, isError, onClick, + desc2OnClick, isOpen, isRejected, className, // applies to the main content @@ -781,7 +784,7 @@ const ToolHeaderWrapper = ({ data-tooltip-content={'Canceled'} data-tooltip-place='top' />} - {desc2 && + {desc2 && {desc2} } {numResults !== undefined && ( @@ -1416,7 +1419,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName const toolParams = _toolParams as ToolCallParams['run_command'] return { desc1: `"${toolParams.command}"`, - desc1Info: toolParams.bgTerminalId + desc1Info: toolParams.persistentTerminalId } }, 'open_persistent_terminal': () => { @@ -1425,7 +1428,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName }, 'kill_persistent_terminal': () => { const toolParams = _toolParams as ToolCallParams['kill_persistent_terminal'] - return { desc1: toolParams.terminalId } + return { desc1: toolParams.persistentTerminalId } }, 'get_dir_tree': () => { const toolParams = _toolParams as ToolCallParams['get_dir_tree'] @@ -2081,6 +2084,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, const accessor = useAccessor() const commandService = accessor.get('ICommandService') const terminalToolsService = accessor.get('ITerminalToolService') + const toolsService = accessor.get('IToolsService') const isError = toolMessage.type === 'tool_error' const title = getTitle(toolMessage) const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) @@ -2090,19 +2094,21 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, const { rawParams, params } = toolMessage const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, } + const { command, persistentTerminalId } = params + if (persistentTerminalId) { + componentParams.desc2 = terminalNameOfId(persistentTerminalId) + componentParams.desc2OnClick = () => terminalToolsService.focusTerminal(persistentTerminalId) + } + + if (toolMessage.type === 'success') { const { result } = toolMessage - const { command } = params - const { resolveReason, result: terminalResult } = result // it's unclear that this is a button and not an icon. // componentParams.desc2 = { terminalToolsService.openTerminal(terminalId) }} // /> - - const additionalDetailsStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null) - : resolveReason.type === 'timeout' ? `\n(timed out)` - : null + const msg = toolsService.stringOfResult['run_command'](params, result) componentParams.children =
@@ -2110,25 +2116,14 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, {`Ran command: `} {command}
- {(terminalResult + additionalDetailsStr).length &&
+ {msg.length &&
{`Result: `} - {terminalResult} - {additionalDetailsStr} + {msg}
}
- - if (params.bgTerminalId) - componentParams.desc2 = `(terminal ${params.bgTerminalId})` - } else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_error' || toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') { - const { bgTerminalId, command } = params - if (bgTerminalId) { - componentParams.desc2 = '(persistent terminal)' - if (terminalToolsService.terminalExists(bgTerminalId)) - componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId) - } if (toolMessage.type === 'tool_error') { const { result } = toolMessage componentParams.children = {result} @@ -2158,12 +2153,9 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, if (toolMessage.type === 'success') { const { result } = toolMessage - const { terminalId } = result - if (terminalId) { - componentParams.desc2 = `(terminal ${terminalId})` - if (terminalToolsService.terminalExists(terminalId)) - componentParams.onClick = () => terminalToolsService.focusTerminal(terminalId) - } + const { persistentTerminalId } = result + componentParams.desc1 = terminalNameOfId(persistentTerminalId) + componentParams.onClick = () => terminalToolsService.focusTerminal(persistentTerminalId) } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage @@ -2181,6 +2173,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, resultWrapper: ({ toolMessage }) => { const accessor = useAccessor() const commandService = accessor.get('ICommandService') + const terminalToolsService = accessor.get('ITerminalToolService') const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor) const title = getTitle(toolMessage) @@ -2195,7 +2188,9 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper, const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, } if (toolMessage.type === 'success') { - const { result } = toolMessage + const { persistentTerminalId } = params + componentParams.desc1 = terminalNameOfId(persistentTerminalId) + componentParams.onClick = () => terminalToolsService.focusTerminal(persistentTerminalId) } else if (toolMessage.type === 'tool_error') { const { result } = toolMessage diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index d9d1f5b4..9a01792f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -726,6 +726,7 @@ export const VoidInputBox2 = forwardRef(fun rows={1} placeholder={placeholder} /> + {/*
{`idx ${optionIdx}`}
*/} {isMenuOpen && (
{ {/* Slice of Void image */}
- + {!isLinux && }
diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index 96e9ec76..cc1b3e21 100644 --- a/src/vs/workbench/contrib/void/browser/terminalToolService.ts +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -9,7 +9,7 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { TerminalExitReason, TerminalLocation } from '../../../../platform/terminal/common/terminal.js'; import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js'; -import { MAX_TERMINAL_CHARS, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js'; +import { MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_CHARS, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js'; import { TerminalResolveReason } from '../common/toolsServiceTypes.js'; @@ -39,11 +39,11 @@ function isCommandComplete(output: string) { } -const nameOfId = (id: string) => { +export const terminalNameOfId = (id: string) => { if (id === '1') return 'Void Agent' return `Void Agent (${id})` } -const idOfName = (name: string) => { +export const idOfTerminalName = (name: string) => { if (name === 'Void Agent') return '1' const match = name.match(/Void Agent \((\d+)\)/) @@ -66,7 +66,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ const initializeTerminal = (terminal: ITerminalInstance) => { // when exit, remove const d = terminal.onExit(() => { - const terminalId = idOfName(terminal.title) + const terminalId = idOfTerminalName(terminal.title) if (terminalId !== null && (terminalId in this.terminalInstanceOfId)) delete this.terminalInstanceOfId[terminalId] d.dispose() }) @@ -75,7 +75,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ // initialize any terminals that are already open for (const terminal of terminalService.instances) { - const proposedTerminalId = idOfName(terminal.title) + const proposedTerminalId = idOfTerminalName(terminal.title) if (proposedTerminalId) this.terminalInstanceOfId[proposedTerminalId] = terminal initializeTerminal(terminal) @@ -111,7 +111,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ const terminalId = this.getValidNewTerminalId(); const terminal = await this.terminalService.createTerminal({ location: TerminalLocation.Panel, - config: { name: nameOfId(terminalId), title: nameOfId(terminalId) }, + config: { name: terminalNameOfId(terminalId), title: terminalNameOfId(terminalId) }, }) @@ -207,26 +207,34 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ // send the command here await terminal.sendText(command, true) - // inactivity-based timeout - const waitUntilInactive = new Promise(res => { - let globalTimeoutId: ReturnType; - const resetTimer = () => { - clearTimeout(globalTimeoutId); - globalTimeoutId = setTimeout(() => { - if (resolveReason) return - + const waitUntilInterrupt = isBG ? + // timeout after X seconds + new Promise((res) => { + setTimeout(() => { resolveReason = { type: 'timeout' }; - res(); - }, MAX_TERMINAL_INACTIVE_TIME * 1000); - }; + res() + }, MAX_TERMINAL_BG_COMMAND_TIME * 1000) + }) + // inactivity-based timeout + : new Promise(res => { + let globalTimeoutId: ReturnType; + const resetTimer = () => { + clearTimeout(globalTimeoutId); + globalTimeoutId = setTimeout(() => { + if (resolveReason) return - const dTimeout = terminal.onData(() => { resetTimer(); }); - disposables.push(dTimeout, toDisposable(() => clearTimeout(globalTimeoutId))); - resetTimer(); - }); + resolveReason = { type: 'timeout' }; + res(); + }, MAX_TERMINAL_INACTIVE_TIME * 1000); + }; + + const dTimeout = terminal.onData(() => { resetTimer(); }); + disposables.push(dTimeout, toDisposable(() => clearTimeout(globalTimeoutId))); + resetTimer(); + }) // wait for result - await Promise.any([waitUntilDone, waitUntilInactive,]) + await Promise.any([waitUntilDone, waitUntilInterrupt]) disposables.forEach(d => d.dispose()) if (!isBG) { diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index 8c77a001..fa4b6787 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -16,7 +16,7 @@ import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js' import { timeout } from '../../../../base/common/async.js' import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js' -import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js' +import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js' import { IVoidSettingsService } from '../common/voidSettingsService.js' @@ -93,7 +93,7 @@ const validateRecursiveParamStr = (paramsUnknown: unknown) => { } const validateProposedTerminalId = (terminalIdUnknown: unknown) => { - if (!terminalIdUnknown) return '1' + if (!terminalIdUnknown) throw new Error(`A value for terminalID must be specified, but the value was "${terminalIdUnknown}"`) const terminalId = terminalIdUnknown + '' return terminalId } @@ -259,19 +259,19 @@ export class ToolsService implements IToolsService { // --- run_command: (params: RawToolParamsObj) => { - const { command: commandUnknown, terminal_id: terminalIdUnknown } = params; + const { command: commandUnknown, persistent_terminal_id: terminalIdUnknown } = params; const command = validateStr('command', commandUnknown); - const proposedTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null; - return { command, bgTerminalId: proposedTerminalId }; + const persistentTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null; + return { command, persistentTerminalId }; }, open_persistent_terminal: (_params: RawToolParamsObj) => { // No parameters needed; will open a new background terminal return {}; }, kill_persistent_terminal: (params: RawToolParamsObj) => { - const { terminal_id: terminalIdUnknown } = params; - const terminalId = validateProposedTerminalId(terminalIdUnknown); - return { terminalId }; + const { persistent_terminal_id: terminalIdUnknown } = params; + const persistentTerminalId = validateProposedTerminalId(terminalIdUnknown); + return { persistentTerminalId }; }, } @@ -425,21 +425,20 @@ export class ToolsService implements IToolsService { return { result: lintErrorsPromise } }, // --- - run_command: async ({ command, bgTerminalId }) => { - const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, bgTerminalId) + run_command: async ({ command, persistentTerminalId }) => { + const { terminalId, resPromise } = await this.terminalToolService.runCommand(command, persistentTerminalId) const interruptTool = () => { this.terminalToolService.killTerminal(terminalId) } return { result: resPromise, interruptTool } }, open_persistent_terminal: async () => { - // Open a new background terminal without waiting for completion - const terminalId = await this.terminalToolService.createTerminal() - return { result: { terminalId } } + const persistentTerminalId = await this.terminalToolService.createTerminal() + return { result: { persistentTerminalId } } }, - kill_persistent_terminal: async ({ terminalId }) => { + kill_persistent_terminal: async ({ persistentTerminalId }) => { // Close the background terminal by sending exit - await this.terminalToolService.killTerminal(terminalId) + await this.terminalToolService.killTerminal(persistentTerminalId) return { result: {} } }, @@ -517,18 +516,18 @@ export class ToolsService implements IToolsService { resolveReason, result: result_, } = result - const { bgTerminalId } = params + const { persistentTerminalId } = params // success if (resolveReason.type === 'done') { - const desc = bgTerminalId ? ` in terminal ${bgTerminalId}` : '' + const desc = persistentTerminalId ? ` in terminal ${persistentTerminalId}` : '' return `Terminal command executed and finished${desc}. Result (exit code ${resolveReason.exitCode}):\n${result_}` } // bg command - if (bgTerminalId !== null) { + if (persistentTerminalId !== null) { if (resolveReason.type === 'timeout') { - return `Terminal command is running in the background in terminal ${bgTerminalId}. Here were the outputs after ${MAX_TERMINAL_INACTIVE_TIME} seconds:\n${result_}` + return `Terminal command is running in terminal ${persistentTerminalId}. Here are the current outputs (after ${MAX_TERMINAL_BG_COMMAND_TIME} seconds):\n${result_}` } } // normal command @@ -541,11 +540,11 @@ export class ToolsService implements IToolsService { throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`) }, open_persistent_terminal: (_params, result) => { - const { terminalId } = result; - return `Successfully created background terminal with ID ${terminalId}`; + const { persistentTerminalId } = result; + return `Successfully created persistent terminal. persistentTerminalId="${persistentTerminalId}"`; }, kill_persistent_terminal: (params, _result) => { - return `Successfully closed terminal ${params.terminalId}.`; + return `Successfully closed terminal "${params.persistentTerminalId}".`; }, } diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 3820c871..a69e87a1 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -27,6 +27,7 @@ export const MAX_CHILDREN_URIs_PAGE = 500 // terminal tool info export const MAX_TERMINAL_CHARS = 100_000 export const MAX_TERMINAL_INACTIVE_TIME = 8 // seconds +export const MAX_TERMINAL_BG_COMMAND_TIME = 5 // Maximum character limits for prefix and suffix context @@ -133,12 +134,12 @@ const changesExampleContent = `\ // {{change 3}} // ... existing code ...` -// const editToolDescriptionExample = `\ -// ${tripleTick[0]} -// ${changesExampleContent} -// ${tripleTick[1]}` +const editToolDescriptionExample = `\ +${tripleTick[0]} +${changesExampleContent} +${tripleTick[1]}` -const fileNameEditExample = `${tripleTick[0]}typescript +const chatSuggestionDiffExample = `${tripleTick[0]}typescript /Users/username/Dekstop/my_project/app.ts ${changesExampleContent} ${tripleTick[1]}` @@ -180,6 +181,13 @@ const paginationParam = { // [K in keyof T as SnakeCase>]: T[K] // }; +const applyToolDescription = (type: 'edit tool' | 'chat suggestion') => `\ +${type === 'edit tool' ? 'A' : 'a'} code diff describing the change to make to the file. \ +Your DIFF is the only context that will be given to another LLM to apply the change, so it must be accurate and complete. \ +Your DIFF MUST be wrapped in triple backticks. \ +NEVER re-write the whole file. Always bias towards writing as little as possible. \ +Use comments like "// ... existing code ..." to condense your writing. \ +Here's an example of a good output:\n${type === 'edit tool' ? editToolDescriptionExample : chatSuggestionDiffExample}` export const voidTools = { @@ -313,7 +321,7 @@ export const voidTools = { description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use edit_file instead. When working with git and other tools that open an editor (e.g. git diff), you should pipe to cat to get all results and not get stuck in vim.`, params: { command: { description: 'The terminal command to run.' }, - bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_persistent_terminal. Runs the command in the terminal with the specified ID.' }, + persistent_terminal_id: { description: 'Optional. Runs the command in the persistent terminal that you created with open_persistent_terminal.' }, }, }, @@ -324,8 +332,8 @@ export const voidTools = { }, kill_persistent_terminal: { name: 'kill_persistent_terminal', - description: `Closes a BG terminal with the given ID.`, - params: { terminal_id: { description: `The terminal ID to interrupt and close.` } } + description: `Interrupts and closes a persistent terminal that you opened with open_persistent_terminal.`, + params: { persistent_terminal_id: { description: `The ID of the persistent terminal.` } } } @@ -421,14 +429,14 @@ Please assist the user with their query.`) - ${os} -- Open workspaces: -${workspaceFolders.join('\n') || 'NO WORKSPACE OPEN'} +- The user's workspace contains these folders: +${workspaceFolders.join('\n') || 'NO FOLDERS OPEN'} - Active file: ${activeURI} - Open files: -${openedURIs.join('\n') || 'NO OPENED EDITORS'}${''/* separator */}${mode === 'agent' && persistentTerminalIDs.length !== 0 ? ` +${openedURIs.join('\n') || 'NO OPENED FILES'}${''/* separator */}${mode === 'agent' && persistentTerminalIDs.length !== 0 ? ` - Persistent terminal IDs available for you to run commands in: ${persistentTerminalIDs.join(', ')}` : ''} `) @@ -460,7 +468,7 @@ ${directoryStr} details.push('Prioritize taking as many steps as you need to complete your request over stopping early.') details.push(`You will OFTEN need to gather context before making a change. Do not immediately make a change unless you have ALL relevant context.`) details.push(`ALWAYS have maximal certainty in a change BEFORE you make it. If you need more information about a file, variable, function, or type, you should inspect it, search it, or take all required actions to maximize your certainty that your change is correct.`) - details.push(`NEVER modify a file outside the user's workspace(s) without permission from the user.`) + details.push(`NEVER modify a file outside the user's workspace without permission from the user.`) } if (mode === 'gather') { @@ -475,12 +483,8 @@ ${directoryStr} - The remaining contents of the file should proceed as usual.`) details.push(`If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S). -- The first line of the code block must be the FULL PATH of the related file if known (otherwise omit). -- The remaining contents should be \ -a brief code description of the change you want to make, with comments like "// ... existing code ..." to condense your writing. \ -NEVER re-write the whole file. Instead, use comments like "// ... existing code ...". Bias towards writing as little as possible. \ -Here's an example of a good edit suggestion: -${fileNameEditExample}.`) +- The first line of the code block must be the FULL PATH of the related file. +- The remaining contents should be ${applyToolDescription('chat suggestion')}`) } details.push(`NEVER write the FULL PATH of a file when speaking with the user. Just write the file name ONLY.`) diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index 0f48992f..c43f157a 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -48,9 +48,9 @@ export type ToolCallParams = { 'create_file_or_folder': { uri: URI, isFolder: boolean }, 'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean }, // --- - 'run_command': { command: string; bgTerminalId: string | null }, + 'run_command': { command: string; persistentTerminalId: string | null }, 'open_persistent_terminal': {}, - 'kill_persistent_terminal': { terminalId: string }, + 'kill_persistent_terminal': { persistentTerminalId: string }, } // RESULT OF TOOL CALL @@ -69,7 +69,7 @@ export type ToolResultType = { 'delete_file_or_folder': {}, // --- 'run_command': { result: string; resolveReason: TerminalResolveReason; }, - 'open_persistent_terminal': { terminalId: string }, + 'open_persistent_terminal': { persistentTerminalId: string }, 'kill_persistent_terminal': {}, }