Forráskód Böngészése

feat: show exact python interpreter to the agent in IPython and Bash (#3448)

* try to fix pip unavailable

* update test case for pip

* force rebuild in CI

* remove extra symlink

* fix newline

* added semi-colon to line 31

* Dockerfile.j2: activate env at the end

* Revert "Dockerfile.j2: activate env at the end"

This reverts commit cf2f5651021fe80d4ab69a35a85f0a35b29dc3d7.

* cleanup Dockerfile

* switch default python image

* remove image agnostic (no longer used)

* fix tests

* simplify integration tests default image

* add nodejs specific runtime tests

* update tests and workflows

* switch to nikolaik/python-nodejs:python3.11-nodejs22

* update build sh to output image name correctly

* increase custom images to test

* fix test

* fix test

* fix double quote

* try fixing ci

* update ghcr workflow

* fix artifact name

* try to fix ghcr again

* fix workflow

* save built image to correct dir

* remove extra -docker-image

* make last tag to be human readable image tag

* fix hyphen to underscore

* run test runtime on all tags

* revert app build

* separate ghcr workflow

* update dockerfile for eval

* fix tag for test run

* try fix tag

* try fix tag via matrix output

* try workflow again

* update comments

* try fixing test matrix

* fix artifact name

* try fix tag again

* Revert "try fix tag again"

This reverts commit b369badd8cccf4a526e36d27eafb77ea2d32f6be.

* tweak filename

* try different path

* fix filepath

* try fix tag artifact path again

* save json instead of line

* update matrix

* print all tags in workflow

* support only streaming diff logs from the runtime client

* remove strip from log line to fix indentation

* get py interpreter for jupyter

* rstrip to remove newline on the rightside for logging

* fix blocking issue for stream logs

* set python interpreter path in bash ps1

* update testcase for jupyter py interpreter path

* remove accidentally added changes

* remove accidentally added changes

* only print dockerfile when debug

* add docs

* remove extra tests that weren't supposed to be in this pr

* add back missing test

* revert

* make LogBuffer synchronous to fix hang in integration tests

* fix integration tests

* Update opendevin/runtime/client/client.py

Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>

* fix test case

* fix integration tests

* change deque to list

* update integration tests

* rename test runtime

* fix docs

* rename opendevin to openhands in tests

---------

Co-authored-by: tobitege <tobitege@gmx.de>
Co-authored-by: Graham Neubig <neubig@gmail.com>
Co-authored-by: tobitege <10787084+tobitege@users.noreply.github.com>
Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
Xingyao Wang 1 éve
szülő
commit
b19b724eae
26 módosított fájl, 174 hozzáadás és 57 törlés
  1. 14 6
      openhands/runtime/client/client.py
  2. 83 15
      openhands/runtime/client/runtime.py
  3. 4 0
      openhands/runtime/plugins/jupyter/__init__.py
  4. 1 1
      openhands/runtime/utils/runtime_build.py
  5. 1 0
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_002.log
  6. 3 2
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_002.log
  7. 4 2
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_003.log
  8. 5 2
      tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_004.log
  9. 1 0
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_002.log
  10. 2 1
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_002.log
  11. 3 1
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_003.log
  12. 4 1
      tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_004.log
  13. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_edits/prompt_002.log
  14. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_edits/prompt_003.log
  15. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_002.log
  16. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_005.log
  17. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_006.log
  18. 1 1
      tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_009.log
  19. 1 1
      tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_003.log
  20. 1 1
      tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_004.log
  21. 1 1
      tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_005.log
  22. 1 1
      tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_006.log
  23. 1 1
      tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_007.log
  24. 1 1
      tests/integration/mock/eventstream_runtime/PlannerAgent/test_write_simple_script/prompt_010.log
  25. 1 1
      tests/integration/mock/eventstream_runtime/PlannerAgent/test_write_simple_script/prompt_011.log
  26. 36 13
      tests/runtime/test_ipython.py

+ 14 - 6
openhands/runtime/client/client.py

@@ -174,12 +174,15 @@ class RuntimeClient:
             encoding='utf-8',
             echo=False,
         )
