forked from All-Hands-AI/OpenHands
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support interactive commands (All-Hands-AI#3653)
* hacky solution for interactive commands * add more behavior * debug * fix continue functionality * remove prints * refactor a bit * reduce test sleep * fix python version * fix pre-commit issue * Regenerate integration tests * Update openhands/runtime/client/client.py * revert some prompt stuff * several integration mock files regenerated * execute_action: remove duplicate exception logging --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: tobitege <[email protected]>
- Loading branch information
1 parent
5100d12
commit ab38515
Showing
69 changed files
with
3,124 additions
and
315 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,7 @@ class ActionRequest(BaseModel): | |
INIT_COMMANDS = [ | ||
'git config --global user.name "openhands" && git config --global user.email "[email protected]" && alias git="git --no-pager"', | ||
] | ||
SOFT_TIMEOUT_SECONDS = 5 | ||
|
||
|
||
class RuntimeClient: | ||
|
@@ -212,6 +213,9 @@ def _get_bash_prompt_and_update_pwd(self): | |
if ps1 == pexpect.EOF: | ||
logger.error(f'Bash shell EOF! {self.shell.after=}, {self.shell.before=}') | ||
raise RuntimeError('Bash shell EOF') | ||
if ps1 == pexpect.TIMEOUT: | ||
logger.warning('Bash shell timeout') | ||
return '' | ||
|
||
# begin at the last occurrence of '[PEXPECT_BEGIN]'. | ||
# In multi-line bash commands, the prompt will be repeated | ||
|
@@ -243,39 +247,56 @@ def _execute_bash( | |
command: str, | ||
timeout: int | None, | ||
keep_prompt: bool = True, | ||
kill_on_timeout: bool = True, | ||
) -> tuple[str, int]: | ||
logger.debug(f'Executing command: {command}') | ||
self.shell.sendline(command) | ||
return self._continue_bash( | ||
timeout=timeout, keep_prompt=keep_prompt, kill_on_timeout=kill_on_timeout | ||
) | ||
|
||
def _interrupt_bash(self, timeout: int | None = None) -> tuple[str, int]: | ||
self.shell.sendintr() # send SIGINT to the shell | ||
self.shell.expect(self.__bash_expect_regex, timeout=timeout) | ||
output = self.shell.before | ||
exit_code = 130 # SIGINT | ||
return output, exit_code | ||
|
||
def _continue_bash( | ||
self, | ||
timeout: int | None, | ||
keep_prompt: bool = True, | ||
kill_on_timeout: bool = True, | ||
) -> tuple[str, int]: | ||
try: | ||
self.shell.sendline(command) | ||
self.shell.expect(self.__bash_expect_regex, timeout=timeout) | ||
|
||
output = self.shell.before | ||
|
||
# Get exit code | ||
self.shell.sendline('echo $?') | ||
logger.debug(f'Executing command for exit code: {command}') | ||
logger.debug('Requesting exit code...') | ||
self.shell.expect(self.__bash_expect_regex, timeout=timeout) | ||
_exit_code_output = self.shell.before | ||
logger.debug(f'Exit code Output: {_exit_code_output}') | ||
exit_code = int(_exit_code_output.strip().split()[0]) | ||
|
||
except pexpect.TIMEOUT as e: | ||
self.shell.sendintr() # send SIGINT to the shell | ||
self.shell.expect(self.__bash_expect_regex, timeout=timeout) | ||
output = self.shell.before | ||
output += ( | ||
'\r\n\r\n' | ||
+ f'[Command timed out after {timeout} seconds. SIGINT was sent to interrupt it.]' | ||
) | ||
exit_code = 130 # SIGINT | ||
logger.error(f'Failed to execute command: {command}. Error: {e}') | ||
if kill_on_timeout: | ||
output, exit_code = self._interrupt_bash() | ||
output += ( | ||
'\r\n\r\n' | ||
+ f'[Command timed out after {timeout} seconds. SIGINT was sent to interrupt it.]' | ||
) | ||
logger.error(f'Failed to execute command. Error: {e}') | ||
else: | ||
output = self.shell.before or '' | ||
exit_code = -1 | ||
|
||
finally: | ||
bash_prompt = self._get_bash_prompt_and_update_pwd() | ||
if keep_prompt: | ||
output += '\r\n' + bash_prompt | ||
logger.debug(f'Command output: {output}') | ||
|
||
return output, exit_code | ||
|
||
async def run_action(self, action) -> Observation: | ||
|
@@ -293,11 +314,23 @@ async def run(self, action: CmdRunAction) -> CmdOutputObservation: | |
commands = split_bash_commands(action.command) | ||
all_output = '' | ||
for command in commands: | ||
output, exit_code = self._execute_bash( | ||
command, | ||
timeout=action.timeout, | ||
keep_prompt=action.keep_prompt, | ||
) | ||
if command == '': | ||
output, exit_code = self._continue_bash( | ||
timeout=SOFT_TIMEOUT_SECONDS, | ||
keep_prompt=action.keep_prompt, | ||
kill_on_timeout=False, | ||
) | ||
elif command.lower() == 'ctrl+c': | ||
output, exit_code = self._interrupt_bash( | ||
timeout=SOFT_TIMEOUT_SECONDS | ||
) | ||
else: | ||
output, exit_code = self._execute_bash( | ||
command, | ||
timeout=SOFT_TIMEOUT_SECONDS, | ||
keep_prompt=action.keep_prompt, | ||
kill_on_timeout=False, | ||
) | ||
if all_output: | ||
# previous output already exists with prompt "user@hostname:working_dir #"" | ||
# we need to add the command to the previous output, | ||
|
@@ -690,5 +723,4 @@ async def list_files(request: Request): | |
return [] | ||
|
||
logger.info(f'Starting action execution API on port {args.port}') | ||
print(f'Starting action execution API on port {args.port}') | ||
run(app, host='0.0.0.0', port=args.port) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
...s/integration/mock/eventstream_runtime/CodeActAgent/test_browse_internet/response_004.log
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
Based on the current Accessibility Tree and the previous actions taken, it appears that we have already successfully navigated to the webpage and clicked the button to reveal the answer. The answer is now visible on the page. | ||
Based on the current Accessibility Tree and the previous actions taken, it appears that we have already navigated to the correct page and clicked the button to reveal the answer. The answer is now visible on the page. | ||
|
||
To accomplish our goal of retrieving the ultimate answer to life, I will now send this information to the user: | ||
To accomplish our goal of retrieving the ultimate answer to life, I will now send a message to the user with the revealed answer: | ||
|
||
``` | ||
send_msg_to_user("The ultimate answer to life, the universe, and everything is: OpenHands is all you need!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.