|
|
@@ -0,0 +1,89 @@
|
|
|
+import subprocess
|
|
|
+import select
|
|
|
+from typing import List
|
|
|
+
|
|
|
+from opendevin.lib.event import Event
|
|
|
+
|
|
|
+class BackgroundCommand:
|
|
|
+ def __init__(self, id: int, command: str, process: subprocess.Popen):
|
|
|
+ self.command = command
|
|
|
+ self.id = id
|
|
|
+ self.process = process
|
|
|
+
|
|
|
+ def _get_log_from_stream(self, stream):
|
|
|
+ logs = ""
|
|
|
+ while True:
|
|
|
+ readable, _, _ = select.select([stream], [], [], .1)
|
|
|
+ if not readable:
|
|
|
+ break
|
|
|
+ next = stream.readline()
|
|
|
+ if next == '':
|
|
|
+ break
|
|
|
+ logs += next
|
|
|
+ if logs == "": return
|
|
|
+ return logs
|
|
|
+
|
|
|
+ def get_logs(self):
|
|
|
+ stdout = self._get_log_from_stream(self.process.stdout)
|
|
|
+ stderr = self._get_log_from_stream(self.process.stderr)
|
|
|
+ exit_code = self.process.poll()
|
|
|
+ return stdout, stderr, exit_code
|
|
|
+
|
|
|
+class CommandManager:
|
|
|
+ def __init__(self):
|
|
|
+ self.cur_id = 0
|
|
|
+ self.background_commands = {}
|
|
|
+
|
|
|
+ def run_command(self, command: str, background=False) -> str:
|
|
|
+ if background:
|
|
|
+ return self.run_background(command)
|
|
|
+ else:
|
|
|
+ return self.run_immediately(command)
|
|
|
+
|
|
|
+ def run_immediately(self, command: str) -> str:
|
|
|
+ result = subprocess.run(["/bin/bash", "-c", command], capture_output=True, text=True)
|
|
|
+ output = result.stdout + result.stderr
|
|
|
+ exit_code = result.returncode
|
|
|
+ if exit_code != 0:
|
|
|
+ raise ValueError('Command failed with exit code ' + str(exit_code) + ': ' + output)
|
|
|
+ return output
|
|
|
+
|
|
|
+ def run_background(self, command: str) -> str:
|
|
|
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
|
|
|
+ bg_cmd = BackgroundCommand(self.cur_id, command, process)
|
|
|
+ self.cur_id += 1
|
|
|
+ self.background_commands[bg_cmd.id] = bg_cmd
|
|
|
+ return "Background command started. To stop it, send a `kill` action with id " + str(bg_cmd.id)
|
|
|
+
|
|
|
+ def kill_command(self, id: int) -> str:
|
|
|
+ # TODO: get log events before killing
|
|
|
+ self.background_commands[id].processs.kill()
|
|
|
+ del self.background_commands[id]
|
|
|
+
|
|
|
+ def get_background_events(self) -> List[Event]:
|
|
|
+ events = []
|
|
|
+ for id, cmd in self.background_commands.items():
|
|
|
+ stdout, stderr, exit_code = cmd.get_logs()
|
|
|
+ if stdout is not None:
|
|
|
+ events.append(Event('output', {
|
|
|
+ 'output': stdout,
|
|
|
+ 'stream': 'stdout',
|
|
|
+ 'id': id,
|
|
|
+ 'command': cmd.command,
|
|
|
+ }))
|
|
|
+ if stderr is not None:
|
|
|
+ events.append(Event('output', {
|
|
|
+ 'output': stderr,
|
|
|
+ 'stream': 'stderr',
|
|
|
+ 'id': id,
|
|
|
+ 'command': cmd.command,
|
|
|
+ }))
|
|
|
+ if exit_code is not None:
|
|
|
+ events.append(Event('output', {
|
|
|
+ 'exit_code': exit_code,
|
|
|
+ 'output': 'Background command %d exited with code %d' % (idx, exit_code),
|
|
|
+ 'id': id,
|
|
|
+ 'command': cmd.command,
|
|
|
+ }))
|
|
|
+ del self.background_commands[id]
|
|
|
+ return events
|