-        self.__bash_PS1 = r'[PEXPECT_BEGIN] \u@\h:\w [PEXPECT_END]'
+        self.__bash_PS1 = (
+            r'[PEXPECT_BEGIN]\n'
+            r'$(which python >/dev/null 2>&1 && echo "[Python Interpreter: $(which python)]\n")'
+            r'\u@\h:\w\n'
+            r'[PEXPECT_END]'
+        )
 
         # This should NOT match "PS1=\u@\h:\w [PEXPECT]$" when `env` is executed
-        self.__bash_expect_regex = (
-            r'\[PEXPECT_BEGIN\] ([a-z0-9_-]*)@([a-zA-Z0-9.-]*):(.+) \[PEXPECT_END\]'
-        )
+        self.__bash_expect_regex = r'\[PEXPECT_BEGIN\]\s*(.*?)\s*([a-z0-9_-]*)@([a-zA-Z0-9.-]*):(.+)\s*\[PEXPECT_END\]'
 
         self.shell.sendline(f'export PS1="{self.__bash_PS1}"; export PS2=""')
         self.shell.expect(self.__bash_expect_regex)
@@ -220,11 +223,12 @@ class RuntimeClient:
         assert (
             matched is not None
         ), f'Failed to parse bash prompt: {ps1}. This should not happen.'
-        username, hostname, working_dir = matched.groups()
+        other_info, username, hostname, working_dir = matched.groups()
+        working_dir = working_dir.rstrip()
         self.pwd = os.path.expanduser(working_dir)
 
         # re-assemble the prompt
-        prompt = f'{username}@{hostname}:{working_dir} '
+        prompt = f'{other_info.strip()}\n{username}@{hostname}:{working_dir} '
         if username == 'root':
             prompt += '#'
         else:
@@ -273,7 +277,9 @@ class RuntimeClient:
 
     async def run_action(self, action) -> Observation:
         action_type = action.action
+        logger.debug(f'Running action: {action}')
         observation = await getattr(self, action_type)(action)
+        logger.debug(f'Action output: {observation}')
         return observation
 
     async def run(self, action: CmdRunAction) -> CmdOutputObservation:
@@ -327,6 +333,7 @@ class RuntimeClient:
             obs: IPythonRunCellObservation = await _jupyter_plugin.run(action)
             obs.content = obs.content.rstrip()
             obs.content += f'\n[Jupyter current working directory: {self.pwd}]'
+            obs.content += f'\n[Jupyter Python interpreter: {_jupyter_plugin.python_interpreter_path}]'
             return obs
         else:
             raise RuntimeError(
@@ -472,6 +479,7 @@ if __name__ == '__main__':
             browsergym_eval_env=args.browsergym_eval_env,
         )
         await client.ainit()
+        logger.info('Runtime client initialized.')
         yield
         # Clean up & release the resources
         client.close()

+ 83 - 15
openhands/runtime/client/runtime.py

@@ -1,6 +1,7 @@
 import asyncio
 import os
 import tempfile
+import threading
 import uuid
 from zipfile import ZipFile
 
@@ -34,9 +35,65 @@ from openhands.runtime.utils import find_available_tcp_port
 from openhands.runtime.utils.runtime_build import build_runtime_image
 
 
+class LogBuffer:
+    """
+    Synchronous buffer for Docker container logs.
+
+    This class provides a thread-safe way to collect, store, and retrieve logs
+    from a Docker container. It uses a list to store log lines and provides methods
+    for appending, retrieving, and clearing logs.
+    """
+
+    def __init__(self, container: docker.models.containers.Container):
+        self.buffer: list[str] = []
+        self.lock = threading.Lock()
+        self.log_generator = container.logs(stream=True, follow=True)
+        self.log_stream_thread = threading.Thread(target=self.stream_logs)
+        self.log_stream_thread.daemon = True
+        self.log_stream_thread.start()
+        self._stop_event = threading.Event()
+
+    def append(self, log_line: str):
+        with self.lock:
+            self.buffer.append(log_line)
+
+    def get_and_clear(self) -> list[str]:
+        with self.lock:
+            logs = list(self.buffer)
+            self.buffer.clear()
+            return logs
+
+    def stream_logs(self):
+        """
+        Stream logs from the Docker container in a separate thread.
+
+        This method runs in its own thread to handle the blocking
+        operation of reading log lines from the Docker SDK's synchronous generator.
+        """
+        try:
+            for log_line in self.log_generator:
+                if self._stop_event.is_set():
+                    break
+                if log_line:
+                    self.append(log_line.decode('utf-8').rstrip())
+        except Exception as e:
+            logger.error(f'Error in stream_logs: {e}')
+
+    def __del__(self):
+        if self.log_stream_thread.is_alive():
+            logger.warn(
+                "LogBuffer was not properly closed. Use 'log_buffer.close()' for clean shutdown."
+            )
+            self.close(timeout=5)
+
+    def close(self, timeout: float = 10.0):
+        self._stop_event.set()
+        self.log_stream_thread.join(timeout)
+
+
 class EventStreamRuntime(Runtime):
     """This runtime will subscribe the event stream.
-    When receive an event, it will send the event to od-runtime-client which run inside the docker environment.
+    When receive an event, it will send the event to runtime-client which run inside the docker environment.
     """
 
     container_name_prefix = 'openhands-sandbox-'
