From a9aa9a26ea697fd550f0fd2214f7a2113dda2a6d Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Fri, 9 May 2025 14:59:27 +0200 Subject: [PATCH 01/10] feat: add support for applying shellscripts and bash directly to terminal --- .../src/markdown/ApplyBlockHoverButtons.tsx | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 41b5a649..7c994e08 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -243,26 +243,51 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri, + language, }: { codeStr: string, applyBoxId: string, -} & ({ + language?: string, +} & { uri: URI | 'current'; -}) -) => { +}) => { const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') + const terminalToolService = accessor.get('ITerminalToolService') const settingsState = useSettingsState() const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) + const isShellLanguage = language === 'bash' || language === 'shellscript' const onClickSubmit = useCallback(async () => { if (currStreamStateRef.current === 'streaming') return + // For shell scripts, run in terminal instead of applying to file + if (isShellLanguage) { + try { + // Create a terminal if none exists or use terminal 1 + const terminalIds = terminalToolService.listPersistentTerminalIds() + let terminalId = '1'; + if (!terminalIds.includes(terminalId)) { + terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + } + await terminalToolService.runCommand( + codeStr, + { type: 'persistent', persistentTerminalId: terminalId } + ); + await terminalToolService.focusPersistentTerminal(terminalId) + metricsService.capture('Execute Shell', { length: codeStr.length }) + } catch (e) { + console.error('Failed to execute in terminal:', e) + } + return; + } + + // Normal file apply logic for non-shell languages await editCodeService.callBeforeApplyOrEdit(uri) const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ @@ -281,7 +306,7 @@ export const ApplyButtonsHTML = ({ }) metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService]) + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, isShellLanguage, terminalToolService]) const onClickStop = useCallback(() => { @@ -451,7 +476,7 @@ export const BlockCodeApplyWrapper = ({
{currStreamState === 'idle-no-changes' && } - +
From 5232b4c30c67546d42748a3749ee215381e2424f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 10 May 2025 02:28:40 -0700 Subject: [PATCH 02/10] update prompt to work better in agent --- src/vs/workbench/contrib/void/common/prompt/prompts.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 613142b8..c558dba1 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -479,12 +479,13 @@ ${directoryStr} details.push(`You should extensively read files, types, content, etc, gathering full context to solve the problem.`) } - - if (mode === 'gather' || mode === 'normal') { - details.push(`If you write any code blocks, please use this format: + details.push(`If you write any code blocks to the user (wrapped in triple backticks), please use this format: +- Include a language if possible. Terminal should have the language 'shell'. - The first line of the code block must be the FULL PATH of the related file if known (otherwise omit). - The remaining contents of the file should proceed as usual.`) + if (mode === 'gather' || mode === 'normal') { + 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 code description of the change to make to the file. \ From 9a905f6cb2cc1937b3bb42ed109d0c2410941cc5 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Mon, 12 May 2025 09:06:22 +0200 Subject: [PATCH 03/10] add support for stopping the terminal command --- .../src/markdown/ApplyBlockHoverButtons.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 89137cbc..b158acc2 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -264,25 +264,30 @@ export const ApplyButtonsHTML = ({ const isShellLanguage = language === 'bash' || language === 'shellscript' + const [isShellRunning, setIsShellRunning] = useState(false) + const interruptToolRef = useRef<(() => void) | null>(null) + const onClickSubmit = useCallback(async () => { - if (currStreamStateRef.current === 'streaming') return + if (currStreamStateRef.current === 'streaming' || isShellRunning) return // For shell scripts, run in terminal instead of applying to file if (isShellLanguage) { try { + setIsShellRunning(true) // Create a terminal if none exists or use terminal 1 const terminalIds = terminalToolService.listPersistentTerminalIds() let terminalId = '1'; if (!terminalIds.includes(terminalId)) { terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) } - await terminalToolService.runCommand( + const { interrupt } = await terminalToolService.runCommand( codeStr, { type: 'persistent', persistentTerminalId: terminalId } ); - await terminalToolService.focusPersistentTerminal(terminalId) + interruptToolRef.current = interrupt metricsService.capture('Execute Shell', { length: codeStr.length }) } catch (e) { + setIsShellRunning(false) console.error('Failed to execute in terminal:', e) } return; @@ -338,6 +343,19 @@ export const ApplyButtonsHTML = ({ const currStreamState = currStreamStateRef.current console.log('currStreamState...', currStreamState) + if (isShellRunning) { + return ( + { + interruptToolRef.current?.(); + setIsShellRunning(false); + }} + {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} + /> + ); + } + if (currStreamState === 'streaming') { return Date: Mon, 12 May 2025 11:50:25 -0700 Subject: [PATCH 04/10] use 1st terminal id --- .../react/src/markdown/ApplyBlockHoverButtons.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index b158acc2..99847fe0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -248,7 +248,6 @@ export const ApplyButtonsHTML = ({ codeStr: string, applyBoxId: string, language?: string, -} & { uri: URI | 'current'; }) => { const accessor = useAccessor() @@ -270,16 +269,19 @@ export const ApplyButtonsHTML = ({ const onClickSubmit = useCallback(async () => { if (currStreamStateRef.current === 'streaming' || isShellRunning) return - // For shell scripts, run in terminal instead of applying to file + // for shell scripts, run in terminal instead of applying to file if (isShellLanguage) { try { setIsShellRunning(true) - // Create a terminal if none exists or use terminal 1 + // create a terminal if none exists or use terminal 1 const terminalIds = terminalToolService.listPersistentTerminalIds() - let terminalId = '1'; - if (!terminalIds.includes(terminalId)) { + + let terminalId: string + if (terminalIds.length !== 0) + terminalId = terminalIds[0] // use 1st terminal id + else terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) - } + const { interrupt } = await terminalToolService.runCommand( codeStr, { type: 'persistent', persistentTerminalId: terminalId } From e8b7c9c470957b3734e7827e804061c721418ecb Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 11:56:08 -0700 Subject: [PATCH 05/10] always create terminal when apply --- .../react/src/markdown/ApplyBlockHoverButtons.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 786fb737..94fae35b 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -273,14 +273,7 @@ export const ApplyButtonsHTML = ({ if (isShellLanguage) { try { setIsShellRunning(true) - // create a terminal if none exists or use terminal 1 - const terminalIds = terminalToolService.listPersistentTerminalIds() - - let terminalId: string - if (terminalIds.length !== 0) - terminalId = terminalIds[0] // use 1st terminal id - else - terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) const { interrupt } = await terminalToolService.runCommand( codeStr, From 3fa680e8bab5c64afa1fd0703d96450392d3f5ae Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 12:09:55 -0700 Subject: [PATCH 06/10] identify more shells --- .../src/markdown/ApplyBlockHoverButtons.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 94fae35b..834909b9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -239,6 +239,21 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: } +const terminalLanguages = new Set([ + 'bash', + 'shellscript', + 'shell', + 'powershell', + 'bat', + 'zsh', + 'sh', + 'fish', + 'nushell', + 'ksh', + 'xonsh', + 'elvish', +]) + export const ApplyButtonsHTML = ({ codeStr, applyBoxId, @@ -261,7 +276,7 @@ export const ApplyButtonsHTML = ({ const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) - const isShellLanguage = language === 'bash' || language === 'shellscript' + const isShellLanguage = !!language && terminalLanguages.has(language) const [isShellRunning, setIsShellRunning] = useState(false) const interruptToolRef = useRef<(() => void) | null>(null) From 1c6edf70234e812327218eea17e99f6fe362ecda Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:45:33 -0700 Subject: [PATCH 07/10] generalize --- .../src/markdown/ApplyBlockHoverButtons.tsx | 134 +++++++++++------- 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 834909b9..4674f9a0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -254,7 +254,69 @@ const terminalLanguages = new Set([ 'elvish', ]) -export const ApplyButtonsHTML = ({ +const ApplyButtonsForTerminal = ({ + codeStr, + applyBoxId, + uri, + language, +}: { + codeStr: string, + applyBoxId: string, + language?: string, + uri: URI | 'current'; +}) => { + const accessor = useAccessor() + const metricsService = accessor.get('IMetricsService') + const terminalToolService = accessor.get('ITerminalToolService') + + const settingsState = useSettingsState() + const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId + + const [isShellRunning, setIsShellRunning] = useState(false) + const interruptToolRef = useRef<(() => void) | null>(null) + + const onClickSubmit = useCallback(async () => { + if (isShellRunning) return + try { + setIsShellRunning(true) + const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + const { interrupt } = await terminalToolService.runCommand( + codeStr, + { type: 'persistent', persistentTerminalId: terminalId } + ); + interruptToolRef.current = interrupt + metricsService.capture('Execute Shell', { length: codeStr.length }) + } catch (e) { + setIsShellRunning(false) + console.error('Failed to execute in terminal:', e) + } + }, [codeStr, uri, applyBoxId, metricsService, terminalToolService, isShellRunning]) + + if (isShellRunning) { + return ( + { + interruptToolRef.current?.(); + setIsShellRunning(false); + }} + {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} + /> + ); + } + if (isDisabled) { + return null + } + return +} + + + +const ApplyButtonsForEdit = ({ codeStr, applyBoxId, uri, @@ -268,7 +330,6 @@ export const ApplyButtonsHTML = ({ const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') - const terminalToolService = accessor.get('ITerminalToolService') const notificationService = accessor.get('INotificationService') const settingsState = useSettingsState() @@ -276,34 +337,9 @@ export const ApplyButtonsHTML = ({ const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) - const isShellLanguage = !!language && terminalLanguages.has(language) - - const [isShellRunning, setIsShellRunning] = useState(false) - const interruptToolRef = useRef<(() => void) | null>(null) - const onClickSubmit = useCallback(async () => { - if (currStreamStateRef.current === 'streaming' || isShellRunning) return + if (currStreamStateRef.current === 'streaming') return - // for shell scripts, run in terminal instead of applying to file - if (isShellLanguage) { - try { - setIsShellRunning(true) - const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) - - const { interrupt } = await terminalToolService.runCommand( - codeStr, - { type: 'persistent', persistentTerminalId: terminalId } - ); - interruptToolRef.current = interrupt - metricsService.capture('Execute Shell', { length: codeStr.length }) - } catch (e) { - setIsShellRunning(false) - console.error('Failed to execute in terminal:', e) - } - return; - } - - // Normal file apply logic for non-shell languages await editCodeService.callBeforeApplyOrEdit(uri) const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ @@ -327,7 +363,7 @@ export const ApplyButtonsHTML = ({ }) metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, isShellLanguage, terminalToolService]) + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, notificationService]) const onClickStop = useCallback(() => { @@ -349,22 +385,7 @@ export const ApplyButtonsHTML = ({ if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'reject', removeCtrlKs: false }) }, [uri, applyBoxId, editCodeService]) - const currStreamState = currStreamStateRef.current - - if (isShellRunning) { - return ( - { - interruptToolRef.current?.(); - setIsShellRunning(false); - }} - {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} - /> - ); - } - if (currStreamState === 'streaming') { return } - if (isDisabled) { return null } - - if (currStreamState === 'idle-no-changes') { return } - if (currStreamState === 'idle-has-changes') { return { + const { language } = params + const isShellLanguage = !!language && terminalLanguages.has(language) + + if (isShellLanguage) { + return + } + else { + return + } +} + + + + + export const EditToolAcceptRejectButtonsHTML = ({ codeStr, applyBoxId, From 3772cf98eabd17518c3ef50ca44170c4a1801662 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:47:40 -0700 Subject: [PATCH 08/10] delete --- src/vs/workbench/contrib/void/browser/terminalToolService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index b079eb5a..d5bb5228 100644 --- a/src/vs/workbench/contrib/void/browser/terminalToolService.ts +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -277,6 +277,8 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ terminal.dispose() if (!isPersistent) delete this.temporaryTerminalInstanceOfId[params.terminalId] + else + delete this.persistentTerminalInstanceOfId[params.persistentTerminalId] } const waitForResult = async () => { From 67ed52415e5ad88bbe0dbcc6fd6716f31d4939bf Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 19:29:45 -0700 Subject: [PATCH 09/10] apply terminal? --- .../workbench/contrib/void/browser/terminalToolService.ts | 7 ++++++- src/vs/workbench/contrib/void/browser/toolsService.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index d5bb5228..2e18511b 100644 --- a/src/vs/workbench/contrib/void/browser/terminalToolService.ts +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -22,7 +22,12 @@ export interface ITerminalToolService { readonly _serviceBrand: undefined; listPersistentTerminalIds(): string[]; - runCommand(command: string, opts: { type: 'persistent', persistentTerminalId: string } | { type: 'ephemeral', cwd: string | null, terminalId: string }): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>; + runCommand(command: string, opts: + | { type: 'persistent', persistentTerminalId: string } + | { type: 'temporary', cwd: string | null, terminalId: string } + // | { type: 'apply', terminalId: string } + ): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>; + focusPersistentTerminal(terminalId: string): Promise persistentTerminalExists(terminalId: string): boolean diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index bba085d8..02edf047 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -430,7 +430,7 @@ export class ToolsService implements IToolsService { }, // --- run_command: async ({ command, cwd, terminalId }) => { - const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'ephemeral', cwd, terminalId }) + const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'temporary', cwd, terminalId }) return { result: resPromise, interruptTool: interrupt } }, run_persistent_command: async ({ command, persistentTerminalId }) => { From 28dcdff189fba6c0e868f6e67ff56cf40a6a1975 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 19:31:05 -0700 Subject: [PATCH 10/10] isdisabled fix --- .../void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 4674f9a0..93e26b0d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -270,10 +270,10 @@ const ApplyButtonsForTerminal = ({ const terminalToolService = accessor.get('ITerminalToolService') const settingsState = useSettingsState() - const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId const [isShellRunning, setIsShellRunning] = useState(false) const interruptToolRef = useRef<(() => void) | null>(null) + const isDisabled = isShellRunning const onClickSubmit = useCallback(async () => { if (isShellRunning) return