eclipse.platform
eclipse.platform copied to clipboard
[terminal] Support an API to notify the command execution is done in terminal
Let's make sure issue is not already fixed in latest builds first.
- [x] I verified latest Integration Build of Eclipse SDK doesn't provide the feature.
Suggestion
I hope to collect the output of a command execution in the terminal, thus I need a signal to let my plugin knows the execution is done.
Community
- [x] I understand suggesting an enhancement doesn't mandate anyone to implement it. Other contributors may consider this suggestion, or not, at their own convenience. The most efficient way to get it fixed is that I implement it myself and contribute it back as a good quality patch to the project.
Could you provide a simple snippet how you open Terminal and execute command?
How we launch the terminal:
https://github.com/eclipse-copilot/eclipse-copilot/blob/9fc215b436968b7b7274048e7e8c2dc837a75ccd/org.eclipse.copilot.ui/src/org/eclipse/copilot/ui/chat/tools/RunInTerminalTool.java#L198-L213
How we parse the output:
https://github.com/eclipse-copilot/eclipse-copilot/blob/9fc215b436968b7b7274048e7e8c2dc837a75ccd/org.eclipse.copilot.ui/src/org/eclipse/copilot/ui/chat/tools/RunInTerminalTool.java#L319-L360
TBH, the logic of parsing the output is too tricky and almost un-maintainable.
@jdneo I fear this does not work well... you are pasting a string to the terminal (what could be anything e.g echo that is not a command at all) so how should there be any API for this?
I assume you want this to work independent of OS as well.
So my main question would be why do you use a shell and pasting text commands? It would be easier for you to create an own process with a terminal attached....
Thank you @laeubi
Could you give more pointers about It would be easier for you to create an own process with a terminal attached....?
Okay, first some background:
What you refer as "Terminal" here has two parts and is actually a "Local Terminal Emulation":
- a UI what basically is VT100TerminalControl
- a shell, usually
bash/shor similar under linux/mac andcmdin windows that accepts some string and execute them in the context of the shell but you can configure this:
So what happens is we start a new process with the commandinterpreter of the OS and then you can type in text commands. What exactly is working is of course highly OS dependent, e.g. under linux you can use <whatever> & to start a new process in background and on windows you would use start <whatever>. Then with your method you can inject such strings that ultimately result in starting a new process and usually the in/out is piped to the commandinterpreter (unless e.g. you do <whatever> > stdout.txt or similar!)
So as you can see "when command finish" and "collect its output" is highly dependent on how you start the command, some commands might never exit and some outputs might never show up in the UI! Maybe even output of such things are interleaved!
-- SNIP --
Now for your question, with the "Terminal Console" we do a different approach, we have a command X and execute it inside a terminal session as a process so we get:
- Direct in/out/error streams
- Exit codes
- and show that in an own UI
You can find this approach here: https://github.com/eclipse-platform/eclipse.platform/tree/master/debug/org.eclipse.debug.terminal
keep in mind, that with that approach while you can possibly reuse the UI part you need to spawn a new process for each command you execute and you can not use any features of the shell e.g. property expansion.
NB: I used copilot to generate this docu https://github.com/eclipse-platform/eclipse.platform/blob/master/terminal/README.md
Maybe it helps or you like to enhance it with specific questions / information. Beside that it might even work to implement an own terminal connector as well (e.g. Copilot Terminal)
Thank you @laeubi for the guidance.
The debug terminal approach seems promising. Though I'm not sure whether features like property expansion is important for LLM or not, my assumption is that this is not a hard requirement.
Or at least I can maybe made a preference to configure the mode how terminal is launched.
@jdneo just let us know when you face any issues, just assume I don't know maybe nothing how you issue commands, e.g. is copilot really issuing command on my local machine like ls-l or cat <whatever> and parsing its outputs?
@jdneo just let us know when you face any issues, just assume I don't know maybe nothing how you issue commands, e.g. is copilot really issuing command on my local machine like
ls-lorcat <whatever>and parsing its outputs?
Yes, we will send the real command to the terminal and use ITerminalServiceOutputStreamMonitorListener to collect the output content from the terminal. When we guess the execution is finished, we send back all the output to LLM.
I just realize that there will be one problem for the debug terminal solution: since it's creating new process for each command execution, the state of the execution will lose.
The LLM may generate new command based on last output, for example, it can first cd to a dir, and then run a command. If we always creating a new process, the state like working directory is lost.
I wonder if you can start bash as a wrapper inside terminal? Then all processes will run in same shell and you can decide based on output whether some task is done or not?
@iloveeclipse
Not sure if I understand your question.
For windows, we use cmd.exe as the shell command. For MacOS and Linux, we do not set the shell command, so it should fallback to use /bin/sh
Then all processes will run in same shell and you can decide based on output whether some task is done or not?
Parsing the output to check if the task is done or not is almost impossible, given the different behavior of different Platform and shell. That is why I wish to have an API that clearly tell the plugin the command execution is done.
For sure, I'm missing all the internal details, so apologies if I oversimplify.
Isn't the Java Process enough to run a command and wait for its termination?
One can capture the output of the Process and print it in the terminal as a visual result, but the real command is executed on a Java process on which you have full control, independently of the running system or shell.
Hi @LorenzoBettini,
See the second part of this comment: https://github.com/eclipse-platform/eclipse.platform/issues/2270#issuecomment-3531733695.
Imagine this case:
- Copilot runs
cd xxx, changes the directory, and it assume you are already in that directory after that. - Copilot runs some command under that directory.
Spawning a new java process to run a command may lose such kind of states.
Parsing the output to check if the task is done or not is almost impossible, given the different behavior of different Platform and shell. That is why I wish to have an API that clearly tell the plugin the command execution is done.
The problem is if you find it "impossible" then how should we make the impossible possible?
So maybe I need to refine my question:
Is it required that the user is interacting with the terminal or is it just the LLM / copilot / ??
I'm specifically wondering because of this one here https://github.com/features/copilot/cli so how is this working? It looks to me that you "just" want to start copilote CLI as a process, connect its in/out to a terminal console and you should have what you need without specifically stumbling about HOW this works...
Is it required that the user is interacting with the terminal or is it just the LLM / copilot / ??
I can imagine many use-cases where this could be useful without LLM/Copilot and so on, and those are long known: For examples:
- if one creates some file with
touchorcpormvand the target is in the workspace, we could imagine a listener that refreshes the corresponding resource. - If one generate a new project with
mvncommand, we could upon completion let the IDE open the import popup (or even import automatically and let user cancel if they don't want to import) - If one starts some particular process from the CLI, one could imagine a suggestion to automatically wrap it as a Launch in the launch view, or if the CLI is identified to have enabled some debug, a remote debug sessions could be initialized automatically
- On a Docker/podman build, the internal registry in Eclipse could be updated accordingly
- When a command mentions a particular technology, inform users that some marketplace extension exists to also handle it in the IDE ...
if one creates some file with touch or cp or mv and the target is in the workspace, we could imagine a listener that refreshes the corresponding resource.
This can much more easier be accomplished with using native file system hooks in the first place especially as all these files are not required to operate on a local file or even can contain any kind of shell magic that is hard to parse
If one generate a new project with mvn command, we could upon completion let the IDE open the import popup (or even import automatically and let user cancel if they don't want to import)
We already have a wizard for that so why bother "guessing" it from the terminal? In any case see above it would be much easier to watch for new pom.xml (but even that is not needed with pomless eg) to appear in the file system.
If one starts some particular process from the CLI, one could imagine a suggestion to automatically wrap it as a Launch in the launch view, or if the CLI is identified to have enabled some debug, a remote debug sessions could be initialized automatically
Debugging would require a much much more deeper integration as you cannot connect to arbitrary processes ... apart from that one could much easier use procmon / ps if one really want this or start the process from the IDE in the first place.
On a Docker/podman build, the internal registry in Eclipse could be updated accordingly
Same as above, just watch the filesystem (or let you inform by docker daemon directly)
When a command mentions a particular technology, inform users that some marketplace extension exists to also handle it in the IDE
PLEASE NOT! The Marketplace integration is already completely wrecked and annoys me all the day with useless "suggestions" (e.g. create a new xyz.blabla file, double click it and it will say it has extensions to install, what it obviously has not) so I have to disable it all the time.
Those examples are still valid, even if you personally don't like them or know alternatives. In general, what I want to highlight is that by allowing to listen to the activity in the terminal (incl completion of commands), we can sometimes anticipate what users is likely to want next and save them the the effort of looking up the next step.
Technically, I don't think a new listener is necessary for that, the Terminal API could instead notify of a new command by sending a ProcessHandle to listener, and the processHandle has a onExit() method that can do the job very well.
Technically, I don't think a new listener is necessary for that, the Terminal API could instead notify of a new command by sending a ProcessHandle to listener, and the processHandle has a onExit() method that can do the job very well.
Contributions are welcome ... but before being to optimistic please read again what was written in https://github.com/eclipse-platform/eclipse.platform/issues/2270#issuecomment-3526673807 and to summarize:
- UI is a text terminal where some arbitrary characters are send, the UI has no clue if you are "typing" on a commandpromt or using some kind of text UI (e.g.
vi,nano...) - The process we start is the
/bin/shorcmd.exeso you have to find a way (platform dependent) on how to hook into these executable to even give you something you can "handle" ... so lets a assume you somehow was able to use strace / ps (or query OS API directly) you still don't know if the USER has run the command or if the process itself has forked it, or the user is executing a shell script that runs multiple commands.
So I really don't want to hold back anyone providing such API, but one should keep in mind that it is not that we just don't tell, we literally don't know whats going on in this session.
UI is a text terminal where some arbitrary characters are send, the UI has no clue if you are "typing" on a commandpromt or using some kind of text UI
I suspect one can find some easyish rule to determine the case of user entering a command in prompt (ie check that the current char has a a prompt char for instance).
The process we start is the /bin/sh or cmd.exe so you have to find a way (platform dependent) on how to hook into these executable to even give you something you can "handle" ...
If one gets the handle from the shell (sh or cmd), then they can call ProcessHandle.children() to get all children, and use the information such as the command and/or the start time to find out which one is the newly created process.
I suspect one can find some easyish rule to determine the case of user entering a command in prompt (ie check that the current char has a a prompt char for instance).
What is a "prompt char" (not mentioning it is of course configurable by the user)? And why should other cases not have such case as well? If you look at the referenced code you will see that currently terminalViewControl.pasteString(finalCommand); is used... so if I open vi in my terminal then this will only write some things into my editor but really not execute a command at all. So I think this approach is flawed anyways, and again I wonder why pasting stings and parsing outputs of o user terminal... one could easily use Runtime.exec in the first place... and for the described case of cd into a directory this can simply change an internal variable for the workingdirectory of the next real process execution.
If one gets the handle from the shell (sh or cmd), then they can call ProcessHandle.children() to get all children, and use the information such as the command and/or the start time to find out which one is the newly created process.
That's why I said it is much more flexible to start an own shell process (of whatever kind) and then you can do many things... e.g. karaf/gogo has also custom shells that can not only execute system commands but osgi commands.
It looks to me that you "just" want to start copilote CLI as a process, connect its in/out to a terminal console
No, nothing to do about the Copilot CLI. I'm talking about the very basic run_in_terminal invocations in agent mode.
and again I wonder why pasting stings and parsing outputs of o user terminal... one could easily use Runtime.exec in the first place...
The cd ... example is just to tell that during a LLM agent conversation, the terminal executions are stateful. The LLM will ask the terminal to run some commands, get the output, understand it, and run others based on the output history. Since LLM's decision and output are always un-predictable, we can never known what kind of command will be invoked, and how to understand and parse those states and pass to the next new Runtime.exec.
If you can't implement a reliable solution anyway for all the operating systems, you can always instruct the Java process to first change the directory (the process build has an API for that).
Better than nothing: in the current state it is unusable because it never works, no matter what...
The
cd ...example is just to tell that during a LLM agent conversation, the terminal executions are stateful. The LLM will ask the terminal to run some commands, get the output, understand it, and run others based on the output history. Since LLM's decision and output are always un-predictable, we can never known what kind of command will be invoked, and how to understand and parse those states and pass to the next newRuntime.exec.
I think the best would then be to make the LLM command execution more "smart" when issuing commands to a terminal e.g in Linux I would do maybe
sh commands.sh > output.txt || touch done
this will then execute all commands, redirect the output to output.txt and create a file done after that... so all you then need is to wait for done to appear in the file system (command is done) and read output.txt ... of course it would be good to use unique names and cleanup the files afterwards.
Something similar could be done for windows for sure.
you can always instruct the Java process to first change the directory (the process build has an API for that).
Like I said before, cd is just one example of the terminal state, you would never know how people will interact with LLM, and what command will LLM decides to run. Everything is possible, like property expansion mentioned by @laeubi.
Besides, parsing the command generated by LLM to know which directory LLM wants to work on is another a tricky task.
I think the best would then be to make the LLM command execution more "smart" when issuing commands to a terminal e.g in Linux I would do maybe
We can change the description of the run_in_terminal tool, letting LLM know that it's stateless, please run all required command in one invocation. But I'm not sure how good it will be. And this is not a common pattern in other AI plugins AFAIK.
@jdneo I don't think it is stateless per se... e.g. my suggestion was just for the implementation of run_in_terminal command, so lets say I call
run_in_terminal("cd xx")
you turn it into
cd xx > output.txt || touch done
then wait for the done file, read the output and return it to the llm then you get a mostly what you want here (if we assume the llm will not try to use vi or similar that requires user input)
@laeubi Ah, I see. That's a brilliant idea. Yes I can try this approach. Thank you.
No, nothing to do about the Copilot CLI. I'm talking about the very basic run_in_terminal invocations in agent mode.
Too bad ;-)
Just out of curiosity I started a small eclipse-plugin to integrate the copilot-cli that demonstrate how a custom terminal type can work here:
https://github.com/user-attachments/assets/23cff325-e3d8-4aa9-af28-6970138e4f4c
The color scheme seems a bit off (@jonahgraham what is the best way to handle this? Can a terminal define its own one?) but beside that it is working, contributions are welcome ;-)
seems to be a general problem on some terminals:
- https://github.com/github/copilot-cli/issues/547
The color scheme seems a bit off [...] what is the best way to handle this?
I can't remember, but in the past the "invert terminal" was supposed to resolve these issues of when the program assume a dark scheme, but UI is light.
Can a terminal define its own one?
IIRC a terminal within a view cannot, but a different view that instantiates its own terminal control can
@jdneo I don't think it is stateless per se... e.g. my suggestion was just for the implementation of
run_in_terminalcommand, so lets say I call
run_in_terminal("cd xx")you turn it into
cd xx > output.txt || touch donethen wait for the done file, read the output and return it to the llm then you get a mostly what you want here (if we assume the llm will not try to use
vior similar that requires user input)
@laeubi Thanks for the suggestion. I experimented with the idea of appending a marker command, but I ran into two issues.
Issue 1: Syntax errors prevent marker execution
If the LLM-generated command contains a syntax error, the entire line fails to parse and the marker never runs.
git status --invalid-flag && ; New-Item marker # Parse error, marker not created
To address this, I tried wrapping the command with eval (Unix) or Invoke-Expression (Windows) to defer parsing to runtime.
Issue 2: Wrappers alter execution semantics
However, wrapping the command changes how it executes. For example:
# Original command
Get-ChildItem | Where-Object { $_.Length -gt 10KB }
# Wrapped version
Invoke-Expression "Get-ChildItem | Where-Object { $_.Length -gt 10KB }"
In the wrapped version, $_ gets expanded prematurely when the double-quoted string is parsed, before Invoke-Expression even runs. Similar issues occur with environment variables, subexpressions, nested quoting, etc.
Given these trade-offs, do you have suggestions for a more robust approach? Or is there perhaps a different mechanism for detecting command completion that doesn't require modifying the command at all?
@jdneo maybe it works to put the command in a sh script or bat file, then execute that? This is a bit similar to your eval (Unix) or Invoke-Expression (Windows) but should expand the expressions correctly.
I also asked copilot and it suggest the following for linux and windows:
For Linux/Unix (bash/sh):
1. Shell Hooks/Traps
The user could leverage bash's DEBUG trap and PROMPT_COMMAND:
# Set up command logging
export PROMPT_COMMAND='echo "COMMAND_COMPLETED: $? at $(date)" >> /tmp/terminal_log'
# Or use DEBUG trap to capture commands before execution
trap 'echo "EXECUTING: $BASH_COMMAND" >> /tmp/terminal_log' DEBUG
2. Process Monitoring via /proc
Monitor child processes of the shell:
# Monitor children of current shell
while true; do
ps --ppid $$ -o pid,cmd --no-headers
sleep 0.5
done
For Windows (cmd.exe/PowerShell):
1. PowerShell Transcription
Start-Transcript -Path "C:\temp\terminal_log.txt"
# All commands and output will be logged
2. Windows Event Tracing (ETW)
Monitor process creation/termination events:
# Use WMI events to monitor process creation
Register-WmiEvent -Query "SELECT * FROM Win32_ProcessStartTrace" -Action {
Write-Host "Process started: $($Event.SourceEventArgs.NewEvent.ProcessName)"
}
3. Job Objects API
Windows Job Objects can track child processes and get notifications.
Cross-platform Plugin Approach:
- Wrap the shell execution - Instead of directly executing sh/cmd, wrap it in a monitoring script:
#!/bin/bash
# wrapper.sh
exec bash --init-file <(echo '
PROMPT_COMMAND="curl -s http://localhost:8080/notify? status=$?"
source ~/. bashrc
')
- Inject a custom prompt that includes markers the plugin can detect in the output stream:
PS1='[EXEC_START]\u@\h:\w\$ '
PS2='[EXEC_CONTINUE]> '