@@ -74,6 +131,9 @@ class EventStreamRuntime(Runtime):
         self.runtime_builder = DockerRuntimeBuilder(self.docker_client)
         logger.debug(f'EventStreamRuntime `{sid}` config:\n{self.config}')
 
+        # Buffer for container logs
+        self.log_buffer: LogBuffer | None = None
+
     async def ainit(self, env_vars: dict[str, str] | None = None):
         if self.config.sandbox.runtime_extra_deps:
             logger.info(
@@ -174,6 +234,7 @@ class EventStreamRuntime(Runtime):
                 environment={'DEBUG': 'true'} if self.config.debug else None,
                 volumes=volumes,
             )
+            self.log_buffer = LogBuffer(container)
             logger.info(f'Container started. Server url: {self.api_url}')
             return container
         except Exception as e:
@@ -193,20 +254,24 @@ class EventStreamRuntime(Runtime):
     )
     async def _wait_until_alive(self):
         logger.debug('Getting container logs...')
-        container = self.docker_client.containers.get(self.container_name)
-        # get logs
-        _logs = container.logs(tail=10).decode('utf-8').split('\n')
-        # add indent
-        _logs = '\n'.join([f'    |{log}' for log in _logs])
-        logger.info(
-            '\n'
-            + '-' * 30
-            + 'Container logs (last 10 lines):'
-            + '-' * 30
-            + f'\n{_logs}'
-            + '\n'
-            + '-' * 90
-        )
+
+        # Print and clear the log buffer
+        assert (
+            self.log_buffer is not None
+        ), 'Log buffer is expected to be initialized when container is started'
+        logs = self.log_buffer.get_and_clear()
+        if logs:
+            formatted_logs = '\n'.join([f'    |{log}' for log in logs])
+            logger.info(
+                '\n'
+                + '-' * 30
+                + 'Container logs:'
+                + '-' * 30
+                + f'\n{formatted_logs}'
+                + '\n'
+                + '-' * 90
+            )
+
         async with aiohttp.ClientSession() as session:
             async with session.get(f'{self.api_url}/alive') as response:
                 if response.status == 200:
@@ -221,6 +286,9 @@ class EventStreamRuntime(Runtime):
         return self.config.workspace_mount_path_in_sandbox
 
     async def close(self, close_client: bool = True):
+        if self.log_buffer:
+            self.log_buffer.close()
+
         if self.session is not None and not self.session.closed:
             await self.session.close()
 

+ 4 - 0
openhands/runtime/plugins/jupyter/__init__.py

@@ -49,6 +49,10 @@ class JupyterPlugin(Plugin):
         logger.info(
             f'Jupyter kernel gateway started at port {self.kernel_gateway_port}. Output: {output}'
         )
+        _obs = await self.run(
+            IPythonRunCellAction(code='import sys; print(sys.executable)')
+        )
+        self.python_interpreter_path = _obs.content.strip()
 
     async def _run(self, action: Action) -> IPythonRunCellObservation:
         """Internal method to run a code cell in the jupyter kernel."""

+ 1 - 1
openhands/runtime/utils/runtime_build.py

