From 71fc2895a8db40172ecc482de8bf7d7956380c9b Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 23 Apr 2025 20:46:30 -0700 Subject: [PATCH] fix persistent terminal ID --- .../react/src/sidebar-tsx/SidebarChat.tsx | 53 +++++++++---------- .../void/browser/terminalToolService.ts | 52 ++++++++++-------- .../contrib/void/browser/toolsService.ts | 8 +-- .../contrib/void/common/prompt/prompts.ts | 1 + 4 files changed, 59 insertions(+), 55 deletions(-) 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 70e4000e..a35a1ca2 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'; @@ -683,6 +684,7 @@ type ToolHeaderParams = { children?: React.ReactNode; bottomChildren?: React.ReactNode; onClick?: () => void; + desc2OnClick?: () => void; isOpen?: boolean; className?: string; } @@ -700,6 +702,7 @@ const ToolHeaderWrapper = ({ bottomChildren, isError, onClick, + desc2OnClick, isOpen, isRejected, className, // applies to the main content @@ -769,7 +772,7 @@ const ToolHeaderWrapper = ({ data-tooltip-content={'Canceled'} data-tooltip-place='top' />} - {desc2 && + {desc2 && {desc2} } {numResults !== undefined && ( @@ -1317,7 +1320,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': () => { @@ -1326,7 +1329,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'] @@ -2053,6 +2056,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) @@ -2062,19 +2066,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 =
@@ -2082,25 +2088,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} @@ -2130,12 +2125,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 @@ -2153,6 +2145,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) @@ -2167,7 +2160,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/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index 7abc98d6..d8f0efb0 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 dd7840a8..8fefb605 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -17,7 +17,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' @@ -507,7 +507,7 @@ export class ToolsService implements IToolsService { // bg command if (persistentTerminalId !== null) { if (resolveReason.type === 'timeout') { - return `Terminal command is running in the background in terminal ${persistentTerminalId}. 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 @@ -521,10 +521,10 @@ export class ToolsService implements IToolsService { }, open_persistent_terminal: (_params, result) => { const { persistentTerminalId } = result; - return `Successfully created background terminal with ID ${persistentTerminalId}`; + return `Successfully created persistent terminal. persistentTerminalId="${persistentTerminalId}"`; }, kill_persistent_terminal: (params, _result) => { - return `Successfully closed terminal ${params.persistentTerminalId}.`; + 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 e9e34117..71795a26 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