@@ -144,7 +144,7 @@ def prep_docker_build_folder(
         skip_init=skip_init,
         extra_deps=extra_deps,
     )
-    logger.info(
+    logger.debug(
         (
             f'===== Dockerfile content start =====\n'
             f'{dockerfile_content}\n'

+ 1 - 0
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_002.log

@@ -411,5 +411,6 @@ with open('/workspace/test.txt', 'w') as file:
 OBSERVATION:
 [Code executed successfully with no output]
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

+ 3 - 2
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_002.log

@@ -410,12 +410,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=aca10c2c34795d92bcf85b43c34a0f7eb39d1cf3a4edf8f69f65ed206619ae51
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=8561279053bc22a85928e69c2bb609aebc9c04526f2dbf420ed0b9c13f94131c
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -425,5 +425,6 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

+ 4 - 2
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_003.log

@@ -410,12 +410,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=aca10c2c34795d92bcf85b43c34a0f7eb39d1cf3a4edf8f69f65ed206619ae51
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=8561279053bc22a85928e69c2bb609aebc9c04526f2dbf420ed0b9c13f94131c
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -425,6 +425,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -443,5 +444,6 @@ with open('/workspace/test.txt', 'w') as file:
 OBSERVATION:
 [Code executed successfully with no output]
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>.

+ 5 - 2
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_004.log

@@ -410,12 +410,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
-  Installing build dependencies ... [?25l- \ | / - \ done
+  Installing build dependencies ... [?25l- \ | / - done
 [?25h  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=aca10c2c34795d92bcf85b43c34a0f7eb39d1cf3a4edf8f69f65ed206619ae51
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=8561279053bc22a85928e69c2bb609aebc9c04526f2dbf420ed0b9c13f94131c
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -425,6 +425,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -443,6 +444,7 @@ with open('/workspace/test.txt', 'w') as file:
 OBSERVATION:
 [Code executed successfully with no output]
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -456,6 +458,7 @@ cat /workspace/test.txt
 OBSERVATION:
 pymsgbox version: 1.0.9
 
+[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 openhands@docker-desktop:/workspace $
 [Command -1 finished with exit code 0]
 

+ 1 - 0
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython/prompt_002.log

@@ -604,5 +604,6 @@ with open('/workspace/test.txt', 'w') as file:
 OBSERVATION:
 [Code executed successfully with no output]
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

+ 2 - 1
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_002.log

@@ -608,7 +608,7 @@ Collecting pymsgbox==1.0.9
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=65f1e9e680d728456ab0dc9e66affd01d7ade89ec5a2a8c3aa80ab24a71247ac
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=cd01971247caa14ba55cb76b476c8c2659d10c03ec9da34ba3a8bdb4bb626438
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -618,5 +618,6 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

+ 3 - 1
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_003.log

@@ -608,7 +608,7 @@ Collecting pymsgbox==1.0.9
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=65f1e9e680d728456ab0dc9e66affd01d7ade89ec5a2a8c3aa80ab24a71247ac
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=cd01971247caa14ba55cb76b476c8c2659d10c03ec9da34ba3a8bdb4bb626438
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -618,6 +618,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -638,5 +639,6 @@ version_info
 OBSERVATION:
 '1.0.9'
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>.

+ 4 - 1
tests/integration/mock/eventstream_runtime/CodeActSWEAgent/test_ipython_module/prompt_004.log

@@ -608,7 +608,7 @@ Collecting pymsgbox==1.0.9
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=65f1e9e680d728456ab0dc9e66affd01d7ade89ec5a2a8c3aa80ab24a71247ac
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7407 sha256=cd01971247caa14ba55cb76b476c8c2659d10c03ec9da34ba3a8bdb4bb626438
   Stored in directory: /home/openhands/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
@@ -618,6 +618,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -638,6 +639,7 @@ version_info
 OBSERVATION:
 '1.0.9'
 [Jupyter current working directory: /workspace]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 
 ----------
 
@@ -651,6 +653,7 @@ cat /workspace/test.txt
 OBSERVATION:
 pymsgbox version: 1.0.9
 
+[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 openhands@docker-desktop:/workspace $
 [Command -1 finished with exit code 0]
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_edits/prompt_002.log

@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
+[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_edits/prompt_003.log

@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}, {"source": "agent", "action": "read", "args": {"path": "bad.txt", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!\n", "extras": {"path": "/workspace/bad.txt"}}]
+[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "bad.txt\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}, {"source": "agent", "action": "read", "args": {"path": "bad.txt", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "This is a stupid typoo.\nReally?\nNo mor typos!\nEnjoy!\n", "extras": {"path": "/workspace/bad.txt"}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_002.log

@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
+[{"source": "agent", "action": "run", "args": {"command": "ls", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "ls", "exit_code": 0}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_005.log

@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]
+[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_006.log

@@ -41,7 +41,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}, {"source": "agent", "action": "run", "args": {"command": "./hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]
+[{"source": "agent", "action": "write", "args": {"path": "hello.sh", "content": "#!/bin/bash\n\necho 'hello'", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "write", "content": "", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "chmod +x hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "chmod +x hello.sh", "exit_code": 0}}, {"source": "agent", "action": "run", "args": {"command": "./hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "./hello.sh", "exit_code": 0}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/DelegatorAgent/test_write_simple_script/prompt_009.log

@@ -39,7 +39,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\necho 'hello'\n", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]
+[{"source": "agent", "action": "read", "args": {"path": "hello.sh", "start": 0, "end": -1, "thought": ""}}, {"source": "agent", "observation": "read", "content": "#!/bin/bash\n\necho 'hello'\n", "extras": {"path": "/workspace/hello.sh"}}, {"source": "agent", "action": "run", "args": {"command": "bash hello.sh", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "bash hello.sh", "exit_code": 0}}]
 
 ## Format
 Your response MUST be in JSON format. It must be an object, and it must contain two fields:

+ 1 - 1
tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_003.log

@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}]
+[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}]
 
 If the last item in the history is an error, you should try to fix it.
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_004.log

@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
+[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
 
 If the last item in the history is an error, you should try to fix it.
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_005.log

@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
+[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
 
 If the last item in the history is an error, you should try to fix it.
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_006.log

@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
+[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
 
 If the last item in the history is an error, you should try to fix it.
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/ManagerAgent/test_simple_task_rejection/prompt_007.log

@@ -28,7 +28,7 @@ as well as observations you've made. This only includes the MOST RECENT
 actions and observations--more may have happened before that.
 They are time-ordered, with your most recent action at the bottom.
 
-[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
+[{"source": "agent", "action": "run", "args": {"command": "git status", "thought": "", "keep_prompt": true, "is_confirmed": "confirmed"}}, {"source": "agent", "observation": "run", "content": "fatal: not a git repository (or any parent up to mount point /)\r\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ", "extras": {"command_id": -1, "command": "git status", "exit_code": 128}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}, {"source": "agent", "observation": "error", "content": "action={'action': 'reject', 'args': {'reason': 'Not a valid git repository.'}} has the wrong arguments", "extras": {}}]
 
 If the last item in the history is an error, you should try to fix it.
 

+ 1 - 1
tests/integration/mock/eventstream_runtime/PlannerAgent/test_write_simple_script/prompt_010.log

@@ -205,7 +205,7 @@ ten actions--more happened before that.
   {
     "source": "agent",
     "observation": "run",
-    "content": "hello\r\n\r\nopenhands@docker-desktop:/workspace $ ",
+    "content": "hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ",
     "extras": {
       "command_id": -1,
       "command": "bash hello.sh",

+ 1 - 1
tests/integration/mock/eventstream_runtime/PlannerAgent/test_write_simple_script/prompt_011.log

@@ -204,7 +204,7 @@ ten actions--more happened before that.
   {
     "source": "agent",
     "observation": "run",
-    "content": "hello\r\n\r\nopenhands@docker-desktop:/workspace $ ",
+    "content": "hello\r\n\r\n[Python Interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]\nopenhands@docker-desktop:/workspace $ ",
     "extras": {
       "command_id": -1,
       "command": "bash hello.sh",

+ 36 - 13
tests/runtime/test_ipython.py

@@ -48,9 +48,10 @@ async def test_simple_cmd_ipython_and_fileop(temp_dir, box_class, run_as_openhan
     assert isinstance(obs, IPythonRunCellObservation)
 
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
-    assert (
-        obs.content.strip()
-        == 'Hello, `World`!\n[Jupyter current working directory: /workspace]'
+    assert obs.content.strip() == (
+        'Hello, `World`!\n'
+        '[Jupyter current working directory: /workspace]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     )
 
     # Test read file (file should not exist)
@@ -122,7 +123,11 @@ async def test_ipython_multi_user(temp_dir, box_class, run_as_openhands):
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     assert (
         obs.content.strip()
-        == '/workspace\n[Jupyter current working directory: /workspace]'
+        == (
+            '/workspace\n'
+            '[Jupyter current working directory: /workspace]\n'
+            '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
+        ).strip()
     )
 
     # write a file
@@ -134,7 +139,11 @@ async def test_ipython_multi_user(temp_dir, box_class, run_as_openhands):
     assert isinstance(obs, IPythonRunCellObservation)
     assert (
         obs.content.strip()
-        == '[Code executed successfully with no output]\n[Jupyter current working directory: /workspace]'
+        == (
+            '[Code executed successfully with no output]\n'
+            '[Jupyter current working directory: /workspace]\n'
+            '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
+        ).strip()
     )
 
     # check file owner via bash
@@ -174,7 +183,14 @@ async def test_ipython_simple(temp_dir, box_class):
     obs = await runtime.run_action(action_ipython)
     assert isinstance(obs, IPythonRunCellObservation)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
-    assert obs.content.strip() == '1\n[Jupyter current working directory: /workspace]'
+    assert (
+        obs.content.strip()
+        == (
+            '1\n'
+            '[Jupyter current working directory: /workspace]\n'
+            '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
+        ).strip()
+    )
 
     await runtime.close()
     await asyncio.sleep(1)
@@ -208,7 +224,8 @@ async def _test_ipython_agentskills_fileop_pwd_impl(
         '1|\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
-        '[Jupyter current working directory: /workspace]'
+        '[Jupyter current working directory: /workspace]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     ).strip().split('\n')
 
     action = CmdRunAction(command='cd test')
@@ -231,7 +248,8 @@ async def _test_ipython_agentskills_fileop_pwd_impl(
         '1|\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
-        '[Jupyter current working directory: /workspace/test]'
+        '[Jupyter current working directory: /workspace/test]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     ).strip().split('\n')
 
     if enable_auto_lint:
@@ -265,6 +283,7 @@ Your changes have NOT been applied. Please fix your edit command and try again.
 You either need to 1) Specify the correct start/end line arguments or 2) Correct your edit code.
 DO NOT re-run the same failed edit command. Running it again will lead to the same error.
 [Jupyter current working directory: /workspace/test]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 """
         ).strip().split('\n')
 
@@ -284,6 +303,7 @@ DO NOT re-run the same failed edit command. Running it again will lead to the sa
 (this is the end of the file)
 [File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
 [Jupyter current working directory: /workspace/test]
+[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]
 """
     ).strip().split('\n')
 
@@ -350,7 +370,8 @@ async def test_ipython_agentskills_fileop_pwd_with_userdir(temp_dir, box_class):
         '1|\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
-        '[Jupyter current working directory: /root]'
+        '[Jupyter current working directory: /root]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     ).strip().split('\n')
 
     action = CmdRunAction(command='cd test')
@@ -373,7 +394,8 @@ async def test_ipython_agentskills_fileop_pwd_with_userdir(temp_dir, box_class):
         '1|\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
-        '[Jupyter current working directory: /root/test]'
+        '[Jupyter current working directory: /root/test]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     ).strip().split('\n')
 
     await runtime.close()
@@ -407,9 +429,10 @@ async def test_ipython_package_install(temp_dir, box_class, run_as_openhands):
     obs = await runtime.run_action(action)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     # import should not error out
-    assert (
-        obs.content.strip()
-        == '[Code executed successfully with no output]\n[Jupyter current working directory: /workspace]'
+    assert obs.content.strip() == (
+        '[Code executed successfully with no output]\n'
+        '[Jupyter current working directory: /workspace]\n'
+        '[Jupyter Python interpreter: /openhands/poetry/openhands-5O4_aCHf-py3.11/bin/python]'
     )
 
     await runtime.close()